# معماری کلان پلتفرم 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` استفاده می‌کند.