Files
s.esmaeili c5a841c35f feat(rbac): implement full admin management, dynamic roles, permissions matrix, and workspace access control
- Add Workspace, Role, Permission, RolePermission, AdminRoleAssignment, and AdminWorkspaceAssignment models to Prisma schema\n- Create database migration and RBAC seed script\n- Implement PermissionsGuard, @RequirePermission decorator, and real-time permission resolution in JwtAdminStrategy\n- Implement AdminManagementModule with endpoints for managers CRUD, roles matrix, permissions catalog, and workspaces\n- Secure all sensitive admin controllers with granular permission requirements\n- Add useAuthStore helpers (can, canAny, fetchMe) and v-permission directive\n- Implement Desktop views: AdminListView, AdminFormDrawer (RTL right-side), RoleMatrixView, WorkspaceListView, and ForbiddenView\n- Implement Mobile views: MAdminListView, MAdminFormView (stepped mobile form), MRoleMatrixView, MWorkspaceListView, and MForbiddenView\n- Fix sticky header behavior across desktop and mobile shells\n- Update route guards and device detection logic for mobile prefixing
2026-10-02 16:29:23 +03:30

273 lines
9.9 KiB
TypeScript

import { AccessScopeType, PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const PERMISSIONS = [
// Campaigns
{ code: 'campaigns:read', module: 'campaigns', name: 'مشاهده کمپین‌ها', description: 'مشاهده لیست و جزئیات کمپین‌ها' },
{ code: 'campaigns:create', module: 'campaigns', name: 'ایجاد کمپین', description: 'ثبت کمپین جدید' },
{ code: 'campaigns:update', module: 'campaigns', name: 'ویرایش کمپین', description: 'تغییر تنظیمات و وضعیت کمپین' },
{ code: 'campaigns:delete', module: 'campaigns', name: 'حذف کمپین', description: 'حذف کمپین از سامانه' },
// Prizes
{ code: 'prizes:read', module: 'prizes', name: 'مشاهده جوایز', description: 'مشاهده لیست جوایز کمپین‌ها' },
{ code: 'prizes:manage', module: 'prizes', name: 'مدیریت جوایز', description: 'افزودن، ویرایش و تنظیم ظرفیت جوایز' },
// Users & Chances
{ code: 'users:read', module: 'users', name: 'مشاهده کاربران', description: 'مشاهده لیست کاربران شرکت‌کننده و پروفایل' },
{ code: 'users:block', module: 'users', name: 'مسدودسازی کاربران', description: 'بلاک یا آن‌بلاک کردن کاربران' },
{ code: 'chances:adjust', module: 'chances', name: 'تنظیم دستی شانس', description: 'افزایش یا کاهش شانس با ثبت لاگ' },
// Winners & Spins
{ code: 'spins:read', module: 'spins', name: 'مشاهده لاگ چرخش‌ها', description: 'بررسی تاریخچه اسپین‌های کاربران' },
{ code: 'winners:read', module: 'winners', name: 'مشاهده برندگان', description: 'مشاهده لیست برندگان و کدهای هدیه' },
{ code: 'winners:manage', module: 'winners', name: 'مدیریت وضعیت جوایز', description: 'تغییر وضعیت کدهای جایزه به تحویل‌شده و...' },
// SMS & Logs
{ code: 'sms:read', module: 'sms', name: 'مشاهده لاگ پیامک', description: 'مشاهده پیامک‌های ارسالی OTP و برندگان' },
{ code: 'sms:resend', module: 'sms', name: 'ارسال مجدد پیامک', description: 'ارسال دوباره پیامک‌های ناموفق' },
{ code: 'audit:read', module: 'audit', name: 'مشاهده لاگ بازرسی', description: 'مشاهده ردپای عملیات و تغییرات مدیران' },
// Reports
{ code: 'reports:read', module: 'reports', name: 'مشاهده گزارش‌ها و آمار', description: 'مشاهده نمودارها، آمار زنده و داشبورد' },
{ code: 'reports:export', module: 'reports', name: 'خروجی داده‌ها', description: 'دریافت خروجی اکسل و CSV گزارش‌ها' },
// Access Control & Admins
{ code: 'admins:read', module: 'admins', name: 'مشاهده مدیران', description: 'مشاهده لیست مدیران و همکاران' },
{ code: 'admins:manage', module: 'admins', name: 'مدیریت مدیران', description: 'ایجاد، ویرایش، تغییر رمز و غیرفعال‌سازی مدیران' },
{ code: 'roles:manage', module: 'roles', name: 'مدیریت نقش‌ها و مجوزها', description: 'تعریف نقش جدید و تخصیص مجوزها' },
{ code: 'workspaces:manage', module: 'workspaces', name: 'مدیریت فضاهای کاری', description: 'تعریف و مدیریت فضاهای کاری سازمانی' },
];
async function main() {
console.log('Seeding RBAC and Workspaces...');
// 1. Ensure Default Workspace exists
let defaultWorkspace = await prisma.workspace.findUnique({
where: { slug: 'default-workspace' },
});
if (!defaultWorkspace) {
defaultWorkspace = await prisma.workspace.create({
data: {
name: 'فضای کاری اصلی',
slug: 'default-workspace',
description: 'فضای کاری پیش‌فرض و سراسری سیستم گردونه شانس',
status: 'ACTIVE',
},
});
console.log('Created default workspace:', defaultWorkspace.id);
}
// Link any unassigned campaigns to default workspace
await prisma.campaign.updateMany({
where: { workspaceId: null },
data: { workspaceId: defaultWorkspace.id },
});
// 2. Seed Permissions
const permissionMap = new Map<string, string>();
for (const perm of PERMISSIONS) {
const p = await prisma.permission.upsert({
where: { code: perm.code },
update: {
module: perm.module,
name: perm.name,
description: perm.description,
},
create: {
code: perm.code,
module: perm.module,
name: perm.name,
description: perm.description,
},
});
permissionMap.set(p.code, p.id);
}
console.log(`Seeded ${permissionMap.size} permissions.`);
// 3. Seed Roles
// SUPER_ADMIN (All permissions)
const superAdminRole = await prisma.role.upsert({
where: { code: 'SUPER_ADMIN' },
update: {
name: 'مدیر ارشد سامانه',
description: 'دسترسی کامل و بدون محدودیت به تمامی بخش‌ها و تنظیمات سامانه',
isSystem: true,
},
create: {
code: 'SUPER_ADMIN',
name: 'مدیر ارشد سامانه',
description: 'دسترسی کامل و بدون محدودیت به تمامی بخش‌ها و تنظیمات سامانه',
isSystem: true,
},
});
// OPERATOR (Operational permissions)
const operatorRole = await prisma.role.upsert({
where: { code: 'OPERATOR' },
update: {
name: 'اپراتور سامانه',
description: 'مدیریت کمپین‌ها، جوایز، شانس‌ها، برندگان و کاربران بدون دسترسی به مدیریت ادمین‌ها',
isSystem: true,
},
create: {
code: 'OPERATOR',
name: 'اپراتور سامانه',
description: 'مدیریت کمپین‌ها، جوایز، شانس‌ها، برندگان و کاربران بدون دسترسی به مدیریت ادمین‌ها',
isSystem: true,
},
});
// SUPPORT (Read-only + winner management)
const supportRole = await prisma.role.upsert({
where: { code: 'SUPPORT' },
update: {
name: 'کارشناس پشتیبانی',
description: 'مشاهده کاربران، برندگان، پیامک‌ها و تغییر وضعیت تحویل جوایز',
isSystem: true,
},
create: {
code: 'SUPPORT',
name: 'کارشناس پشتیبانی',
description: 'مشاهده کاربران، برندگان، پیامک‌ها و تغییر وضعیت تحویل جوایز',
isSystem: true,
},
});
// 4. Assign permissions to roles
// SUPER_ADMIN gets all permissions
for (const permId of permissionMap.values()) {
await prisma.rolePermission.upsert({
where: {
roleId_permissionId: {
roleId: superAdminRole.id,
permissionId: permId,
},
},
update: {},
create: {
roleId: superAdminRole.id,
permissionId: permId,
},
});
}
// OPERATOR permissions
const operatorPermCodes = [
'campaigns:read', 'campaigns:create', 'campaigns:update', 'campaigns:delete',
'prizes:read', 'prizes:manage',
'users:read', 'users:block', 'chances:adjust',
'spins:read', 'winners:read', 'winners:manage',
'sms:read', 'sms:resend',
'reports:read', 'reports:export',
'audit:read',
];
for (const code of operatorPermCodes) {
const permId = permissionMap.get(code);
if (permId) {
await prisma.rolePermission.upsert({
where: {
roleId_permissionId: {
roleId: operatorRole.id,
permissionId: permId,
},
},
update: {},
create: {
roleId: operatorRole.id,
permissionId: permId,
},
});
}
}
// SUPPORT permissions
const supportPermCodes = [
'campaigns:read',
'prizes:read',
'users:read',
'spins:read',
'winners:read', 'winners:manage',
'sms:read',
];
for (const code of supportPermCodes) {
const permId = permissionMap.get(code);
if (permId) {
await prisma.rolePermission.upsert({
where: {
roleId_permissionId: {
roleId: supportRole.id,
permissionId: permId,
},
},
update: {},
create: {
roleId: supportRole.id,
permissionId: permId,
},
});
}
}
// 5. Backfill existing admins
const admins = await prisma.admin.findMany();
for (const admin of admins) {
const isSuper = admin.role === 'SUPER_ADMIN';
await prisma.admin.update({
where: { id: admin.id },
data: {
isSuperAdmin: isSuper,
},
});
const targetRoleId = isSuper
? superAdminRole.id
: admin.role === 'OPERATOR'
? operatorRole.id
: supportRole.id;
await prisma.adminRoleAssignment.upsert({
where: {
adminId_roleId: {
adminId: admin.id,
roleId: targetRoleId,
},
},
update: {},
create: {
adminId: admin.id,
roleId: targetRoleId,
},
});
await prisma.adminWorkspaceAssignment.upsert({
where: {
adminId_workspaceId: {
adminId: admin.id,
workspaceId: defaultWorkspace.id,
},
},
update: {},
create: {
adminId: admin.id,
workspaceId: defaultWorkspace.id,
scopeType: AccessScopeType.ALL,
},
});
}
console.log(`Backfilled ${admins.length} existing admins with roles and workspace assignments.`);
console.log('RBAC seed completed successfully.');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});