- 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.
824 lines
33 KiB
Markdown
824 lines
33 KiB
Markdown
# 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 به سادگی قابل رفع هستند.
|
||
|
||
---
|
||
|
||
*آخرین بروزرسانی: ۱۴۰۵/۰۳/۱۸ — بعد از اصلاح کامل*
|