TorbatYar/docs/architecture.md
Mortezakoohjani 800b0ba2c5 Deploy TorbatYar for torbatyar.ir with nginx multi-tenant routing.
Wire production domain, CORS for tenant subdomains, celery volume mounts, and nginx reverse proxy configs for apex, API, identity, auth, and wildcard tenants.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 21:43:33 +03:30

390 lines
20 KiB
Markdown
Raw 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.

# معماری کلان پلتفرم SuperApp SaaS
## ۱. اهداف
ساخت یک SuperApp SaaS **چندمستأجری (Multi-tenant)**، **ماژولار**،
**API-first** و **microservice-ready** که ابتدا روی VPS و در آینده روی
زیرساخت مقیاس‌پذیر اجرا شود.
فاز ۱ تنها **Core Platform** را می‌سازد؛ اما ساختار پروژه به‌گونه‌ای است که
افزودن سرویس‌های بعدی بدون بازنویسی معماری ممکن باشد.
## ۲. سبک معماری
- **Service-oriented / Microservice-ready:** هر قابلیت بزرگ یک سرویس مستقل است.
- **Database-per-service:** هر سرویس فقط دیتابیس خودش را می‌شناسد.
- **API-first:** همه تعامل‌ها از طریق API/Event تعریف‌شده انجام می‌شوند.
- **Multi-tenancy:** همه جداول بیزینسی ستون `tenant_id` دارند.
### سرویس‌های اصلی (فعلی و آینده)
Core Platform (فاز ۱)، Identity & Access، Subscription & Entitlement،
Accounting، CRM، Ecommerce، Website Builder، Live Chat، AI Assistant،
Smart Messenger، SMS Panel، Link Shortener، Notification، File Storage،
Restaurant و ماژول‌های آینده Marketplace.
> در فاز ۱ فقط Core Platform پیاده شده است. بقیه سرویس‌ها در پوشه `backend/services/`
> به‌صورت placeholder با README مسئولیت‌ها موجودند.
## ۲.۱ Frontend & Backend Separation (اجباری)
### قانون معماری (سخت‌گیرانه)
پروژه **باید** از معماری کاملاً جدا (decoupled) پیروی کند. Backend و Frontend
دو اپلیکیشن کاملاً مستقل هستند و **هرگز نباید با هم مخلوط شوند.**
### Backend
تمام کد منبع backend فقط داخل پوشه `backend/` قرار می‌گیرد (Core Service و
سرویس‌های آینده).
**مسئولیت‌ها:**
- FastAPI
- SQLAlchemy
- Alembic
- Business Logic
- Authentication
- Authorization
- Database
- Background Workers
- APIs
- Event Processing
**Backend هرگز نباید شامل موارد زیر باشد:**
- React / Next.js
- Tailwind
- UI Components
- صفحات HTML
- Frontend assets
### Frontend
Frontend یک اپلیکیشن Next.js کاملاً جدا در پوشه `frontend/` است.
**مسئولیت‌ها:**
- UI
- Dashboard
- Forms
- Pages
- Components
- Layouts
- State Management
- API Client
- Theme
**Frontend هرگز نباید شامل موارد زیر باشد:**
- کد FastAPI
- دسترسی مستقیم به دیتابیس
- مدل‌های SQLAlchemy
- Alembic
- Business Logic
- کوئری مستقیم دیتابیس
### ارتباط (Communication)
Frontend فقط از طریق **REST API نسخه‌دار** (و در آینده WebSocket در صورت نیاز)
با backend ارتباط برقرار می‌کند.
هیچ کد منبع مشترکی بین frontend و backend وجود ندارد، **به‌جز:**
- قراردادهای API
- اسکیماهای مشترک
- SDKهای تولیدشده
- تعاریف نوع (type definitions) مشترک
### ساختار پوشه‌ها (اجباری)
```
superapp-platform/
├── backend/
│ ├── core-service/
│ ├── shared-lib/
│ └── services/
├── frontend/
│ ├── app/
│ ├── components/
│ ├── lib/
│ ├── hooks/
│ ├── styles/
│ └── public/
├── docs/
├── docker-compose.yml
├── .env.example
└── README.md
```
> این جداسازی در کل پروژه اجباری است. فایل‌های frontend هرگز نباید به پوشه‌های
> backend منتقل شوند و بالعکس — حتی برای راحتی. هر قابلیت جدید باید این
> معماری را حفظ کند.
## ۳. تصمیم معماری دیتابیس
- الگوی **Database-per-service**.
- **ارتباط مستقیم بین دیتابیس سرویس‌ها ممنوع است.**
- ارتباط بین سرویس‌ها فقط از طریق:
- REST API
- Webhook
- Async Event
- الگوی Outbox/Inbox
- در فاز ۱ فقط دیتابیس `core_platform_db` ساخته می‌شود.
- طراحی اولیه دیتابیس سرویس‌های آینده در `database_schema.md` آمده اما
migration واقعی آن‌ها ساخته نشده است.
## ۴. Core Platform Service
مسئول مفاهیم مشترک و مرکزی پلتفرم:
- مدیریت Tenant و Domain
- مدیریت Plan / Feature / Subscription و بررسی دسترسی (Entitlement)
- Service Registry و Module Registry
- توکن‌های داخلی سرویس (Internal Service Tokens)
- الگوی Outbox/Inbox و Audit Log
### لایه‌بندی داخلی سرویس هسته
```
API (routers) → Services (business logic) → Repositories → Models (DB)
Schemas (Pydantic)
```
- **core/**: زیرساخت (config, database, cache, security, logging)
- **middlewares/**: تشخیص tenant
- **workers/**: Celery و taskها
## ۵. Multi-tenancy و Tenant Resolution
ترتیب تشخیص tenant در middleware:
1. هدر `X-Tenant-ID`
2. هدر `X-Tenant-Slug`
3. Subdomain (از روی Host و `base_domain`)
4. Custom domain (از روی Host)
نتیجه در `request.state.tenant_id` و `request.state.tenant_slug` قرار می‌گیرد.
برای endpointهای tenant-aware از dependency `require_tenant` استفاده می‌شود که
در نبود tenant خطای `tenant_not_resolved` برمی‌گرداند.
## ۶. Entitlement (بررسی دسترسی قابلیت)
تابع مرکزی `EntitlementService.check_feature_access(tenant_id, feature_key)`:
1. ابتدا Redis cache بررسی می‌شود.
2. در نبود cache، از دیتابیس محاسبه می‌شود.
3. نتیجه با TTL در Redis ذخیره می‌گردد.
قواعد: اگر tenant غیرفعال باشد، اشتراک غیرفعال باشد، یا قابلیت در پلن نباشد →
دسترسی false. دسترسی سفارشی (custom access) بالاترین اولویت را دارد.
## ۷. رویدادها (Outbox/Inbox)
- رویدادهای خروجی ابتدا در جدول `outbox_events` و در **همان تراکنش** عملیات
بیزینسی ذخیره می‌شوند (تضمین atomicity).
- یک task دوره‌ای Celery (`process_outbox_events`) رویدادهای pending را پردازش
و وضعیت آن‌ها را به‌روزرسانی می‌کند.
- جدول `inbox_events` برای idempotency رویدادهای ورودی است.
## ۸. امنیت و SSO (فاز ۲ و ۳)
### اصل یکپارچگی: شماره موبایل الزامی است
در سامانه یکپارچه TorbatYar، **شماره موبایل برای همه کاربران واجب** است و
با **OTP پیامکی** تأیید می‌شود.
### لایه‌های هویت (تفکیک اجباری)
| لایه | چه کسی | SSO مرکزی Keycloak |
|------|--------|-------------------|
| **هسته TorbatYar** | مدیر پلتفرم، صاحب/کارمند tenant | **اجباری** |
| **زیرسیستم‌ها (staff)** | پنل کافه، CRM، … | **اجباری** — همان JWT |
| **مشتری tenant** | سفارش‌دهنده منوی دیجیتال | **اختیاری** — auth محلی tenant |
- `frontend/lib/optional-sso.ts` — برای UIs زیرسیستم: اگر session مرکزی هست، login دوباره لازم نیست.
- مشتری end-user در `restaurant_db` / `crm_db` ذخیره می‌شود، نه Keycloak مرکزی.
**ورود مرکزی فقط از Keycloak** (تم torbatyar) با دو تب «رمز عبور» و «موبایل»؛ frontend فقط redirect می‌کند.
```
┌─────────────────────────────────────────────────────────────────┐
│ F) ورود مرکزی Keycloak — تنها نقطه ورود کاربر │
├─────────────────────────────────────────────────────────────────┤
│ Keycloak /realms/superapp/.../auth (تم torbatyar) │
│ تب «رمز عبور»: نام کاربری / ایمیل / موبایل + رمز │
│ تب «موبایل»: OTP + ثبت‌نام inline (mobile-auth.js) │
│ → Identity /auth/mobile/* → handoff → /auth/callback │
│ Frontend /login و /register → redirect به Keycloak │
│ Admin: همان SSO — /admin/login → Keycloak → /admin/tenants │
└─────────────────────────────────────────────────────────────────┘
```
### احراز هویت OTP (Core Service)
```
کاربر → Frontend → POST /api/v1/auth/otp/request?context=public|admin
→ Payamak (pattern 245189)
→ POST /api/v1/auth/otp/verify
→ JWT محلی (HS256)
```
- `context=public`: نقش پیش‌فرض `user` (ثبت‌نام عمومی)
- `context=admin`: نقش `pending_tenant_admin` (پنل tenant)
- `PLATFORM_ADMIN_MOBILES`: ارتقا به `platform_admin`
جدول `users` در Core: `mobile` (unique)، `mobile_verified`، `keycloak_sub` (لینک SSO).
### معماری SSO مرکزی
```
کاربر → Frontend → Keycloak (OIDC) → JWT
Identity /auth/me → requires_mobile_verification?
همه سرویس‌ها JWT را validate می‌کنند
```
### Identity & Access Service
- دیتابیس: `identity_access_db`
- جدول `user_profiles`: `mobile`, `mobile_verified`, `core_user_id`
- APIهای جدید:
- `POST /api/v1/auth/mobile/start` — تشخیص login/register + ارسال OTP
- `POST /api/v1/auth/mobile/complete` — تأیید OTP + صدور توکن SSO
- `POST /api/v1/auth/session/redeem` — handoff از Keycloak theme
- `POST /api/v1/auth/register` (legacy)
- `POST /api/v1/auth/register/verify-mobile`
- `POST /api/v1/auth/register/mobile`
- `POST /api/v1/auth/register/mobile/verify`
- `POST /api/v1/auth/mobile/request` (کاربر SSO لاگین‌شده)
- `POST /api/v1/auth/mobile/verify`
- OTP از طریق Core Service (`CoreOtpClient`) — بدون تکرار منطق SMS
### Keycloak
- تم `torbatyar`: **ورود مرکزی** — تب رمز عبور + تب موبایل (OTP)
- `theme.properties`: `identityApiUrl`, `frontendCallbackUrl`
- attribute کاربر: `mobile` در Admin API
- Realm: `superapp` — زبان پیش‌فرض `fa`
### اعتبارسنجی JWT
- کتابخانه مشترک: `shared/auth/jwt.py`
- نرمال‌سازی موبایل مشترک: `shared/phone.py`
## ۹. زیرساخت
- Docker و docker-compose (postgres, redis, keycloak, core-service,
identity-access-service, frontend, celery-worker, celery-beat).
- همه تنظیمات از `.env` خوانده می‌شوند؛ **هیچ چیز hardcode نمی‌شود**.
- Nginx/Traefik در فازهای بعدی به‌عنوان reverse proxy اضافه می‌شوند.
## ۱۰. White-label
برند (نام، رنگ، لوگو، ایمیل پشتیبانی) از `env`/config/دیتابیس خوانده می‌شود.
Frontend در `frontend/` رنگ‌ها را از طریق **CSS Variables** و `public/theme.config.json`
اعمال می‌کند تا تغییر برند بدون build مجدد ممکن باشد.
## ۱۱. فاز ۴ — Tenant Onboarding و Workspace Activation
> در بریف پروژه این کار با عنوان **«Phase 3: operationalizing tenant onboarding
> and workspace activation»** معرفی شده است؛ چون در شماره‌گذاری داخلی مستندات
> پیش‌تر «فاز ۳» به OTP Login + Tenant Management اختصاص یافته بود (نگاه کنید
> به `progress.md`)، این کار به‌عنوان **فاز ۴** پروژه ثبت می‌شود تا تاریخچه
> پیاده‌سازی واقعی حفظ شود. محتوای این فاز دقیقاً همان چیزی است که در بریف
> «Phase 3» خوانده می‌شود.
### هدف
سیستم را از «هویت + لیست ادمین» به یک **workspace عملیاتی چندمستأجری واقعی**
تبدیل می‌کند: کاربر OTP/SSO می‌زند، در نبود tenant به onboarding هدایت می‌شود،
یک workspace (tenant) می‌سازد، مالک آن می‌شود، پلن پیش‌فرض و دامنه اولیه
می‌گیرد، برندینگ را تنظیم می‌کند و در پایان وارد یک داشبورد واقعی tenant
می‌شود (نه فقط صفحات لیست ادمین).
### مدل عضویت Tenant (Tenant Membership)
جدول جدید `tenant_memberships` در **`core_platform_db`** رابطهٔ واقعی
کاربر↔tenant را نگه می‌دارد (مستقل از جدول هم‌نام و ساده‌تر
`tenant_memberships` در `identity_access_db` که برای مدیریت اعضای هویتی
استفاده می‌شود؛ این دو جدول در دو دیتابیس/سرویس متفاوت‌اند و هیچ ارتباط
مستقیمی ندارند).
نقش‌ها: `platform_admin`, `tenant_owner`, `tenant_admin`, `tenant_editor`,
`tenant_viewer`. وضعیت عضویت: `active` / `invited` / `disabled`. هر tenant
باید حداقل یک `tenant_owner` فعال داشته باشد (بررسی در زمان
`POST /onboarding/tenant/{id}/complete`). یک کاربر می‌تواند در آینده عضو چند
tenant باشد (`current_tenant_id` روی `users` مشخص می‌کند کدام tenant «جاری»
است).
### چرخهٔ عمر Tenant (Activation Lifecycle)
`tenant.status` اکنون این مقادیر عملیاتی را پشتیبانی می‌کند:
```
draft → pending_activation → active → suspended / archived
```
- tenant تازه‌ساخته‌شده از مسیر onboarding با `pending_activation` شروع می‌شود.
- با تکمیل onboarding (`POST .../complete`) و اعتبارسنجی (نام/slug موجود و
حداقل یک owner فعال) به `active` تغییر می‌کند.
- `suspended`/`archived` فقط توسط پنل ادمین (فاز‌های قبلی، `PATCH
/admin/tenants/{id}`) قابل تنظیم‌اند.
- مقادیر قدیمی `inactive`/`deleted` برای سازگاری با داده‌های فاز ۱ حفظ شده‌اند.
### پروفایل و برندینگ Tenant
ستون‌های برندینگ/تنظیمات مستقیماً روی جدول `tenants` اضافه شده‌اند (بدون
جدول جدا، چون حجم کم و همیشه ۱به۱ با tenant است): `business_type`,
`default_locale`, `timezone`, `primary_color`, `secondary_color`,
`logo_url`, `favicon_url`, `onboarding_completed`.
### پلن / اشتراک (بدون درگاه پرداخت)
ساختار `plans` و `tenant_subscriptions` از فاز ۱ موجود بود؛ در این فاز:
- یک پلن پیش‌فرض «FREE» (و «STARTER» برای آینده) از طریق migration seed
می‌شود (`PlanService.ensure_default_plan` idempotent است).
- هنگام `POST /onboarding/tenant`، به‌صورت خودکار یک `tenant_subscriptions`
با `plan=FREE` و `status=active` ساخته می‌شود.
- درگاه پرداخت پیاده‌سازی **نشده** — فقط ساختار provisioning است.
### نگاشت دامنه (Tenant Domain Mapping)
جدول `domains` (فاز ۱) با دو ستون جدید تقویت شده: `is_primary` (دامنهٔ
اصلی/پیش‌فرض tenant) و `verification_status` (`pending`/`verified`/`failed`).
هنگام `POST /onboarding/tenant`، اگر `PLATFORM_BASE_DOMAIN` تنظیم شده باشد،
زیردامنهٔ `{slug}.{PLATFORM_BASE_DOMAIN}` به‌صورت خودکار و از پیش
verified ساخته می‌شود. دامنهٔ اختصاصی (custom) از طریق
`PATCH /onboarding/tenant/{id}/domain` با وضعیت `pending` اضافه می‌شود و
تأیید واقعی آن به فازهای بعدی موکول شده است.
### Tenant Resolution و Current Tenant Context
علاوه بر middleware قبلی (`X-Tenant-ID` → `X-Tenant-Slug` → subdomain →
custom domain)، این dependencyهای جدید اضافه شدند:
- `get_current_core_user` / `get_optional_core_user`: رکورد واقعی
`users` را چه برای JWT محلی (OTP) و چه برای JWT کیکلوک (SSO، بر اساس
`keycloak_sub`) resolve می‌کنند.
- `get_tenant_resolution`: اول هدر/دامنه (middleware)، در نبود آن
`current_tenant_id` کاربر جاری را برمی‌گرداند؛ endpointهای platform-admin
فعلی تحت تأثیر قرار نمی‌گیرند.
- `TenantContextService.resolve_current_tenant`: برای `GET /tenant/current`
استفاده می‌شود (بر اساس `current_tenant_id` یا اولین عضویت کاربر).
### رابطهٔ ورود OTP/SSO با provisioning workspace
هر دو مسیر احراز هویت (JWT محلی OTP فاز ۱ صادرشده از Core، و JWT کیکلوک SSO
فاز ۲ صادرشده از Identity) در نهایت باید به یک رکورد `users` در
`core_platform_db` متصل شوند تا onboarding معنا پیدا کند:
```
JWT محلی (HS256) → users.id ──┐
├─→ UserService.resolve_current() → User
JWT کیکلوک (RS256) → users.keycloak_sub ──┘
```
اگر کاربر SSO هنوز به هیچ رکورد Core لینک نشده باشد (یعنی هرگز از مسیر OTP
محلی Core عبور نکرده)، `resolve_current` خطای `forbidden` برمی‌گرداند — به
این معنا که JIT provisioning کامل کاربر Core از JWT کیکلوک فعلاً در محدودهٔ
این فاز پیاده نشده و به فاز بعدی موکول شده (نگاه کنید به `last_step.md`).
### APIهای Onboarding
جزئیات کامل در `services_contracts.md`؛ خلاصه:
`GET /me`, `GET /me/tenants`, `POST /onboarding/tenant`,
`PATCH /onboarding/tenant/{id}/branding`,
`PATCH /onboarding/tenant/{id}/domain`,
`POST /onboarding/tenant/{id}/complete`, `GET /tenant/current`,
`POST /tenant/switch`.
### Authorization
`MembershipService.ensure_role` بررسی می‌کند که کاربر جاری روی tenant مقصد
نقش `tenant_owner` یا `tenant_admin` داشته باشد (پیش‌فرض برای مدیریت
برندینگ/دامنه/تکمیل onboarding)؛ `platform_admin` همیشه bypass می‌شود.
endpointهای onboarding فقط نیاز به کاربر احرازهویت‌شده دارند (بدون بررسی
نقش برای ساخت tenant جدید، چون هر کاربر می‌تواند workspace خودش را بسازد).
### Frontend
- **Bootstrap:** پس از ورود، `hooks/useMe.ts` نتیجهٔ `GET /api/v1/me` را
می‌گیرد. صفحهٔ `/dashboard` اگر `onboarding_required=true` باشد کاربر را
به `/onboarding` هدایت می‌کند.
- **Onboarding Wizard:** صفحهٔ تک‌فایلی `app/onboarding/page.tsx` با ۴ گام
(اطلاعات کسب‌وکار → برندینگ → دامنه → بازبینی) که به‌ترتیب APIهای
onboarding را صدا می‌زند و در پایان به `/dashboard` هدایت می‌کند.
- **Tenant Dashboard:** `app/dashboard/page.tsx` اطلاعات tenant جاری
(`GET /tenant/current`) را نمایش می‌دهد: نام، وضعیت، پلن، دامنه، وضعیت
onboarding و نقش کاربر.
- **Tenant Switcher:** `components/TenantSwitcher.tsx` — فقط وقتی کاربر
بیش از یک عضویت داشته باشد نمایش داده می‌شود و از `POST /tenant/switch`
استفاده می‌کند.