# رفع 404 پروفایل کاربر: resolve با user-uuid و بازگرداندن پروفایل خالی برای کاربر تازه

## پروژه

`clinicpro` (Backend). **این پرامپت اول اجرا شود.**

> **Cross-repo:** قرارداد `GET /api/v1/user-profile/{uuid}` توسط سایت عمومی (`nobat724_front`) مصرف می‌شود. پرامپت همتای frontend:
> `nobat724_front/.claude/prompt/user-profile-no-404.md`

## زمینه

سایت عمومی بعد از لاگین، پروفایل کاربر را با `GET /api/v1/user-profile/{uuid}` می‌گیرد و `uuid` را از کوکی auth (یعنی **uuid کاربر**) می‌فرستد. اما این endpoint پروفایل را با `findByUuid($uuid)` پیدا می‌کند که `{uuid}` را **uuid خودِ پروفایل** فرض می‌کند — نه uuid کاربر. `UserProfile` یک `uuid` مستقل دارد (هنگام ساخت پروفایل تولید می‌شود) که با uuid کاربر فرق دارد.

علاوه بر این، کاربری که تازه با OTP ثبت‌نام کرده **اصلاً ردیف پروفایل ندارد** (فلوی OTP فقط `User` می‌سازد؛ پروفایل با `POST /user-profile` ساخته می‌شود).

نتیجه: برای کاربر `09210651788` (تأییدشده در DB: `user_uuid=4d19830c-842a-4a79-b56c-198110cd73e2`, `profile_uuid=NULL`)، فراخوانی `GET /user-profile/4d19830c-...` همیشه **404 «پروفایل یافت نشد»** می‌دهد — هم به‌خاطر mismatch و هم به‌خاطر نبودِ پروفایل. این داشبورد و مرحله‌ی تکمیل اطلاعاتِ نوبت‌گیری را برای هر کاربر جدید می‌شکند.

## مشکل / هدف

`GET /api/v1/user-profile/{uuid}` باید:
1. `{uuid}` را **هم به‌عنوان uuid پروفایل و هم uuid کاربر** قبول کند (مثل الگوی weekly-schedule که هر دو را امتحان می‌کند).
2. اگر کاربرِ احرازشده پروفایل ندارد، به‌جای 404 یک **پروفایل خالی برای همان کاربر بسازد و برگرداند** (یا یک شیء پروفایل پیش‌فرض با مقادیر null) — تا frontend همیشه یک پروفایل قابل‌نمایش/ویرایش بگیرد.
3. کنترل دسترسی حفظ شود: کاربر فقط پروفایل خودش (یا ادمین هر پروفایلی).

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `src/UserProfile/Controller/UserProfileController.php` | `show()` (GET) — محل اصلی رفع؛ `canAccess`, `hydrate` |
| `src/UserProfile/Repository/UserProfileRepository.php` | `findByUuid`, `findByUser` |
| `src/Auth/Repository/UserRepository.php` | `findByUuid` (برای resolve با user-uuid) |
| `src/UserProfile/Entity/UserProfile.php` | `uuid` مستقل + `getUser()`؛ `toArray()` شامل `uuid` و `user_uuid` |
| `docs/api/user-profile.md` | مستندسازی رفتار جدید GET |

## وضعیت فعلی (کد واقعی)

### `UserProfileController::show()` — فقط با profile-uuid

```php
#[Route('/api/v1/user-profile/{uuid}', methods: ['GET'])]
public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
{
    $profile = $this->repository->findByUuid($uuid);   // ❌ فقط profile uuid
    if ($profile === null) {
        return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);  // ❌ کاربر جدید همیشه اینجا
    }
    if (!$this->canAccess($profile, $user)) {
        return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
    }
    return $this->success(['data' => $profile->toArray()]);
}
```

### `UserProfile` — uuid مستقل از کاربر

```php
#[ORM\Column(type: 'string', length: 36, unique: true)]
private string $uuid;                 // پروفایل، تولیدشده هنگام ساخت

#[ORM\OneToOne(targetEntity: User::class)]
private User $user;                   // کاربر مالک
// toArray(): 'uuid' => profile uuid, 'user_uuid' => $this->user->getUuid()
```

### repositoryها

```php
// UserProfileRepository
public function findByUser(User $user): ?UserProfile { ... }
public function findByUuid(string $uuid): ?UserProfile { ... }
// UserRepository
public function findByUuid(string $uuid): ?User { ... }
```

## وظایف

### ۱. تزریق `UserRepository` به کنترلر

```php
public function __construct(
    private readonly UserProfileRepository $repository,
    private readonly UserRepository        $userRepository,
) {}
```

### ۲. بازنویسی `show()` — resolve با profile-uuid یا user-uuid + پروفایل خالی برای کاربر جدید

```php
#[Route('/api/v1/user-profile/{uuid}', methods: ['GET'])]
public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
{
    // 1) تلاش با profile uuid
    $profile = $this->repository->findByUuid($uuid);

    // 2) fallback: uuid را به‌عنوان user uuid تفسیر کن
    if ($profile === null) {
        $targetUser = $this->userRepository->findByUuid($uuid);
        if ($targetUser !== null) {
            $profile = $this->repository->findByUser($targetUser);

            // 3) کاربر وجود دارد ولی پروفایل ندارد → برای خودِ کاربر، پروفایل خالی بساز/برگردان
            if ($profile === null) {
                if ($targetUser->getId() !== $user->getId() && !$user->hasRole('ROLE_ADMIN')) {
                    return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
                }
                $profile = new UserProfile($targetUser);
                $this->repository->save($profile);   // lazy-create تا frontend پروفایل قابل‌ویرایش بگیرد
            }
        }
    }

    if ($profile === null) {
        return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
    }

    if (!$this->canAccess($profile, $user)) {
        return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
    }

    return $this->success(['data' => $profile->toArray()]);
}
```

> **تصمیم lazy-create:** ساخت پروفایل خالی هنگام اولین GET، ساده‌ترین راه است تا frontend همیشه یک شیء با `uuid`/`user_uuid` بگیرد و PATCH بعدی کار کند. اگر ترجیح می‌دهی **بدون ساخت در DB** فقط یک پروفایل پیش‌فرضِ in-memory برگردانی (بدون persist)، آن هم قابل‌قبول است — ولی آنگاه PATCH با profile-uuid کار نمی‌کند تا اول POST شود. **یکی را انتخاب کن و در گزارش ذکر کن؛ lazy-create توصیه می‌شود.**

### ۳. (اختیاری) PATCH هم با user-uuid کار کند

- اگر می‌خواهی فلوی frontend بدون نیاز به دانستن profile-uuid کامل شود، در `update()` (PATCH) هم همان resolve دو-مرحله‌ای را اعمال کن (profile uuid → user uuid → پروفایلِ کاربر). اگر پروفایل نبود، یا بساز یا 404. این کار `POST` و `PATCH` جداگانه در frontend را ساده می‌کند.
- اگر این را انجام دادی، در مستندات ذکر کن.

### ۴. مستندسازی

در `docs/api/user-profile.md` بخش GET: توضیح بده `{uuid}` می‌تواند **profile uuid یا user uuid** باشد، و برای کاربری که هنوز پروفایل ندارد یک پروفایل خالی (با فیلدهای null) برگردانده می‌شود (و در صورت lazy-create، ساخته می‌شود). همان برای PATCH اگر تغییر دادی.

## نکات مهم

- **هیچ تغییر Entity یا migration لازم نیست** (فقط منطق کنترلر).
- **دسترسی:** کاربر فقط پروفایل خودش؛ ادمین همه. هنگام resolve با user-uuid، قبل از lazy-create حتماً چک کن `targetUser` همان کاربر احرازشده است (یا ادمین) — وگرنه 403. سپس `canAccess` نهایی هم اعمال شود.
- **idempotency:** `POST /user-profile` از قبل اگر پروفایل وجود داشته باشد 409 می‌دهد؛ بعد از lazy-create در GET، فراخوانی POST بعدیِ frontend ممکن است 409 بگیرد — frontend باید با این سازگار شود (در پرامپت frontend ذکر شده). به همین دلیل بهتر است frontend به‌جای POST از PATCH استفاده کند.
- پاسخ‌ها از `BaseController` (`success`/`error`)؛ خطاها با `ErrorCodes`.
- `toArray()` هم `uuid` (پروفایل) و هم `user_uuid` را برمی‌گرداند — frontend می‌تواند بعد از اولین GET، `uuid` پروفایل را برای PATCHهای بعدی نگه دارد.
- تست:
  - `ddev exec php -l src/UserProfile/Controller/UserProfileController.php`
  - با توکن کاربر `09210651788` (که `profile_uuid=NULL` است): `GET /api/v1/user-profile/4d19830c-842a-4a79-b56c-198110cd73e2` باید **200** با پروفایل خالی برگرداند (نه 404).
  - GET با profile-uuid واقعی هم باید کار کند (عدم رگرسیون).
  - GET با user-uuidِ کاربر دیگر (نه ادمین) → 403.
  - بعد از تغییر، `docs/api/user-profile.md` را به‌روز کن.
