ساختار مالکیت پلتفرم: یک مالک، مدیرانِ ساختهٔ مالک، سازمانهای مدیریتشده
- 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:
@@ -215,13 +215,15 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
</button>
|
||||
))}
|
||||
<div className="my-1 border-t border-surface-border" />
|
||||
<Link
|
||||
href="/onboarding"
|
||||
className="transition-colors block rounded-md px-2 py-2 text-xs text-slate-300 hover:bg-surface-raised"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
ساخت سازمان جدید
|
||||
</Link>
|
||||
{(user?.is_platform_owner || user?.is_manager) && (
|
||||
<Link
|
||||
href="/onboarding"
|
||||
className="transition-colors block rounded-md px-2 py-2 text-xs text-slate-300 hover:bg-surface-raised"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
ساخت سازمان جدید
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -245,6 +247,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
{user?.name || user?.email}
|
||||
</span>
|
||||
{user?.is_platform_owner && <Badge tone="info">مالک سامانه</Badge>}
|
||||
{user?.is_manager && <Badge tone="success">مدیر</Badge>}
|
||||
</button>
|
||||
{userMenuOpen && (
|
||||
<div className="anim-slide-up absolute end-0 top-11 z-40 w-56 rounded-lg border border-surface-border bg-surface-overlay p-1.5">
|
||||
@@ -255,6 +258,13 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
<p className="truncate text-xs text-slate-500">{user?.email}</p>
|
||||
</div>
|
||||
<div className="my-1 border-t border-surface-border" />
|
||||
<Link
|
||||
href="/account"
|
||||
onClick={() => setUserMenuOpen(false)}
|
||||
className="transition-colors block rounded-md px-2 py-1.5 text-xs text-slate-300 hover:bg-surface-raised"
|
||||
>
|
||||
حساب کاربری و رمز عبور
|
||||
</Link>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setUserMenuOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user