Files
clinicpro/docs/Architecture_Audit.md
hamed de1a78a235 feat: Implement SMS sending functionality with KavehNegar and Rangineh providers
- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
2026-06-09 22:00:34 +03:30

824 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture Audit — ClinicPro Symfony 7 Migration
**تاریخ:** ۱۴۰۵/۰۳/۱۸
**بررسی‌کننده:** Senior Software Architect
**نسخه مستند:** ۱.۰
---
## Executive Summary
پروژه **ClinicPro** یک مهاجرت از Drupal به Symfony 7 است. سیستم یک پلتفرم Multi-tenant نوبت‌دهی پزشکی است با ۱۷ ماژول و ~۹۵ Endpoint.
معماری پیشنهادی از نظر انتخاب تکنولوژی مناسب و ساختار پایگاه داده قابل قبول است، اما **بیش از ۳۰ Endpoint فاقد مستندات Response هستند**، Business Logic های کلیدی (پرداخت، محاسبه امتیاز، نوبت‌دهی) تعریف‌نشده‌اند، و الگوی Entity-Bundle درایت‌شده از Drupal بدون تطبیق صحیح به Symfony منتقل شده است.
---
## Architecture Score (بعد از اصلاحات)
```
Overall Score: 78 / 100 ↑ از 61
```
| بُعد | امتیاز قبل | امتیاز بعد | تغییرات |
|------|-----------|-----------|---------|
| Scalability | 55/100 | 60/100 | Messenger async، Redis cache پیش‌بینی شد |
| Security | 65/100 | **82/100** | Refresh Token، hash_equals، CORS fix، Security Headers، Audit Log، Open Redirect fix |
| Maintainability | 60/100 | 80/100 | Domain-Driven structure، DTO، Error Codes، Response format یکپارچه |
| Performance | 58/100 | 65/100 | Eager loading، Redis cache plan |
| Reliability | 55/100 | 72/100 | Circuit Breaker، Idempotency، Payment flow کامل |
---
## ۱. تطابق معماری با PRD
### ماژول‌های شناسایی‌شده در PRD
| # | ماژول | Endpoint ها | وضعیت در معماری |
|---|--------|------------|-----------------|
| ۱ | Authentication (احراز هویت) | ۸ | ✅ تعریف شده |
| ۲ | User Profile (پروفایل کاربر) | ۴ | ✅ تعریف شده |
| ۳ | Blog (وبلاگ) | ۷ | ✅ تعریف شده |
| ۴ | Doctor (دکتر) | ۱۰ | ✅ تعریف شده |
| ۵ | Clinic (کلینیک) | ۷ | ✅ تعریف شده |
| ۶ | Agent (نماینده) | ۳ | ✅ تعریف شده |
| ۷ | Categories (دسته‌بندی‌ها) | ۱۰ | ✅ تعریف شده |
| ۸ | Doctor Insurance (بیمه دکتر) | ۴ | ✅ تعریف شده |
| ۹ | Appointment Settings (تنظیمات نوبت) | ۱۱ | ✅ تعریف شده |
| ۱۰ | Appointment (نوبت‌دهی) | ۴ | ✅ تعریف شده |
| ۱۱ | Payment (پرداخت) | ۳ | ✅ تعریف شده |
| ۱۲ | Rating & Comments (امتیاز و نظرات) | ۱۲ | ✅ تعریف شده |
| ۱۳ | Likes (لایک) | ۲ | ✅ تعریف شده |
| ۱۴ | Secretary (منشی) | ۵ | ✅ تعریف شده |
| ۱۵ | Representation Dashboard (داشبورد) | ۵ | ✅ تعریف شده |
| ۱۶ | SMS | — | ⚠️ بدون task.md |
| ۱۷ | File Upload | — | ⚠️ Embedded در سایر ماژول‌ها |
| — | Subscription Payments | — | ❌ Endpoint تعریف نشده |
| — | Health Check | — | ❌ اصلاً وجود ندارد |
| — | Refresh Token | — | ❌ وجود ندارد |
### موارد پوشش‌داده‌نشده از PRD
- **Subscription Payment Endpoints** — جدول `subscription_payments` وجود دارد اما هیچ endpoint برای مدیریت آن نیست
- **City-specific management** — City bundle دارای ۸+ فیلد توسعه‌یافته (domain، SEO، footer) است که هیچ endpoint ای برای مدیریت آنها نیست
- **Comment Nesting** — فیلد `field_parent` در DB وجود دارد اما در API تعریف نشده
- **Appointment Cancellation Flow** — لغو نوبت و refund پرداخت مستند نشده
---
## ۲. تحلیل معماری فعلی
### Scalability
**نقاط قوت:**
- UUID در public API — امکان sharding در آینده را حفظ می‌کند
- Redis برای OTP — scalable و stateless
**نقاط ضعف:**
- Single MySQL instance — هیچ read replica تعریف نشده؛ با رشد کاربر، query های سنگین لیست دکتر/کلینیک با فیلتر چندگانه روی master اجرا می‌شوند
- JSON columns بدون index — فیلدهایی مثل `weekly_schedules.setting` و `appointments.slot` با JSON ذخیره می‌شوند اما قابل index نیستند؛ جستجو روی آنها Full Table Scan است
- هیچ cache strategy فراتر از OTP وجود ندارد — لیست دسته‌بندی‌ها، تخصص‌ها، استان‌ها با هر request از DB خوانده می‌شوند
### Maintainability
**نقاط قوت:**
- Task decomposition منطقی با dependency graph مشخص
- UUID، timestamps، naming convention یکپارچه
**نقاط ضعف:**
- ۳۰+ endpoint بدون response schema — هر developer می‌تواند خروجی متفاوتی بسازد
- نام‌گذاری ناسازگار: `img` در doctor، `images_clinic` در clinic، `field_image` در blog
- Business logic (فرمول rating، منطق free_turn) مستند نشده
### Security
**نقاط قوت:**
- OTP-based login — بدون password در پیام
- JWT با TTL مشخص (3600s)
- Rate limiting با Redis (50 req/hr per IP، 30 req/hr per mobile)
- MIME type validation در file upload
**نقاط ضعف:**
- بدون Refresh Token — کاربر هر ساعت باید re-login کند یا OTP مجدد دریافت کند
- CSRF inconsistent — در بعضی endpoint ها الزامی، در بعضی خیر
- بدون audit log — هیچ‌جا ثبت نمی‌شود چه کسی چه تغییری داده
- Secrets در `.env` — برای production باید Vault یا محیط CI/CD مدیریت شود
### Performance
**نقاط قوت:**
- Index های مناسب روی uuid، mobile_number، created_at، doctor_id + start_time
- Redis برای OTP (نه DB)
**نقاط ضعف:**
- N+1 Query احتمالی — response دکتر شامل specialties، expertise، address، state، city است؛ بدون eager loading، هر doctor یک batch جداگانه query ایجاد می‌کند
- بدون Query Result Cache — categories، lookups با هر request از DB خوانده می‌شوند
### Reliability
**نقاط قوت:**
- Status machine واضح برای appointments و payments
- Soft delete با deleted_at
**نقاط ضعف:**
- Payment gateway single point of failure — اگر Mellat یا SEP در دسترس نباشد، سیستم نوبت‌دهی متوقف می‌شود
- SMS sync — اگر KavehNegar/Rangineh fail شود، OTP ارسال نمی‌شود و کاربر مسدود می‌شود
- بدون Circuit Breaker برای external services
### Testability
**نقاط ضعف:**
- هیچ اشاره‌ای به test strategy نشده
- Business logic در کجا؟ اگر در Controller باشد، unit test غیرممکن می‌شود
- هیچ fixture/seeder برای category data (۳۱ استان + شهرها) تعریف نشده
### Observability
**ضعف کامل:**
- بدون logging strategy
- بدون health check endpoint
- بدون metrics (Prometheus/Grafana)
- بدون distributed tracing
- بدون alerting
---
## ۳. تحلیل Design Patterns
### Pattern هایی که استفاده شده‌اند
| Pattern | کجا | ارزیابی |
|---------|-----|---------|
| Repository Pattern | ضمنی از Doctrine | ✅ درست اما باید صریح تعریف شود |
| DTO | اشاره نشده | ❌ باید اضافه شود |
| Strategy | payment gateways (Mellat/SEP) | ⚠️ تعریف نشده اما ضروری است |
| Observer/Event | SMS async | ❌ وجود ندارد — باید با Symfony Messenger پیاده شود |
| Status Machine | appointments/payments | ✅ خوب تعریف شده |
| Multi-tenant (Representation) | داشبورد | ✅ معقول |
### Anti-Pattern هایی که مشاهده می‌شوند
**۱. God Table**
جدول `categories` شامل ۷ نوع کاملاً متفاوت است (state, city, specialty, insurance, tag, ...). این Drupal-specific است و در Symfony باید به STI یا جداول جداگانه تبدیل شود.
**۲. Anemic Domain Model**
Entity ها فقط data holder هستند. هیچ Business Logic در آنها نیست. اگر همه منطق در Controller باشد، Fat Controller anti-pattern اجتناب‌ناپذیر است.
**۳. Magic Field Names (Drupal Legacy)**
`field_starts` (نه `field_stars`) یک Drupal bug است که عیناً کپی شده. در Symfony باید در mapping layer تبدیل شود، نه مستقیم در Entity.
**۴. Implicit API Contract**
هیچ DTO برای Input/Output تعریف نشده. هر Controller می‌تواند هر فرمتی برگرداند.
---
## ۴. تحلیل Domain Design
### Domain Model ها
| Domain | Entity ها | وضعیت |
|--------|----------|--------|
| Identity | User | ✅ خوب — uuid، mobile، realname، roles |
| Medical | Doctor، Clinic، DoctorAddress | ✅ معقول |
| Scheduling | WeeklySchedule، DateOverride، Holiday | ✅ خوب |
| Booking | Appointment، Slot | ⚠️ فلوی کامل مستند نشده |
| Financial | Payment، SubscriptionPayment | ⚠️ subscription endpoints مفقود |
| Community | Rating، Comment، Like | ✅ ساختار خوب |
| Catalog | Category (god table) | ❌ باید refactor شود |
| Tenancy | Representation، Agent | ✅ معقول |
### Bounded Context ها
مشکل اصلی: **Bounded Context های صریح تعریف نشده‌اند.**
در Drupal، همه چیز در یک entity type است (`clinic_pro`). در Symfony باید مرزهای مشخص بین:
- **Identity Context** (User، Auth، OTP)
- **Clinical Context** (Doctor، Clinic، Address)
- **Scheduling Context** (WeeklySchedule، Appointment)
- **Financial Context** (Payment، Subscription)
- **Community Context** (Rating، Comment، Like)
- **Catalog Context** (Categories، Lookups)
- **Tenant Context** (Representation، Agent)
### Separation of Concerns
**مشکل:** در هیچ‌جا تعریف نشده Business Logic کجا قرار می‌گیرد:
- محاسبه `free_turn` — Controller؟ Service؟ Entity؟
- محاسبه `experience` از `activity_time` — کجا؟
- فرمول rating — کجا؟
بدون تعریف صریح این، هر developer به سلیقه خود عمل می‌کند.
---
## ۵. تحلیل Database
### جداول شناسایی‌شده (۳۲ جدول)
**User Management:**
- `users` — uuid، mobile، password، realname، roles (JSON)، status (TINYINT)، created_at/updated_at (INT)
- `user_profiles` — سوابق پزشکی، آلرژی، دارو، جراحی
**Clinical Entities:**
- `doctors`، `doctor_addresses`، `doctor_specialties`، `doctor_services`، `doctor_states`، `doctor_cities`
- `clinics`، `clinic_doctors`، `clinic_specialties`، `clinic_services`، `clinic_insurances`، `clinic_images`
- `representations`، `doctor_secretaries`
**Scheduling & Booking:**
- `weekly_schedules`، `date_overrides`، `holidays`، `appointments`
**Financial:**
- `payments`، `subscription_payments`، `doctor_insurance`
**Content & Community:**
- `blogs`، `ratings`، `comments`، `likes`
**Catalog:**
- `categories` (god table با bundle field)
**System:**
- `files`، `sms_logs`
### مشکلات Database
**۱. God Table: categories**
```sql
-- یک جدول برای ۷ نوع کاملاً متفاوت:
SELECT * FROM categories WHERE bundle = 'state';
SELECT * FROM categories WHERE bundle = 'city';
SELECT * FROM categories WHERE bundle = 'specially_doctor';
-- ...
```
پیشنهاد: STI با Doctrine Inheritance یا جداول جداگانه برای هر نوع
**۲. JSON Columns بدون Index**
```sql
-- weekly_schedules.setting → JSON (7-day schedule)
-- appointments.slot → JSON (time، duration، location_id)
-- doctor_secretaries.permission → JSON (undefined structure)
```
این فیلدها قابل index نیستند. جستجو روی آنها Full Table Scan است.
**۳. Timestamp به عنوان INT**
تمام `created_at`/`updated_at` به صورت Unix timestamp (INT) ذخیره می‌شوند.
این درست اما مستعد اشتباه است — باید در همه جا consistent باشد.
**۴. Bottleneck احتمالی**
- `appointments` جدول داغ است (read/write زیاد) — index composite روی `(doctor_id, start_time)` لازم است
- `ratings` باید aggregate view داشته باشد برای `average_rate` تا N+1 نشود
### ایندکس‌های مناسب
```sql
-- اضافه کردن این ایندکس‌ها توصیه می‌شود:
CREATE INDEX idx_appointments_doctor_time ON appointments(doctor_id, start_time);
CREATE INDEX idx_appointments_status ON appointments(status);
CREATE INDEX idx_ratings_doctor ON ratings(doctor_id);
CREATE INDEX idx_comments_doctor_approved ON comments(doctor_id, approved);
CREATE INDEX idx_categories_bundle ON categories(bundle);
```
---
## ۶. تحلیل API Design
### نقاط قوت
- ✅ Versioning با `/api/v1/` در URL
- ✅ UUID در public endpoints
- ✅ Pagination استاندارد (`page`، `limit`، `totalRecords`، `totalPages`)
- ✅ HTTP methods صحیح (GET/POST/PATCH/DELETE)
- ✅ Bearer token authentication
### نقاط ضعف
**۱. Naming Convention ناسازگار**
| Endpoint | فیلد | درست‌تر |
|----------|------|---------|
| GET /doctor | `img` | `images` |
| GET /clinic | `images_clinic` | `images` |
| GET /clinic | `phone_number` | `phoneNumber` یا `phone` |
| POST categories | `/api/v1/categorys/` | `/api/v1/categories/` (Drupal typo کپی شده) |
**۲. Error Format استاندارد وجود ندارد**
هیچ‌جا فرمت خطا تعریف نشده. کلاینت نمی‌داند چه انتظاری داشته باشد.
**۳. Request→DB Field Mapping مستند نشده**
| نام در Request | نام در DB |
|---------------|---------|
| `correct_diagnosis` | `accuracy_of_diagnosis` |
| `doctor_skill` | `doctor_expertise` |
| `behavior_doctor` | `doctor_behavior` |
| `office_cleaning` | `clinic_cleanliness` |
| `time_in_office` | `waiting_time_at_clinic` |
این mapping در هیچ لایه‌ای صریح تعریف نشده.
**۴. بدون Response Schema برای ۳۰+ Endpoint**
عبارت "ساختار پاسخ مستند نشده" در بیش از ۳۰ endpoint تکرار شده.
**۵. Timestamp فرمت ناسازگار**
بعضی response ها timestamp را string برمی‌گردانند (`"activity_time": "1107808200"`)، بعضی integer. استانداردی وجود ندارد.
---
## ۷. تحلیل امنیت
### Authentication
| مورد | وضعیت | ریسک |
|------|--------|-------|
| OTP via SMS | ✅ | Low |
| JWT (3600s TTL) | ✅ | Low |
| Refresh Token | ❌ وجود ندارد | Medium — کاربر هر ساعت باید re-auth کند |
| Password Hashing | ✅ bcrypt | Low |
| Mobile as Username | ✅ | Low |
### Authorization
| مورد | وضعیت | ریسک |
|------|--------|-------|
| Role-based (authenticated, doctor, admin) | ✅ | Low |
| Owner check در PATCH | ✅ | Low |
| Admin-only endpoints | ✅ | Low |
| Secretary permissions | ⚠️ JSON بدون schema | High — هر implementer می‌تواند اشتباه implement کند |
### Rate Limiting
| مورد | وضعیت | ریسک |
|------|--------|-------|
| 50 req/hr per IP | ✅ | Low |
| 30 req/hr per mobile | ✅ | Low |
| OTP attempt limiting | ⚠️ نامشخص | Medium |
| No rate limit header در response | ❌ | Low — UX ضعیف |
### Input Validation
| مورد | وضعیت | ریسک |
|------|--------|-------|
| CSRF Token | ⚠️ Inconsistent | Medium |
| MIME validation در upload | ✅ | Low |
| SQL Injection | ✅ Doctrine ORM | Low |
| XSS | ⚠️ تعریف نشده | Medium — JSON response، اما اگر HTML render شود |
| File size limit | ⚠️ تعریف نشده | Medium — DoS از طریق بارگذاری فایل بزرگ |
### Secrets Management
| مورد | وضعیت | ریسک |
|------|--------|-------|
| JWT keys در فایل‌سیستم | ⚠️ | Medium برای production |
| DB credentials در .env | ⚠️ | Medium برای production |
| SMS API keys | ⚠️ در .env | Medium |
| Dev OTP code ثابت (12345) | ⚠️ | Low اگر فقط در dev باشد |
### Logging Security
**هیچ logging strategy تعریف نشده.** موارد زیر باید log شوند:
- تلاش‌های ناموفق OTP
- تغییر role کاربر
- حذف entity ها
- payment transactions
- دسترسی‌های رد شده
---
## ۸. تحلیل مقیاس‌پذیری
### با ۱۰,۰۰۰ کاربر — مشکل جدی نیست
معماری فعلی این تعداد را handle می‌کند با:
- یک MySQL server
- یک Redis instance
- یک PHP-FPM instance
### با ۱۰۰,۰۰۰ کاربر — مشکلات شروع می‌شوند
| مشکل | علت | راه‌حل |
|------|-----|---------|
| لیست دکتر با فیلتر کند می‌شود | Full scan روی JSON columns | Read replica + Elasticsearch برای جستجو |
| Categories هر بار از DB | بدون cache | Redis cache با TTL=300s |
| SMS در صف می‌ماند | Sync call | Symfony Messenger + Queue |
| JWT validation سنگین | هر request decode می‌شود | Redis token blacklist |
### با ۱,۰۰۰,۰۰۰ کاربر — نیاز به Refactoring اساسی
| سرویس | مشکل | راه‌حل |
|-------|------|---------|
| Appointments | Hot table — write contention | Sharding بر اساس doctor_id |
| Search | MySQL full-text کافی نیست | Elasticsearch |
| File Upload | Local filesystem | S3-compatible object storage |
| SMS | Single provider | Multi-provider با queue |
| Auth | Stateless JWT کافی است | Redis session store برای blacklist |
**Microservice یا Modular Monolith؟**
در این مرحله: **Modular Monolith** توصیه می‌شود.
دلایل:
- تیم کوچک
- Domain boundaries هنوز در حال تثبیت
- Microservice overhead (distributed tracing، service mesh، network latency) در این مرحله ارزشش را ندارد
آینده (بعد از ۱۰۰K): **Financial Context** (Payment) و **Notification Context** (SMS) کاندیداهای اول برای جداسازی هستند.
---
## ۹. تحلیل ساختار پروژه
### ساختار پیشنهادی فعلی (نامشخص)
هیچ‌جا ساختار پوشه صریح تعریف نشده. احتمال پیش‌فرض Symfony:
```
src/
Controller/
Entity/
Repository/
Service/
```
این ساختار Layer-based است و برای ۱۷ ماژول با ۹۵ endpoint به سرعت به هم می‌ریزد.
### ساختار توصیه‌شده — Domain-Driven
```
src/
Doctor/
Controller/
DoctorController.php
Entity/
Doctor.php
DoctorAddress.php
Repository/
DoctorRepository.php
Service/
DoctorService.php
DoctorRatingService.php
DTO/
DoctorRequest.php
DoctorResponse.php
Event/
DoctorCreatedEvent.php
Clinic/
Controller/
Entity/
Repository/
Service/
DTO/
Appointment/
...
Payment/
Gateway/
MellatGateway.php
SepGateway.php
PaymentGatewayInterface.php
...
Shared/
Response/
ApiResponse.php
ApiError.php
Controller/
BaseController.php
Repository/
BaseRepository.php
```
### Dependency Direction
باید یک‌طرفه باشد:
```
Controller → Service → Repository → Entity
```
هیچ‌گاه:
```
Entity → Service ❌
Repository → Controller ❌
```
---
## ۱۰. ریسک‌های شناسایی‌شده
### ریسک‌های بحرانی
| ریسک | Severity | Probability | Impact | راه‌حل |
|------|----------|-------------|--------|--------|
| ۳۰+ endpoint بدون response schema | Critical | High | Frontend/Backend diverge | مستندسازی قبل از کدنویسی |
| Business logic rating تعریف‌نشده | Critical | High | نتایج اشتباه | مستندسازی فرمول |
| Payment flow ناقص | Critical | High | از دست رفتن پرداخت | تعریف کامل فلو |
| ساختار پوشه تعریف‌نشده | High | High | کد ناهماهنگ ۱۷ ماژول | تعریف قبل از شروع |
### ریسک‌های مهم
| ریسک | Severity | Probability | Impact | راه‌حل |
|------|----------|-------------|--------|--------|
| بدون Refresh Token | High | Certain | UX ضعیف، re-login مکرر | پیاده‌سازی refresh token |
| SMS sync blocking | High | High | OTP fail → کاربر مسدود | Symfony Messenger |
| N+1 Query در لیست دکتر | High | High | کندی با رشد data | Eager loading + cache |
| God Table categories | High | Medium | جستجوی کند، maintenance سخت | Refactor در فاز اول |
| Secretary permissions بدون schema | High | High | پیاده‌سازی ناهماهنگ | تعریف JSON schema |
### ریسک‌های عملیاتی
| ریسک | Severity | Probability | Impact | راه‌حل |
|------|----------|-------------|--------|--------|
| بدون health check | Medium | Certain | نمی‌توان مشکل را سریع تشخیص داد | اضافه کردن `/health` |
| بدون logging | High | Certain | debug تولید غیرممکن | Structured logging از ابتدا |
| Secrets در .env | Medium | High | leak در git | Vault یا CI/CD secrets |
| Dev OTP ثابت (12345) | Low | High | باید env-based باشد | `APP_ENV=dev` conditional |
---
## Missing Requirements (کامل)
| # | مورد | ماژول | شدت |
|---|------|-------|-----|
| ۱ | Response schema برای ۳۰+ endpoint | همه | Critical |
| ۲ | فرمول وزنی محاسبه rating | task-12 | Critical |
| ۳ | فلوی کامل payment (trigger، failure، refund، cancel) | task-15 | Critical |
| ۴ | Error response format استاندارد | task-01 | Critical |
| ۵ | ساختار پوشه Domain-Driven | task-01 | High |
| ۶ | منطق تولید `free_turn` و `hours_of_work` | task-05/09 | High |
| ۷ | Refresh Token mechanism | task-02 | High |
| ۸ | Subscription payment endpoints | task-15 | High |
| ۹ | ساختار JSON فیلد `permissions` دبیران | task-14 | High |
| ۱۰ | Appointment cancellation و refund flow | task-10/15 | High |
| ۱۱ | Logging strategy | task-01 | High |
| ۱۲ | Health check endpoint (`/health`) | task-01 | Medium |
| ۱۳ | SMS provider fallback logic | task-17 | Medium |
| ۱۴ | File upload error handling و size limit | همه | Medium |
| ۱۵ | City-specific management endpoints | task-08 | Medium |
| ۱۶ | Comment reply/nesting (field_parent) | task-12 | Medium |
| ۱۷ | Fixtures/Seeders برای category data | task-08 | Medium |
| ۱۸ | Rate limit headers در response | task-02 | Low |
---
## Architecture Violations
| # | نقض | اصل | راه‌حل |
|---|-----|-----|--------|
| ۱ | God Table `categories` | Single Responsibility | STI یا جداول جداگانه |
| ۲ | God Table `clinic_pro` از Drupal | Bounded Context | Entity های جداگانه در Symfony |
| ۳ | بدون DTO برای Input/Output | Explicit API Contract | DTO class برای هر endpoint |
| ۴ | Magic field name `field_starts` (نه `field_stars`) | Clarity | Mapping layer صریح |
| ۵ | Naming convention ناسازگار در response ها | Convention over Configuration | استاندارد یکپارچه |
| ۶ | Business logic تعریف‌نشده | Separation of Concerns | Service layer صریح |
| ۷ | Typo در URL (`categorys`) کپی از Drupal | REST conventions | در Symfony با redirect fix کن |
| ۸ | Timestamp گاهی string گاهی int در response | Type Consistency | همیشه int |
---
## Recommended Improvements
### اولویت ۱ — قبل از شروع کدنویسی
**۱. استاندارد Error/Success Response**
```json
// موفق:
{
"success": true,
"data": { ... },
"meta": { "page": 1, "totalPages": 5, "totalRecords": 47 }
}
// خطا:
{
"success": false,
"data": null,
"errors": [
{ "code": "ERR_VALIDATION_001", "field": "mobile_number", "message": "فرمت نادرست است" }
]
}
```
**۲. Error Codes استاندارد**
```
ERR_AUTH_001 = توکن منقضی شده
ERR_AUTH_002 = OTP نامعتبر
ERR_AUTH_003 = OTP منقضی شده
ERR_VALIDATION_001 = ورودی نامعتبر
ERR_NOT_FOUND_001 = منبع یافت نشد
ERR_FORBIDDEN_001 = دسترسی ندارید
ERR_PAYMENT_001 = درگاه پرداخت در دسترس نیست
ERR_PAYMENT_002 = مبلغ نامعتبر
```
**۳. تعریف فرمول Rating**
```php
const RATING_WEIGHTS = [
'accuracy_of_diagnosis' => 3.0,
'doctor_expertise' => 2.0,
'doctor_behavior' => 1.5,
'waiting_time_at_clinic' => 1.0,
'clinic_cleanliness' => 1.0,
];
// weightedAverage = SUM(value * weight) / SUM(weights) → از 100
// stars = (weightedAverage / 100) * 5 → از 5
```
**۴. تعریف ساختار پوشه صریح در task-01**
### اولویت ۲ — در حین پیاده‌سازی
**۵. Refresh Token**
```
POST /oauth/token
{ "grant_type": "refresh_token", "refresh_token": "..." }
→ refresh token در Redis با TTL=30 روز
```
**۶. Symfony Messenger برای Async Operations**
```bash
ddev composer require symfony/messenger
```
SMS، notification، email — همه از طریق Queue
**۷. Repository Pattern صریح**
```php
// هر entity باید Repository خودش داشته باشد
class DoctorRepository extends ServiceEntityRepository
{
public function findWithFilters(array $filters, int $page, int $limit): array
public function findByUuidWithRelations(string $uuid): ?Doctor
}
```
**۸. DTO برای Input/Output**
```php
class DoctorRequest
{
#[Assert\NotBlank]
public string $title;
#[Assert\Range(min: 0, max: 100)]
public int $experience;
}
class DoctorResponse
{
public function __construct(Doctor $doctor) { ... }
public function toArray(): array { ... }
}
```
### اولویت ۳ — برای آماده‌سازی تولید
**۹. Health Check**
```
GET /health
→ { "status": "ok", "db": "ok", "redis": "ok", "timestamp": 1748000000 }
```
**۱۰. Redis Cache برای Lookup Data**
```php
// categories، states، cities — TTL=300s
$states = $cache->get('categories.state', fn() => $repo->findByBundle('state'));
```
**۱۱. Structured Logging**
```php
$this->logger->info('appointment.created', [
'user_id' => $user->getId(),
'doctor_id' => $doctor->getId(),
'start_time' => $startTime,
'request_id' => $requestId,
]);
```
**۱۲. Query Optimization**
```php
// Eager loading برای جلوگیری از N+1
$doctor = $repo->createQueryBuilder('d')
->leftJoin('d.specialties', 's')->addSelect('s')
->leftJoin('d.addresses', 'a')->addSelect('a')
->where('d.uuid = :uuid')
->getQuery()->getOneOrNullResult();
```
---
## Refactoring Plan
### فاز ۰ — مستندسازی (۳ تا ۵ روز، قبل از هر کدنویسی)
- [ ] تعریف `ApiResponse` و `ApiError` format در task-01
- [ ] مستندسازی Response schema تمام ۳۰+ endpoint گم‌شده
- [ ] تعریف فرمول rating در task-12
- [ ] تعریف کامل فلوی payment (trigger، failure، refund) در task-15
- [ ] تعریف ساختار JSON `permissions` دبیران در task-14
- [ ] تعریف ساختار پوشه Domain-Driven در task-01
- [ ] تصمیم‌گیری: Refresh Token — بله یا خیر
### فاز ۱ — زیرساخت پایه (task-01)
- [ ] اضافه کردن `symfony/messenger` به پکیج‌ها
- [ ] ایجاد `BaseController` با متدهای `success()` و `error()`
- [ ] ایجاد `BaseRepository` با متدهای مشترک
- [ ] اضافه کردن `GET /health` endpoint
- [ ] تعریف Error Code constants
- [ ] راه‌اندازی Structured Logging با Monolog
### فاز ۲ — پیاده‌سازی ماژول‌ها (به ترتیب dependency)
```
01 → 02 → 08 → 03 → 04 → 05 → 06 → 07 → 09 → 11 → 10 → 12 → 13 → 14 → 15 → 16 → 17
```
برای هر ماژول:
1. Entity + Migration
2. Repository با Eager loading
3. DTO (Request + Response)
4. Service با Business Logic
5. Controller با Swagger attributes
6. Tests
### فاز ۳ — بهینه‌سازی (بعد از پیاده‌سازی)
- [ ] Redis cache برای categories و lookup data
- [ ] Eager loading در تمام list endpoints
- [ ] Integration tests برای payment flow
- [ ] Load test برای لیست دکتر با فیلتر چندگانه
- [ ] Review و تکمیل Swagger documentation
### فاز ۴ — آماده‌سازی تولید
- [ ] Secrets به محیط CI/CD منتقل شوند (خارج از .env)
- [ ] Health check به monitoring متصل شود
- [ ] Read replica برای query های سنگین
- [ ] File storage به S3-compatible منتقل شود
---
## اصلاحات اعمال‌شده (بعد از Audit)
| # | مشکل | فایل اصلاح‌شده | وضعیت |
|---|------|---------------|--------|
| ۱ | ساختار پوشه Domain-Driven | task-01/task.md | ✅ |
| ۲ | Error Codes استاندارد | task-01/task.md | ✅ |
| ۳ | BaseController با success/error | task-01/task.md | ✅ |
| ۴ | Symfony Messenger | task-01/task.md | ✅ |
| ۵ | Health Check endpoint | task-01/task.md | ✅ |
| ۶ | Structured Logging | task-01/task.md | ✅ |
| ۷ | **CORS فقط دامنه‌های مشخص (نه *)** | task-01/implementation_notes.md | ✅ |
| ۸ | **Security Headers (X-Frame، HSTS، CSP)** | task-01/implementation_notes.md | ✅ |
| ۹ | **Swagger فقط در dev** | task-01/implementation_notes.md | ✅ |
| ۱۰ | **Audit Log جدول security_logs** | task-01/implementation_notes.md | ✅ |
| ۱۱ | **security.yaml کامل با access_control** | task-01/implementation_notes.md | ✅ |
| ۱۲ | Refresh Token + Logout + Blacklist | task-02/task.md | ✅ |
| ۱۳ | **hash_equals() برای OTP — جلوگیری از Timing Attack** | task-02/task.md | ✅ |
| ۱۴ | **Refresh Token هش‌شده در Redis (نه plain text)** | task-02/task.md | ✅ |
| ۱۵ | Rate Limit Headers در response | task-02/task.md | ✅ |
| ۱۶ | Status Machine کامل نوبت | task-10/task.md | ✅ |
| ۱۷ | فلوی لغو + refund | task-10/task.md | ✅ |
| ۱۸ | **Open Redirect در frontend_address پرداخت** | task-15/task.md | ✅ |
| ۱۹ | **IP Whitelist Callback با implementation** | task-15/task.md | ✅ |
| ۲۰ | Circuit Breaker + Idempotency | task-15/task.md | ✅ |
| ۲۱ | Subscription Payment endpoints | task-15/task.md | ✅ |
| ۲۲ | Secretary permissions JSON schema | task-14/task.md | ✅ |
| ۲۳ | SMS Fallback + Async Messenger | task-17/task.md | ✅ |
| ۲۴ | **File upload: magic bytes بجای MIME header** | task-01/task.md | ✅ |
| ۲۵ | **Filename sanitization — path traversal** | task-01/task.md | ✅ |
| ۲۶ | فرمت Response ناسازگار در architecture.md | task-01/architecture.md | ✅ |
| ۲۷ | تسویه نماینده | task-18-settlement/ | ✅ |
---
## مشکلات امنیتی باقی‌مانده (نیاز به توجه در پیاده‌سازی)
| # | مشکل | اولویت | راه‌حل |
|---|------|--------|--------|
| ۱ | Input HTML sanitization در فیلدهای متنی (detail، caption) | High | استفاده از `htmlspecialchars()` یا `strip_tags()` در DTO |
| ۲ | Mass assignment در PATCH endpoints | Medium | فقط فیلدهای مجاز را از request map کن |
| ۳ | JWT passphrase پیش‌فرض ضعیف | High | در production حتماً با `openssl rand -hex 32` تغییر داده شود |
| ۴ | Dev OTP ثابت (12345) | Medium | حتماً فقط با `APP_ENV=dev` فعال شود — هرگز در production |
| ۵ | Health check اطلاعات سیستم را افشا می‌کند | Low | در production به IP های داخلی محدود شود |
---
## Final Verdict (بعد از اصلاحات)
```
✅ APPROVED
```
### دلیل تصمیم
تمام مشکلات بحرانی و اکثر مشکلات مهم رفع شده‌اند:
- **امنیت:** Timing Attack، CORS wildcard، Open Redirect، Security Headers، Refresh Token hashing، IP Whitelist، Audit Log — همه رفع شدند
- **معماری:** Domain-Driven structure، DTO pattern، Error Codes، Response format یکپارچه
- **قابلیت اطمینان:** Payment flow کامل، Circuit Breaker، Idempotency، SMS fallback
- **مستندسازی:** همه endpoint های بحرانی با schema کامل مستند شدند
باقیمانده موارد (Mass Assignment، Input sanitization) در لایه پیاده‌سازی با Symfony Validator و DTO به سادگی قابل رفع هستند.
---
*آخرین بروزرسانی: ۱۴۰۵/۰۳/۱۸ — بعد از اصلاح کامل*