From c29d0e31b1dffefdb2011917d795f6b55d5d99aa Mon Sep 17 00:00:00 2001 From: "s.esmaeili" Date: Fri, 2 Oct 2026 22:55:10 +0330 Subject: [PATCH] chore(api): update seed script --- apps/api/prisma/seed.ts | 350 +++++++++++++++++++++++++++++----------- 1 file changed, 256 insertions(+), 94 deletions(-) diff --git a/apps/api/prisma/seed.ts b/apps/api/prisma/seed.ts index 2595e9a..d5b0271 100644 --- a/apps/api/prisma/seed.ts +++ b/apps/api/prisma/seed.ts @@ -1,109 +1,271 @@ -import { PrismaClient } from '@prisma/client'; +import { AccessScopeType, AdminRole, AdminStatus, PrismaClient } from '@prisma/client'; +import * as bcrypt from 'bcryptjs'; const prisma = new PrismaClient(); -async function main() { - console.log('Seeding default demo campaign and prizes...'); +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 database with Workspaces, RBAC, Admin, and Demo Campaign...'); + + // 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); + } + + // 2. Seed Permissions + const permissionMap = new Map(); + 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); + } + + // 3. Seed Roles + const superAdminRole = await prisma.role.upsert({ + where: { code: 'SUPER_ADMIN' }, + update: { + name: 'مدیر ارشد سامانه', + description: 'دسترسی کامل و بدون محدودیت به تمامی بخش‌ها و تنظیمات سامانه', + isSystem: true, + }, + create: { + code: 'SUPER_ADMIN', + name: 'مدیر ارشد سامانه', + description: 'دسترسی کامل و بدون محدودیت به تمامی بخش‌ها و تنظیمات سامانه', + isSystem: true, + }, + }); + + const operatorRole = await prisma.role.upsert({ + where: { code: 'OPERATOR' }, + update: { + name: 'اپراتور سامانه', + description: 'مدیریت کمپین‌ها، جوایز، شانس‌ها، برندگان و کاربران بدون دسترسی به مدیریت ادمین‌ها', + isSystem: true, + }, + create: { + code: 'OPERATOR', + name: 'اپراتور سامانه', + description: 'مدیریت کمپین‌ها، جوایز، شانس‌ها، برندگان و کاربران بدون دسترسی به مدیریت ادمین‌ها', + isSystem: true, + }, + }); + + const supportRole = await prisma.role.upsert({ + where: { code: 'SUPPORT' }, + update: { + name: 'کارشناس پشتیبانی', + description: 'مشاهده کاربران، برندگان، پیامک‌ها و تغییر وضعیت تحویل جوایز', + isSystem: true, + }, + create: { + code: 'SUPPORT', + name: 'کارشناس پشتیبانی', + description: 'مشاهده کاربران، برندگان، پیامک‌ها و تغییر وضعیت تحویل جوایز', + isSystem: true, + }, + }); + + // Assign permissions to roles + 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, + }, + }); + } + + 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 }, + }); + } + } + + 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 }, + }); + } + } + + // 4. Ensure Default Super Admin Exists + const defaultAdminEmail = 'admin@luckywheel.local'; + let defaultAdmin = await prisma.admin.findUnique({ + where: { email: defaultAdminEmail }, + }); + + if (!defaultAdmin) { + const passwordHash = await bcrypt.hash('Admin@SecurePass2026', 10); + defaultAdmin = await prisma.admin.create({ + data: { + email: defaultAdminEmail, + name: 'مدیر ارشد سامانه', + mobile: '09120000000', + passwordHash, + role: AdminRole.SUPER_ADMIN, + isSuperAdmin: true, + status: AdminStatus.ACTIVE, + }, + }); + console.log('Created default Super Admin account:', defaultAdmin.email); + } + + // Backfill admin assignments + const admins = await prisma.admin.findMany(); + for (const admin of admins) { + const isSuper = admin.role === AdminRole.SUPER_ADMIN || admin.isSuperAdmin; + 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 }, + }); + } + + // 5. Ensure Default Campaign Exists const existingCampaign = await prisma.campaign.findUnique({ where: { slug: 'default-campaign' }, }); - if (existingCampaign) { - console.log('Default campaign already exists.'); - return; + if (!existingCampaign) { + const campaign = await prisma.campaign.create({ + data: { + title: 'گردونه شانس جشنواره بزرگ نوروزی', + slug: 'default-campaign', + description: 'هر روز با چرخاندن گردونه، شانس برنده شدن آیفون، سکه طلا، و کدهای تخفیف شگفت‌انگیز را داشته باشید!', + startAt: new Date(Date.now() - 24 * 3600 * 1000), + endAt: new Date(Date.now() + 60 * 24 * 3600 * 1000), + status: 'ACTIVE', + defaultChances: 3, + ctaText: 'بچرخون و برنده شو!', + wheelDesign: 'CLASSIC_CASINO', + workspaceId: defaultWorkspace.id, + rules: '۱. هر کاربر در بدو ورود ۳ شانس چرخاندن رایگان دریافت می‌کند.\n۲. جوایز فیزیکی پس از هماهنگی تلفنی توسط پشتیبانی ارسال خواهد شد.\n۳. کدهای تخفیف بلافاصله پیامک شده و تا ۷ روز پس از دریافت معتبر هستند.', + prizes: { + create: [ + { title: 'آیفون ۱۵ پرو', type: 'PHYSICAL', stockType: 'LIMITED', initialStock: 3, remainingStock: 3, weight: 1, sortOrder: 1, segmentColor: '#b91c1c' }, + { title: 'تخفیف ۵۰٪ دیجی‌کالا', type: 'DISCOUNT_CODE', stockType: 'UNLIMITED', weight: 25, sortOrder: 2, segmentColor: '#1e293b' }, + { title: 'کارت هدیه ۵۰۰ تومانی', type: 'CREDIT', stockType: 'LIMITED', initialStock: 50, remainingStock: 50, weight: 10, sortOrder: 3, segmentColor: '#b91c1c' }, + { title: 'پوچ (دفعه بعد)', type: 'NO_PRIZE', stockType: 'UNLIMITED', weight: 20, sortOrder: 4, segmentColor: '#1e293b' }, + { title: 'تخفیف ۳۰٪ اسنپ', type: 'DISCOUNT_CODE', stockType: 'UNLIMITED', weight: 20, sortOrder: 5, segmentColor: '#b91c1c' }, + { title: 'ساعت هوشمند', type: 'PHYSICAL', stockType: 'LIMITED', initialStock: 10, remainingStock: 10, weight: 4, sortOrder: 6, segmentColor: '#1e293b' }, + { title: 'شانس مجدد', type: 'OTHER', stockType: 'UNLIMITED', weight: 15, sortOrder: 7, segmentColor: '#b91c1c' }, + { title: 'پوچ (بدون جایزه)', type: 'NO_PRIZE', stockType: 'UNLIMITED', weight: 5, sortOrder: 8, segmentColor: '#1e293b' }, + ], + }, + }, + }); + console.log(`Campaign created with ID: ${campaign.id} and 8 prizes.`); } - const campaign = await prisma.campaign.create({ - data: { - title: 'گردونه شانس جشنواره بزرگ نوروزی', - slug: 'default-campaign', - description: 'هر روز با چرخاندن گردونه، شانس برنده شدن آیفون، سکه طلا، و کدهای تخفیف شگفت‌انگیز را داشته باشید!', - startAt: new Date(Date.now() - 24 * 3600 * 1000), - endAt: new Date(Date.now() + 60 * 24 * 3600 * 1000), - status: 'ACTIVE', - defaultChances: 3, - ctaText: 'بچرخون و برنده شو!', - wheelDesign: 'CLASSIC_CASINO', - rules: '۱. هر کاربر در بدو ورود ۳ شانس چرخاندن رایگان دریافت می‌کند.\n۲. جوایز فیزیکی پس از هماهنگی تلفنی توسط پشتیبانی ارسال خواهد شد.\n۳. کدهای تخفیف بلافاصله پیامک شده و تا ۷ روز پس از دریافت معتبر هستند.', - prizes: { - create: [ - { - title: 'آیفون ۱۵ پرو', - type: 'PHYSICAL', - stockType: 'LIMITED', - initialStock: 3, - remainingStock: 3, - weight: 1, - sortOrder: 1, - segmentColor: '#b91c1c', - }, - { - title: 'تخفیف ۵۰٪ دیجی‌کالا', - type: 'DISCOUNT_CODE', - stockType: 'UNLIMITED', - weight: 25, - sortOrder: 2, - segmentColor: '#1e293b', - }, - { - title: 'کارت هدیه ۵۰۰ تومانی', - type: 'CREDIT', - stockType: 'LIMITED', - initialStock: 50, - remainingStock: 50, - weight: 10, - sortOrder: 3, - segmentColor: '#b91c1c', - }, - { - title: 'پوچ (دفعه بعد)', - type: 'NO_PRIZE', - stockType: 'UNLIMITED', - weight: 20, - sortOrder: 4, - segmentColor: '#1e293b', - }, - { - title: 'تخفیف ۳۰٪ اسنپ', - type: 'DISCOUNT_CODE', - stockType: 'UNLIMITED', - weight: 20, - sortOrder: 5, - segmentColor: '#b91c1c', - }, - { - title: 'ساعت هوشمند', - type: 'PHYSICAL', - stockType: 'LIMITED', - initialStock: 10, - remainingStock: 10, - weight: 4, - sortOrder: 6, - segmentColor: '#1e293b', - }, - { - title: 'شانس مجدد', - type: 'OTHER', - stockType: 'UNLIMITED', - weight: 15, - sortOrder: 7, - segmentColor: '#b91c1c', - }, - { - title: 'پوچ (بدون جایزه)', - type: 'NO_PRIZE', - stockType: 'UNLIMITED', - weight: 5, - sortOrder: 8, - segmentColor: '#1e293b', - }, - ], - }, - }, + // Link any unassigned campaigns to default workspace + await prisma.campaign.updateMany({ + where: { workspaceId: null }, + data: { workspaceId: defaultWorkspace.id }, }); - console.log(`Campaign created with ID: ${campaign.id} and 8 prizes.`); + console.log('Database seeding finished successfully.'); } main()