library-picturebook-activity/backend/scripts/init-linksea-tenant.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

211 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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}`;
// scripts 目录的父目录就是 backend 目录
const backendDir = path.resolve(__dirname, '..');
const envPath = path.resolve(backendDir, envFile);
// 尝试加载环境特定的配置文件
dotenv.config({ path: envPath });
// 如果环境特定文件不存在,尝试加载默认的 .env 文件
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.resolve(backendDir, '.env') });
}
// 验证必要的环境变量
if (!process.env.DATABASE_URL) {
console.error('❌ 错误: 未找到 DATABASE_URL 环境变量');
console.error(` 请确保存在以下文件之一:`);
console.error(` - ${envPath}`);
console.error(` - ${path.resolve(backendDir, '.env')}`);
console.error(` 或者设置 NODE_ENV 环境变量(当前: ${nodeEnv})`);
process.exit(1);
}
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('🚀 开始创建 LinkSea 普通租户...\n');
const tenantCode = 'linksea';
const menuNames = ['活动管理', '系统管理'];
// 1. 查找或创建租户
console.log(`📋 步骤 1: 查找或创建租户 "${tenantCode}"...`);
let tenant = await prisma.tenant.findUnique({
where: { code: tenantCode },
});
if (!tenant) {
// 创建普通租户
tenant = await prisma.tenant.create({
data: {
name: 'LinkSea 租户',
code: tenantCode,
domain: tenantCode,
description: 'LinkSea 普通租户',
isSuper: 0,
validState: 1,
},
});
console.log(`✅ 租户创建成功: ${tenant.name} (${tenant.code})\n`);
} else {
if (tenant.validState !== 1) {
console.error(`❌ 错误: 租户 "${tenantCode}" 状态无效!`);
process.exit(1);
}
console.log(`✅ 找到租户: ${tenant.name} (${tenant.code})\n`);
}
// 2. 查找指定的菜单(顶级菜单)
console.log(`📋 步骤 2: 查找菜单 "${menuNames.join('", "')}"...`);
const menus = await prisma.menu.findMany({
where: {
name: { in: menuNames },
parentId: null, // 只查找顶级菜单
validState: 1,
},
});
if (menus.length === 0) {
console.error(`❌ 错误: 未找到指定的菜单!`);
console.error(` 请确保菜单 "${menuNames.join('", "')}" 已初始化`);
console.error(` 运行: pnpm init:menus`);
process.exit(1);
}
if (menus.length !== menuNames.length) {
const foundMenuNames = menus.map((m) => m.name);
const missingMenus = menuNames.filter(
(name) => !foundMenuNames.includes(name),
);
console.warn(`⚠️ 警告: 部分菜单未找到: ${missingMenus.join(', ')}`);
console.log(` 找到的菜单: ${foundMenuNames.join(', ')}\n`);
} else {
console.log(`✅ 找到 ${menus.length} 个菜单:`);
menus.forEach((menu) => {
console.log(`${menu.name}`);
});
console.log('');
}
// 3. 递归获取菜单及其所有子菜单
console.log(`📋 步骤 3: 获取菜单及其所有子菜单...`);
const menuIds = new Set<number>();
// 递归函数获取菜单及其所有子菜单的ID
async function getMenuAndChildrenIds(menuId: number) {
menuIds.add(menuId);
// 获取所有子菜单
const children = await prisma.menu.findMany({
where: {
parentId: menuId,
validState: 1,
},
});
// 递归获取子菜单的子菜单
for (const child of children) {
await getMenuAndChildrenIds(child.id);
}
}
// 为每个顶级菜单获取所有子菜单
for (const menu of menus) {
await getMenuAndChildrenIds(menu.id);
}
const menuIdArray = Array.from(menuIds);
console.log(`✅ 共找到 ${menuIdArray.length} 个菜单(包括子菜单)\n`);
// 4. 获取租户已分配的菜单
console.log(`📋 步骤 4: 检查租户已分配的菜单...`);
const existingTenantMenus = await prisma.tenantMenu.findMany({
where: {
tenantId: tenant.id,
},
select: {
menuId: true,
},
});
const existingMenuIds = new Set(existingTenantMenus.map((tm) => tm.menuId));
// 5. 为租户分配菜单(只分配新的菜单)
console.log(`📋 步骤 5: 为租户分配菜单...`);
const menusToAdd = menuIdArray.filter((id) => !existingMenuIds.has(id));
if (menusToAdd.length === 0) {
console.log(`✅ 租户已拥有所有指定的菜单\n`);
} else {
let addedCount = 0;
const menuNamesToAdd: string[] = [];
for (const menuId of menusToAdd) {
const menu = await prisma.menu.findUnique({
where: { id: menuId },
select: { name: true },
});
await prisma.tenantMenu.create({
data: {
tenantId: tenant.id,
menuId: menuId,
},
});
addedCount++;
if (menu) {
menuNamesToAdd.push(menu.name);
}
}
console.log(`✅ 为租户添加了 ${addedCount} 个菜单:`);
menuNamesToAdd.forEach((name) => {
console.log(`${name}`);
});
console.log(
`\n✅ 租户现在拥有 ${menuIdArray.length} 个菜单(包括子菜单)\n`,
);
}
// 6. 验证结果
console.log('📊 初始化结果:');
console.log('========================================');
console.log('租户信息:');
console.log(` 租户编码: ${tenant.code}`);
console.log(` 租户名称: ${tenant.name}`);
console.log(` 租户类型: ${tenant.isSuper === 1 ? '超级租户' : '普通租户'}`);
console.log(` 访问链接: http://your-domain.com/?tenant=${tenant.code}`);
console.log('========================================');
console.log('分配的菜单:');
console.log(` 顶级菜单: ${menuNames.join(', ')}`);
console.log(` 菜单总数: ${menuIdArray.length} 个(包括子菜单)`);
console.log('========================================');
console.log('\n💡 提示:');
console.log(' 如需创建管理员账号,请运行: pnpm init:tenant-admin linksea');
console.log('========================================');
}
main()
.then(() => {
console.log('\n🎉 LinkSea 租户创建脚本执行完成!');
process.exit(0);
})
.catch((error) => {
console.error('\n💥 LinkSea 租户创建脚本执行失败:', error);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});