hamedandClaude Opus 5 c7fdb92df4 feat(admin): practice domain settings, treatment case list and platform domain CRUD
Completes the panel side. A clinic picks its practice domain in settings, where
the copy says plainly that this is not the specialty label the public site shows;
picking nothing stays valid and changes nothing.

Cases and the unbooked queue share one page rather than two, because they answer
the same question — which patient is where in their course and what is still
owed. The queue explains why booking is not automatic instead of leaving the
reader to wonder.

Platform admins get domain CRUD with a column showing whether a domain has a
dedicated workflow or falls back to the default, so the gap is visible rather
than guessed at; the code field is locked after creation because workflows bind
to it.

Also puts the staff session screens in the sidebar — they were reachable only by
typing the URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:51:21 +03:30
2026-07-26 10:40:39 +03:30
2026-07-02 10:51:48 +03:30

ClinicPro — مستندات کامل پروژه

آخرین به‌روزرسانی: ۱۴۰۵/۰۳/۲۱ | Symfony: 7.x | PHP: ≥ 8.2 | React: 19


فهرست مطالب

  1. معرفی پروژه
  2. تکنولوژی Stack
  3. راه‌اندازی محیط توسعه
  4. آدرس‌های مهم
  5. متغیرهای محیطی
  6. دستورات Console
  7. امنیت و احراز هویت
  8. پایگاه داده — جداول و موجودیت‌ها
  9. API Endpoints
  10. پنل ادمین React
  11. Migrations
  12. سرویس‌های خارجی
  13. ساختار دایرکتوری

۱. معرفی پروژه

ClinicPro سیستم جامع مدیریت کلینیک و نوبت‌دهی پزشکی است — مهاجرت‌یافته از Drupal به Symfony 7. شامل یک REST API کامل و یک پنل ادمین React SPA داخل Symfony.

ویژگی‌های اصلی

  • احراز هویت OTP + رمز عبور + JWT
  • مدیریت پزشکان با پروفایل کامل، تخصص، آدرس‌های مطب
  • مدیریت کلینیک‌ها با گالری، نقشه، دعوت پزشک
  • سیستم نوبت‌دهی با اسلات هفتگی، روزهای استثناء، تعطیلات
  • درگاه پرداخت (ملت + سپ) با کیف پول و تسویه‌حساب
  • مدیریت نمایندگان (agents) با کمیسیون
  • منشی با دسترسی‌های دانه‌ای (fine-grained permissions)
  • سیستم دعوتنامه پزشک به کلینیک با توکن ۷۲ ساعته
  • ارسال پیامک async از طریق KaveNegar / Rangineh
  • امتیاز و نظرات پزشکان
  • بلاگ با مدیریت کامل
  • داشبورد آمار real-time
  • Swagger UI محافظت‌شده با HTTP Basic Auth

۲. تکنولوژی Stack

Backend

لایه تکنولوژی
Framework Symfony 7.x
PHP ≥ 8.2
ORM Doctrine ORM
Auth LexikJWT + OTP
Queue Symfony Messenger
Cache Redis
Database MariaDB 11.8
API Docs NelmioApiDocBundle + swagger-php
Rate Limiting Symfony Rate Limiter

Frontend (Admin SPA)

لایه تکنولوژی
Framework React 19 + TypeScript
Routing React Router DOM v7
Server State TanStack Query v5
Client State Zustand v5 (persist)
Forms React Hook Form + Zod
Charts Recharts
Maps React Leaflet 5
Icons Heroicons v2
Notifications Sonner
Date jalaali-js
Build Webpack Encore 6

۳. راه‌اندازی محیط توسعه

پیش‌نیازها

  • ddev نصب‌شده

مراحل

# ۱. clone و وارد شدن به پروژه
git clone <repo-url> clinic-pro-symfony
cd clinic-pro-symfony

# ۲. راه‌اندازی ddev
ddev start

# ۳. نصب وابستگی‌های PHP
ddev exec composer install

# ۴. تولید کلیدهای JWT
ddev exec php bin/console lexik:jwt:generate-keypair

# ۵. اجرای migration های پایگاه داده
ddev exec php bin/console doctrine:migrations:migrate --no-interaction

# ۶. seed دسته‌بندی‌ها (استان، شهر، تخصص، خدمات پزشک) از data/seed/*.json
ddev exec php bin/console app:seed-categories

# ۷. نصب وابستگی‌های Node و بیلد فرانت‌اند
ddev exec yarn install
ddev exec yarn dev

# ۸. ایجاد اولین ادمین (اختیاری)
ddev exec php bin/console app:create-admin

دستورات روزانه

# بیلد فرانت‌اند (یکبار)
ddev exec yarn dev

# حالت watch (هنگام توسعه)
ddev exec yarn watch

# پاک کردن cache بعد از تغییر backend
ddev exec php bin/console cache:clear

# اجرای queue worker
ddev exec php bin/console messenger:consume async

۴. آدرس‌های مهم

سرویس آدرس
وب‌سایت https://clinic-pro.ddev.site
پنل ادمین https://clinic-pro.ddev.site/admin
Swagger UI https://clinic-pro.ddev.site/api/doc
Health Check https://clinic-pro.ddev.site/health

ورود به Swagger UI

آدرس: https://clinic-pro.ddev.site/api/doc

فیلد مقدار
نام کاربری admin
رمز عبور clinic123

پس از ورود، برای احراز هویت API درون Swagger ابتدا از /api/v1/user/login توکن بگیرید، سپس دکمه Authorize را بزنید و Bearer <token> وارد کنید.


۵. متغیرهای محیطی

فایل .env (مقادیر پیش‌فرض توسعه):

# ── برنامه ─────────────────────────────────────────────────────
APP_ENV=dev
APP_SECRET=clinic_pro_secret_change_in_prod
DEFAULT_URI=https://clinic-pro.ddev.site
APP_BASE_URL=https://clinic-pro.ddev.site

# ── پایگاه داده ──────────────────────────────────────────────────
DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=8.0&charset=utf8mb4"

# ── JWT ─────────────────────────────────────────────────────────
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=<رشته تصادفی>

# ── CORS ────────────────────────────────────────────────────────
CORS_ALLOW_ORIGIN='^https?://(clinic-pro\.ddev\.site|localhost)(:[0-9]+)?$'
ALLOWED_FRONTEND_HOSTS=clinic-pro.ddev.site,localhost
# پروداکشن چند-دامنه‌ای (فرانتِ هر شهر): مقدار این دو متغیر را با اسکریپت بساز —
#   ddev exec php docker/gen-cors-env.php
# دامنه‌ها از docker/frontend-domains.json خوانده می‌شوند؛ برای افزودن شهر، آن فایل را
# ویرایش و دوباره اجرا کن و خروجی را در env سرور بگذار.

# ── صف پیام و Cache ──────────────────────────────────────────────
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages
REDIS_URL=redis://redis:6379

# ── توکن‌ها ──────────────────────────────────────────────────────
REFRESH_TOKEN_TTL=2592000   # ۳۰ روز (ثانیه)
OTP_TTL=1200                # ۲۰ دقیقه (ثانیه)

# ── پیامک ────────────────────────────────────────────────────────
SMS_PROVIDER=kavenegar
KAVENEGAR_API_KEY=<کلید>
KAVENEGAR_SENDER=<شماره فرستنده>
RANGINEH_API_KEY=<کلید>
RANGINEH_SENDER=<شماره فرستنده>

# ── فایل آپلود ───────────────────────────────────────────────────
MAX_FILE_SIZE_BYTES=5242880   # 5MB
UPLOAD_DIR=var/uploads

# ── درگاه پرداخت ─────────────────────────────────────────────────
MELLAT_TERMINAL_ID=<شناسه>
MELLAT_USERNAME=<کاربری>
MELLAT_PASSWORD=<رمز>
SEP_TERMINAL_ID=<شناسه>

# ── Swagger UI ───────────────────────────────────────────────────
API_DOC_USERNAME=admin
API_DOC_PASSWORD=<bcrypt hash>   # plain text: admin@admin

۶. دستورات Console

# ── Cache ────────────────────────────────────────────────────────
ddev exec php bin/console cache:clear

# ── Database ─────────────────────────────────────────────────────
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php bin/console doctrine:migrations:diff --no-interaction   # بعد از تغییر entity
ddev exec php bin/console doctrine:migrations:status

# ── Debug ────────────────────────────────────────────────────────
ddev exec php bin/console debug:router | grep api
ddev exec php bin/console debug:container | grep -i service

# ── CORS چند-دامنه‌ای (تولید CORS_ALLOW_ORIGIN + ALLOWED_FRONTEND_HOSTS) ──
ddev exec php docker/gen-cors-env.php   # خروجی را در env سرور (Liara/Coolify) بگذار

# ── Queue ────────────────────────────────────────────────────────
ddev exec php bin/console messenger:consume async

# ── Test ─────────────────────────────────────────────────────────
ddev exec php bin/phpunit
ddev exec php bin/phpunit tests/SomeTest.php

# ── Static Analysis ──────────────────────────────────────────────
ddev exec php vendor/bin/phpstan analyse

# ── Frontend ─────────────────────────────────────────────────────
ddev exec yarn dev          # بیلد یکبار
ddev exec yarn watch        # حالت watch
ddev exec yarn build        # پروداکشن
ddev exec npx tsc --noEmit  # فقط type check

۷. امنیت و احراز هویت

جریان احراز هویت

۱. POST /api/v1/user/send-code   → ارسال OTP به موبایل
۲. POST /api/v1/user/verify-code → تأیید OTP → دریافت uuid
۳. POST /oauth/token             → ارسال uuid → دریافت JWT + refresh token
۴. Authorization: Bearer <jwt>  → همراه هر درخواست محافظت‌شده

جریان Login با رمز عبور

POST /api/v1/user/login
{ "mobile_number": "09...", "password": "..." }
→ { "access_token": "...", "refresh_token": "..." }

نقش‌های کاربری

نقش توضیح دسترسی
ROLE_USER پایه — همه کاربران نوبت‌گیری، پروفایل
ROLE_ADMIN مدیر کل پنل ادمین کامل
ROLE_CLINIC صاحب کلینیک مدیریت کلینیک خود
ROLE_DOCTOR پزشک مدیریت نوبت‌ها، پروفایل
ROLE_SECRETARY منشی بر اساس permissions دکتر

دسترسی‌های منشی (JSON)

{
    "version": 1,
    "resources": {
        "appointments": {
            "view": true,
            "create": true,
            "cancel": false,
            "update_status": true
        },
        "addresses": {
            "view": true,
            "create": false,
            "update": false,
            "delete": false
        },
        "clinic_info": { "view": true, "update": false },
        "insurances": {
            "view": true,
            "create": false,
            "update": false,
            "delete": false
        }
    }
}

JWT

  • مدت اعتبار access token: ۱ ساعت
  • Refresh token: ۳۰ روز (ذخیره در Redis)
  • پیلود: { username: mobile_number, roles: [...], iat, exp }
  • تجدید: POST /oauth/token/refresh

Firewall های security.yaml

Firewall Pattern توضیح
api_doc ^/api/doc HTTP Basic Auth (admin / clinic123)
public_endpoints OTP، OAuth، endpoint های عمومی بدون احراز هویت
payment_callback /api/v1/payment/callback/ بدون احراز هویت
api ^/(api|oauth|file/) JWT stateless

۸. پایگاه داده — جداول و موجودیت‌ها

جداول اصلی

جدول موجودیت توضیح
users User uuid، mobile_number (unique)، roles (JSON)، status
doctors Doctor user_id (OneToOne)، degree، doctor_rate، active_doctor_appointment
doctor_addresses DoctorAddress doctor_id، name، latitude، longitude
clinics Clinic user_id (owner)، name، telephone، is_24_7، latitude، longitude، images_clinic (JSON)
clinic_doctor_invitations ClinicDoctorInvitation token (unique 96char hex)، status، expires_at (+72h)، mobile، uuid
appointments Appointment doctor_id، user_id، slot_start، slot_end، status، price
weekly_schedules WeeklySchedule doctor_id (OneToOne)، setting (JSON)
date_overrides DateOverride doctor_id، date، active، setting (JSON)
holidays Holiday doctor_id، start_date، end_date
payments Payment order_id (unique)، amount_rials، status، gateway (mellat|sep)
settlements Settlement user_id، amount_rials، status، bank_account (JSON)
wallet_transactions WalletTransaction user_id، amount_rials، type (credit|debit)، balance_after
representations Representation full_name، commission_percent، city_id
doctor_secretaries DoctorSecretary doctor_id، secretary_id، permissions (JSON)، active
rates Rate doctor_id، user_id، overall، diagnosis_accuracy، skill، behavior
comments Comment doctor_id، user_id، body، status (approved|pending|rejected)
likes Like comment_id، user_id
blogs Blog author_id، title، slug (unique)، body، status
sms_templates SmsTemplate name، body، provider_code، status
sms_logs SmsLog mobile، message، provider، success
profiles UserProfile blood_type، gender، insurance_id
provinces Province name
cities City name، province_id (FK)
specialties Specialty name، parent_id (nullable برای زیر-تخصص)
doctor_services DoctorService name
insurances Insurance name، type (basic|supplementary)
tags Tag name

جداول رابطه‌ای (ManyToMany)

جدول رابطه
clinic_doctors Clinic ↔ Doctor
clinic_specialties Clinic ↔ Specialty
clinic_services Clinic ↔ DoctorService
clinic_insurances Clinic ↔ Insurance
doctor_specialties Doctor ↔ Specialty
doctor_expertise Doctor ↔ Category
doctor_provinces Doctor ↔ Province

وابستگی‌های کلیدی

User ──OneToOne──► Doctor ──ManyToMany──► Specialty
     ──OneToOne──► Representation
     ──OneToMany──► Appointment
     ──OneToMany──► Clinic (owner)

Doctor ──OneToMany──► DoctorAddress
       ──OneToMany──► Appointment
       ──ManyToMany──► Clinic

Clinic ──ManyToOne──► User (owner)
       ──ManyToMany──► Doctor
       ──OneToMany──► ClinicDoctorInvitation

نکته timestamp

همه فیلدهای تاریخ (createdAt، updatedAt، slotStart، ...) به صورت integer Unix timestamp ذخیره می‌شوند — نه DateTime object.


۹. API Endpoints

احراز هویت

متد آدرس توضیح دسترسی
POST /api/v1/user/send-code ارسال OTP عمومی
POST /api/v1/user/verify-code تأیید OTP عمومی
POST /api/v1/user/register ثبت‌نام عمومی
POST /api/v1/user/login لاگین با رمز عبور عمومی
POST /oauth/token دریافت JWT عمومی
POST /oauth/token/refresh تجدید توکن عمومی
GET /oauth/userinfo اطلاعات کاربر جاری احراز هویت
POST /oauth/logout خروج احراز هویت

پزشکان

متد آدرس توضیح دسترسی
GET /api/v1/doctors لیست پزشکان عمومی
GET /api/v1/doctor/{uuid} جزئیات پزشک عمومی
POST /api/v1/doctor ایجاد پروفایل احراز هویت
PATCH /api/v1/doctor/{uuid} ویرایش احراز هویت
DELETE /api/v1/doctor/{uuid} حذف احراز هویت
POST /file/upload/clinic_pro/doctor/field_image آپلود تصویر احراز هویت
GET /api/v1/clinic-pro/doctor-addresses/{doctorId} آدرس‌های مطب عمومی
POST /api/v1/clinic-pro/doctor-address افزودن آدرس احراز هویت
PATCH /api/v1/clinic-pro/doctor-address/{id} ویرایش آدرس احراز هویت
DELETE /api/v1/clinic-pro/doctor-address/{id} حذف آدرس احراز هویت
POST /api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid} ایجاد از کلینیک احراز هویت

کلینیک‌ها

متد آدرس توضیح دسترسی
GET /api/v1/clinics لیست کلینیک‌ها عمومی
GET /api/v1/clinic/{uuid} جزئیات کلینیک عمومی
POST /api/v1/clinic ایجاد کلینیک احراز هویت
PATCH /api/v1/clinic/{uuid} ویرایش احراز هویت
GET /api/v1/clinic/doctor-list/{clinicUuid} پزشکان کلینیک عمومی
POST /file/upload/clinic_pro/clinic/field_clinic_logo آپلود لوگو احراز هویت
POST /file/upload/clinic_pro/clinic/field_image_clinic آپلود گالری احراز هویت

دعوتنامه پزشک به کلینیک

متد آدرس توضیح دسترسی
POST /api/v1/admin/clinic/{uuid}/invite-doctor ارسال دعوتنامه ROLE_ADMIN
GET /api/v1/admin/clinic/{uuid}/invitations لیست دعوتنامه‌ها ROLE_ADMIN
POST /api/v1/admin/clinic/invitation/{invUuid}/resend ارسال مجدد پیامک ROLE_ADMIN
PATCH /api/v1/admin/clinic/invitation/{invUuid}/status تغییر وضعیت ROLE_ADMIN
DELETE /api/v1/admin/clinic/invitation/{invUuid} حذف ROLE_ADMIN
GET /api/v1/clinic-invitation/{token} مشاهده دعوتنامه عمومی
POST /api/v1/clinic-invitation/{token}/accept پذیرش توسط دکتر عمومی
POST /api/v1/clinic-invitation/{token}/reject رد کردن توسط دکتر عمومی

توکن ۹۶ کاراکتر hex (bin2hex(random_bytes(48))) — مدت اعتبار ۷۲ ساعت. پیامک async از طریق Symfony Messenger ارسال می‌شود.

نوبت‌دهی

متد آدرس توضیح دسترسی
GET /api/v1/appointment-slots اسلات‌های خالی عمومی
POST /api/v1/appointment رزرو نوبت احراز هویت
GET /api/v1/appointment/{uuid} جزئیات نوبت احراز هویت
PATCH /api/v1/appointment/{uuid}/status تغییر وضعیت احراز هویت
GET /api/v1/appointments/doctor/{doctorUuid} نوبت‌های دکتر احراز هویت
GET /api/v1/appointments/user نوبت‌های کاربر احراز هویت

تنظیمات نوبت‌دهی

متد آدرس توضیح
POST/PATCH/GET /api/v1/appointment-settings/weekly-schedule برنامه هفتگی
POST/PATCH/DELETE/GET /api/v1/appointment-settings/date-override روز استثناء
POST/PATCH/DELETE/GET /api/v1/appointment-settings/holidays تعطیلات

پرداخت

متد آدرس توضیح دسترسی
POST /api/v1/payment/appointment شروع پرداخت نوبت احراز هویت
POST/GET /api/v1/payment/callback/{gateway} callback (mellat|sep) عمومی
GET /api/v1/payment/{uuid} وضعیت پرداخت احراز هویت
POST /api/v1/subscription-payment پرداخت اشتراک احراز هویت

کیف پول و تسویه‌حساب

متد آدرس توضیح دسترسی
GET /api/v1/wallet/balance موجودی کیف پول احراز هویت
GET /api/v1/wallet/transactions تاریخچه تراکنش‌ها احراز هویت
POST /api/v1/settlement درخواست تسویه احراز هویت
GET /api/v1/settlement لیست تسویه‌های خودم احراز هویت
POST /api/v1/settlement/{uuid}/approve تأیید ROLE_ADMIN
POST /api/v1/settlement/{uuid}/reject رد ROLE_ADMIN

امتیاز و نظرات

متد آدرس توضیح دسترسی
POST /api/v1/rate ثبت امتیاز احراز هویت
GET /api/v1/rate/{doctorUuid} میانگین امتیاز عمومی
POST /api/v1/comment ثبت نظر احراز هویت
GET /api/v1/comments/{doctorUuid} نظرات تأییدشده عمومی
DELETE /api/v1/comment/{uuid} حذف نظر احراز هویت
POST /api/v1/like/{commentUuid} لایک احراز هویت

منشی

متد آدرس توضیح دسترسی
POST /api/v1/secretary ایجاد منشی ROLE_DOCTOR
GET /api/v1/secretary/{uuid} جزئیات احراز هویت
PATCH /api/v1/secretary/{uuid} ویرایش permissions ROLE_DOCTOR
DELETE /api/v1/secretary/{uuid} حذف ROLE_DOCTOR
GET /api/v1/secretaries/{doctorUuid} لیست منشی‌های دکتر احراز هویت

نمایندگان

متد آدرس توضیح دسترسی
POST/GET/PATCH/DELETE /api/v1/representation/{uuid?} مدیریت نماینده احراز هویت
GET /api/v1/representation/{uuid}/dashboard/monthly آمار ماهانه احراز هویت
GET /api/v1/representation/{uuid}/dashboard/yearly آمار سالانه احراز هویت

پیامک

متد آدرس توضیح دسترسی
POST /api/v1/sms/send ارسال مستقیم احراز هویت
POST /api/v1/sms/send-template ارسال با قالب احراز هویت
POST/PATCH/GET/DELETE /api/v1/sms/template/{uuid?} مدیریت قالب احراز هویت
POST /api/v1/sms/template/{uuid}/submit ارسال برای تأیید احراز هویت
POST /api/v1/admin/sms/template/{uuid}/approve تأیید قالب ROLE_ADMIN

تخصص، خدمات، بیمه، تگ، موقعیت

GET  /api/v1/specialties              عمومی
GET  /api/v1/doctor-services          عمومی
GET  /api/v1/insurances               عمومی
GET  /api/v1/tags                     عمومی
GET  /api/v1/provinces                عمومی
GET  /api/v1/cities?province_id=      عمومی
GET  /api/v1/categorys/{bundle}       عمومی (legacy — bundle: state|city|specially_doctor|doctor_services|insurance_type|supplementary_insurance|tag)

POST/PATCH/DELETE /api/v1/admin/specialty/{id?}        ROLE_ADMIN
POST/PATCH/DELETE /api/v1/admin/doctor-service/{id?}   ROLE_ADMIN
POST/PATCH/DELETE /api/v1/admin/insurance/{id?}        ROLE_ADMIN
POST/PATCH/DELETE /api/v1/admin/tag/{id?}              ROLE_ADMIN
POST/PATCH/DELETE /api/v1/admin/province/{id?}         ROLE_ADMIN
POST/PATCH/DELETE /api/v1/admin/city/{id?}             ROLE_ADMIN

بلاگ

متد آدرس توضیح دسترسی
GET /api/v1/blogs لیست بلاگ‌های منتشرشده عمومی
GET /api/v1/blog/{slug} جزئیات بلاگ عمومی
POST /api/v1/blog نوشتن بلاگ احراز هویت
PATCH /api/v1/blog/{uuid} ویرایش احراز هویت
DELETE /api/v1/blog/{uuid} حذف احراز هویت
POST /file/upload/clinic_pro/blog/field_image آپلود تصویر احراز هویت

پنل ادمین API

همه endpoint های زیر نیاز به ROLE_ADMIN دارند.

آدرس توضیح
GET /api/v1/admin/dashboard/stats ۱۲ KPI (کاربران، پزشکان، نوبت، درآمد، ...)
GET /api/v1/admin/dashboard/charts نمودار ۳۰ روزه نوبت و درآمد
GET /api/v1/admin/dashboard/recent آخرین نوبت‌ها، پرداخت‌ها، کاربران
GET /api/v1/admin/users لیست کاربران (paginated)
GET /api/v1/admin/users/{uuid} جزئیات کاربر
PUT /api/v1/admin/users/{uuid}/role تغییر نقش
POST /api/v1/admin/users/{uuid}/status فعال/غیرفعال
DELETE /api/v1/admin/users/{uuid} حذف کاربر
GET /api/v1/admin/doctors لیست پزشکان
GET /api/v1/admin/clinics لیست کلینیک‌ها
PATCH /api/v1/admin/clinic/{uuid}/status تغییر وضعیت کلینیک
DELETE /api/v1/admin/clinic/{uuid} حذف کلینیک
GET /api/v1/admin/appointments لیست نوبت‌ها
GET /api/v1/admin/payments لیست پرداخت‌ها
GET /api/v1/admin/settlements لیست تسویه‌ها
GET /api/v1/admin/representations لیست نمایندگان
GET /api/v1/admin/secretaries لیست منشی‌ها
GET /api/v1/admin/rates لیست امتیازها
GET /api/v1/admin/comments لیست نظرات
GET /api/v1/admin/sms/logs لاگ پیامک‌ها
GET /api/v1/admin/sms/templates قالب‌های پیامک

فرمت پاسخ‌های API

// Single resource
{ "success": true, "data": { ... } }

// Paginated list
{ "success": true, "data": [...], "meta": { "totalRecords": N, "totalPages": N, "currentPage": N } }

// Error
{ "success": false, "data": null, "errors": [{ "code": "ERR_...", "message": "..." }] }

نکته double-nested: برخی endpoint ها پاسخ را داخل data اضافه می‌کنند:
$this->success(['data' => $entity->toArray()]) → فرانت باید با data?.data?.data استخراج کند.


۱۰. پنل ادمین React

Route های پنل

مسیر صفحه توضیح
/admin/login LoginPage ورود با موبایل + رمز
/admin/dashboard DashboardPage آمار KPI، نمودار، فعالیت‌های اخیر
/admin/users UsersPage لیست + جستجو + فیلتر نقش
/admin/users/:uuid UserDetailPage پروفایل + ویرایش نقش
/admin/doctors DoctorsPage لیست پزشکان
/admin/doctors/new DoctorFormPage ایجاد پزشک جدید
/admin/doctors/:uuid DoctorDetailPage پروفایل کامل + تخصص + نوبت‌ها
/admin/clinics ClinicsPage لیست کلینیک‌ها
/admin/clinics/:uuid ClinicDetailPage پروفایل + نقشه + گالری + دعوتنامه‌ها
/admin/appointments AppointmentsPage لیست نوبت‌ها + فیلتر
/admin/appointments/:uuid AppointmentDetailPage جزئیات نوبت
/admin/payments PaymentsPage لیست پرداخت‌ها
/admin/settlements SettlementsPage تسویه‌حساب
/admin/representations RepresentationsPage نمایندگان
/admin/representations/:uuid RepresentationDetailPage جزئیات نماینده
/admin/comments CommentsPage مدیریت نظرات
/admin/ratings RatingsPage امتیازها
/admin/sms SmsPage مدیریت پیامک
/admin/categories CategoriesPage تخصص/خدمات/بیمه/تگ
/admin/blogs BlogsPage لیست بلاگ‌ها
/admin/blogs/new BlogFormPage نوشتن بلاگ جدید
/admin/secretaries SecretariesPage مدیریت منشی‌ها

الگوی دریافت داده

// لیست paginated
const { data } = useQuery({
    queryKey: ["resource", page, filters],
    queryFn: () =>
        api.get<PaginatedResponse<T>>(`/api/v1/admin/...?page=${page}`),
});
const items = data?.data ?? []; // آرایه آیتم‌ها
const total = data?.meta?.totalRecords ?? 0;

// Single resource (double-nested — برخی endpoint ها)
const raw = data?.data;
const item = (raw as any)?.data ?? raw;

// Category API (triple-nested legacy)
const cats = data?.data?.data ?? [];

ویژگی‌های فنی فرانت‌اند

  • Auth Store (Zustand persist): توکن در localStorage['clinicpro-auth']state.token
  • Modal ها: از createPortal(modal, document.body) استفاده می‌کنند تا از z-index Leaflet بگریزند
  • CSS: کلاس‌های template اختصاصی — .card، .btn.primary/.ghost/.soft/.sm، .badge.green/.gray/.blue، .overlay، .seg، .skeleton، .empty
  • Avatar: OKLCH gradient با HUES_LIST = [256, 205, 162, 295, 272]

۱۱. Migrations

# ایجاد migration جدید بعد از تغییر entity
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 doctrine:migrations:status

لیست migration های موجود

فایل توضیح
Version20260609130407 جداول پایه: users, doctors, appointments
Version20260609131304 پرداخت، کیف پول
Version20260609131553 امتیاز، نظرات
Version20260609132009 نمایندگان
Version20260609132708 منشی
Version20260609133121 کلینیک
Version20260609133334 پیامک
Version20260609133546 بلاگ
Version20260609134112 تخصص، بیمه، تگ
Version20260609134741 استان، شهر
Version20260609135126 پروفایل، آدرس مطب
Version20260609135514 برنامه هفتگی
Version20260609135704 روز استثناء، تعطیلات
Version20260609135923 ManyToMany: clinic_doctors, clinic_specialties
Version20260609140223 ایندکس‌های اضافی
Version20260609140423 doctor_insurances
Version20260610062539 فیلدهای اضافی clinic
Version20260610175105 فیلد is_active برای clinic
Version20260610183655 جدول clinic_doctor_invitations

۱۲. سرویس‌های خارجی

پیامک

KaveNegar (پیش‌فرض)

  • API: https://api.kavenegar.com/v1/{KEY}/sms/send.json
  • پشتیبانی از: send (متن آزاد) + sendTemplate (قالب)

Rangineh

  • API: https://rest.payamresan.com/api/v1/send
  • پشتیبانی از: send + sendTemplate

ارسال async:

$this->smsService->dispatchAsync($mobile, $message);
// → Symfony Messenger → Redis → SendSmsHandler

درگاه پرداخت

بانک ملت (Mellat)

  • SOAP WebService: https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl
  • تأیید: ResCode=0 + SettlePayment

سپ (SEP)

  • REST API با ترمینال ID

Redis

  • صف پیام: redis://redis:6379/messages
  • Cache: redis://redis:6379
  • Refresh Token: کلید refresh_<sha256>

۱۳. ساختار دایرکتوری

clinic-pro-symfony/
├── assets/
│   └── admin/                    # React SPA (پنل ادمین)
│       ├── App.tsx               # React Router routes
│       ├── pages/                # یک فایل به ازای هر صفحه
│       ├── components/
│       │   ├── layout/           # AdminLayout, Sidebar, Topbar
│       │   └── ui/               # DataTable, Modal, ConfirmDialog, Pagination, ...
│       ├── lib/
│       │   ├── api.ts            # fetch wrapper (JWT از localStorage)
│       │   └── utils.ts          # formatRial, formatDate, formatDateTime, formatNumber
│       ├── stores/
│       │   ├── authStore.ts      # Zustand — token, isAuthenticated
│       │   └── uiStore.ts        # sidebar state
│       ├── types/index.ts        # TypeScript interfaces
│       └── styles.css            # کلاس‌های template
├── config/
│   ├── packages/
│   │   ├── security.yaml         # firewalls, access_control
│   │   └── lexik_jwt_authentication.yaml
│   └── services.yaml             # dependency injection
├── docs/
│   └── tasks/                    # مستندات فنی هر feature
├── migrations/                   # Doctrine migrations
├── src/
│   ├── Admin/Controller/         # AdminApiController — همه endpoint های ادمین
│   ├── Appointment/              # نوبت‌دهی، زمان‌بندی، SlotCalculatorService
│   ├── Auth/                     # احراز هویت، JWT، OTP، User entity
│   ├── Blog/                     # بلاگ
│   ├── Category/                 # کتگوری legacy (bundle system)
│   ├── Clinic/                   # کلینیک
│   ├── ClinicInvitation/         # دعوتنامه پزشک به کلینیک
│   ├── Doctor/                   # پزشک، آدرس مطب
│   ├── DoctorService/            # خدمات پزشکی
│   ├── Insurance/                # بیمه
│   ├── Location/                 # استان، شهر
│   ├── Payment/                  # پرداخت، درگاه‌ها (Mellat, Sep)
│   ├── Rating/                   # امتیاز، نظرات، لایک
│   ├── Representation/           # نمایندگان
│   ├── Secretary/                # منشی
│   ├── Settlement/               # تسویه‌حساب، کیف پول
│   ├── Sms/                      # پیامک، قالب، لاگ
│   ├── Specialty/                # تخصص پزشکی
│   ├── Tag/                      # تگ بلاگ
│   ├── UserProfile/              # پروفایل پزشکی کاربر
│   └── Shared/
│       ├── Controller/BaseController.php   # success() / paginated() / error()
│       ├── Exception/AppException.php      # domain exception با httpStatus
│       ├── Constant/ErrorCodes.php         # کدهای خطا به صورت const string
│       └── EventSubscriber/ExceptionSubscriber.php
├── public/
│   └── build/                    # webpack output (gitignore شده)
├── var/
│   └── uploads/                  # فایل‌های آپلود‌شده
├── .ddev/config.yaml             # PHP 8.3، MariaDB 11.8، nginx-fpm
├── .env                          # متغیرهای پیش‌فرض (commit نشود .env.local)
└── webpack.config.js             # Webpack Encore

یادآور توسعه: تمام دستورات را با پیشوند ddev exec اجرا کنید.
CSS فرانت‌اند از کلاس‌های template اختصاصی استفاده می‌کند (نه Tailwind).
بعد از هر تغییر backend: cache:clear
بعد از هر تغییر entity: migrations:diff سپس migrations:migrate


🛡️ ALTCHA — کپچای خودمیزبان (ضدِ اسپم/بات)

کپچای Proof-of-Work مبتنی بر ALTCHA — بدون تصویر، بدون تایپ، بدون سرویس خارجی (مناسب کاربران داخل ایران). فقط روی endpointهای عمومی و بدون احراز هویت اعمال می‌شود.

نصب (انجام‌شده)

  • بک‌اند: composer require altcha-org/altcha (پکیج رسمی PHP، از V1 API استفاده می‌شود).
  • فرانت‌اند: yarn add altcha (web-component محلی، بدون CDN؛ Encore آن را bundle می‌کند).

تنظیمات (.env / برای prod در .env.local)

متغیر پیش‌فرض توضیح
ALTCHA_ENABLED false در prod روی true؛ در dev/test غیرفعال بماند
ALTCHA_HMAC_KEY change-me-in-env-local کلید امضای سرور — حتماً در prod عوض شود و مخفی بماند
ALTCHA_MAX_NUMBER 100000 سقف اعداد PoW = سختی؛ بالاتر = سنگین‌تر برای مرورگر
ALTCHA_EXPIRE_SECONDS 300 عمر هر challenge (ثانیه)

جریان

  1. کلاینت GET /api/v1/altcha/challenge را می‌گیرد (challenge امضاشده).
  2. <altcha-widget> در پس‌زمینه PoW را حل می‌کند.
  3. مقدار حل‌شده (base64) با کلید altcha در بدنه‌ی درخواستِ endpoint عمومی ارسال می‌شود.
  4. سرور با CaptchaGuard::assertValid($request) اعتبارسنجی می‌کند (امضا + انقضا + یک‌بارمصرف بودن).

endpointهای محافظت‌شده

send-code، register، otp-login، reset-password، pre-registration.

rate/comment پشت JWT هستند و کپچا نمی‌گیرند (بی‌فایده است).

افزودن کپچا به endpoint عمومی جدید

CaptchaGuard را inject کن و اولین خط handler:

$this->captcha->assertValid($request); // پرتاب ERR_CAPTCHA_001 (422) در صورت شکست

سپس مسیر را در فرانت به بدنه‌ی درخواست altcha وصل کن.

تغییر سختی

ALTCHA_MAX_NUMBER را بالا/پایین ببر (مثلاً 1000000 برای سخت‌تر).

غیرفعال‌سازی

ALTCHA_ENABLED=false → guard کاملاً no-op می‌شود (dev/test همیشه این‌طور است).

Troubleshooting

  • همیشه ERR_CAPTCHA_001: کلید ALTCHA_HMAC_KEY بین challenge و verify باید یکسان باشد؛ اگر بعد از صدور challenge کلید عوض شود، امضا نامعتبر می‌شود.
  • challenge منقضی: ساعت سرور را چک کن؛ ALTCHA_EXPIRE_SECONDS خیلی کوتاه نباشد.
  • replay: هر challenge یک‌بار مصرف است؛ برای هر submit یک challenge تازه بگیر.
  • خطای Redis: pool اختصاصی altcha.pool روی REDIS_URL است؛ در دسترس بودن Redis لازم است.
S
Description
No description provided
Readme MIT
30 MiB
Languages
PHP 59.8%
TypeScript 37.5%
Twig 0.9%
CSS 0.9%
JavaScript 0.8%