194 lines
7.2 KiB
TypeScript
194 lines
7.2 KiB
TypeScript
|
|
/**
|
|||
|
|
* 广东省立中山图书馆 - 租户初始化脚本
|
|||
|
|
*
|
|||
|
|
* 运行方式:
|
|||
|
|
* npx ts-node -r tsconfig-paths/register scripts/init-guangdong-library.ts
|
|||
|
|
*
|
|||
|
|
* 功能:
|
|||
|
|
* 1. 创建广东省图租户(library 类型)
|
|||
|
|
* 2. 创建管理员账号
|
|||
|
|
* 3. 分配必要的角色和权限
|
|||
|
|
* 4. 分配菜单
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { PrismaClient } from '@prisma/client';
|
|||
|
|
import * as bcrypt from 'bcrypt';
|
|||
|
|
|
|||
|
|
const prisma = new PrismaClient();
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
console.log('开始初始化广东省图数据...\n');
|
|||
|
|
|
|||
|
|
// 1. 创建租户
|
|||
|
|
const tenantCode = 'gdlib';
|
|||
|
|
let tenant = await prisma.tenant.findUnique({ where: { code: tenantCode } });
|
|||
|
|
|
|||
|
|
if (tenant) {
|
|||
|
|
console.log(`租户 ${tenantCode} 已存在 (ID: ${tenant.id}),跳过创建`);
|
|||
|
|
} else {
|
|||
|
|
tenant = await prisma.tenant.create({
|
|||
|
|
data: {
|
|||
|
|
name: '广东省立中山图书馆',
|
|||
|
|
code: tenantCode,
|
|||
|
|
tenantType: 'library',
|
|||
|
|
description: '广东省图少儿绘本创作活动主办方',
|
|||
|
|
isSuper: 0,
|
|||
|
|
validState: 1,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
console.log(`创建租户: ${tenant.name} (ID: ${tenant.id})`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const tenantId = tenant.id;
|
|||
|
|
|
|||
|
|
// 2. 创建权限
|
|||
|
|
const permissions = [
|
|||
|
|
// 活动管理
|
|||
|
|
{ code: 'contest:create', resource: 'contest', action: 'create', name: '创建活动', description: '允许创建活动' },
|
|||
|
|
{ code: 'contest:read', resource: 'contest', action: 'read', name: '查看活动', description: '允许查看活动' },
|
|||
|
|
{ code: 'contest:update', resource: 'contest', action: 'update', name: '更新活动', description: '允许更新活动' },
|
|||
|
|
{ code: 'contest:delete', resource: 'contest', action: 'delete', name: '删除活动', description: '允许删除活动' },
|
|||
|
|
{ code: 'contest:publish', resource: 'contest', action: 'publish', name: '发布活动', description: '允许发布活动' },
|
|||
|
|
{ code: 'contest:finish', resource: 'contest', action: 'finish', name: '结束活动', description: '允许结束活动' },
|
|||
|
|
// 报名管理
|
|||
|
|
{ code: 'registration:approve', resource: 'registration', action: 'approve', name: '审核报名', description: '允许审核报名' },
|
|||
|
|
{ code: 'registration:read', resource: 'registration', action: 'read', name: '查看报名', description: '允许查看报名记录' },
|
|||
|
|
// 评委管理
|
|||
|
|
{ code: 'judge:read', resource: 'judge', action: 'read', name: '查看评委', description: '允许查看评委' },
|
|||
|
|
{ code: 'judge:create', resource: 'judge', action: 'create', name: '添加评委', description: '允许添加评委' },
|
|||
|
|
{ code: 'judge:assign', resource: 'judge', action: 'assign', name: '分配评委', description: '允许分配评委' },
|
|||
|
|
// 评审规则
|
|||
|
|
{ code: 'review-rule:read', resource: 'review-rule', action: 'read', name: '查看评审规则', description: '允许查看评审规则' },
|
|||
|
|
{ code: 'review-rule:create', resource: 'review-rule', action: 'create', name: '创建评审规则', description: '允许创建评审规则' },
|
|||
|
|
// 成果发布
|
|||
|
|
{ code: 'result:read', resource: 'result', action: 'read', name: '查看成果', description: '允许查看活动成果' },
|
|||
|
|
{ code: 'result:publish', resource: 'result', action: 'publish', name: '发布成果', description: '允许发布活动成果' },
|
|||
|
|
// 公告
|
|||
|
|
{ code: 'notice:create', resource: 'notice', action: 'create', name: '创建公告', description: '允许创建活动公告' },
|
|||
|
|
{ code: 'notice:read', resource: 'notice', action: 'read', name: '查看公告', description: '允许查看活动公告' },
|
|||
|
|
// 用户管理
|
|||
|
|
{ code: 'user:read', resource: 'user', action: 'read', name: '查看用户', description: '允许查看用户' },
|
|||
|
|
{ code: 'user:create', resource: 'user', action: 'create', name: '创建用户', description: '允许创建用户' },
|
|||
|
|
// 角色管理
|
|||
|
|
{ code: 'role:read', resource: 'role', action: 'read', name: '查看角色', description: '允许查看角色' },
|
|||
|
|
// 菜单
|
|||
|
|
{ code: 'menu:read', resource: 'menu', action: 'read', name: '查看菜单', description: '允许查看菜单' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const permissionIds: number[] = [];
|
|||
|
|
for (const perm of permissions) {
|
|||
|
|
const existing = await prisma.permission.findFirst({
|
|||
|
|
where: { tenantId, code: perm.code },
|
|||
|
|
});
|
|||
|
|
if (existing) {
|
|||
|
|
permissionIds.push(existing.id);
|
|||
|
|
} else {
|
|||
|
|
const created = await prisma.permission.create({
|
|||
|
|
data: { ...perm, tenantId, validState: 1 },
|
|||
|
|
});
|
|||
|
|
permissionIds.push(created.id);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log(`权限已创建/确认: ${permissionIds.length} 个`);
|
|||
|
|
|
|||
|
|
// 3. 创建管理员角色
|
|||
|
|
let adminRole = await prisma.role.findFirst({
|
|||
|
|
where: { tenantId, code: 'tenant_admin' },
|
|||
|
|
});
|
|||
|
|
if (!adminRole) {
|
|||
|
|
adminRole = await prisma.role.create({
|
|||
|
|
data: {
|
|||
|
|
tenantId,
|
|||
|
|
name: '机构管理员',
|
|||
|
|
code: 'tenant_admin',
|
|||
|
|
description: '广东省图机构管理员,管理活动和报名',
|
|||
|
|
validState: 1,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
console.log(`创建角色: ${adminRole.name}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分配所有权限给管理员角色
|
|||
|
|
for (const permId of permissionIds) {
|
|||
|
|
const existing = await prisma.rolePermission.findFirst({
|
|||
|
|
where: { roleId: adminRole.id, permissionId: permId },
|
|||
|
|
});
|
|||
|
|
if (!existing) {
|
|||
|
|
await prisma.rolePermission.create({
|
|||
|
|
data: { roleId: adminRole.id, permissionId: permId },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log(`角色权限已分配`);
|
|||
|
|
|
|||
|
|
// 4. 创建管理员账号
|
|||
|
|
const adminUsername = 'admin';
|
|||
|
|
let adminUser = await prisma.user.findFirst({
|
|||
|
|
where: { tenantId, username: adminUsername },
|
|||
|
|
});
|
|||
|
|
if (!adminUser) {
|
|||
|
|
const hashedPassword = await bcrypt.hash('admin@gdlib', 10);
|
|||
|
|
adminUser = await prisma.user.create({
|
|||
|
|
data: {
|
|||
|
|
tenantId,
|
|||
|
|
username: adminUsername,
|
|||
|
|
password: hashedPassword,
|
|||
|
|
nickname: '广东省图管理员',
|
|||
|
|
userSource: 'admin_created',
|
|||
|
|
status: 'enabled',
|
|||
|
|
validState: 1,
|
|||
|
|
roles: {
|
|||
|
|
create: [{ roleId: adminRole.id }],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
console.log(`创建管理员: ${adminUsername} / admin@gdlib`);
|
|||
|
|
} else {
|
|||
|
|
console.log(`管理员 ${adminUsername} 已存在`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 分配菜单(活动管理 + 系统管理)
|
|||
|
|
const menuNames = ['活动管理', '系统管理'];
|
|||
|
|
const menus = await prisma.menu.findMany({
|
|||
|
|
where: { name: { in: menuNames }, parentId: null },
|
|||
|
|
include: { children: true },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (const menu of menus) {
|
|||
|
|
// 分配父菜单
|
|||
|
|
const existing = await prisma.tenantMenu.findFirst({
|
|||
|
|
where: { tenantId, menuId: menu.id },
|
|||
|
|
});
|
|||
|
|
if (!existing) {
|
|||
|
|
await prisma.tenantMenu.create({
|
|||
|
|
data: { tenantId, menuId: menu.id },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// 分配子菜单
|
|||
|
|
for (const child of menu.children || []) {
|
|||
|
|
const childExisting = await prisma.tenantMenu.findFirst({
|
|||
|
|
where: { tenantId, menuId: child.id },
|
|||
|
|
});
|
|||
|
|
if (!childExisting) {
|
|||
|
|
await prisma.tenantMenu.create({
|
|||
|
|
data: { tenantId, menuId: child.id },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log(`菜单已分配: ${menuNames.join(', ')}`);
|
|||
|
|
|
|||
|
|
console.log('\n广东省图初始化完成!');
|
|||
|
|
console.log(`登录地址: /${tenantCode}/login`);
|
|||
|
|
console.log(`账号: admin / admin@gdlib`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main()
|
|||
|
|
.catch((e) => {
|
|||
|
|
console.error('初始化失败:', e);
|
|||
|
|
process.exit(1);
|
|||
|
|
})
|
|||
|
|
.finally(async () => {
|
|||
|
|
await prisma.$disconnect();
|
|||
|
|
});
|