forked from admin/hair-keeper
195 lines
9.3 KiB
TypeScript
195 lines
9.3 KiB
TypeScript
import { PrismaClient } from '@prisma/client'
|
||
import bcrypt from 'bcryptjs'
|
||
import { Permissions, ALL_PERMISSIONS } from '../src/constants/permissions'
|
||
import fs from 'fs'
|
||
import path from 'path'
|
||
import pLimit from 'p-limit'
|
||
|
||
const prisma = new PrismaClient()
|
||
|
||
// 从环境变量获取并发限制,默认为16
|
||
const dbParallelLimit = pLimit(parseInt(process.env.DB_PARALLEL_LIMIT || '16', 10))
|
||
|
||
// 解析 JSON 文件并导入院系数据
|
||
async function importDepartments() {
|
||
const jsonPath = path.join(__dirname, 'init_data', '院系.json')
|
||
const jsonContent = fs.readFileSync(jsonPath, 'utf-8')
|
||
const departments = JSON.parse(jsonContent)
|
||
|
||
console.log(`开始导入 ${departments.length} 个院系...`)
|
||
|
||
await Promise.all(
|
||
departments.map((dept: any) => {
|
||
return dbParallelLimit(() => prisma.dept.upsert({
|
||
where: { code: dept.id },
|
||
update: {
|
||
name: dept.name,
|
||
fullName: dept.full_name,
|
||
},
|
||
create: {
|
||
code: dept.id,
|
||
name: dept.name,
|
||
fullName: dept.full_name,
|
||
},
|
||
}))
|
||
})
|
||
)
|
||
console.log('院系数据导入完成')
|
||
}
|
||
|
||
async function main() {
|
||
console.log('开始数据库初始化...')
|
||
|
||
// 插入权限
|
||
await Promise.all(
|
||
ALL_PERMISSIONS.map((permName) => {
|
||
return dbParallelLimit(() => prisma.permission.upsert({
|
||
where: { name: permName },
|
||
update: {},
|
||
create: { name: permName },
|
||
}))
|
||
})
|
||
)
|
||
|
||
// 角色与权限映射
|
||
const rolePermissionsMap: Record<string, string[]> = {
|
||
系统管理员: ALL_PERMISSIONS,
|
||
}
|
||
|
||
// 插入角色
|
||
await Promise.all(
|
||
Object.entries(rolePermissionsMap).map(([roleName, perms]) => {
|
||
return dbParallelLimit(() => prisma.role.upsert({
|
||
where: { name: roleName },
|
||
update: {},
|
||
create: {
|
||
name: roleName,
|
||
permissions: {
|
||
connect: perms.map((name) => ({ name })),
|
||
},
|
||
},
|
||
}))
|
||
})
|
||
)
|
||
|
||
await importDepartments()
|
||
|
||
// 创建测试用户
|
||
const usersToCreate = [
|
||
{ id: 'user1', name: '用户甲', status: '在校', deptCode: '00001', roleNames: [] },
|
||
{ id: 'sys_admin', name: '系统管理员', status: '在校', deptCode: '00001', roleNames: ['系统管理员'] },
|
||
{ id: 'super_admin', password: process.env.SUPER_ADMIN_PASSWORD, name: '超级管理员', status: '在校', deptCode: '00001', roleNames: [], isSuperAdmin: true },
|
||
{ id: 'unknown', name: '未知用户', status: '在校', deptCode: '00001', roleNames: [] },
|
||
]
|
||
|
||
await Promise.all(
|
||
usersToCreate.map((u) => {
|
||
return dbParallelLimit(async () => {
|
||
const password = await bcrypt.hash(u.password || process.env.USER_DEFAULT_PASSWORD || 'jeep4ahxahx7ee7U', 12)
|
||
await prisma.user.upsert({
|
||
where: { id: u.id },
|
||
update: {
|
||
name: u.name,
|
||
status: u.status,
|
||
deptCode: u.deptCode,
|
||
password,
|
||
isSuperAdmin: u.isSuperAdmin ?? false,
|
||
roles: {
|
||
set: u.roleNames.map((name) => ({ name })),
|
||
},
|
||
},
|
||
create: {
|
||
id: u.id,
|
||
name: u.name,
|
||
status: u.status,
|
||
deptCode: u.deptCode,
|
||
password,
|
||
isSuperAdmin: u.isSuperAdmin ?? false,
|
||
roles: {
|
||
connect: u.roleNames.map((name) => ({ name })),
|
||
},
|
||
},
|
||
})
|
||
})
|
||
})
|
||
)
|
||
|
||
// 插入文件类型(仅开发环境)
|
||
const fileTypes = [
|
||
{ id: 'COMPONENT_UI', name: 'UI组件', description: '使用"use client",不涉及复杂数据获取,专注于UI和交互,通用性强' },
|
||
{ id: 'COMPONENT_FEATURE', name: '业务组件', description: '使用"use client",不涉及复杂数据获取,专注于UI和交互,与业务关联性强' },
|
||
{ id: 'COMPONENT_PAGE', name: '页面组件', description: 'src/app下的page.tsx文件,是页面的主入口' },
|
||
{ id: 'COMPONENT_LAYOUT', name: '布局组件', description: '不对应某个特定页面的前端组件,例如定义页面布局的文件' },
|
||
{ id: 'COMPONENT_REF', name: '组件关联文件', description: '前端组件相关的其他文件,例如定义表格列属性的columns.tsx文件' },
|
||
{ id: 'API_TRPC', name: 'tRPC API', description: '基于tRPC的API' },
|
||
{ id: 'API_NEXT', name: 'NextJS原生API', description: '直接基于NextJS框架构建的API' },
|
||
{ id: 'BACKGROUND_JOB', name: '后台任务', description: '执行后台任务的文件(worker/queue)' },
|
||
{ id: 'HOOK', name: 'React Hook', description: '文件名以use开头,导出自定义React Hook' },
|
||
{ id: 'UTIL', name: '工具函数', description: '提供纯函数、常量等通用工具' },
|
||
{ id: 'SCHEMA', name: '数据模式', description: '定义数据长什么样的文件,通常用于保证前后端数据的一致性,对数据进行校验' },
|
||
{ id: 'STYLES', name: '样式文件', description: '全局或局部样式文件' },
|
||
{ id: 'ASSET', name: '资源文件', description: '图片(包括svg)、视频、音频、文本等' },
|
||
{ id: 'TYPE_DEFINITION', name: '类型定义', description: '主要用于导出TypeScript类型和常量' },
|
||
{ id: 'GENERATE', name: '自动生成', description: '自动生成的文件' },
|
||
{ id: 'SCRIPT', name: '脚本文件', description: '独立于项目,单独运行的文件,例如prisma/seed.ts' },
|
||
{ id: 'FRAMEWORK', name: '框架配置', description: '各种前端库约定俗成的配置文件,例如schema.prisma' },
|
||
{ id: 'CONFIG', name: '项目配置', description: '项目级别的配置文件,通常位于项目根目录下,例如package.json' },
|
||
{ id: 'OTHER', name: '其他', description: '无法归入以上分类的文件' },
|
||
]
|
||
|
||
await Promise.all(
|
||
fileTypes.map((fileType, index) => {
|
||
return dbParallelLimit(() => prisma.devFileType.upsert({
|
||
where: { id: fileType.id },
|
||
update: {
|
||
name: fileType.name,
|
||
description: fileType.description,
|
||
order: (index + 1) * 10,
|
||
},
|
||
create: {...fileType, order: (index + 1) * 10},
|
||
}))
|
||
})
|
||
)
|
||
console.log('文件类型数据初始化完成')
|
||
|
||
// 插入依赖包类型(仅开发环境)
|
||
const pkgTypes = [
|
||
{ id: 'CORE_FRAMEWORK', name: '核心框架', description: '构成应用程序骨架的基础技术,决定了项目的基本结构和运行方式,例如next、react、react-dom、vue、angular、svelte、remix、solid-js、nuxt、gatsby。' },
|
||
{ id: 'UI_INTERACTION', name: 'UI & 交互', description: '负责构建用户界面、处理用户交互和视觉呈现的所有库,例如radix-ui/react-xxx、shadcn/ui、@dnd-kit/core、@tanstack/react-table、react-hook-form、lucide-react、framer-motion、antd、mui、chakra-ui、bootstrap、tailwindcss、emotion、styled-components、react-select、react-datepicker、react-toastify、react-icons。' },
|
||
{ id: 'API_DATA_COMMS', name: 'API & 数据通信', description: '负责前后端数据交换、API定义和请求,例如@trpc/server、@trpc/client、@tanstack/react-query、axios、graphql-request、apollo-client、ws。' },
|
||
{ id: 'DATA_LAYER', name: '数据层', description: '负责与数据库、缓存、对象存储等进行交互,例如@prisma/client、ioredis、minio、mongoose、typeorm、sequelize、knex、redis、mongodb、pg、mysql、sqlite3、firebase、supabase。' },
|
||
{ id: 'BACKGROUND_JOBS', name: '后台任务', description: '用于处理异步、长时间运行或计划任务,例如bullmq、agenda、node-cron、bree、bull、kue、bee-queue、sqs-consumer、rabbitmq、bull-board、bull-arena。' },
|
||
{ id: 'SECURITY_AUTH', name: '安全 & 认证', description: '负责用户身份验证、授权和数据加密,例如next-auth、bcryptjs、jsonwebtoken、passport、oauth2orize、casl、argon2、express-session、helmet、csrf、@auth0/auth0-react。' },
|
||
{ id: 'AI_LLM', name: 'AI & 大模型', description: '专门用于与大型语言模型或其他AI服务进行交互,例如ai、@ai-sdk/openai、@ai-sdk/anthropic、openai、@huggingface/inference、langchain、replicate、cohere-ai、stability-sdk、transformers、vertex-ai。' },
|
||
{ id: 'UTILITIES', name: '通用工具', description: '提供特定功能的辅助函数库,如日期处理、状态管理、验证等,例如zod、date-fns、nanoid、lodash、ramda、moment、uuid、joi、yup、clsx、tailwind-merge、deepmerge、numeral、dayjs、chalk、debug、dotenv。' },
|
||
{ id: 'NODEJS_CORE', name: 'Node.js核心', description: 'Node.js运行时内置的模块,用于底层操作如文件系统、路径处理等,例如fs、path、child_process、util、module、os、http、crypto、events、stream、process、net、url、assert。' },
|
||
]
|
||
|
||
await Promise.all(
|
||
pkgTypes.map((pkgType, index) => {
|
||
return dbParallelLimit(() => prisma.devPkgType.upsert({
|
||
where: { id: pkgType.id },
|
||
update: {
|
||
name: pkgType.name,
|
||
description: pkgType.description,
|
||
order: (index + 1) * 10,
|
||
},
|
||
create: {...pkgType, order: (index + 1) * 10},
|
||
}))
|
||
})
|
||
)
|
||
console.log('依赖包类型数据初始化完成')
|
||
|
||
console.log('数据库初始化完成')
|
||
}
|
||
|
||
main()
|
||
.then(async () => {
|
||
await prisma.$disconnect()
|
||
})
|
||
.catch(async (e) => {
|
||
console.error(e)
|
||
await prisma.$disconnect()
|
||
process.exit(1)
|
||
})
|