Files
clinicpro/.claude/prompt/full-backend-audit.md
T
hamedandClaude Opus 4.8 aa87b4a9cb fix(db): prevent double-booking a slot via unique active_slot_key (H2)
A non-unique index on (doctor_id, slot_start) plus a count-then-insert check
left a TOCTOU race: two concurrent requests could both pass isSlotTaken and
both insert. wrapInTransaction alone doesn't stop the phantom under InnoDB
REPEATABLE-READ.

Add a nullable, unique active_slot_key on Appointment = "doctorId:slotStart"
while the booking occupies the slot (pending/confirmed — in lockstep with
isSlotTaken); NULL once expired/completed/no_show/cancelled (NULLs don't collide
in a MySQL unique index, so released slots rebook freely). bookAtomically now:
catches the unique violation -> SlotTakenException, and expires lapsed pendings
in-transaction so the ~1-min window before the expiry cron doesn't wrongly block
rebooking. All three booking paths (online / my / admin) routed through it.

Migration backfills one row per (doctor, slot) — the latest id — so the index
builds even on dirty historical data without destructively cancelling bookings.
(Backfill surfaced a real pre-existing double-booked slot in dev data.)

Regression: tests/Appointment/SlotUniquenessTest. Adjusted the expiry-service
test fixture to use distinct slots (one live booking per slot is now enforced).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:04:54 +03:30

11 KiB

بازبینی کامل بک‌اند 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)

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های متوالی) تا ادامه‌پذیر باشد.