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.
This commit is contained in:
hamed
2026-06-09 22:00:34 +03:30
commit de1a78a235
222 changed files with 36388 additions and 0 deletions
+823
View File
@@ -0,0 +1,823 @@
# 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 به سادگی قابل رفع هستند.
---
*آخرین بروزرسانی: ۱۴۰۵/۰۳/۱۸ — بعد از اصلاح کامل*