2026-10-02 22:55:10 +03:30
import { AccessScopeType , AdminRole , AdminStatus , PrismaClient } from '@prisma/client' ;
import * as bcrypt from 'bcryptjs' ;
2026-09-10 01:58:29 +03:30
const prisma = new PrismaClient ();
2026-10-02 22:55:10 +03:30
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 : 'حذف کمپین از سامانه' },
2026-09-10 01:58:29 +03:30
2026-10-02 22:55:10 +03:30
// 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 < 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 );
}
// 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
2026-09-10 01:58:29 +03:30
const existingCampaign = await prisma . campaign . findUnique ({
where : { slug : 'default-campaign' },
});
2026-10-02 22:55:10 +03:30
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.` );
2026-09-10 01:58:29 +03:30
}
2026-10-02 22:55:10 +03:30
// Link any unassigned campaigns to default workspace
await prisma . campaign . updateMany ({
where : { workspaceId : null },
data : { workspaceId : defaultWorkspace.id },
2026-09-10 01:58:29 +03:30
});
2026-10-02 22:55:10 +03:30
console . log ( 'Database seeding finished successfully.' );
2026-09-10 01:58:29 +03:30
}
main ()
. catch (( e ) => {
console . error ( e );
process . exit ( 1 );
})
. finally ( async () => {
await prisma . $disconnect ();
});