library-picturebook-activity/backend/scripts/init-admin.ts
aid 418aa57ea8 Day4: 超管端设计优化 + UGC绘本创作社区P0实现
一、超管端设计优化
- 文档管理SOP体系建立,docs目录重组
- 统一用户管理:跨租户全局视角,合并用户管理+公众用户
- 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作)
- 菜单精简:移除评委管理/评审规则/通知管理
- Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一

二、UGC绘本创作社区P0
- 数据库:10张新表(user_works/user_work_pages/work_tags等)
- 子女账号独立化:Child升级为独立User,家长切换+独立登录
- 用户作品库:CRUD+发布审核,8个API
- AI创作流程:提交→生成→保存到作品库,4个API
- 作品广场:首页改造为推荐流,标签+搜索+排序
- 内容审核(超管端):作品审核+作品管理+标签管理
- 活动联动:WorkSelector作品选择器
- 布局改造:底部5Tab(发现/创作/活动/作品库/我的)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:20:25 +08:00

278 lines
13 KiB
TypeScript

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
// 初始化超级管理员脚本(支持多租户)
import * as dotenv from 'dotenv';
import * as path from 'path';
// 根据 NODE_ENV 加载对应的环境配置文件
const nodeEnv = process.env.NODE_ENV || 'development';
const envFile = `.env.${nodeEnv}`;
const backendDir = path.resolve(__dirname, '..');
const envPath = path.resolve(backendDir, envFile);
dotenv.config({ path: envPath });
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.resolve(backendDir, '.env') });
}
if (!process.env.DATABASE_URL) {
console.error('❌ 错误: 未找到 DATABASE_URL 环境变量');
process.exit(1);
}
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
// 超级管理员基础权限
const permissions = [
// 工作台
{ code: 'workbench:read', resource: 'workbench', action: 'read', name: '查看工作台', description: '允许查看工作台' },
// 用户管理
{ code: 'user:create', resource: 'user', action: 'create', name: '创建用户', description: '允许创建新用户' },
{ code: 'user:read', resource: 'user', action: 'read', name: '查看用户', description: '允许查看用户列表和详情' },
{ code: 'user:update', resource: 'user', action: 'update', name: '更新用户', description: '允许更新用户信息' },
{ code: 'user:delete', resource: 'user', action: 'delete', name: '删除用户', description: '允许删除用户' },
// 角色管理
{ code: 'role:create', resource: 'role', action: 'create', name: '创建角色', description: '允许创建新角色' },
{ code: 'role:read', resource: 'role', action: 'read', name: '查看角色', description: '允许查看角色列表和详情' },
{ code: 'role:update', resource: 'role', action: 'update', name: '更新角色', description: '允许更新角色信息' },
{ code: 'role:delete', resource: 'role', action: 'delete', name: '删除角色', description: '允许删除角色' },
{ code: 'role:assign', resource: 'role', action: 'assign', name: '分配角色', description: '允许给用户分配角色' },
// 权限管理
{ code: 'permission:read', resource: 'permission', action: 'read', name: '查看权限', description: '允许查看权限列表' },
// 菜单管理
{ code: 'menu:read', resource: 'menu', action: 'read', name: '查看菜单', description: '允许查看菜单列表' },
// 租户管理
{ code: 'tenant:create', resource: 'tenant', action: 'create', name: '创建租户', description: '允许创建租户' },
{ code: 'tenant:read', resource: 'tenant', action: 'read', name: '查看租户', description: '允许查看租户列表' },
{ code: 'tenant:update', resource: 'tenant', action: 'update', name: '更新租户', description: '允许更新租户信息' },
{ code: 'tenant:delete', resource: 'tenant', action: 'delete', name: '删除租户', description: '允许删除租户' },
// 活动管理
{ 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: 'review-rule:create', resource: 'review-rule', action: 'create', name: '创建评审规则', description: '允许创建评审规则' },
{ code: 'review-rule:read', resource: 'review-rule', action: 'read', name: '查看评审规则', description: '允许查看评审规则' },
{ code: 'review-rule:update', resource: 'review-rule', action: 'update', name: '更新评审规则', description: '允许更新评审规则' },
{ code: 'review-rule:delete', resource: 'review-rule', action: 'delete', name: '删除评审规则', description: '允许删除评审规则' },
// 评委管理
{ code: 'judge:create', resource: 'judge', action: 'create', name: '添加评委', description: '允许添加评委' },
{ code: 'judge:read', resource: 'judge', action: 'read', name: '查看评委', description: '允许查看评委列表' },
{ code: 'judge:update', resource: 'judge', action: 'update', name: '更新评委', description: '允许更新评委信息' },
{ code: 'judge:delete', resource: 'judge', action: 'delete', name: '删除评委', description: '允许删除评委' },
{ code: 'judge:assign', resource: 'judge', action: 'assign', name: '分配评委', description: '允许为活动分配评委' },
// 报名管理
{ code: 'registration:read', resource: 'registration', action: 'read', name: '查看报名', description: '允许查看报名记录' },
{ code: 'registration:approve', resource: 'registration', action: 'approve', name: '审核报名', description: '允许审核报名' },
// 作品管理
{ code: 'work:read', resource: 'work', action: 'read', name: '查看作品', description: '允许查看参赛作品' },
// 公告管理
{ code: 'notice:create', resource: 'notice', action: 'create', name: '创建公告', description: '允许创建活动公告' },
{ code: 'notice:read', resource: 'notice', action: 'read', name: '查看公告', description: '允许查看活动公告' },
{ code: 'notice:update', resource: 'notice', action: 'update', name: '更新公告', description: '允许更新公告信息' },
{ code: 'notice:delete', resource: 'notice', action: 'delete', name: '删除公告', description: '允许删除公告' },
// 系统管理
{ code: 'dict:create', resource: 'dict', action: 'create', name: '创建字典', description: '允许创建新字典' },
{ code: 'dict:read', resource: 'dict', action: 'read', name: '查看字典', description: '允许查看字典列表和详情' },
{ code: 'dict:update', resource: 'dict', action: 'update', name: '更新字典', description: '允许更新字典信息' },
{ code: 'dict:delete', resource: 'dict', action: 'delete', name: '删除字典', description: '允许删除字典' },
{ code: 'config:create', resource: 'config', action: 'create', name: '创建配置', description: '允许创建新配置' },
{ code: 'config:read', resource: 'config', action: 'read', name: '查看配置', description: '允许查看配置列表和详情' },
{ code: 'config:update', resource: 'config', action: 'update', name: '更新配置', description: '允许更新配置信息' },
{ code: 'config:delete', resource: 'config', action: 'delete', name: '删除配置', description: '允许删除配置' },
{ code: 'log:read', resource: 'log', action: 'read', name: '查看日志', description: '允许查看系统日志' },
{ code: 'log:delete', resource: 'log', action: 'delete', name: '删除日志', description: '允许删除系统日志' },
];
async function initAdmin() {
try {
console.log('🚀 开始初始化超级管理员...\n');
// 1. 获取或创建超级租户
console.log('🏢 步骤 1: 获取超级租户...');
let superTenant = await prisma.tenant.findFirst({
where: { isSuper: 1, validState: 1 }
});
if (!superTenant) {
console.log(' 未找到超级租户,正在创建...');
superTenant = await prisma.tenant.create({
data: {
name: '超级租户',
code: 'super',
isSuper: 1,
validState: 1,
}
});
console.log(` ✓ 创建超级租户: ${superTenant.name} (${superTenant.code})`);
} else {
console.log(` ✓ 找到超级租户: ${superTenant.name} (ID: ${superTenant.id})`);
}
const tenantId = superTenant.id;
// 2. 创建权限
console.log('\n📝 步骤 2: 创建基础权限...');
const createdPermissions: any[] = [];
for (const perm of permissions) {
// 使用 tenantId + code 作为唯一约束
let permission = await prisma.permission.findFirst({
where: { tenantId, code: perm.code }
});
if (permission) {
permission = await prisma.permission.update({
where: { id: permission.id },
data: { ...perm, tenantId }
});
} else {
permission = await prisma.permission.create({
data: { ...perm, tenantId, validState: 1 }
});
}
createdPermissions.push(permission);
}
console.log(` ✓ 共创建/更新 ${createdPermissions.length} 个权限`);
// 3. 创建超级管理员角色
console.log('\n👤 步骤 3: 创建超级管理员角色...');
let adminRole = await prisma.role.findFirst({
where: { tenantId, code: 'super_admin' }
});
if (adminRole) {
adminRole = await prisma.role.update({
where: { id: adminRole.id },
data: {
name: '超级管理员',
description: '拥有系统所有权限的超级管理员角色',
}
});
console.log(` ✓ 更新角色: ${adminRole.name}`);
} else {
adminRole = await prisma.role.create({
data: {
tenantId,
name: '超级管理员',
code: 'super_admin',
description: '拥有系统所有权限的超级管理员角色',
validState: 1,
}
});
console.log(` ✓ 创建角色: ${adminRole.name}`);
}
// 4. 分配权限给角色
console.log('\n🔗 步骤 4: 分配权限给角色...');
// 先获取已有的角色权限
const existingRolePermissions = await prisma.rolePermission.findMany({
where: { roleId: adminRole.id },
select: { permissionId: true }
});
const existingPermissionIds = new Set(existingRolePermissions.map(rp => rp.permissionId));
let addedCount = 0;
for (const perm of createdPermissions) {
if (!existingPermissionIds.has(perm.id)) {
await prisma.rolePermission.create({
data: {
roleId: adminRole.id,
permissionId: perm.id,
}
});
addedCount++;
}
}
console.log(` ✓ 新增 ${addedCount} 个权限分配`);
// 5. 创建 admin 用户
console.log('\n👤 步骤 5: 创建 admin 用户...');
const password = `admin@${superTenant.code}`;
const hashedPassword = await bcrypt.hash(password, 10);
let adminUser = await prisma.user.findFirst({
where: { tenantId, username: 'admin' }
});
if (adminUser) {
adminUser = await prisma.user.update({
where: { id: adminUser.id },
data: {
password: hashedPassword,
nickname: '超级管理员',
validState: 1,
}
});
console.log(` ✓ 更新用户: ${adminUser.username}`);
} else {
adminUser = await prisma.user.create({
data: {
tenantId,
username: 'admin',
password: hashedPassword,
nickname: '超级管理员',
validState: 1,
}
});
console.log(` ✓ 创建用户: ${adminUser.username}`);
}
// 6. 给用户分配角色
console.log('\n🔗 步骤 6: 分配角色给用户...');
const existingUserRole = await prisma.userRole.findFirst({
where: { userId: adminUser.id, roleId: adminRole.id }
});
if (!existingUserRole) {
await prisma.userRole.create({
data: {
userId: adminUser.id,
roleId: adminRole.id,
}
});
console.log(` ✓ 分配角色: ${adminRole.name}`);
} else {
console.log(` ✓ 角色已分配: ${adminRole.name}`);
}
// 7. 输出结果
console.log('\n' + '='.repeat(50));
console.log('🎉 超级管理员初始化完成!');
console.log('='.repeat(50));
console.log(` 租户编码: ${superTenant.code}`);
console.log(` 用户名: admin`);
console.log(` 密码: ${password}`);
console.log(` 角色: ${adminRole.name}`);
console.log(` 权限数量: ${createdPermissions.length}`);
console.log('='.repeat(50));
console.log('\n💡 提示: 请运行以下命令初始化菜单:');
console.log(' npm run init:menus');
} catch (error) {
console.error('❌ 初始化失败:', error);
throw error;
} finally {
await prisma.$disconnect();
}
}
// 执行初始化
initAdmin()
.then(() => {
console.log('\n✅ 初始化脚本执行完成!');
process.exit(0);
})
.catch((error) => {
console.error('\n💥 初始化脚本执行失败:', error);
process.exit(1);
});