feat(doctor): claim captcha+mobile, owner profile delete, admin map zoom fix
- DoctorClaimController: ALTCHA CaptchaGuard on /claim (dev no-op via ALTCHA_ENABLED=false); optional `mobile` field must match the logged-in user's number (422 ERR_CONFLICT_001 on mismatch) - DoctorController::delete: now IS_AUTHENTICATED_FULLY — admin (any) or the owner of a claimed profile (IDOR-guarded); FK appointment guard kept - DoctorDetailPage address map: MapController calls map.invalidateSize() before flyTo (fixes needing to pick a city twice on a freshly-mounted map); geocode retries once (nominatim empty/429 on first hit) - tests: mobile mismatch, owner-delete allowed + others 403, unclaimed not deletable by random user - docs: doctor-claim.md (mobile+captcha), doctor.md (delete permission) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق
|
||||
|
||||
**Permission:** `IS_AUTHENTICATED_FULLY` — کاربر با OTP لاگین شده (موبایلش تأییدشده است)
|
||||
**Rate limit:** limiter `doctor_claim` — ۵ تلاش در ساعت بهازای هر (کاربر، پزشک)
|
||||
**Captcha:** ALTCHA — بدنه باید payload کپچا بفرستد (`CaptchaGuard::assertValid`)؛ در dev با `ALTCHA_ENABLED=false` بیاثر است، در prod اجباری. خطا → `ERR_CAPTCHA_001` (۴۲۲).
|
||||
|
||||
### Request
|
||||
```json
|
||||
@@ -48,7 +49,9 @@ unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق
|
||||
"national_code": "0010007700",
|
||||
"birth_date": "1371/1/1",
|
||||
"first_name": "فرخنده",
|
||||
"last_name": "حسینی"
|
||||
"last_name": "حسینی",
|
||||
"mobile": "09121234567",
|
||||
"altcha": "<payload کپچا>"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -57,6 +60,8 @@ unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق
|
||||
| `national_code` | ✅ | ۱۰ رقم (ارقام فارسی پذیرفته و نرمال میشوند) |
|
||||
| `birth_date` | ✅ | شمسی `Y/m/d` |
|
||||
| `first_name` / `last_name` | ✅ | با هویت ثبت احوال و نام پروفایل تطبیق داده میشود (نرمالسازی ي/ی، ك/ک، نیمفاصله — `PersianText`) |
|
||||
| `mobile` | ❌ | اگر داده شود، باید با موبایل حساب کاربری یکی باشد وگرنه `422 ERR_CONFLICT_001`؛ اگر خالی باشد از موبایل کاربر لاگینشده استفاده میشود |
|
||||
| `altcha` | prod | payload کپچای ALTCHA |
|
||||
|
||||
### مراحل سرور (اتمیک/ضد race)
|
||||
|
||||
|
||||
+3
-3
@@ -269,7 +269,7 @@ Updated doctor object (same structure as GET single).
|
||||
|
||||
Delete a doctor profile.
|
||||
|
||||
**Permission:** `ROLE_ADMIN`
|
||||
**Permission:** `IS_AUTHENTICATED_FULLY` — **admin** (any profile) **or the owner** of a `claimed` profile (`doctor.user === current user`). Other users get `403`.
|
||||
|
||||
> **Side effect:** the doctor's insurance configuration (`tenant_insurances`, `entity_insurance_pricing`, and their `tenant_service_coverages`) is purged in the same request — these reference the doctor via a polymorphic `entity_id` with no DB FK, so the cleanup is enforced in the application.
|
||||
|
||||
@@ -292,8 +292,8 @@ Delete a doctor profile.
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_001` | 401 | Missing token |
|
||||
| `ERR_AUTH_006` | 403 | Not admin |
|
||||
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
||||
| `ERR_FORBIDDEN_001` | 403 | Not admin and not the owner of this claimed profile |
|
||||
| `ERR_VALIDATION_002` | 404 | Doctor not found |
|
||||
| `ERR_CONFLICT_001` | 409 | Doctor has existing appointments and cannot be deleted |
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user