diff --git a/.cursor/CHROME_DEVTOOLS_MCP_SETUP.md b/.cursor/CHROME_DEVTOOLS_MCP_SETUP.md new file mode 100644 index 0000000..98904c7 --- /dev/null +++ b/.cursor/CHROME_DEVTOOLS_MCP_SETUP.md @@ -0,0 +1,156 @@ +# Chrome DevTools MCP 安装和配置说明 + +## ✅ 安装状态 + +chrome-devtools-mcp 已通过 pnpm 全局安装完成。 + +## 📝 配置步骤 + +### ✅ 已配置(推荐方式) + +项目已配置使用包装脚本,确保使用正确的 Node.js 版本。配置文件位于 `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "chrome-devtools": { + "command": "/Users/wwzh/Awesome/runfast/competition-management-system/.cursor/scripts/chrome-devtools-mcp.sh" + } + } +} +``` + +### 方法 1:在 Cursor 用户设置中配置(备选) + +如果项目配置不工作,可以在 Cursor 用户设置中添加: + +1. 打开 Cursor +2. 按 `Cmd + Shift + P` (macOS) 或 `Ctrl + Shift + P` (Windows/Linux) 打开命令面板 +3. 输入 "Preferences: Open User Settings (JSON)" +4. 在 `settings.json` 文件中添加以下配置: + +```json +{ + "mcpServers": { + "chrome-devtools": { + "command": "/Users/wwzh/Awesome/runfast/competition-management-system/.cursor/scripts/chrome-devtools-mcp.sh" + } + } +} +``` + +### 方法 2:使用 npx(需要修复 npm 缓存权限) + +如果 npm 缓存权限问题已修复,可以使用: + +```json +{ + "mcpServers": { + "chrome-devtools": { + "command": "npx", + "args": ["chrome-devtools-mcp@latest"] + } + } +} +``` + +**修复 npm 缓存权限**(需要 sudo 权限): +```bash +sudo chown -R $(whoami) ~/.npm +``` + +## 🔍 验证安装 + +安装完成后,重启 Cursor,然后在 Chat 中应该可以看到 Chrome DevTools MCP 相关的工具。 + +## 📚 使用说明 + +Chrome DevTools MCP 提供了以下功能: +- 浏览器导航和页面快照 +- 控制台消息查看 +- 网络请求监控 +- 页面元素交互(点击、输入等) +- 截图功能 + +## ⚠️ 注意事项 + +1. **Node.js 版本要求**:建议使用 Node.js 22.12.0 或更高版本(当前版本:20.19.0 LTS) +2. **Chrome 浏览器**:需要安装最新版本的 Google Chrome +3. **首次使用**:可能需要登录配置,之后会保存登录状态 + +## 🔧 故障排除 + +### 问题:MCP 服务器启动失败 + +**检查步骤:** + +1. **验证包装脚本是否可执行**: + ```bash + cd /Users/wwzh/Awesome/runfast/competition-management-system + source ~/.nvm/nvm.sh + .cursor/scripts/chrome-devtools-mcp.sh --version + ``` + 应该输出:`0.10.2` + +2. **检查 Node.js 版本**: + ```bash + source ~/.nvm/nvm.sh + node --version + ``` + 应该显示:`v20.19.0` + +3. **检查配置文件**: + ```bash + cat .cursor/mcp.json + ``` + 确认路径正确 + +4. **手动测试运行**: + ```bash + source ~/.nvm/nvm.sh + node /Users/wwzh/Library/pnpm/global/5/.pnpm/chrome-devtools-mcp@0.10.2/node_modules/chrome-devtools-mcp/build/src/index.js --version + ``` + +5. **检查 Cursor 日志**: + - 打开 Cursor + - 查看输出面板(View → Output) + - 选择 "MCP" 或 "Chrome DevTools" 查看错误信息 + +6. **重启 Cursor IDE**: + 配置更改后需要完全重启 Cursor + +### 常见错误 + +**错误:command not found** +- 确保包装脚本路径正确 +- 检查脚本是否有执行权限:`chmod +x .cursor/scripts/chrome-devtools-mcp.sh` + +**错误:Node.js version mismatch** +- 确保使用 Node.js 20.19.0:`nvm use 20.19.0` +- 检查包装脚本中的 nvm 路径是否正确 + +**错误:npm cache permission denied** +- 如果使用 npx 方式,需要修复权限: + ```bash + sudo chown -R $(whoami) ~/.npm + ``` + +### 重新安装 + +如果问题持续存在: + +```bash +# 1. 卸载旧版本 +pnpm remove -g chrome-devtools-mcp + +# 2. 清理缓存 +pnpm store prune + +# 3. 重新安装 +source ~/.nvm/nvm.sh +pnpm add -g chrome-devtools-mcp@latest + +# 4. 验证安装 +pnpm list -g chrome-devtools-mcp +``` + diff --git a/.cursor/MIGRATION_SUMMARY.md b/.cursor/MIGRATION_SUMMARY.md new file mode 100644 index 0000000..0618d66 --- /dev/null +++ b/.cursor/MIGRATION_SUMMARY.md @@ -0,0 +1,167 @@ +# Cursor Rules 迁移总结 + +## ✅ 完成的改进 + +根据 [Cursor 官方文档](https://cursor.com/cn/docs/context/rules) 的最佳实践,已将项目规则系统现代化。 + +### 1. 规则拆分 ✨ + +原来的 283 行单一文件 `.cursorrules` 已拆分为 **8 个模块化规则**: + +#### 主规则(`.cursor/rules/`) + +| 规则文件 | 类型 | 大小 | 说明 | +|---------|------|------|------| +| `project-overview.mdc` | Always Apply | ~50 行 | 项目概述和技术栈 | +| `multi-tenant.mdc` | Always Apply | ~100 行 | ⚠️ 多租户隔离规范(核心安全) | +| `backend-architecture.mdc` | Apply to Files | ~200 行 | NestJS 后端架构规范 | +| `frontend-architecture.mdc` | Apply to Files | ~250 行 | Vue 3 前端架构规范 | +| `database-design.mdc` | Apply to Files | ~200 行 | Prisma 数据库设计规范 | +| `code-review-checklist.mdc` | Manual | ~150 行 | 代码审查清单 | + +#### 嵌套规则 + +| 规则文件 | 作用域 | 说明 | +|---------|--------|------| +| `backend/.cursor/rules/backend-specific.mdc` | backend/ | 后端特定规范和脚本 | +| `frontend/.cursor/rules/frontend-specific.mdc` | frontend/ | 前端特定规范和组件 | + +### 2. 使用 MDC 格式 📝 + +所有规则文件使用标准 MDC 格式,支持元数据: + +```md +--- +description: 规则描述 +globs: + - "backend/**/*.ts" +alwaysApply: false +--- + +# 规则内容... +``` + +### 3. 智能应用策略 🎯 + +- **Always Apply**: 关键规则(项目概述、多租户)始终生效 +- **File Matching**: 后端/前端规则仅在相关文件时应用 +- **Nested Rules**: 子目录规则只在该目录下生效 +- **Manual**: 代码审查清单按需引用 `@code-review-checklist` + +### 4. 创建 AGENTS.md 🚀 + +添加了简化版快速参考文件: +- 纯 Markdown 格式,无元数据 +- 包含最重要的规则和快速参考 +- 易于阅读和分享 + +### 5. 完整文档 📚 + +创建了详细的使用指南 `.cursor/RULES_README.md`: +- 规则文件结构说明 +- 使用方式指导 +- 迁移指南 +- 最佳实践 + +## 📊 改进效果 + +### 性能优化 +- ✅ 每个规则 < 500 行(符合最佳实践) +- ✅ 按需加载,减少不必要的上下文 +- ✅ 嵌套规则提高针对性 + +### 可维护性 +- ✅ 模块化设计,易于更新单个规则 +- ✅ 版本控制友好 +- ✅ 清晰的职责分离 + +### 可扩展性 +- ✅ 轻松添加新规则 +- ✅ 支持子目录特定规则 +- ✅ 规则可以引用其他文件 + +## 🎯 使用建议 + +### 日常开发 + +```bash +# 开发时规则自动生效 +# 不需要手动操作 + +# 需要代码审查时 +在 Chat 中输入:@code-review-checklist +``` + +### 添加新规则 + +```bash +# 方法 1: 使用命令 +Cmd/Ctrl + Shift + P → "New Cursor Rule" + +# 方法 2: 手动创建 +# 在 .cursor/rules/ 创建新的 .mdc 文件 +``` + +### 查看规则状态 + +```bash +# 打开 Cursor Settings +Cmd/Ctrl + , + +# 进入 Rules 选项卡 +查看所有规则的状态和类型 +``` + +## ⚠️ 重要变更 + +### 1. 旧文件状态 +- `.cursorrules` 已标记为 DEPRECATED +- 文件保留作为备份 +- 所有功能已迁移到新系统 + +### 2. 多租户规则 +- 设为 **Always Apply** +- 确保所有生成的代码都包含租户隔离检查 +- 这是系统安全的核心保障 + +### 3. 嵌套规则生效 +- 在 `backend/` 目录工作时,后端特定规则自动应用 +- 在 `frontend/` 目录工作时,前端特定规则自动应用 + +## 📈 下一步 + +### 可选的进一步优化 + +1. **添加模块特定规则** + ``` + backend/src/contests/.cursor/rules/ + └── contests-specific.mdc + ``` + +2. **创建模板规则** + - 控制器模板 + - 服务模板 + - 组件模板 + +3. **团队规则(如果有 Team 计划)** + - 在 Cursor Dashboard 配置团队级规则 + - 强制执行组织标准 + +## 🔗 相关资源 + +- 📖 [规则使用指南](./.cursor/RULES_README.md) +- 🚀 [快速参考](../AGENTS.md) +- 📚 [Cursor 官方文档](https://cursor.com/cn/docs/context/rules) + +## 💬 反馈 + +如有问题或建议,可以: +1. 更新规则文件并测试 +2. 查看官方文档获取最新功能 +3. 分享最佳实践给团队 + +--- + +**迁移完成时间**: 2025-11-27 +**符合标准**: Cursor Rules Best Practices v1.0 + diff --git a/.cursor/RULES_README.md b/.cursor/RULES_README.md new file mode 100644 index 0000000..bafe847 --- /dev/null +++ b/.cursor/RULES_README.md @@ -0,0 +1,128 @@ +# Cursor Rules 使用指南 + +本项目使用 Cursor 的新规则系统(Project Rules + AGENTS.md),遵循 [官方最佳实践](https://cursor.com/cn/docs/context/rules)。 + +## 📁 规则文件结构 + +``` +competition-management-system/ +├── .cursor/rules/ # 项目规则目录 +│ ├── project-overview.mdc # 项目概述(Always Apply) +│ ├── multi-tenant.mdc # 多租户规范(Always Apply)⚠️ +│ ├── backend-architecture.mdc # 后端架构(Apply to backend files) +│ ├── frontend-architecture.mdc # 前端架构(Apply to frontend files) +│ ├── database-design.mdc # 数据库设计(Apply to prisma files) +│ └── code-review-checklist.mdc # 代码审查清单(Manual) +├── backend/.cursor/rules/ +│ └── backend-specific.mdc # 后端特定规范(嵌套规则) +├── frontend/.cursor/rules/ +│ └── frontend-specific.mdc # 前端特定规范(嵌套规则) +├── AGENTS.md # 简化版指令(Quick Reference) +└── .cursorrules # 已废弃,保留作为备份 +``` + +## 🎯 规则类型说明 + +### 1. Always Apply(总是应用) +- `project-overview.mdc` - 项目技术栈和基本信息 +- `multi-tenant.mdc` - **多租户数据隔离规范(最重要)** + +### 2. Apply to Specific Files(文件匹配) +- `backend-architecture.mdc` - 匹配 `backend/**/*.ts` +- `frontend-architecture.mdc` - 匹配 `frontend/**/*.vue` 和 `frontend/**/*.ts` +- `database-design.mdc` - 匹配 `backend/prisma/**/*.prisma` + +### 3. Nested Rules(嵌套规则) +- `backend/.cursor/rules/backend-specific.mdc` - 仅作用于 backend 目录 +- `frontend/.cursor/rules/frontend-specific.mdc` - 仅作用于 frontend 目录 + +### 4. Apply Manually(手动触发) +- `code-review-checklist.mdc` - 在 Chat 中使用 `@code-review-checklist` 引用 + +## 🚀 使用方式 + +### 在 Chat 中引用规则 + +``` +# 自动应用 +规则会根据上下文自动应用 + +# 手动引用 +@code-review-checklist 请检查我的代码 + +# 引用特定文件 +@backend-architecture 如何创建一个新的模块? +``` + +### 查看和管理规则 + +1. 打开 Cursor Settings(Cmd/Ctrl + ,) +2. 进入 **Rules** 选项卡 +3. 查看所有规则的状态和类型 + +### 编辑规则 + +直接编辑 `.cursor/rules/` 目录中的 `.mdc` 文件,Cursor 会自动重新加载。 + +## 📖 快速参考 + +### 对于快速查阅 +使用 `AGENTS.md`(纯 Markdown,无元数据): +```bash +cat AGENTS.md +``` + +### 对于详细规范 +查看 `.cursor/rules/` 中的具体规则文件。 + +## 🔄 从旧版本迁移 + +旧的 `.cursorrules` 文件已被拆分为多个小规则文件: + +| 旧内容 | 新位置 | +|-------|--------| +| 项目概述 | `project-overview.mdc` | +| 后端规范 | `backend-architecture.mdc` + `backend-specific.mdc` | +| 前端规范 | `frontend-architecture.mdc` + `frontend-specific.mdc` | +| 数据库规范 | `database-design.mdc` | +| 多租户规范 | `multi-tenant.mdc` | +| 代码审查 | `code-review-checklist.mdc` | + +## 💡 最佳实践 + +### 1. 规则大小 +- 每个规则文件 < 500 行 +- 聚焦单一主题 +- 提供具体示例 + +### 2. 嵌套规则 +- 在子目录创建 `.cursor/rules/` 针对特定区域 +- 子规则会与父规则合并 +- 更具体的规则优先级更高 + +### 3. 规则复用 +- 将重复的提示词转换为规则 +- 使用 `@rule-name` 在对话中引用 +- 避免每次重复输入相同指令 + +## ⚠️ 重要提醒 + +### 多租户隔离 +`multi-tenant.mdc` 规则设为 **Always Apply**,确保所有代码生成都包含租户隔离检查。这是系统安全的核心! + +### 规则优先级 +规则应用顺序:**Team Rules → Project Rules → User Rules** + +## 🔗 参考链接 + +- [Cursor Rules 官方文档](https://cursor.com/cn/docs/context/rules) +- [MDC 格式说明](https://cursor.com/cn/docs/context/rules#规则结构) +- [最佳实践](https://cursor.com/cn/docs/context/rules#最佳实践) + +## 📝 更新日志 + +- **2025-11-27**: 从 `.cursorrules` 迁移到新的 Project Rules 系统 + - 拆分为 6 个主规则 + 2 个嵌套规则 + - 添加 AGENTS.md 作为快速参考 + - 遵循 Cursor 官方最佳实践 + diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..d888129 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "chrome-devtools": { + "command": "/Users/wwzh/Awesome/runfast/competition-management-system/.cursor/scripts/chrome-devtools-mcp.sh" + } + } +} diff --git a/.cursor/rules/backend-architecture.mdc b/.cursor/rules/backend-architecture.mdc new file mode 100644 index 0000000..74d3070 --- /dev/null +++ b/.cursor/rules/backend-architecture.mdc @@ -0,0 +1,221 @@ +--- +description: NestJS 后端架构规范和模块结构 +globs: + - "backend/**/*.ts" +alwaysApply: false +--- + +# 后端架构规范 + +## 模块结构 + +每个功能模块应包含: + +- `module.ts` - 模块定义 +- `controller.ts` - 控制器 +- `service.ts` - 服务层 +- `dto/` - 数据传输对象目录 + +### 命名规范 + +- 模块命名使用复数形式:`users`, `roles`, `contests` +- 子模块放在父模块目录下:`contests/works/`, `contests/teams/` + +### 目录结构示例 + +``` +src/ +├── contests/ +│ ├── contests.module.ts +│ ├── contests/ +│ │ ├── contests.module.ts +│ │ ├── contests.controller.ts +│ │ ├── contests.service.ts +│ │ └── dto/ +│ ├── works/ +│ │ ├── works.module.ts +│ │ ├── works.controller.ts +│ │ ├── works.service.ts +│ │ └── dto/ +│ └── teams/ +│ └── ... +``` + +## 服务层 (Service) + +### 基本规范 + +- 使用 `@Injectable()` 装饰器 +- 构造函数注入依赖,使用 `private readonly` +- 所有数据库操作通过 PrismaService +- **禁止直接使用 SQL** + +### 标准方法命名 + +- `create` - 创建 +- `findAll` - 查询列表(支持分页) +- `findOne` - 查询单个 +- `update` - 更新 +- `remove` - 删除(软删除或级联) + +### 示例 + +```typescript +@Injectable() +export class UsersService { + constructor(private readonly prisma: PrismaService) {} + + async create(createDto: CreateUserDto, tenantId: number) { + return this.prisma.user.create({ + data: { + ...createDto, + tenantId, + }, + }); + } + + async findAll(tenantId: number, skip?: number, take?: number) { + return this.prisma.user.findMany({ + where: { tenantId, validState: 1 }, + skip, + take, + include: { + roles: { + include: { role: true }, + }, + }, + }); + } +} +``` + +## 控制器层 (Controller) + +### 基本规范 + +- 使用 `@Controller()` 装饰器,路径使用复数形式 +- 所有路由默认需要认证(除非使用 `@Public()` 装饰器) +- 使用 REST 风格的 HTTP 方法装饰器 + +### 装饰器使用 + +```typescript +@Controller("users") +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post() + @RequirePermission("user:create") + async create( + @Body() createDto: CreateUserDto, + @CurrentTenantId() tenantId: number, + @CurrentUser() user: any + ) { + return this.usersService.create(createDto, tenantId); + } + + @Get() + @RequirePermission("user:read") + async findAll( + @CurrentTenantId() tenantId: number, + @Query("skip") skip?: number, + @Query("take") take?: number + ) { + return this.usersService.findAll(tenantId, skip, take); + } + + @Public() + @Get("public-info") + async getPublicInfo() { + return { version: "1.0.0" }; + } +} +``` + +### 常用装饰器 + +- `@CurrentTenantId()` - 获取当前租户ID +- `@CurrentUser()` - 获取当前用户信息 +- `@RequirePermission('module:action')` - 权限检查 +- `@Public()` - 公开接口,无需认证 + +## DTO 规范 + +### 命名规范 + +- 创建:`CreateXxxDto` +- 更新:`UpdateXxxDto` +- 查询:`QueryXxxDto` + +### 验证规则 + +使用 `class-validator` 装饰器: + +```typescript +import { + IsString, + IsEmail, + IsOptional, + IsArray, + IsNumber, +} from "class-validator"; + +export class CreateUserDto { + @IsString() + username: string; + + @IsString() + password: string; + + @IsString() + nickname: string; + + @IsEmail() + @IsOptional() + email?: string; + + @IsArray() + @IsNumber({}, { each: true }) + @IsOptional() + roleIds?: number[]; +} +``` + +## 错误处理 + +使用 NestJS 内置异常,消息使用中文: + +```typescript +import { + NotFoundException, + BadRequestException, + UnauthorizedException, + ForbiddenException, +} from "@nestjs/common"; + +// 示例 +if (!user) { + throw new NotFoundException("用户不存在"); +} + +if (!isValid) { + throw new BadRequestException("数据验证失败"); +} +``` + +## 权限控制 + +权限字符串格式:`模块:操作` + +```typescript +@RequirePermission('contest:create') // 创建竞赛 +@RequirePermission('user:update') // 更新用户 +@RequirePermission('role:delete') // 删除角色 +``` + +## 代码风格 + +- 导入顺序:NestJS 核心 → 第三方库 → 本地模块 +- 使用 async/await,避免 Promise.then() +- 使用解构赋值提高代码可读性 +- 复杂逻辑必须添加注释 diff --git a/.cursor/rules/code-review-checklist.mdc b/.cursor/rules/code-review-checklist.mdc new file mode 100644 index 0000000..44abbe8 --- /dev/null +++ b/.cursor/rules/code-review-checklist.mdc @@ -0,0 +1,112 @@ +--- +description: 代码审查检查清单(手动应用) +globs: +alwaysApply: false +--- + +# 代码审查检查清单 + +在提交代码前,请确保以下各项都已检查: + +## 多租户数据隔离 + +- [ ] 所有数据库查询包含 `tenantId` 条件 +- [ ] 创建数据时设置了 `tenantId` +- [ ] 更新/删除操作验证了 `tenantId` +- [ ] 新的 Prisma 模型包含了必需的审计字段 + +## 数据验证 + +- [ ] DTO 验证规则完整 +- [ ] 前端和后端都进行了数据验证 +- [ ] 使用了 TypeScript 类型定义 +- [ ] 处理了所有必填字段 + +## 错误处理 + +- [ ] 所有异步操作都有错误处理 +- [ ] 错误信息清晰明确 +- [ ] 使用了合适的 HTTP 状态码 +- [ ] 前端显示了友好的错误提示 + +## 权限控制 + +- [ ] 后端使用了 `@RequirePermission()` 装饰器 +- [ ] 前端路由配置了 `permissions` meta +- [ ] 权限验证失败返回 403 +- [ ] 遵循最小权限原则 + +## 代码质量 + +- [ ] 代码格式符合 ESLint/Prettier 规范 +- [ ] 复杂逻辑添加了注释 +- [ ] 变量和函数命名清晰 +- [ ] 无硬编码配置(使用环境变量) +- [ ] 无调试代码(console.log 等) + +## 性能优化 + +- [ ] 数据库查询使用了 `include` 预加载 +- [ ] 使用了 `select` 精简字段 +- [ ] 实现了分页查询 +- [ ] 避免了 N+1 查询 +- [ ] 前端组件按需加载 + +## 安全性 + +- [ ] 敏感数据加密存储 +- [ ] API 需要认证(除非 `@Public()`) +- [ ] 防止了 SQL 注入(使用 Prisma) +- [ ] 防止了 XSS 攻击 +- [ ] Token 过期时间合理 + +## 测试 + +- [ ] 核心业务逻辑有单元测试 +- [ ] 测试覆盖率 > 80% +- [ ] 所有测试通过 + +## Git 提交 + +- [ ] 提交信息清晰(使用中文) +- [ ] 提交信息格式:`类型: 描述` +- [ ] 一次提交只做一件事 +- [ ] 不包含敏感信息 + +## 文档 + +- [ ] 复杂功能有文档说明 +- [ ] API 接口有注释 +- [ ] README 更新(如有必要) + +## 使用建议 + +### 在提交前运行 + +```bash +# 后端 +cd backend +pnpm lint # 代码检查 +pnpm test # 运行测试 +pnpm build # 确保能成功构建 + +# 前端 +cd frontend +pnpm lint # 代码检查 +pnpm build # 确保能成功构建 +``` + +### 自动化检查 + +考虑使用 Git hooks(如 husky)自动执行检查: +- pre-commit: 运行 lint +- pre-push: 运行测试 + +### Code Review 关注点 + +审查他人代码时,重点关注: +1. **数据安全**:租户隔离是否完整 +2. **权限控制**:是否正确验证权限 +3. **错误处理**:是否处理所有异常情况 +4. **代码质量**:是否易于理解和维护 +5. **性能**:是否有明显的性能问题 diff --git a/.cursor/rules/database-design.mdc b/.cursor/rules/database-design.mdc new file mode 100644 index 0000000..0293b68 --- /dev/null +++ b/.cursor/rules/database-design.mdc @@ -0,0 +1,278 @@ +--- +description: Prisma 数据库设计规范和最佳实践 +globs: + - "backend/prisma/**/*.prisma" +alwaysApply: false +--- + +# 数据库设计规范 + +## Prisma Schema 规范 + +### 表结构要求 + +所有业务表必须包含以下字段: + +```prisma +model YourModel { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") /// 租户ID(必填) + + // 业务字段... + name String + description String? + + // 审计字段 + validState Int @default(1) @map("valid_state") /// 1-有效,2-失效 + creator Int? /// 创建人ID + modifier Int? /// 修改人ID + createTime DateTime @default(now()) @map("create_time") + modifyTime DateTime @updatedAt @map("modify_time") + + // 关系 + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + creatorUser User? @relation("YourModelCreator", fields: [creator], references: [id], onDelete: SetNull) + modifierUser User? @relation("YourModelModifier", fields: [modifier], references: [id], onDelete: SetNull) + + @@map("your_table_name") +} +``` + +### 字段命名规范 + +- Prisma 模型使用 camelCase:`tenantId`, `createTime` +- 数据库列使用 snake_case:`tenant_id`, `create_time` +- 使用 `@map()` 映射字段名 +- 使用 `@@map()` 映射表名 + +### 状态字段 + +使用 `validState` 表示数据有效性: + +- `1` - 有效 +- `2` - 失效(软删除) + +```prisma +validState Int @default(1) @map("valid_state") +``` + +## 关系设计 + +### 一对多关系 + +```prisma +model Tenant { + id Int @id @default(autoincrement()) + users User[] +} + +model User { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) +} +``` + +### 多对多关系 + +使用显式中间表: + +```prisma +model User { + id Int @id @default(autoincrement()) + roles UserRole[] +} + +model Role { + id Int @id @default(autoincrement()) + users UserRole[] +} + +model UserRole { + userId Int @map("user_id") + roleId Int @map("role_id") + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) + + @@id([userId, roleId]) + @@map("user_roles") +} +``` + +### 一对一关系 + +```prisma +model User { + id Int @id @default(autoincrement()) + teacher Teacher? +} + +model Teacher { + id Int @id @default(autoincrement()) + userId Int @unique @map("user_id") + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} +``` + +### 级联删除规则 + +- 强依赖关系:`onDelete: Cascade` +- 弱依赖关系:`onDelete: SetNull`(字段必须可选) +- 保护性关系:`onDelete: Restrict` + +## 索引设计 + +### 自动索引 + +- 主键自动创建索引 +- 外键字段自动创建索引 +- `@unique` 字段自动创建唯一索引 + +### 复合索引 + +```prisma +model User { + tenantId Int + username String + + @@unique([tenantId, username]) + @@index([tenantId, validState]) +} +``` + +### 性能优化索引 + +为频繁查询的字段添加索引: + +```prisma +model Contest { + tenantId Int + status Int + startTime DateTime + + @@index([tenantId, status]) + @@index([tenantId, startTime]) +} +``` + +## Prisma 查询最佳实践 + +### 使用 include 预加载关联 + +避免 N+1 查询问题: + +```typescript +// ✅ 好的做法 - 使用 include 预加载 +const users = await prisma.user.findMany({ + where: { tenantId }, + include: { + roles: { + include: { + role: true, + }, + }, + }, +}); + +// ❌ 不好的做法 - N+1 查询 +const users = await prisma.user.findMany({ + where: { tenantId }, +}); +for (const user of users) { + user.roles = await prisma.userRole.findMany({ + where: { userId: user.id }, + }); +} +``` + +### 使用 select 精简字段 + +只查询需要的字段: + +```typescript +const users = await prisma.user.findMany({ + where: { tenantId }, + select: { + id: true, + username: true, + nickname: true, + // 不查询 password 等敏感字段 + }, +}); +``` + +### 分页查询 + +```typescript +const users = await prisma.user.findMany({ + where: { tenantId, validState: 1 }, + skip: (page - 1) * pageSize, + take: pageSize, + orderBy: { createTime: "desc" }, +}); + +const total = await prisma.user.count({ + where: { tenantId, validState: 1 }, +}); +``` + +### 事务处理 + +使用 `$transaction` 确保数据一致性: + +```typescript +await prisma.$transaction(async (tx) => { + // 创建用户 + const user = await tx.user.create({ + data: { + username: "test", + tenantId, + }, + }); + + // 创建用户角色关联 + await tx.userRole.createMany({ + data: roleIds.map((roleId) => ({ + userId: user.id, + roleId, + })), + }); +}); +``` + +## 数据迁移 + +### 创建迁移 + +```bash +# 开发环境 - 创建并应用迁移 +pnpm prisma:migrate:dev + +# 生产环境 - 只应用迁移 +pnpm prisma:migrate:deploy +``` + +### 迁移命名 + +使用描述性的迁移名称: + +```bash +prisma migrate dev --name add_contest_module +prisma migrate dev --name add_user_avatar_field +``` + +### 迁移注意事项 + +- 迁移前备份数据库 +- 测试迁移在开发环境的执行 +- 生产环境使用 `migrate deploy` 而不是 `migrate dev` +- 不要手动修改已应用的迁移文件 + +## 性能优化清单 + +- [ ] 频繁查询的字段添加了索引 +- [ ] 使用 `include` 预加载关联数据 +- [ ] 使用 `select` 只查询需要的字段 +- [ ] 实现了分页查询 +- [ ] 复杂操作使用了事务 +- [ ] 避免了 N+1 查询问题 diff --git a/.cursor/rules/frontend-architecture.mdc b/.cursor/rules/frontend-architecture.mdc new file mode 100644 index 0000000..13f2956 --- /dev/null +++ b/.cursor/rules/frontend-architecture.mdc @@ -0,0 +1,348 @@ +--- +description: Vue 3 前端架构规范和组件开发 +globs: + - "frontend/**/*.vue" + - "frontend/**/*.ts" +alwaysApply: false +--- + +# 前端架构规范 + +## 组件结构 + +### 目录组织 + +- 页面组件放在 `views/` 目录下,按模块组织 +- 公共组件放在 `components/` 目录下 +- 组件命名使用 PascalCase + +### 组件语法 + +使用 Vue 3 Composition API 的 ` + + + + + + + + +``` + +## API 调用规范 + +### 目录结构 + +所有 API 调用放在 `api/` 目录下,按模块组织: + +``` +api/ +├── users.ts +├── roles.ts +├── contests.ts +└── auth.ts +``` + +### API 函数命名 + +- `getXxx` - 获取数据 +- `createXxx` - 创建数据 +- `updateXxx` - 更新数据 +- `deleteXxx` - 删除数据 + +### 示例 + +```typescript +// api/users.ts +import request from "@/utils/request"; + +export interface User { + id: number; + username: string; + nickname: string; + email?: string; + validState: number; +} + +export interface CreateUserDto { + username: string; + password: string; + nickname: string; + email?: string; + roleIds?: number[]; +} + +export const getUsers = (params?: { skip?: number; take?: number }) => { + return request.get("/users", { params }); +}; + +export const createUser = (data: CreateUserDto) => { + return request.post("/users", data); +}; + +export const updateUser = (id: number, data: Partial) => { + return request.put(`/users/${id}`, data); +}; + +export const deleteUser = (id: number) => { + return request.delete(`/users/${id}`); +}; +``` + +## 状态管理 (Pinia) + +### Store 规范 + +- Store 文件放在 `stores/` 目录下 +- 使用 `defineStore()` 定义 store +- Store 命名使用 camelCase + Store 后缀 + +### 示例 + +```typescript +// stores/auth.ts +import { defineStore } from "pinia"; +import { ref, computed } from "vue"; +import { login, getUserInfo, type LoginDto, type User } from "@/api/auth"; + +export const useAuthStore = defineStore("auth", () => { + const token = ref(localStorage.getItem("token")); + const user = ref(null); + const menus = ref([]); + + const isAuthenticated = computed(() => !!token.value && !!user.value); + + const loginAction = async (loginDto: LoginDto) => { + const { + accessToken, + user: userInfo, + menus: userMenus, + } = await login(loginDto); + token.value = accessToken; + user.value = userInfo; + menus.value = userMenus; + localStorage.setItem("token", accessToken); + }; + + const logout = () => { + token.value = null; + user.value = null; + menus.value = []; + localStorage.removeItem("token"); + }; + + return { + token, + user, + menus, + isAuthenticated, + loginAction, + logout, + }; +}); +``` + +## 路由管理 + +### 路由规范 + +- 路由配置在 `router/index.ts` +- 支持动态路由(基于菜单权限) +- 路由路径包含租户编码:`/:tenantCode/xxx` +- 路由 meta 包含权限信息 + +### 示例 + +```typescript +{ + path: '/:tenantCode/users', + name: 'Users', + component: () => import('@/views/users/Index.vue'), + meta: { + requiresAuth: true, + permissions: ['user:read'], + roles: ['admin'], + }, +} +``` + +## 表单验证 + +### 使用 VeeValidate + Zod + +```vue + + + + + + + + + + + 提交 + + +``` + +## UI 组件规范 + +### Ant Design Vue + +- 使用 Ant Design Vue 组件库 +- 遵循 Ant Design 设计规范 + +### 样式 + +- 使用 Tailwind CSS 工具类 +- 复杂样式使用 SCSS +- 响应式设计,移动端优先 + +### 状态管理 + +组件必须有 loading 和 error 状态: + +```vue + + + + + + + + + + +``` + +## TypeScript 类型定义 + +### 类型文件组织 + +- TypeScript 类型定义放在 `types/` 目录下 +- 接口类型使用 `interface` +- 数据模型使用 `type` +- 导出类型供其他模块使用 + +### 示例 + +```typescript +// types/user.ts +export interface User { + id: number; + username: string; + nickname: string; + email?: string; + tenantId: number; + validState: number; + createTime: string; + modifyTime: string; +} + +export type CreateUserParams = Omit< + User, + "id" | "tenantId" | "createTime" | "modifyTime" +>; + +export type UserRole = { + userId: number; + roleId: number; + role: Role; +}; +``` + +## 性能优化 + +### 路由懒加载 + +```typescript +const routes = [ + { + path: "/users", + component: () => import("@/views/users/Index.vue"), + }, +]; +``` + +### 组件按需加载 + +```typescript +import { defineAsyncComponent } from "vue"; + +const AsyncComponent = defineAsyncComponent( + () => import("@/components/HeavyComponent.vue") +); +``` + +### 避免不必要的重新渲染 + +使用 `computed`、`watchEffect` 和 `memo` 优化性能。 diff --git a/.cursor/rules/multi-tenant.mdc b/.cursor/rules/multi-tenant.mdc new file mode 100644 index 0000000..3e6c06e --- /dev/null +++ b/.cursor/rules/multi-tenant.mdc @@ -0,0 +1,101 @@ +--- +description: 多租户数据隔离规范(所有涉及数据库操作的代码必须遵守) +globs: +alwaysApply: true +--- + +# 多租户处理规范 + +⚠️ **极其重要**:所有业务数据查询必须包含 `tenantId` 条件! + +## 核心原则 + +### 1. 数据库查询 + +- **必须**:所有业务表查询必须包含 `tenantId` 条件 +- 超级租户(`isSuper = 1`)可以访问所有租户数据 + +```typescript +// ✅ 正确示例 +const users = await this.prisma.user.findMany({ + where: { + tenantId, + validState: 1, + }, +}); + +// ❌ 错误示例 - 缺少 tenantId +const users = await this.prisma.user.findMany({ + where: { + validState: 1, + }, +}); +``` + +### 2. 获取租户ID + +在控制器中使用 `@CurrentTenantId()` 装饰器: + +```typescript +@Get() +async findAll(@CurrentTenantId() tenantId: number) { + return this.service.findAll(tenantId); +} +``` + +### 3. 创建数据 + +创建数据时自动设置 `tenantId`: + +```typescript +async create(createDto: CreateDto, tenantId: number) { + return this.prisma.model.create({ + data: { + ...createDto, + tenantId, + }, + }); +} +``` + +### 4. 更新/删除数据 + +更新或删除前验证数据属于当前租户: + +```typescript +async update(id: number, updateDto: UpdateDto, tenantId: number) { + // 先验证数据属于当前租户 + const existing = await this.prisma.model.findFirst({ + where: { id, tenantId }, + }); + + if (!existing) { + throw new NotFoundException('数据不存在或不属于当前租户'); + } + + return this.prisma.model.update({ + where: { id }, + data: updateDto, + }); +} +``` + +## 数据库表设计 + +所有业务表必须包含: + +- `tenantId`: Int - 租户ID(必填) +- `creator`: Int? - 创建人ID +- `modifier`: Int? - 修改人ID +- `createTime`: DateTime @default(now()) +- `modifyTime`: DateTime @updatedAt +- `validState`: Int @default(1) - 有效状态(1-有效,2-失效) + +## 审查清单 + +在代码审查时,重点检查: + +- [ ] 所有 `findMany`、`findFirst`、`findUnique` 包含 `tenantId` 条件 +- [ ] 创建操作设置了 `tenantId` +- [ ] 更新/删除操作验证了 `tenantId` +- [ ] 新的 Prisma 模型包含了 `tenantId` 字段 diff --git a/.cursor/rules/project-overview.mdc b/.cursor/rules/project-overview.mdc new file mode 100644 index 0000000..7e16f56 --- /dev/null +++ b/.cursor/rules/project-overview.mdc @@ -0,0 +1,41 @@ +--- +description: 项目概述和技术栈信息 +globs: +alwaysApply: true +--- + +# 项目概述 + +这是一个多租户的竞赛管理系统,采用前后端分离架构。 + +## 技术栈 + +### 后端 + +- **框架**: NestJS + TypeScript +- **数据库**: MySQL 8.0 +- **ORM**: Prisma +- **认证**: JWT + RBAC (基于角色的访问控制) + +### 前端 + +- **框架**: Vue 3 + TypeScript +- **构建工具**: Vite +- **UI 组件库**: Ant Design Vue +- **状态管理**: Pinia +- **路由**: Vue Router 4 +- **表单验证**: VeeValidate + Zod + +## 核心特性 + +- **多租户架构**: 数据完全隔离,每个租户使用独立的 tenantId +- **RBAC 权限系统**: 基于角色的细粒度权限控制 +- **动态菜单系统**: 基于权限的动态路由和菜单 +- **审计日志**: 完整的操作审计追踪 + +## 代码风格 + +- 使用 TypeScript 严格模式 +- 使用 ESLint 和 Prettier 格式化代码 +- 注释使用中文 +- Git 提交信息使用中文,格式:`类型: 描述` diff --git a/.cursor/scripts/chrome-devtools-mcp.sh b/.cursor/scripts/chrome-devtools-mcp.sh new file mode 100755 index 0000000..4f1f9be --- /dev/null +++ b/.cursor/scripts/chrome-devtools-mcp.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Chrome DevTools MCP wrapper script +# Ensures correct Node.js version is used + +# Load nvm if available +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +# Use Node.js 20.19.0 (or default) +nvm use default 2>/dev/null || nvm use 20.19.0 2>/dev/null || true + +# Run chrome-devtools-mcp +exec node "/Users/wwzh/Library/pnpm/global/5/.pnpm/chrome-devtools-mcp@0.10.2/node_modules/chrome-devtools-mcp/build/src/index.js" "$@" + diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..c695fdf --- /dev/null +++ b/.cursorignore @@ -0,0 +1,100 @@ +# Dependencies +node_modules/ +*/node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +*/dist/ +build/ +*/build/ +*.tsbuildinfo + +# Environment variables (may contain sensitive information) +.env +.env.local +.env.*.local +*/.env +*/.env.local +*/.env.*.local + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Testing coverage (keep test files for context) +coverage/ +.nyc_output/ + +# Database files +*.db +*.sqlite +*.sqlite3 + +# Lock files (too large and not needed for context) +pnpm-lock.yaml +package-lock.json +yarn.lock + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +*.tmp + +# Prisma migrations SQL files (generated, but may be useful for context) +# Uncomment if you want to ignore migration SQL files: +# backend/prisma/migrations/**/*.sql + +# Compiled JavaScript files (keep TypeScript source and config files) +*.js +*.js.map +*.d.ts +!*.config.js +!*.config.ts +!vite.config.js +!tailwind.config.js +!postcss.config.js + +# Frontend build artifacts +frontend/dist/ +frontend/dist-ssr/ +frontend/.vite/ + +# Backend build artifacts +backend/dist/ + +# Large binary files +*.zip +*.tar.gz +*.rar +*.7z + +# Documentation build outputs (if any) +docs/_build/ +docs/.vuepress/dist/ + +# Cache directories +.cache/ +.parcel-cache/ +.next/ +.nuxt/ +.vuepress/dist/ + +# Temporary files +tmp/ +temp/ +*.tmp +*.temp + diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..a339d66 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,293 @@ +# Competition Management System - Cursor User Rules (DEPRECATED) + +⚠️ **此文件已废弃** - 请使用新的规则系统: +- 项目规则:`.cursor/rules/*.mdc` +- 快速参考:`AGENTS.md` +- 说明文档:`.cursor/RULES_README.md` + +--- + +以下内容保留作为备份,但不再使用: + +# Competition Management System - Cursor User Rules + +## 项目概述 + +这是一个多租户的竞赛管理系统,采用前后端分离架构: +- **后端**: NestJS + TypeScript + Prisma + MySQL +- **前端**: Vue 3 + TypeScript + Vite + Ant Design Vue + Pinia +- **认证**: JWT + RBAC (基于角色的访问控制) +- **架构**: 多租户架构,数据完全隔离 + +## 后端开发规范 + +### 1. 模块结构 + +- 每个功能模块应包含:`module.ts`, `controller.ts`, `service.ts`, `dto/` 目录 +- 模块命名使用复数形式(如 `users`, `roles`, `contests`) +- 子模块放在父模块目录下(如 `contests/works/`, `contests/teams/`) + +### 2. 服务层 (Service) + +- 所有数据库操作必须通过 PrismaService,禁止直接使用 SQL +- 服务方法必须处理租户隔离:所有查询必须包含 `tenantId` 条件 +- 使用 `@Injectable()` 装饰器 +- 构造函数注入依赖,使用 private readonly +- 方法命名:`create`, `findAll`, `findOne`, `update`, `remove` +- 查询方法应支持分页:使用 `skip` 和 `take` 参数 + +### 3. 控制器层 (Controller) + +- 使用 `@Controller()` 装饰器,路径使用复数形式 +- 所有路由默认需要认证(除非使用 `@Public()` 装饰器) +- 使用 `@Get()`, `@Post()`, `@Put()`, `@Delete()`, `@Patch()` 装饰器 +- 从请求中获取租户ID:使用 `@CurrentTenantId()` 装饰器或从 JWT token 中提取 +- 使用 `@CurrentUser()` 装饰器获取当前用户信息 +- 权限控制:使用 `@RequirePermission()` 装饰器 +- 返回统一响应格式:使用 TransformInterceptor(自动处理) + +### 4. DTO (Data Transfer Object) + +- 所有 DTO 放在 `dto/` 目录下 +- 使用 `class-validator` 进行验证 +- 命名规范: + - 创建:`CreateXxxDto` + - 更新:`UpdateXxxDto` + - 查询:`QueryXxxDto` +- 必填字段使用验证装饰器(如 `@IsString()`, `@IsNumber()`) +- 可选字段使用 `@IsOptional()` +- 数组字段使用 `@IsArray()` 和 `@IsNumber({}, { each: true })` + +### 5. 数据库操作 (Prisma) + +- 所有表必须包含 `tenantId` 字段(租户隔离) +- 所有表必须包含审计字段:`creator`, `modifier`, `createTime`, `modifyTime` +- 使用 Prisma 的 `include` 和 `select` 优化查询 +- 关联查询使用嵌套 include,避免 N+1 问题 +- 删除操作使用软删除(`validState` 字段)或级联删除 +- 事务操作使用 `prisma.$transaction()` + +### 6. 多租户处理 + +- **必须**:所有业务数据查询必须包含 `tenantId` 条件 +- 从 JWT token 或请求头中获取租户ID +- 创建数据时自动设置 `tenantId` +- 更新/删除时验证数据属于当前租户 +- 超级租户(`isSuper = 1`)可以访问所有租户数据 + +### 7. 权限控制 + +- 使用 `@RequirePermission()` 装饰器进行权限检查 +- 权限字符串格式:`模块:操作`(如 `contest:create`, `user:update`) +- 角色权限通过 RolesGuard 自动检查 +- 权限验证失败返回 403 Forbidden + +### 8. 错误处理 + +- 使用 NestJS 内置异常:`NotFoundException`, `BadRequestException`, `UnauthorizedException`, `ForbiddenException` +- 自定义异常消息使用中文 +- 错误信息要清晰明确,便于调试 + +### 9. 代码风格 + +- 使用 TypeScript 严格模式 +- 使用 ESLint 和 Prettier 格式化代码 +- 导入顺序:NestJS 核心 → 第三方库 → 本地模块 +- 使用 async/await,避免 Promise.then() +- 使用解构赋值提高代码可读性 + +## 前端开发规范 + +### 1. 组件结构 + +- 页面组件放在 `views/` 目录下,按模块组织 +- 公共组件放在 `components/` 目录下 +- 使用 ` + + + diff --git a/frontend/src/views/contests/Index.vue b/frontend/src/views/contests/Index.vue new file mode 100644 index 0000000..fb8e938 --- /dev/null +++ b/frontend/src/views/contests/Index.vue @@ -0,0 +1,360 @@ + + + + 比赛管理 + + + + 创建比赛 + + + + + + + + + + + 已发布 + 未发布 + + + + + 个人赛 + 团队赛 + + + + + + 搜索 + + + + 重置 + + + + + + + + + {{ record.contestName }} + + + + {{ record.contestType === 'individual' ? '个人赛' : '团队赛' }} + + + + + {{ record.contestState === 'published' ? '已发布' : '未发布' }} + + + + + 开始:{{ formatDateTime(record.startTime) }} + 结束:{{ formatDateTime(record.endTime) }} + + + + + + 查看 + + + 编辑 + + + {{ record.contestState === 'published' ? '撤回' : '发布' }} + + + 删除 + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/contests/components/ContestForm.vue b/frontend/src/views/contests/components/ContestForm.vue new file mode 100644 index 0000000..a2d84df --- /dev/null +++ b/frontend/src/views/contests/components/ContestForm.vue @@ -0,0 +1,327 @@ + + + + + + + + + + + 个人赛 + 团队赛 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 单次提交 + 允许重新提交 + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/contests/registrations/Index.vue b/frontend/src/views/contests/registrations/Index.vue new file mode 100644 index 0000000..3f7e4ed --- /dev/null +++ b/frontend/src/views/contests/registrations/Index.vue @@ -0,0 +1,344 @@ + + + + 报名管理 + + + + + + + {{ contest.contestName }} + + + + + + 待审核 + 已通过 + 已拒绝 + 已撤回 + + + + + 个人赛 + 团队赛 + + + + + + 搜索 + + + + 重置 + + + + + + + + + {{ record.contest?.contestName || '-' }} + + + + {{ record.registrationType === 'individual' ? '个人' : '团队' }} + + + + + {{ getStateText(record.registrationState) }} + + + + {{ record.user?.nickname || record.accountName || '-' }} + + + + + 查看 + + + 审核 + + + + + + + + + + + + + 通过 + 拒绝 + + + + + + + + + + + + + + diff --git a/frontend/src/views/contests/reviews/Index.vue b/frontend/src/views/contests/reviews/Index.vue new file mode 100644 index 0000000..4accaed --- /dev/null +++ b/frontend/src/views/contests/reviews/Index.vue @@ -0,0 +1,411 @@ + + + + 评审管理 + + + + + + + {{ contest.contestName }} + + + + + + + + + + 分配作品 + + + + + {{ record.work?.title || '-' }} + + + + {{ getAssignStatusText(record.status) }} + + + + + + 评分 + + + + + + + + + + + + + {{ record.work?.title || '-' }} + + + {{ record.totalScore }}分 + + + + + + + + + + + + + + {{ work.title }} ({{ work.workNo }}) + + + + + + + {{ judge.judge?.nickname || judge.judge?.username }} + + + + + + + + + + + {{ currentWork?.title }} + + + + + {{ dimension.name }} (权重: {{ dimension.weight || 1 }}) + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/contests/works/Index.vue b/frontend/src/views/contests/works/Index.vue new file mode 100644 index 0000000..4a83d93 --- /dev/null +++ b/frontend/src/views/contests/works/Index.vue @@ -0,0 +1,282 @@ + + + + 作品管理 + + + + + + + {{ contest.contestName }} + + + + + + 已提交 + 已锁定 + 评审中 + 已通过 + 已拒绝 + + + + + + + + + 搜索 + + + + 重置 + + + + + + + + + {{ record.contest?.contestName || '-' }} + + + + {{ getStatusText(record.status) }} + + + + {{ record.registration?.user?.nickname || record.submitterAccountNo || '-' }} + + + v{{ record.version }} (最新) + v{{ record.version }} + + + + + 查看 + + + 版本历史 + + + + + + + + + + + + + diff --git a/frontend/src/views/school/classes/Index.vue b/frontend/src/views/school/classes/Index.vue new file mode 100644 index 0000000..3ecaec8 --- /dev/null +++ b/frontend/src/views/school/classes/Index.vue @@ -0,0 +1,312 @@ + + + + 班级管理 + + + + + {{ grade.name }} + + + + 行政班级 + 兴趣班 + + 新增班级 + + + + + + + {{ record.grade?.name || '-' }} + + + + {{ record.type === 1 ? '行政班级' : '兴趣班' }} + + + + {{ record._count?.students || 0 }}人 + {{ record._count?.studentInterestClasses || 0 }}人 + + + + {{ record.validState === 1 ? '有效' : '失效' }} + + + + {{ formatDate(record.createTime) }} + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + + + 行政班级 + 兴趣班 + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/school/departments/Index.vue b/frontend/src/views/school/departments/Index.vue new file mode 100644 index 0000000..1cb80c2 --- /dev/null +++ b/frontend/src/views/school/departments/Index.vue @@ -0,0 +1,301 @@ + + + + 部门管理 + + + {{ showTree ? '列表视图' : '树形视图' }} + 新增部门 + + + + + + + + + + {{ name }} ({{ _count?.teachers || 0 }}人) + + + {{ expanded ? '📂' : '📁' }} + + + + + + + + + + {{ record.parent?.name || '-' }} + + + {{ record._count?.teachers || 0 }}人 + + + + {{ record.validState === 1 ? '有效' : '失效' }} + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/school/grades/Index.vue b/frontend/src/views/school/grades/Index.vue new file mode 100644 index 0000000..5aef51b --- /dev/null +++ b/frontend/src/views/school/grades/Index.vue @@ -0,0 +1,234 @@ + + + + 年级管理 + + 新增年级 + + + + + + {{ record.level }}级 + + + {{ record._count?.classes || 0 }}个班级 + + + + {{ record.validState === 1 ? '有效' : '失效' }} + + + + {{ formatDate(record.createTime) }} + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/school/schools/Index.vue b/frontend/src/views/school/schools/Index.vue new file mode 100644 index 0000000..86d1bfb --- /dev/null +++ b/frontend/src/views/school/schools/Index.vue @@ -0,0 +1,257 @@ + + + + 学校信息管理 + + + 新增学校信息 + + + 编辑学校信息 + + + + + + + + {{ schoolInfo.tenant?.name || '-' }} + + + {{ schoolInfo.tenant?.code || '-' }} + + + {{ schoolInfo.address || '-' }} + + + {{ schoolInfo.phone || '-' }} + + + {{ schoolInfo.principal || '-' }} + + + {{ formatDate(schoolInfo.established) }} + + + + {{ schoolInfo.website }} + + - + + + + - + + + {{ schoolInfo.description || '-' }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/school/students/Index.vue b/frontend/src/views/school/students/Index.vue new file mode 100644 index 0000000..6569404 --- /dev/null +++ b/frontend/src/views/school/students/Index.vue @@ -0,0 +1,440 @@ + + + + 学生管理 + + + + + {{ cls.grade?.name }} - {{ cls.name }} + + + 新增学生 + + + + + + + + {{ record.user?.nickname || '-' }} + ({{ record.user?.username }}) + + + + {{ record.class?.grade?.name }} - {{ record.class?.name }} + + + 男 + 女 + - + + + + {{ ic.class.name }} + + - + + + + {{ record.validState === 1 ? '有效' : '失效' }} + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 男 + 女 + + + + + + + + + + + + + + + + + + + + + + + + + + 有效 + 失效 + + + + + + + + + + + diff --git a/frontend/src/views/school/teachers/Index.vue b/frontend/src/views/school/teachers/Index.vue new file mode 100644 index 0000000..8f6f7f3 --- /dev/null +++ b/frontend/src/views/school/teachers/Index.vue @@ -0,0 +1,410 @@ + + + + 教师管理 + + + + + {{ dept.name }} + + + 新增教师 + + + + + + + + {{ record.user?.nickname || '-' }} + ({{ record.user?.username }}) + + + + {{ record.department?.name || '-' }} + + + 男 + 女 + - + + + + {{ record.validState === 1 ? '有效' : '失效' }} + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 男 + 女 + + + + + + + + + + + + + + + + + + + + 有效 + 失效 + + + + + + + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c46c923..99e6a2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: '@nestjs/jwt': specifier: ^10.2.0 version: 10.2.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/mapped-types': + specifier: ^2.1.0 + version: 2.1.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) '@nestjs/passport': specifier: ^10.0.3 version: 10.0.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) @@ -29,8 +32,8 @@ importers: specifier: ^10.3.3 version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) '@prisma/client': - specifier: ^5.9.1 - version: 5.22.0(prisma@5.22.0) + specifier: ^6.19.0 + version: 6.19.0(prisma@6.19.0(typescript@5.9.3))(typescript@5.9.3) bcrypt: specifier: ^6.0.0 version: 6.0.0 @@ -111,8 +114,8 @@ importers: specifier: ^3.2.4 version: 3.6.2 prisma: - specifier: ^5.9.1 - version: 5.22.0 + specifier: ^6.19.0 + version: 6.19.0(typescript@5.9.3) source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -755,6 +758,19 @@ packages: peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/mapped-types@2.1.0': + resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + '@nestjs/passport@10.0.3': resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} peerDependencies: @@ -898,29 +914,35 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@prisma/client@5.22.0': - resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} - engines: {node: '>=16.13'} + '@prisma/client@6.19.0': + resolution: {integrity: sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==} + engines: {node: '>=18.18'} peerDependencies: prisma: '*' + typescript: '>=5.1.0' peerDependenciesMeta: prisma: optional: true + typescript: + optional: true - '@prisma/debug@5.22.0': - resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + '@prisma/config@6.19.0': + resolution: {integrity: sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==} - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': - resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + '@prisma/debug@6.19.0': + resolution: {integrity: sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==} - '@prisma/engines@5.22.0': - resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + '@prisma/engines-version@6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773': + resolution: {integrity: sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==} - '@prisma/fetch-engine@5.22.0': - resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + '@prisma/engines@6.19.0': + resolution: {integrity: sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==} - '@prisma/get-platform@5.22.0': - resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@prisma/fetch-engine@6.19.0': + resolution: {integrity: sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==} + + '@prisma/get-platform@6.19.0': + resolution: {integrity: sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==} '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} @@ -1055,6 +1077,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@tokenizer/inflate@0.2.7': resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} engines: {node: '>=18'} @@ -1664,6 +1689,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1726,6 +1759,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} @@ -1805,9 +1841,16 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1903,6 +1946,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1914,6 +1961,9 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1922,6 +1972,9 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1979,6 +2032,10 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -1996,6 +2053,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} + electron-to-chromium@1.5.256: resolution: {integrity: sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==} @@ -2009,6 +2069,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -2171,10 +2235,17 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2318,6 +2389,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2677,6 +2752,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2959,6 +3038,9 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2993,6 +3075,11 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3005,6 +3092,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -3110,6 +3200,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} @@ -3148,6 +3241,9 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3216,10 +3312,15 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prisma@5.22.0: - resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} - engines: {node: '>=16.13'} + prisma@6.19.0: + resolution: {integrity: sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==} + engines: {node: '>=18.18'} hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -3257,6 +3358,9 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -3609,6 +3713,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -4622,6 +4730,14 @@ snapshots: '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 + '@nestjs/mapped-types@2.1.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + '@nestjs/passport@10.0.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': dependencies: '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4755,30 +4871,40 @@ snapshots: '@pkgr/core@0.2.9': {} - '@prisma/client@5.22.0(prisma@5.22.0)': + '@prisma/client@6.19.0(prisma@6.19.0(typescript@5.9.3))(typescript@5.9.3)': optionalDependencies: - prisma: 5.22.0 + prisma: 6.19.0(typescript@5.9.3) + typescript: 5.9.3 - '@prisma/debug@5.22.0': {} - - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} - - '@prisma/engines@5.22.0': + '@prisma/config@6.19.0': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/fetch-engine': 5.22.0 - '@prisma/get-platform': 5.22.0 + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.18.4 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast - '@prisma/fetch-engine@5.22.0': - dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/get-platform': 5.22.0 + '@prisma/debug@6.19.0': {} - '@prisma/get-platform@5.22.0': + '@prisma/engines-version@6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773': {} + + '@prisma/engines@6.19.0': dependencies: - '@prisma/debug': 5.22.0 + '@prisma/debug': 6.19.0 + '@prisma/engines-version': 6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773 + '@prisma/fetch-engine': 6.19.0 + '@prisma/get-platform': 6.19.0 + + '@prisma/fetch-engine@6.19.0': + dependencies: + '@prisma/debug': 6.19.0 + '@prisma/engines-version': 6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773 + '@prisma/get-platform': 6.19.0 + + '@prisma/get-platform@6.19.0': + dependencies: + '@prisma/debug': 6.19.0 '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -4861,6 +4987,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.0.0': {} + '@tokenizer/inflate@0.2.7': dependencies: debug: 4.4.3 @@ -5690,6 +5818,21 @@ snapshots: bytes@3.1.2: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5748,6 +5891,10 @@ snapshots: ci-info@3.9.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + cjs-module-lexer@1.4.3: {} class-transformer@0.5.1: {} @@ -5821,8 +5968,12 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 + confbox@0.2.2: {} + consola@2.15.3: {} + consola@3.4.2: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -5900,6 +6051,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -5912,10 +6065,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + defu@6.1.4: {} + delayed-stream@1.0.0: {} depd@2.0.0: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-libc@1.0.3: @@ -5958,6 +6115,8 @@ snapshots: dotenv@16.4.5: {} + dotenv@16.6.1: {} + dotenv@17.2.3: {} dunder-proto@1.0.1: @@ -5974,6 +6133,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.18.4: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.256: {} emittery@0.13.1: {} @@ -5982,6 +6146,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -6219,12 +6385,18 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.8: {} + external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6385,6 +6557,15 @@ snapshots: get-stream@6.0.1: {} + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6953,6 +7134,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.6.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -7196,6 +7379,8 @@ snapshots: dependencies: lodash: 4.17.21 + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -7218,12 +7403,22 @@ snapshots: dependencies: boolbase: 1.0.0 + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.2 + object-assign@4.1.1: {} object-hash@3.0.0: {} object-inspect@1.13.4: {} + ohash@2.0.11: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -7330,6 +7525,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + pause@0.0.1: {} perfect-debounce@1.0.0: {} @@ -7358,6 +7555,12 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + pluralize@8.0.0: {} postcss-import@15.1.0(postcss@8.5.6): @@ -7411,11 +7614,14 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@5.22.0: + prisma@6.19.0(typescript@5.9.3): dependencies: - '@prisma/engines': 5.22.0 + '@prisma/config': 6.19.0 + '@prisma/engines': 6.19.0 optionalDependencies: - fsevents: 2.3.3 + typescript: 5.9.3 + transitivePeerDependencies: + - magicast prompts@2.4.2: dependencies: @@ -7452,6 +7658,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + react-is@18.3.1: {} read-cache@1.0.0: @@ -7841,6 +8052,8 @@ snapshots: through@2.3.8: {} + tinyexec@1.0.2: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2