148 lines
6.6 KiB
Markdown
148 lines
6.6 KiB
Markdown
# دسترسی صاحب کلینیک به پروفایل پزشکان عضو کلینیک
|
||
|
||
## زمینه
|
||
|
||
وقتی صاحب کلینیک (`ROLE_CLINIC`) در صفحه کلینیک روی آیکون چشم کنار نام یک پزشک کلیک میکند، به جای مشاهده پروفایل پزشک، به داشبورد redirect میشود. دلیل: route `doctors/:uuid` در `App.tsx` فقط `['admin', 'doctor']` را قبول دارد و `RoleRoute` صاحب کلینیک را به `/admin/dashboard` میفرستد.
|
||
|
||
## مشکل
|
||
|
||
دو لایه مشکل وجود دارد:
|
||
|
||
**۱. Frontend — Route permission:**
|
||
```tsx
|
||
// assets/admin/App.tsx — خط 141
|
||
<Route path="doctors/:uuid" element={<RoleRoute roles={['admin', 'doctor']}><DoctorDetailPage /></RoleRoute>} />
|
||
```
|
||
`RoleRoute` فقط `primaryRole` را چک میکند — اگر `clinic` بود، redirect به dashboard میکند.
|
||
|
||
**۲. Backend — API permission:**
|
||
```php
|
||
// src/Doctor/Controller/DoctorController.php — خط 140-141
|
||
#[Route('/api/v1/doctor/{uuid}', methods: ['GET'])]
|
||
public function show(string $uuid): JsonResponse // بدون IsGranted — PUBLIC است
|
||
```
|
||
`GET /api/v1/doctor/{uuid}` عمومی است، اما برای اطمینان از امنیت باید یک endpoint اختصاصی برای کلینیک وجود داشته باشد که تأیید کند پزشک واقعاً عضو کلینیک مربوطه است.
|
||
|
||
## فایلهای مرتبط
|
||
|
||
| فایل | نقش |
|
||
|------|-----|
|
||
| `assets/admin/App.tsx` | تعریف route و RoleRoute — جای اصلی مشکل frontend |
|
||
| `assets/admin/pages/DoctorDetailPage.tsx` | صفحه پروفایل پزشک |
|
||
| `src/Doctor/Controller/DoctorController.php` | `GET /api/v1/doctor/{uuid}` |
|
||
| `src/Clinic/Repository/ClinicRepository.php` | دارای `findByDoctor()` و `findByUser()` |
|
||
|
||
## وضعیت فعلی
|
||
|
||
```tsx
|
||
// App.tsx خط 141
|
||
<Route path="doctors/:uuid" element={<RoleRoute roles={['admin', 'doctor']}><DoctorDetailPage /></RoleRoute>} />
|
||
|
||
// RoleRoute خط 70-74
|
||
function RoleRoute({ roles, children }: { roles: string[]; children: React.ReactNode }) {
|
||
const primaryRole = useAuthStore((s) => s.primaryRole);
|
||
if (!primaryRole) return <div>در حال بارگذاری...</div>;
|
||
if (!roles.includes(primaryRole)) return <Navigate to="/admin/dashboard" replace />;
|
||
return <>{children}</>;
|
||
}
|
||
```
|
||
|
||
```php
|
||
// DoctorController.php خط 140-163 — PUBLIC, no auth check
|
||
#[Route('/api/v1/doctor/{uuid}', methods: ['GET'])]
|
||
public function show(string $uuid): JsonResponse
|
||
{
|
||
$doctor = $this->doctorRepo->findByUuid($uuid);
|
||
if ($doctor === null) {
|
||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
|
||
}
|
||
// ... بدون چک مالکیت یا عضویت در کلینیک
|
||
return $this->success(['data' => array_merge($doctor->toDetailArray(), ['clinics' => $clinicData])]);
|
||
}
|
||
```
|
||
|
||
## وظایف
|
||
|
||
### ۱. Frontend — اضافه کردن `clinic` به RoleRoute مسیر پزشک
|
||
|
||
```tsx
|
||
// App.tsx — خط 141 را تغییر بده
|
||
<Route
|
||
path="doctors/:uuid"
|
||
element={<RoleRoute roles={['admin', 'doctor', 'clinic']}><DoctorDetailPage /></RoleRoute>}
|
||
/>
|
||
```
|
||
|
||
### ۲. Backend — endpoint اختصاصی برای کلینیک
|
||
|
||
یک endpoint جدید در `DoctorController` اضافه کن که صاحب کلینیک بتواند پروفایل پزشکان عضو کلینیکش را ببیند، با تأیید عضویت:
|
||
|
||
```php
|
||
// src/Doctor/Controller/DoctorController.php
|
||
#[Route('/api/v1/clinic/my-doctor/{doctorUuid}', methods: ['GET'])]
|
||
#[IsGranted('ROLE_CLINIC')]
|
||
public function showForClinic(string $doctorUuid, #[CurrentUser] User $user): JsonResponse
|
||
{
|
||
$clinic = $this->clinicRepo->findByUser($user);
|
||
if ($clinic === null) {
|
||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کلینیک یافت نشد', 404);
|
||
}
|
||
|
||
$doctor = $this->doctorRepo->findByUuid($doctorUuid);
|
||
if ($doctor === null) {
|
||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پزشک یافت نشد', 404);
|
||
}
|
||
|
||
// فقط پزشکانی که عضو این کلینیک هستند قابل مشاهدهاند
|
||
if (!$clinic->getDoctors()->contains($doctor)) {
|
||
return $this->error(ErrorCodes::ERR_AUTH_006, 'این پزشک عضو کلینیک شما نیست', 403);
|
||
}
|
||
|
||
$clinicData = [[
|
||
'id' => (string) $clinic->getId(),
|
||
'uuid' => $clinic->getUuid(),
|
||
'name' => $clinic->getName(),
|
||
'address' => $clinic->getAddress(),
|
||
'telephone' => $clinic->getTelephone(),
|
||
]];
|
||
|
||
return $this->success(['data' => array_merge($doctor->toDetailArray(), ['clinics' => $clinicData])]);
|
||
}
|
||
```
|
||
|
||
**وابستگیهای constructor**: `ClinicRepository $clinicRepo` باید به `DoctorController` اضافه شود (بررسی کن آیا از قبل موجود است).
|
||
|
||
### ۳. Frontend — DoctorDetailPage: fetch با endpoint مناسب
|
||
|
||
در `DoctorDetailPage.tsx`، وقتی `primaryRole === 'clinic'`، از endpoint جدید استفاده کن:
|
||
|
||
```tsx
|
||
// در DoctorDetailPage.tsx — در useQuery که doctor را fetch میکند
|
||
const { data: doctorData } = useQuery({
|
||
queryKey: ['doctor', uuid, primaryRole],
|
||
queryFn: () =>
|
||
primaryRole === 'clinic'
|
||
? api.get(`/api/v1/clinic/my-doctor/${uuid}`)
|
||
: api.get(`/api/v1/doctor/${uuid}`),
|
||
enabled: !!uuid,
|
||
});
|
||
```
|
||
|
||
### ۴. Frontend — DoctorDetailPage: محدود کردن قابلیتهای ویرایش برای clinic
|
||
|
||
وقتی `primaryRole === 'clinic'`:
|
||
- دکمه ویرایش پروفایل نمایش داده **نشود** (فقط مشاهده)
|
||
- دکمه حذف نمایش داده **نشود**
|
||
- بخش دعوتنامهها (`ClinicInvitationsSection`) نمایش داده **نشود**
|
||
|
||
بررسی کن چه بخشهایی با `isOwnProfile` یا `primaryRole === 'admin'` guard شدهاند و `clinic` را به عنوان read-only viewer handle کن.
|
||
|
||
## نکات مهم
|
||
|
||
- `ClinicRepository::findByUser(User $user)` وجود دارد — از آن برای پیدا کردن کلینیک صاحب استفاده کن
|
||
- `Clinic::getDoctors()` یک `Collection` است — از `->contains($doctor)` برای چک عضویت استفاده کن
|
||
- `GET /api/v1/doctor/{uuid}` را تغییر نده — این endpoint عمومی است و frontend برای admin/doctor از آن استفاده میکند
|
||
- migration لازم نیست — هیچ entity تغییر نمیکند
|
||
- بعد از تغییر route باید `ddev exec php bin/console cache:clear` اجرا شود
|
||
- مستندات: `docs/api/doctor.md` را با endpoint جدید بهروزرسانی کن
|