ساختار مالکیت پلتفرم: یک مالک، مدیرانِ ساختهٔ مالک، سازمان‌های مدیریت‌شده

- users.is_platform_owner → users.platform_role (owner|manager|user) با مهاجرت
  d4e5f6a7b8c9 و backfill. ایندکس یکتای جزئی uq_single_platform_owner تضمین
  می‌کند دقیقاً یک مالک وجود داشته باشد — در سطح دیتابیس، نه فقط در کد.
- ثبت‌نام عمومی حذف شد (POST /auth/signup). حساب‌ها فقط با POST /admin/users
  (فقط مالک، با رمز موقت یک‌بارمصرف) یا با پذیرش لینک دعوت ساخته می‌شوند.
  در مسیر دعوت، اگر حساب وجود نداشته باشد خودِ دعوت‌شده رمز تعیین می‌کند؛ اگر
  وجود داشته باشد لینک هرگز جای reset رمز را نمی‌گیرد.
- ساخت سازمان فقط برای مالک و مدیران؛ کاربر عادی با دعوت عضو می‌شود.
- تنزل یا حذف مدیر، سازمان‌هایش را به مالک سامانه منتقل می‌کند (مالکیت + عضویت،
  با audit). حساب مالک از API تغییرناپذیر است؛ جابه‌جایی صندلی فقط با
  POST /admin/transfer-ownership و در یک تراکنش انجام می‌شود.
- افزودن POST /auth/change-password برای تبدیل رمز موقت به رمز خصوصی.
- فرانت: صفحهٔ /admin/users، صفحهٔ /account، بازنویسی /accept-invite، حذف /signup،
  گارد /onboarding برای کاربر عادی، و نشان نقش پلتفرم در پوستهٔ اپ.
- PRAGMA foreign_keys=ON برای SQLite تا ON DELETE CASCADE در dev و prod یکسان
  رفتار کند (پیش‌تر حذف کاربر، عضویت‌هایش را یتیم می‌گذاشت).
- تست: ۳۰۰ تست بک‌اند (شامل tests/test_platform_hierarchy.py با ۲۲ تست) و ۶۴ تست
  فرانت‌اند؛ ruff، mypy، tsc، eslint و build تمیز.
This commit is contained in:
mjalmoudy94
2026-10-13 21:58:41 +03:30
parent 6937b88ca7
commit 8eb631aedf
41 changed files with 2952 additions and 454 deletions
+30 -11
View File
@@ -21,11 +21,28 @@ provider. این‌ها فقط در **Admin Console** و فقط برای مال
پلتفرم سه لایهٔ نقش دارد:
1. **مالک سامانه (platform owner)** — اکانت‌های agy، مدل‌ها، routing policy،
تعرفه‌ها، sandbox templateها، مشاهدهٔ کل مصرف. agy تنها ارائه‌دهندهٔ مدل
سامانه است و بخش جداگانهٔ «ارائه‌دهنده‌ها» وجود ندارد.
2. **مدیر سازمان مشتری** — ساخت سازمان، دعوت عضو، پروژه، فعال‌سازی کارمند، مصرف.
3. **عضو سازمان** — گفتگو با مدیر/کارمند، ساخت task، آپلود فایل، دیدن خروجی.
1. **مالک سامانه (platform owner)** — **دقیقاً یک حساب** و صاحب کل سیستم. اکانت‌های
agy، مدل‌ها، routing policy، تعرفه‌ها، sandbox templateها، مشاهدهٔ کل مصرف، و
ساخت/تنزل **مدیران**. agy تنها ارائه‌دهندهٔ مدل سامانه است و بخش جداگانهٔ
«ارائه‌دهنده‌ها» وجود ندارد.
2. **مدیر (platform manager)** — حسابی که فقط مالک سامانه می‌سازد. هر مدیر
می‌تواند **سازمان‌های خودش را بسازد** (و مالک آن‌ها شود) یا با **دعوت** صاحب
سازمان، در سازمان مدیران دیگر عضو شود. مدیر به کنسول مالک سامانه دسترسی ندارد.
3. **کاربر عادی (platform user)** — سازمان نمی‌سازد؛ فقط داخل سازمان‌هایی که با
دعوت به آن‌ها پیوسته کار می‌کند.
**ثبت‌نام عمومی وجود ندارد.** حساب‌ها از دو مسیر ساخته می‌شوند:
- **ساخت مستقیم توسط مالک سامانه** در `/admin/users` (برای مدیر یا کاربر عادی) که
یک رمز موقت یک‌بارمصرف برمی‌گرداند؛
- **پذیرش لینک دعوت**: دعوت‌شده اگر حساب نداشته باشد، در همان صفحه رمز خودش را
تعیین می‌کند و حساب ساخته می‌شود. اگر حساب داشته باشد باید اول وارد شود — یک
لینک دعوت هرگز جای reset رمز را نمی‌گیرد.
«فقط یک مالک» یک قاعدهٔ دیتابیسی است: ایندکس یکتای جزئی روی
`users.platform_role = 'owner'`، پس حتی یک درج مستقیم هم نمی‌تواند مالک دوم بسازد.
جابه‌جایی صندلی مالکیت فقط با `POST /admin/transfer-ownership` انجام می‌شود که در
یک تراکنش، مالک فعلی را به مدیر تنزل می‌دهد و جانشین را مالک می‌کند.
---
@@ -79,14 +96,16 @@ pnpm dev
### حساب‌های نمونه (فقط محیط توسعه)
`pnpm db:seed` دو حساب می‌سازد و **هر دو عضو سازمان نمونه هستند**، پس بعد از ورود
بلافاصله یک داشبورد پر می‌بینید:
`pnpm db:seed` دو حساب می‌سازد:
| نقش | ایمیل | رمز |
|---|---|---|
| کاربر نمونه | `demo@example.com` | `demo12345` |
| مالک سامانه (admin console) | `owner@example.com` | `owner12345` |
| نقش | ایمیل | رمز | چه می‌بیند |
|---|---|---|---|
| مالک سامانه (admin console) | `owner@example.com` | `owner12345` | کنسول مالک سامانه، بدون هیچ سازمانی |
| مدیر نمونه | `demo@example.com` | `demo12345` | سازمان نمونه که خودش مالک آن است |
> مالک سامانه **عضو هیچ سازمانی نمی‌شود** — او مستأجرها را از کنسول می‌بیند، نه از
> داخل داده‌شان. برای دیدن داشبورد سازمانی با حساب مدیر نمونه وارد شوید.
>
> این‌ها رمزهای **توسعه** هستند و در `app/seed.py` سخت‌کد شده‌اند. هیچ‌وقت در
> production از این‌ها استفاده نکنید و `JWT_SECRET` / `ENCRYPTION_KEY` واقعی
> بگذارید (نگهبان config در حالت production مقادیر placeholder را رد می‌کند).