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

- 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
+26
View File
@@ -29,6 +29,32 @@
> `src/test/motion.test.tsx` Tests می‌شود تا بازنویسی بعدی آن را بی‌صدا
> حذف نکند.
>
> **افزودهٔ بعد از بستن مراحل (درخواست کاربر):** ساختار مالکیت پلتفرم. **دقیقاً یک
> مالک سامانه** وجود دارد که صاحب کل سیستم است و تنها او می‌تواند **مدیر** بسازد؛
> هر مدیر می‌تواند سازمان‌های خودش را بسازد یا با دعوت در سازمان مدیران دیگر عضو
> شود. جزئیات:
>
> - `users.is_platform_owner` (boolean) جای خود را به `platform_role`
> (`owner` | `manager` | `user`) داد، با مهاجرت `d4e5f6a7b8c9` و backfill.
> - «فقط یک مالک» یک قاعدهٔ دیتابیسی است: ایندکس یکتای جزئی
> `uq_single_platform_owner ... WHERE platform_role = 'owner'`.
> - **ثبت‌نام عمومی حذف شد** (`POST /auth/signup` وجود ندارد). حساب‌ها از
> `POST /admin/users` (فقط مالک) یا از پذیرش لینک دعوت ساخته می‌شوند؛ در مسیر
> دعوت، اگر حساب وجود نداشته باشد خود دعوت‌شده رمز تعیین می‌کند، و اگر وجود
> داشته باشد لینک هرگز جای reset رمز را نمی‌گیرد.
> - ساخت سازمان فقط برای مالک و مدیران مجاز است؛ کاربر عادی با دعوت عضو می‌شود.
> - تنزل یا حذف یک مدیر، سازمان‌هایش را به مالک سامانه منتقل می‌کند (مالکیت +
> عضویت، با audit). حساب مالک از API قابل تنزل/غیرفعال/حذف نیست؛ جابه‌جایی
> صندلی فقط با `POST /admin/transfer-ownership`.
> - فرانت‌اند: صفحهٔ جدید `/admin/users`، صفحهٔ `/signup` حذف شد، صفحهٔ
> `/accept-invite` برای ساخت حساب بازنویسی شد، و لینک «ساخت سازمان جدید» برای
> کاربران عادی پنهان است.
> - تست‌ها: `tests/test_platform_hierarchy.py` (۲۲ تست) + به‌روزرسانی
> `test_auth.py`, `test_invites.py`, `test_seed.py`, `test_realtime.py`؛
> جمع بک‌اند از ۲۱۶ به **۳۰۰ تست** و فرانت‌اند از ۴۵ به **۶۴ تست** رسید.
> `PRAGMA foreign_keys=ON` برای SQLite فعال شد تا `ON DELETE CASCADE` در dev و
> prod یکسان رفتار کند (پیش‌تر حذف کاربر، عضویت‌هایش را یتیم می‌گذاشت).
>
> **چیزهایی که عمداً باز مانده‌اند:** در `docs/security-checklist.md` دو مورد
> «⚠️ بدون تست اختصاصی» علامت خورده‌اند (نبود secret در frontend، و مقدار پیش‌فرض
> `NetworkPolicy.DENY_ALL`). هر دو کنترل در کد درست کار می‌کنند، فقط تست مستقل