# بازبینی کامل بک‌اند ClinicPro (معماری/امنیت/Performance/کیفیت/DevOps) — تسک‌به‌تسک با تست

## پروژه

`clinicpro` (Symfony 7.4 / PHP ≥8.2 / Doctrine / MariaDB؛ React 19 admin)

## زمینه

پروژه بزرگ است: **۳۸ controller، ۵۵ entity، ~۳۰ دامنه** زیر `src/`. تست تقریباً وجود ندارد (فقط `tests/Billing`، `tests/bootstrap.php`)، CI نیست (`.github/workflows` خالی)، phpstan روی level 5. اخیراً چند باگ prod-only رخ داده (نمونه‌ها پایین) که نشان می‌دهد بازبینی سیستماتیک لازم است.

این یک ممیزی (audit) کامل است که **تسک‌به‌تسک، با تست، و با اجرای ایمن** انجام می‌شود. نه یک‌جا، نه سطحی.

## هدف

Backlog کامل از تسک‌های کوچک قابل‌تست بساز، اولویت‌بندی کن (Critical→High→Medium→Low)، سپس **یکی‌یکی** هر تسک را تحلیل/رفع/تست/گزارش کن تا هیچ Critical/High باقی نماند.

## قوانین اجرا (الزامی)

1. **اول کل پروژه را اسکن کن و Backlog بساز** (با `TodoWrite` — هر تسک یک آیتم). هیچ کدی قبل از Backlog عوض نشود.
2. **فقط یک تسک In Progress** در هر لحظه. ترتیب: Critical → High → Medium → Low.
3. هر تسک کامل می‌شود فقط وقتی: fix اعمال شد + تست نوشته شد + تست **PASS** شد + گزارش ثبت شد.
4. برای هر باگ، **تست regression** بنویس که بدون fix fail و با fix pass شود.
5. تغییرات روی **برنچ جدا** (مثلاً `backend-audit`) — prod فعلی (main) دست نخورد. هر چند تسک، commit جدا.
6. بعد از هر تغییر API → فایل مربوط در `docs/api/` آپدیت (قانون استاندارد پروژه).
7. بعد از تغییر Entity → migration بساز (`doctrine:migrations:diff`) و اجرا کن.

## محیط و دستورات تست (همه داخل ddev)

```bash
ddev exec php bin/phpunit                                  # کل تست‌ها
ddev exec php bin/phpunit tests/Path/To/SomeTest.php       # یک فایل
ddev exec php vendor/bin/phpstan analyse                   # level 5
ddev exec php bin/console doctrine:migrations:diff --no-interaction
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php bin/console lint:container                   # سلامت DI
ddev exec php bin/console debug:router | grep api
```

> زیرساخت تست الان ضعیف است. اگر `tests/` پایه‌ی کافی (WebTestCase، DB تستی، fixtures) ندارد، **اولین تسک‌های High** باید زیرساخت تست را بسازند (phpunit env، DB تست، factory/fixture سبک، یک `ApiTestCase` پایه که login/JWT را هندل کند).

## وضعیت فعلی (واقعی — بررسی‌شده)

- دامنه‌ها: `Admin, Appointment, Auth, Blog, Category, Clinic, ClinicInvitation, ClinicService, Config, Dashboard, Doctor, DoctorService, Insurance, Location, Patient, Payment, Rating, Representation, Secretary, Sms, Specialty, Staff, Subscription, Tag, UserProfile, Settlement, Billing`.
- همه controllerها از `BaseController` ارث می‌برند؛ پاسخ‌ها `success()/paginated()/error()/validationError()`.
- خطاها: `AppException(ErrorCodes::ERR_XXX,...)` → `ExceptionSubscriber` تبدیل به `error()`. کد عمومی prod: `ERR_INTERNAL_001`.
- Auth: JWT (lexik) + OAuth `oauth/token`؛ firewall `public_endpoints` (stateless) در `config/packages/security.yaml`؛ `access_control` روی `^/api` = `IS_AUTHENTICATED_FULLY`.
- Rate limiter: `config/packages/rate_limiter.yaml` + `enforceLimit()` در `AuthController`/`PasswordAuthenticator`.
- تاریخ‌ها: Unix timestamp (نه DateTime).
- لیست‌های admin: DQL `getArrayResult()` (۱۲ مورد).
- ۳۴ entity index صریح دارند، ۲۱ ندارند.
- Cache: redis (`cache.adapter.redis`). Messenger: `async` (redis) + `scheduler_default` + `failed` (doctrine).

## باگ‌کلاس‌های شناخته‌شده در همین کدبیس (به‌عنوان seed برای Backlog)

این‌ها واقعی‌اند و باید در Backlog به‌صورت تسک‌های با تست regression بیایند + مشابه‌یاب شوند:

1. **repositoryClass گمشده** — قبلاً ۲۵ entity `#[ORM\Entity]` بدون `repositoryClass` داشتند → روی prod (opcache.preload) `getRepository()` repo پیش‌فرض می‌داد → `BadMethodCallException` روی متد custom (مثل `findAllActiveBySecretary`) → ۵۰۰. رفع شد، ولی **تست regression ندارد**. تسک: تستی که هر entity دارای custom repo را تأیید کند `getRepository()` همان custom class را برمی‌گرداند (data provider روی همه‌ی entityها).
2. **double-nested response** — `$this->success(['data' => ...])` → `{data:{data:...}}`؛ frontend باید `data?.data?.data` بخواند. تسک: یافتن همه‌ی موارد ناخواسته‌ی nest، یکدست‌سازی قرارداد، تست پاسخ.
3. **migration روی DB خالی** — قبلاً چند migration (drop قبل از create، FK تکراری) روی fresh DB می‌ترکید. تسک: تست/CI که `migrate` را روی DB کاملاً خالی اجرا کند و سبز بماند (smoke).
4. **`getArrayResult()` در لیست admin** — برای اجتناب از getter روی entity. تسک: تأیید همه‌ی لیست‌های admin این الگو را دارند و N+1 ندارند.

## نمونه ابعاد بررسی برای ساخت Backlog (هر کدام به تسک‌های کوچک بشکن)

- **امنیت:** IDOR روی endpointهای دارای `{uuid}`/`{id}` (آیا ownership/scope چک می‌شود؟ مخصوصاً doctor/clinic/secretary scope و representation)؛ mass-assignment در bodyها؛ نشت اطلاعات در پاسخ خطا؛ صحت `public_endpoints` (هیچ endpoint حساسی اشتباهی public نباشد)؛ rate limit روی login/OTP/reset؛ اعتبارسنجی ورودی (DTO + validator) به‌جای آرایه‌ی خام؛ JWT TTL/refresh؛ بررسی SSRF در `API_IR` و callbackهای پرداخت؛ امضا/verify callback درگاه (mellat/sep).
- **Performance / Doctrine:** N+1 (join/fetch)، نبود index روی ستون‌های فیلتر/FK پرتکرار (۲۱ entity بدون index صریح)، pagination درست (`paginated()` + count بهینه)، کوئری‌های سنگین dashboard/admin، استفاده‌ی درست از redis cache و invalidation.
- **API design:** status codeهای درست (۲۰۰/۲۰۱/۴۰۰/۴۰۱/۴۰۳/۴۲۲/۵۰۰)، یکدستی پاکت پاسخ، ولیدیشن ۴۲۲، نسخه‌بندی، هماهنگی با `docs/api/*` و مصرف‌کننده‌ها (`nobat724_front/services/*` و `clinic-pro-tauri/src/service/*`).
- **کیفیت/معماری:** controllerهای چاق (منطق به Service منتقل شود)، SOLID، تکرار، `ErrorCodes` کامل، exception handling در boundary نه پراکنده.
- **DevOps/Observability:** نبود CI → یک workflow `phpunit + phpstan + migrate-on-empty-db`؛ لاگ ساختاریافته (الان monolog نیست — تصمیم بگیر اضافه شود یا نه)؛ healthcheck (`/health` هست)؛ بدون secret در repo.
- **Database integrity:** FK/onDelete درست، unique constraintها، nullability، یکدستی timestamp.

## وظایف

### ۱. اسکن و ساخت Backlog (اولین و مهم‌ترین خروجی)

کل `src/` را دامنه‌به‌دامنه اسکن کن. برای هر یافته یک تسک کوچک بساز با: عنوان، فایل دقیق، دسته (security/perf/quality/api/db/devops/test)، **سطح ریسک**، و «چطور تست شود». خروجی = جدول Backlog اولویت‌بندی‌شده + `TodoWrite`.

> برای پوشش گسترده، می‌توانی اسکن دامنه‌ها را با subagentهای موازی انجام دهی (هر دامنه/بُعد یک agent، خروجی structured)، سپس نتایج را در یک Backlog واحد ادغام و dedup کن. هر یافته باید قبل از تبدیل‌شدن به fix، با خواندن کد واقعی تأیید (verify) شود تا false-positive نرود.

### ۲. زیرساخت تست (اگر ناکافی بود — قبل از تسک‌های دیگر)

`ApiTestCase` پایه (WebTestCase + DB تست + helper برای ساخت user/JWT و login)، fixture/factory سبک، تنظیم `phpunit.dist.xml` برای env تست و DB جدا. خروجی: یک تست سبز نمونه (مثلاً `/health` و یک endpoint عمومی).

### ۳..N — اجرای تسک‌به‌تسک

به ترتیب اولویت، برای هر تسک دقیقاً این چرخه:

```
① تحلیل کد همان تسک  ② یافتن bug/smell/security/perf  ③ تأیید (verify) یافته با کد واقعی
④ fix  ⑤ تست (unit/functional/security + regression برای باگ)  ⑥ اجرای تست تا PASS
⑦ phpstan + lint:container سبز  ⑧ آپدیت docs/api اگر API عوض شد  ⑨ گزارش تسک  ⑩ تسک بعدی
```

## خروجی هر تسک

- عنوان · هدف · مشکلات پیداشده · سطح ریسک (Low/Medium/High/Critical) · اصلاحات · تست‌های نوشته‌شده · نتیجه‌ی تست (PASS/FAIL) · جمع‌بندی کوتاه.

## شرط پایان + گزارش نهایی

پروژه تمام است وقتی: همه‌ی تسک‌ها done، هیچ Critical/High باقی نمانده، همه تست‌ها PASS، هیچ fix بدون تست regression. گزارش نهایی:
- تعداد باگ‌ها · تعداد مشکلات امنیتی · تعداد مشکلات performance · میزان پوشش تست (واقعی، با `--coverage-text` اگر xdebug/pcov هست) · **Production readiness (%)** · لیست اولویت‌بندی‌شده‌ی باقی‌مانده.

## نکات مهم (الگوهای پروژه که باید رعایت شوند)

- همه controllerها `BaseController`؛ پاسخ‌ها فقط با `success/paginated/error/validationError`. خطاها با `AppException + ErrorCodes` (پیام فارسی).
- لیست admin: `getArrayResult()` — getter روی entity در این کوئری‌ها استفاده نشود.
- تاریخ‌ها Unix timestamp (نه DateTime).
- تغییر Entity → migration؛ تغییر API → `docs/api/*`.
- تست‌ها داخل ddev اجرا شوند؛ DB تست جدا از DB توسعه باشد (داده‌ی dev خراب نشود).
- روی **برنچ جدا** کار شود؛ هیچ‌چیز مستقیم روی main نرود تا review نشود.
- مصرف‌کننده‌های cross-repo (`nobat724_front`, `clinic-pro-tauri`) موقع تغییر قرارداد API لحاظ شوند — در build خطا نمی‌دهند.
- دامنه‌بودن کار: این یک تلاش بزرگ است؛ اگر یک‌جلسه تمام نشد، Backlog و وضعیت تسک‌ها باید پایدار بماند (TodoWrite + commitهای متوالی) تا ادامه‌پذیر باشد.
