diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 22f72dd..5ea502e 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -65,6 +65,15 @@ jobs: - name: Typecheck run: npm run typecheck + - name: Install Playwright Chromium + run: npx playwright install chromium + + - name: Integration tests + run: npm run test:integration + + - name: E2E full regression tests + run: npm run test:e2e + # 2. 增加 Next.js 构建缓存 - name: Cache Next.js build uses: actions/cache@v3 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index fa245a0..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,360 +0,0 @@ -# Next_Edu Architecture RFC - -**Status**: PROPOSED -**Date**: 2025-12-22 -**Author**: Principal Software Architect -**Version**: 1.0.0 - ---- - -## 1. 核心原则 (Core Principles) - -本架构设计遵循以下核心原则,旨在构建一个高性能、可扩展、易维护的企业级在线教育平台。 - -1. **Vertical Slice Architecture (垂直切片)**: 拒绝传统的按技术分层(Layered Architecture),采用按业务功能分层。代码应根据“它属于哪个功能”而不是“它是什么文件”来组织。 -2. **Type Safety First (类型安全优先)**: 全链路 TypeScript (Strict Mode)。从数据库 Schema 到 API 再到 UI 组件,必须保持类型一致性。 -3. **Server-First (服务端优先)**: 充分利用 Next.js 15 App Router 的 RSC (React Server Components) 能力,减少客户端 Bundle 体积。 -4. **Performance by Default (默认高性能)**: 严禁引入重型动画库,动效优先使用 CSS Native 实现。Web Vitals 指标作为 CI 阻断标准。 -5. **Strict Engineering (严格工程化)**: CI/CD 流程标准化,代码风格统一,自动化测试覆盖。 - ---- - -## 2. 技术栈全景图 (Technology Panorama) - -### 核心框架 -* **Framework**: Next.js 15 (App Router) -* **Language**: TypeScript 5.x (Strict Mode enabled) - * *Correction*: 鉴于 Next.js App Router 的特性,`.tsx` 仅用于包含 JSX 的组件文件。所有的业务逻辑、Hooks、API 路由、Lib 工具函数必须使用 `.ts` 后缀,以明确区分“渲染层”与“逻辑层”。 -* **Runtime**: Node.js 20.x (LTS) - -### 数据层 -* **Database**: MySQL 8.0+ -* **ORM**: Drizzle ORM (轻量、无运行时开销、类型安全) -* **Driver**: `mysql2` (配合连接池) 或 `serverless-mysql` (针对特定 Serverless 环境) -* **Validation**: Zod (Schema 定义与运行时验证) - -### UI/UX 层 -* **Styling**: Tailwind CSS v3.4+ -* **Components**: Shadcn/UI (基于 Radix UI 的 Headless 组件拷贝) -* **Icons**: Lucide React -* **Animations**: CSS Transitions / Tailwind `animate-*` / `tailwindcss-animate` - * *Complex Interactions*: Framer Motion (仅限按需加载 `LazyMotion`) - -### 身份验证与授权 -* **Auth**: Auth.js v5 (NextAuth) - * *Decision Driver*: 相比 Clerk,Auth.js 提供了完全的**数据所有权**和**无 Vendor Lock-in**。 - * *Enterprise Needs*: 允许自定义 Session 结构(如注入 `Role` 字段)并直接对接现有 MySQL 数据库,满足复杂的企业级权限管理需求。 - -### 状态管理 -* **Server State**: TanStack Query v5 (仅用于复杂客户端轮询/无限加载) -* **URL State (Primary)**: Nuqs (Type-safe search params state manager) - * *Principle*: 绝大多数状态(筛选、分页、Tab)应存在 URL 中,以支持分享和书签。 -* **Global Client State (Secondary)**: Zustand - * *Usage*: 仅限极少数全局交互状态(如播放器悬浮窗、全局 Modal)。 - * *Anti-pattern*: **严禁使用 Redux**。避免不必要的样板代码和 Bundle 体积。 - -### 基础设施 & DevOps -* **CI/CD**: GitHub Actions (Strictly v3) -* **Linting**: ESLint (Next.js config), Prettier -* **Package Manager**: pnpm (推荐) 或 npm - ---- - -## 3. 项目目录结构规范 (Project Structure) - -采用 **Feature-based / Vertical Slice** 架构。所有业务逻辑应封装在 `src/modules` 中。 - -文档存放位置: -* 架构设计文档: `docs/architecture/` -* API 规范文档: `docs/api/` - -### 目录树 (Directory Tree) - -``` -Next_Edu/ -├── .github/ -│ └── workflows/ -│ └── ci.yml # GitHub Actions (v3 strict) -├── docs/ -│ └── architecture/ # 架构决策记录 (ADR) -├── drizzle/ # 数据库迁移文件 (Generated) -├── public/ # 静态资源 -├── src/ -│ ├── app/ # [路由层] 极薄,仅负责路由分发和布局 -│ │ ├── (auth)/ # 路由组 -│ │ ├── (dashboard)/ -│ │ ├── api/ # Webhooks / External APIs -│ │ ├── layout.tsx -│ │ └── page.tsx -│ │ -│ ├── modules/ # [核心业务层] 垂直切片 -│ │ ├── courses/ # 课程模块 -│ │ │ ├── components/ # 模块私有组件 (CourseCard, Player) -│ │ │ ├── actions.ts # Server Actions (业务逻辑入口) -│ │ │ ├── service.ts # 领域服务 (可选,复杂逻辑拆分) -│ │ │ ├── data-access.ts # 数据库查询 (DTOs) -│ │ │ └── types.ts # 模块私有类型 -│ │ │ -│ │ ├── users/ # 用户模块 -│ │ ├── payments/ # 支付模块 -│ │ └── community/ # 社区模块 -│ │ -│ ├── shared/ # [共享层] 仅存放真正通用的代码 -│ │ ├── components/ # 通用 UI (Button, Dialog - Shadcn) -│ │ ├── lib/ # 通用工具 (utils, date formatting) -│ │ ├── db/ # Drizzle Client & Schema -│ │ │ ├── index.ts # DB 连接实例 -│ │ │ └── schema.ts # 全局 Schema 定义 (或按模块拆分导出) -│ │ └── hooks/ # 通用 Hooks -│ │ -│ ├── env.mjs # 环境变量类型检查 -│ └── middleware.ts # 边缘中间件 (Auth check) -├── drizzle.config.ts # Drizzle 配置文件 -├── next.config.mjs -└── package.json -``` - ---- - -## 4. 数据库层设计 (Database Strategy) - -### 连接配置 (Connection Pooling) -在 Next.js 的 Serverless/Edge 环境中,直接连接 MySQL 可能导致连接数耗尽。我们采取以下策略: - -1. **开发环境**: 使用 Global Singleton 模式防止 Hot Reload 导致连接泄露。 -2. **生产环境**: - * 推荐使用支持 HTTP 连接或内置连接池的 Serverless MySQL 方案 (如 PlanetScale)。 - * 若使用标准 MySQL,必须配置连接池 (`connectionLimit`) 并合理设置空闲超时。 - -**代码示例 (`src/shared/db/index.ts`)**: - -```typescript -import { drizzle } from "drizzle-orm/mysql2"; -import mysql from "mysql2/promise"; -import * as schema from "./schema"; - -// Global cache to prevent connection exhaustion in development -const globalForDb = globalThis as unknown as { - conn: mysql.Pool | undefined; -}; - -const poolConnection = globalForDb.conn ?? mysql.createPool({ - uri: process.env.DATABASE_URL, - waitForConnections: true, - connectionLimit: 10, // 根据数据库规格调整 - queueLimit: 0 -}); - -if (process.env.NODE_ENV !== "production") globalForDb.conn = poolConnection; - -export const db = drizzle(poolConnection, { schema, mode: "default" }); -``` - -### Migration 策略 -* 使用 `drizzle-kit` 进行迁移管理。 -* 严禁在生产环境运行时自动执行 Migration。 -* **流程**: - 1. 修改 Schema (`schema.ts`). - 2. 运行 `pnpm drizzle-kit generate` 生成 SQL 文件。 - 3. Review SQL 文件。 - 4. 在 CI/CD 部署前置步骤或手动运行 `pnpm drizzle-kit migrate`。 - -### Server Components 中的数据查询 -* **Colocation**: 查询逻辑应尽量靠近使用它的组件,或者封装在 `data-access.ts` 中。 -* **Request Memoization**: 即使在一个请求中多次调用相同的查询函数,Next.js 的 `cache` (或 React `cache`) 也会自动去重。 - -```typescript -// src/modules/courses/data-access.ts -import { cache } from 'react'; -import { db } from '@/shared/db'; -import { eq } from 'drizzle-orm'; -import { courses } from '@/shared/db/schema'; - -// 使用 React cache 确保单次请求内的去重 -export const getCourseById = cache(async (id: string) => { - return await db.query.courses.findFirst({ - where: eq(courses.id, id), - }); -}); -``` - ---- - -## 5. UI/UX 动效规范 (Animation Guidelines) - -### 核心策略 -* **CSS Native First**: 90% 的交互通过 CSS `transition` 和 `animation` 实现。 -* **Hardware Acceleration**: 确保动画属性触发 GPU 加速 (`transform`, `opacity`)。 -* **Micro-interactions**: 关注 `:hover`, `:active`, `:focus-visible` 状态。 - -### 高性能通用组件示例 (Interactive Card) - -这是一个符合规范的卡片组件,使用了 Tailwind 的 `group` 和 `transform` 属性实现丝滑的微交互,且没有 JS 运行时开销。 - -```tsx -// src/shared/components/ui/interactive-card.tsx -import { cn } from "@/shared/lib/utils"; - -interface CardProps extends React.HTMLAttributes { - children: React.ReactNode; -} - -export function InteractiveCard({ className, children, ...props }: CardProps) { - return ( -
- {/* 光泽效果 (Shimmer Effect) - 仅 CSS */} - - ); -} -``` - ---- - -## 6. CI/CD 配置文件模板 (GitHub Actions) - -**警告**: 必须严格遵守 `v3` 版本限制。严禁使用 `v4`。 - -文件路径: `.github/workflows/ci.yml` - -```yaml -name: CI/CD Pipeline - -on: - push: - branches: [ "main", "develop" ] - pull_request: - branches: [ "main", "develop" ] - -env: - NODE_VERSION: '20.x' - -jobs: - quality-check: - name: Quality & Type Check - runs-on: ubuntu-latest - steps: - # 强制使用 v3 - - name: Checkout Code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' # 或 'pnpm' - - - name: Install Dependencies - run: npm ci - - - name: Linting (ESLint) - run: npm run lint - - - name: Type Checking (TSC) - # 确保没有 TS 错误 - run: npx tsc --noEmit - - test: - name: Unit Tests - needs: quality-check - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install Dependencies - run: npm ci - - - name: Run Tests - run: npm run test - - build-check: - name: Production Build Check - needs: test - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install Dependencies - run: npm ci - - - name: Cache Next.js build - uses: actions/cache@v3 - with: - path: | - ~/.npm - ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- - - - name: Build Application - run: npm run build - env: - # 构建时跳过 ESLint/TS 检查 (因为已经在 quality-check job 做过了,加速构建) - NEXT_TELEMETRY_DISABLED: 1 -``` - -## 工作记录(2026-01-12) - -### 注册与首次登录引导 -- 注册流程调整为“仅创建账户并跳转登录”,首次登录后通过全局弹窗分步骤完成资料配置 -- 全局引导弹窗包含:选择角色 → 通用信息(姓名/电话/住址)→ 角色信息(可跳过,后续在设置中补全)→ 完成 -- 新增/补齐用户扩展字段与迁移:phone、address、gender、age、gradeId、departmentId、onboardedAt -- 新增引导状态与提交接口:`/api/onboarding/status`、`/api/onboarding/complete` - -相关文件: -- src/shared/components/onboarding-gate.tsx -- src/app/api/onboarding/status/route.ts -- src/app/api/onboarding/complete/route.ts -- src/shared/db/schema.ts -- drizzle/0008_add_user_profile_fields.sql - -### 注册失败排查与错误提示 -- 注册 server action 增强错误信息(可识别重复邮箱、未迁移、权限错误、连接失败等),开发环境可返回更具体的底层错误消息 -- 本地排查曾出现 `ECONNREFUSED`,属于数据库连接不可达问题(需检查 MySQL 服务状态与 DATABASE_URL 配置) - -相关文件: -- src/app/(auth)/register/page.tsx - -### 顶部头像信息修复 -- 修复右上角头像/下拉信息写死为 admin 的问题,改为从 NextAuth session 动态读取当前用户 name/email 并生成头像 fallback - -相关文件: -- src/modules/layout/components/site-header.tsx diff --git a/docs/architecture/001_database_schema_design.md b/docs/architecture/001_database_schema_design.md deleted file mode 100644 index a3a0513..0000000 --- a/docs/architecture/001_database_schema_design.md +++ /dev/null @@ -1,112 +0,0 @@ -# 架构决策记录 (ADR): 数据库 Schema 设计方案 v1.0 - -**状态**: 已实施 (IMPLEMENTED) -**日期**: 2025-12-23 -**作者**: 首席系统架构师 -**背景**: Next_Edu 平台 - K12 智慧教育管理系统 - ---- - -## 1. 概述 (Overview) - -本文档详细记录了 Next_Edu 平台的数据库 Schema 架构设计。本设计优先考虑 **可扩展性 (Scalability)**、**灵活性 (Flexibility)**(针对复杂的嵌套内容)以及 **严格的类型安全 (Strict Type Safety)**,并完全符合 PRD 中规定的领域驱动设计 (DDD) 原则。 - -## 2. 技术栈决策 (Technology Stack Decisions) - -| 组件 | 选择 | 理由 | -| :--- | :--- | :--- | -| **数据库** | MySQL 8.0+ | 强大的关系型支持,完善的 JSON 能力,行业标准。 | -| **ORM** | Drizzle ORM | 轻量级,零运行时开销 (Zero-runtime overhead),业界一流的 TypeScript 类型推断。 | -| **ID 策略** | CUID2 | 分布式友好 (k-sortable),安全(防连续猜测攻击),比 UUID 更短。 | -| **认证方案** | Auth.js v5 | 标准化的 OAuth 流程,支持自定义数据库适配器。 | - ---- - -## 3. 核心 Schema 领域模型 (Core Schema Domains) - -物理文件位于 `src/shared/db/schema.ts`,逻辑上分为三大领域。 - -### 3.1 身份与访问管理 (IAM) - -我们采用了 **Auth.js 标准表** 与 **自定义 RBAC** 相结合的混合模式。 - -* **标准表**: `users`, `accounts` (OAuth), `sessions`, `verificationTokens`。 -* **RBAC 扩展**: - * `roles`: 定义系统角色(例如:`grade_head` 年级主任, `teacher` 老师)。 - * `users_to_roles`: 多对多关联表。 - * **设计目标**: 解决“一人多职”问题(例如:一个老师同时也是年级主任),避免在 `users` 表中堆砌字段。 - -### 3.2 智能题库中心 (Intelligent Question Bank) - 核心 - -这是最复杂的领域,需要支持无限层级嵌套和富文本内容。 - -#### 实体定义: -1. **`questions` (题目表)**: - * `id`: CUID2。 - * `content`: **JSON 类型**。存储结构化内容(如 SlateJS 节点),支持富文本、图片和公式混排。 - * `parentId`: **自引用 (Self-Reference)**。 - * *若为 NULL*: 独立题目 或 “大题干” (Parent)。 - * *若有值*: 子题目 (Child)(例如:一篇阅读理解下的第1小题)。 - * `type`: 枚举 (`single_choice`, `text`, `composite` 等)。 -2. **`knowledge_points` (知识点表)**: - * 通过 `parentId` 实现树状结构。 - * 支持无限层级 (学科 -> 章 -> 节 -> 知识点)。 -3. **`questions_to_knowledge_points`**: - * 多对多关联。一道题可考察多个知识点;一个知识点可关联数千道题。 - -### 3.3 教务教学流 (Academic Teaching Flow) - -将物理世界的教学过程映射为数字实体。 - -* **`textbooks` & `chapters`**: 标准的教材大纲映射。`chapters` 同样支持通过 `parentId` 进行嵌套。 -* **`exams`**: 考试/作业的聚合实体。 -* **`exam_submissions`**: 代表一名学生的单次答题记录。 -* **`submission_answers`**: 细粒度的答题详情,记录每道题的答案,支持自动评分 (`score`) 和人工反馈 (`feedback`)。 - ---- - -## 4. 关键设计模式 (Key Design Patterns) - -### 4.1 无限嵌套 ("Composite" Pattern) -我们没有为“题干”和“题目”创建单独的表,而是在 `questions` 表上使用 **自引用 (Self-Referencing)** 模式。 - -* **优点**: - * 统一的查询接口 (`db.query.questions.findFirst({ with: { children: true } })`)。 - * 递归逻辑可统一应用。 - * 当内容结构变化时,迁移更简单。 -* **缺点**: - * 需要处理递归查询逻辑(已通过 Drizzle Relations 解决)。 - -### 4.2 CUID2 优于 自增 ID -* **安全性**: 防止 ID 枚举攻击(猜测下一个用户 ID)。 -* **分布式**: 支持在客户端或多服务器节点生成,无碰撞风险。 -* **性能**: `k-sortable` 特性保证了比随机 UUID v4 更好的索引局部性。 - -### 4.3 JSON 存储内容 -* 教育内容不仅仅是“文本”。它包含格式、LaTeX 公式和图片引用。 -* 使用 `JSON` 存储允许前端 (Next.js) 直接渲染富组件,无需解析复杂的 HTML 字符串。 - ---- - -## 5. 安全与索引策略 (Security & Indexing Strategy) - -### 索引 (Indexes) -* **外键**: 所有外键列 (`author_id`, `parent_id` 等) 均显式建立索引。 -* **性能**: - * `parent_id_idx`: 对树形结构的遍历性能至关重要。 - * `email_idx`: 登录查询的核心索引。 - -### 类型安全 (Type Safety) -* 严格的 TypeScript 定义直接从 `src/shared/db/schema.ts` 导出。 -* Zod Schema (待生成) 将与这些 Drizzle 定义保持 1:1 对齐。 - ---- - -## 6. 目录结构 (Directory Structure) - -```bash -src/shared/db/ -├── index.ts # 单例数据库连接池 -├── schema.ts # 物理表结构定义 -└── relations.ts # 逻辑 Drizzle 关系定义 -``` diff --git a/docs/architecture/002_exam_structure_migration.md b/docs/architecture/002_exam_structure_migration.md deleted file mode 100644 index 1d82f3d..0000000 --- a/docs/architecture/002_exam_structure_migration.md +++ /dev/null @@ -1,52 +0,0 @@ -# Database Schema Change Request: Exam Structure Support - -## 1. Table: `exams` - -### Change -**Add Column**: `structure` - -### Details -- **Type**: `JSON` -- **Nullable**: `TRUE` (Default: `NULL`) - -### Reason -To support hierarchical exam structures (e.g., Sections/Groups containing Questions). The existing flat `exam_questions` table only supports a simple list of questions and is insufficient for complex exam layouts (e.g., "Part A: Reading", "Part B: Writing"). - -### Before vs After - -**Before**: -`exams` table only stores metadata (`title`, `description`, etc.). Question ordering relies solely on `exam_questions.order`. - -**After**: -`exams` table includes `structure` column to store the full tree representation: -```json -[ - { "id": "uuid-1", "type": "group", "title": "Section A", "children": [...] }, - { "id": "uuid-2", "type": "question", "questionId": "q1", "score": 10 } -] -``` -*Note: `exam_questions` table is retained for relational integrity and efficient querying of question usage, but the presentation order/structure is now driven by this new JSON column.* - ---- - -## 2. Table: `questions_to_knowledge_points` - -### Change -**Rename Foreign Key Constraints** - -### Details -- Rename constraint for `question_id` to `q_kp_qid_fk` -- Rename constraint for `knowledge_point_id` to `q_kp_kpid_fk` - -### Reason -The default generated foreign key names (e.g., `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk`) exceed the MySQL identifier length limit (64 characters), causing migration failures. - -### Before vs After - -**Before**: -- FK Name: `questions_to_knowledge_points_question_id_questions_id_fk` (Too long) -- FK Name: `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk` (Too long) - -**After**: -- FK Name: `q_kp_qid_fk` -- FK Name: `q_kp_kpid_fk` diff --git a/docs/architecture/003_frontend_engineering_standards.md b/docs/architecture/003_frontend_engineering_standards.md deleted file mode 100644 index a2167ed..0000000 --- a/docs/architecture/003_frontend_engineering_standards.md +++ /dev/null @@ -1,438 +0,0 @@ -# Frontend Engineering Standards (Next_Edu) - -**Status**: ACTIVE -**Owner**: Frontend Team -**Scope**: Next.js App Router 前端工程规范(编码、目录、交互、样式、数据流、质量门禁) -**Applies To**: `src/app/*`, `src/modules/*`, `src/shared/*`, `docs/design/*` - ---- - -## 0. 目标与非目标 - -### 0.1 目标 - -- 让新加入的前端工程师在 30 分钟内完成对齐并开始稳定迭代 -- 保证 UI 一致性(Design Token + Shadcn/UI 复用) -- 充分利用 App Router + RSC(Server-First)降低 bundle、提升性能 -- 保证类型安全与可维护性(Vertical Slice、数据访问边界清晰) -- 形成可执行的质量门禁(lint/typecheck/build 与评审清单) - -### 0.2 非目标 - -- 不规定具体业务模块的需求细节(业务规则以 `docs/design/*` 与 PRD 为准) -- 不引入与当前仓库技术栈不一致的新框架/库(新增依赖需明确收益与替代方案) - ---- - -## 1. 接手流程(Onboarding Checklist) - -### 1.1 先读什么(按顺序) - -- 设计系统与 UI 规范:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md) -- 角色路由与目录规范:[docs/architecture/002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md) -- 项目架构总览:[ARCHITECTURE.md](file:///c:/Users/xiner/Desktop/CICD/ARCHITECTURE.md) -- 你将要改动的模块实现文档:`docs/design/00*_*.md` - -### 1.2 开发前对齐(必须) - -- 核对 Design Tokens 与暗色模式变量: - - Tailwind 语义色映射:[tailwind.config.ts](file:///c:/Users/xiner/Desktop/CICD/tailwind.config.ts) - - CSS 变量定义:[src/app/globals.css](file:///c:/Users/xiner/Desktop/CICD/src/app/globals.css) -- 盘点可复用 UI 组件:`src/shared/components/ui/*` -- 盘点通用工具(`cn` 等):[src/shared/lib/utils.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/lib/utils.ts) - -### 1.3 环境变量与配置校验(必须) - -- 统一使用 `@t3-oss/env-nextjs` 的 `env` 入口读取环境变量,禁止在业务代码中散落 `process.env.*` -- Schema 定义与校验入口:[src/env.mjs](file:///c:/Users/xiner/Desktop/CICD/src/env.mjs) -- 任何新增环境变量: - - 必须先在 `src/env.mjs` 增加 schema - - 必须在 docs 中更新部署/运行说明(就近更新对应模块文档或全局架构文档) - -### 1.3 本地跑通(推荐顺序) - -- 安装依赖:`npm install` -- 启动开发:`npm run dev` -- 质量检查: - - `npm run lint` - - `npm run typecheck` - - `npm run build` - ---- - -## 2. 核心工程原则(必须遵守) - -### 2.1 Vertical Slice(按业务功能组织) - -- 业务必须放在 `src/modules//*` -- `src/app/*` 是路由层,只负责: - - 布局组合(layout) - - 读取 `searchParams` / `params` - - 调用模块的数据访问函数(`data-access.ts`) - - 组合模块组件渲染 -- 通用能力放在 `src/shared/*`: - - 通用 UI:`src/shared/components/ui/*` - - 通用工具:`src/shared/lib/*` - - DB 与 schema:`src/shared/db/*` - -### 2.2 Server-First(默认 Server Component) - -- 默认写 Server Component -- 只有在需要以下能力时,才把“最小子组件”标记为 Client Component: - - `useState/useEffect/useMemo`(与交互/浏览器相关) - - DOM 事件(`onClick/onChange` 等) - - `useRouter/usePathname` 等客户端导航 hooks - - Radix/Portal 类组件需要客户端(Dialog/Dropdown 等通常在 client 内组合使用) - -### 2.3 不重复造轮子(Shadcn/UI 优先) - -- 禁止手写 Modal/Dropdown/Tooltip 等基础交互容器 -- 优先组合 `src/shared/components/ui/*`(Button/Card/Dialog/DropdownMenu/AlertDialog/Skeleton/EmptyState 等) -- 若现有基础组件无法满足需求: - 1. 优先通过 Composition 在业务模块里封装“业务组件” - 2. 仅在存在 bug 或需要全局一致性调整时,才考虑改动 `src/shared/components/ui/*`(并在 PR 中明确影响面) -- **图表库**:统一使用 `Recharts`,禁止引入其他图表库(Chart.js / ECharts 等)。 - - 使用 `src/shared/components/ui/chart.tsx` 进行封装。 - - 遵循 Shadcn/UI Chart 规范。 - -### 2.4 Client Component 引用边界(强制) - -- 禁止在 Client Component 中导入任何“服务端实现”代码(例如 DB 实例、data-access、server-only 模块) -- Client Component 允许导入: - - `src/shared/components/ui/*`(基础 UI) - - `src/shared/lib/*`(纯前端工具函数) - - Server Actions(`"use server"` 导出的 action 函数) - - 类型定义必须使用 `import type`(避免把服务端依赖带入 client bundle) -- 所有 `data-access.ts` 必须包含 `import "server-only"`,并将其视为强制安全边界(不是可选优化) - ---- - -## 3. 目录与路由规范 - -### 3.1 路由目录(App Router) - -- 认证域:`src/app/(auth)/*` -- 控制台域(共享 App Shell):`src/app/(dashboard)/*` -- 角色域:`src/app/(dashboard)/teacher|student|admin/*` -- `/dashboard` 作为入口页(重定向/分发到具体角色 dashboard):参考 [002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md) - -### 3.2 页面文件职责 - -- `page.tsx`:页面组装(RSC),不承载复杂交互 -- `loading.tsx`:路由级加载态(Skeleton) -- `error.tsx`:路由级错误边界(友好 UI) -- `not-found.tsx`:路由级 404 - -### 3.3 错误处理与用户反馈(Error Handling & Feedback) - -- 路由级错误(404/500/未捕获异常): - - 交由 `not-found.tsx` / `error.tsx` 处理 - - 禁止在 `error.tsx` 里弹 Toast 堆栈刷屏,错误页输出必须友好且可恢复(例如提供 retry / 返回入口) -- 业务操作反馈(表单提交/按钮操作/行级动作): - - 统一由 Client Component 在调用 Server Action 后触发 `sonner` toast - - 只在“成功”或“明确失败(业务/校验错误)”时触发 toast;未知异常由 action 归一为失败 message - -### 3.4 异步组件与 Suspense(Streaming) - -- 对于数据加载超过 300ms 的非核心 UI 区块(例如:仪表盘某张统计卡片/图表/第三方数据块): - - 必须用 `}>` 包裹,以避免全页阻塞 -- 禁止在 `page.tsx` 顶层用多个串行 `await` 造成瀑布请求: - - 多个独立请求必须使用 `Promise.all` - - 或拆分为多个 async 子组件并行流式渲染(用 `Suspense` 分段展示) - -### 3.3 动态渲染策略(避免 build 阶段查库) - -当页面在渲染时会查询数据库或依赖 request-time 数据,且无法安全静态化时: - -- 在页面入口显式声明: - - `export const dynamic = "force-dynamic"` -- 该策略已用于教师端班级与作业相关页面,见相应 design 文档(例如教师班级模块更新记录) - ---- - -## 4. 模块内文件结构(强制) - -每个业务模块使用统一结构(可按复杂度增减,但命名必须一致): - -``` -src/modules// -├── components/ # 仅该模块使用的 UI 组件(可含 client 组件) -├── actions.ts # Server Actions(写入/变更 + revalidatePath) -├── data-access.ts # 数据查询与聚合(server-only + cache) -├── schema.ts # Zod schema(若需要) -└── types.ts # 类型定义(与 DB/DTO 对齐) -``` - -约束: - -- `actions.ts` 必须包含 `"use server"` -- `data-access.ts` 必须包含 `import "server-only"`(防止误导入到 client bundle) -- 复杂页面组件必须下沉到 `src/modules//components/*`,路由层只做组装 - ---- - -## 5. Server / Client 边界与拆分策略 - -### 5.1 最小化 Client Component 的落地方式 - -- 页面保持 RSC -- 把需要交互的部分抽成独立 `components/*` 子组件并标记 `"use client"` -- Client 组件向上暴露“数据变化事件”,由 Server Action 完成写入并 `revalidatePath` - -### 5.4 Hydration 一致性(必须) - -- 所有 Client Component 的首屏渲染必须保证与 SSR 产出的 HTML 一致 -- 禁止在 render 分支中使用: - - `typeof window !== "undefined"` 之类的 server/client 分支 - - `Date.now()` / `Math.random()` 等不稳定输入 - - 依赖用户 locale 的时间格式化(除非服务端与客户端完全一致并带 snapshot) -- 对于 Radix 等组件生成的动态 aria/id 导致的属性差异: - - 优先通过组件封装确保首屏稳定 - - 若确认差异不可避免且不影响交互,可在最小范围使用 `suppressHydrationWarning` - -### 5.2 页面必须只做“拼装”,功能模块必须独立 - -- 任何功能模块都必须在 `src/modules//components/*` 内独立实现 -- `page.tsx` 只负责: - - 读取 `params/searchParams` - - 调用 `data-access.ts` 获取数据 - - 以组合方式拼装模块组件(不在 page 内实现具体交互与复杂 UI) -- 行数不是拆分依据,只是“路由层变厚”的信号;一旦出现成块的功能 UI,应立即下沉到模块组件 - -### 5.3 什么时候允许在 Client 中做“局部工作台” - -当交互复杂到“页面需要类似 SPA 的局部体验”,允许将工作台容器作为 Client: - -- 典型场景:三栏工作台、拖拽排序编辑器、复杂筛选器组合、富交互表格 -- 但仍要求: - - 初始数据由 RSC 获取并传入 Client - - 写操作通过 Server Actions - - UI 状态尽量 URL 化(能分享/回溯) - ---- - -## 6. 样式与 UI 一致性(Design System 强制项) - -### 6.1 Token 优先(语义化颜色/圆角) - -- 颜色必须使用语义 token: - - `bg-background`, `bg-card`, `bg-muted`, `text-foreground`, `text-muted-foreground`, `border-border` 等 -- 禁止硬编码颜色值(`#fff`/`rgb()`)与随意引入灰度(如 `bg-gray-100`) -- 圆角、边框、阴影遵循设计系统: - - 常规组件使用 `rounded-md` 等语义半径(由 `--radius` 映射) - -### 6.2 className 规范 - -- 所有条件样式必须使用 `className={cn(...)}` -- `cn` 入口为 `@/shared/lib/utils` - -### 6.3 禁止 Arbitrary Values(默认) - -- 默认禁止 `w-[123px]` 等任意值 -- 只有在设计系统或现有实现明确允许、并且无法用 token/栅格解决时,才可使用,并在 PR 描述说明原因 - -### 6.4 微交互与状态(必须有) - -- 按钮 hover:必须有 transition(现有 Button 组件已内置) -- 列表项 hover:使用 `hover:bg-muted/50` 等轻量反馈 -- Loading:必须使用 `Skeleton`(路由级 `loading.tsx` 或组件内 skeleton) -- Empty:必须使用 `EmptyState` -- Toast:统一使用 `sonner` - ---- - -## 7. 图标规范(lucide-react) - -- 统一使用 `lucide-react` -- 图标尺寸统一:默认 `h-4 w-4`,需要强调时 `h-5 w-5` -- 颜色使用语义化:例如 `text-muted-foreground` - ---- - -## 8. 数据流规范(查询、写入、状态) - -### 8.1 查询(data-access.ts) - -- 所有查询放在 `src/modules//data-access.ts` -- 需要复用/去重的查询优先用 `cache` 包裹(React cache) -- 查询函数返回“UI 直接可消费的 DTO”,避免页面层再做复杂映射 - -### 8.2 写入(actions.ts) - -- 所有写操作必须通过 Server Actions -- 每个 action: - - 校验输入(Zod 或手写 guard) - - 执行 DB 写入 - - 必须 `revalidatePath`(以页面为单位) - -### 8.3 Server Action 返回结构(统一反馈协议) - -- 所有 Server Action 必须返回统一结构,用于前端统一处理 toast 与表单错误 -- 统一使用类型:[src/shared/types/action-state.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/types/action-state.ts) - -```ts -export type ActionState = { - success: boolean - message?: string - errors?: Record - data?: T -} -``` - -约束: - -- `errors` 必须对齐 `zod` 的 `error.flatten().fieldErrors` 结构 -- 禁止在各模块内重复定义自有的 ActionState 类型 - -### 8.4 Toast 触发时机(强制) - -- Client Component 在调用 Server Action 后: - - `success: true`:触发 `toast.success(message)`(或使用模块内约定的成功文案) - - `success: false`: - - 存在 `errors`:优先渲染表单字段错误;可选触发 `toast.error(message)` - - 不存在 `errors`:触发 `toast.error(message || "Action failed")` -- 对于路由级异常与边界错误,禁止用 toast 替代 `error.tsx` - -### 8.5 URL State(nuqs 优先) - -- 列表页筛选/分页/Tab/排序等“可分享状态”必须放 URL -- 使用 `nuqs` 做类型安全的 query state 管理 - -### 8.6 Data Access 权限边界(Security / IDOR 防护) - -- `data-access.ts` 不是纯 DTO 映射层,必须承担数据归属权校验 -- 允许两种合规方式(二选一,但模块内必须统一): - - **方式 A(强制传参)**:所有 data-access 函数显式接收 `actor`(userId/role)并在查询条件中约束归属(例如 teacherId) - - **方式 B(函数内获取)**:data-access 函数首行获取 session/user 并校验 role/归属,再执行查询 -- 禁止把权限校验放在 page.tsx 或 client 组件中作为唯一屏障 - ---- - -## 9. 数据完整性与 Seed 规则(禁止 Mock) - -项目默认不使用 Mock 数据。 - -当某功能缺失实际数据,开发者必须把数据补齐到数据库与种子数据中,而不是在前端临时模拟。 - -执行规范: - -- 若缺失的是“表结构/字段/关系”: - - 修改 `src/shared/db/schema.ts` 与 `src/shared/db/relations.ts`(按既有模式) - - 生成并提交 Drizzle migration(`drizzle/*.sql`) -- 若缺失的是“可演示的业务数据”: - - 更新 `scripts/seed.ts`,确保 `npm run db:seed` 可一键生成可用数据 -- 文档同步(必须): - - 在 [schema-changelog.md](file:///c:/Users/xiner/Desktop/CICD/docs/db/schema-changelog.md) 记录本次新增/变更的数据表、字段、索引与外键 - - 在对应模块的 `docs/design/00*_*.md` 中补充“新增了哪些数据/为什么需要/如何验证(db:seed + 页面路径)” - -### 9.1 Seed 分层(降低阻塞) - -- Seed 分为两类: - - **Baseline Seed**:全项目必备的最小集合(核心用户/角色/基础字典数据等),保证任何页面都不因“数据空”而无法进入流程 - - **Scenario Seed(按模块)**:面向具体模块的可演示数据包(例如:班级/题库/试卷/作业),用于复现与验证该模块交互 -- 任何模块新增数据依赖,必须以 “Scenario Seed” 的形式落到 `scripts/seed.ts`,而不是把数据要求隐含在前端逻辑里 - -### 9.2 Seed 可复现与数据锚点(保证跨模块联动) - -- Seed 必须可重复执行(idempotent),避免开发环境多次执行后产生脏数据与重复数据 -- 对跨模块联动依赖的关键实体,必须提供可稳定引用的数据锚点: - - 固定标识(如固定 email/slug/title 组合)或可预测 ID(按现有 seed 约定) - - 文档必须写明锚点是什么、依赖它的模块有哪些、如何验证 -- 禁止在 UI 里依赖“随机生成数据顺序”来定位实体(例如 “取第一条记录作为 demo 用户” 这类逻辑应退化为明确锚点) - -### 9.3 外部服务的例外(仅限 Adapter Mock) - -- 内部业务数据严格遵守“DB + Migration + Seed”,不允许 Mock -- 仅当对接外部不可控服务(支付/短信/第三方 AI 流式等)且无法用本地 seed 复现时: - - 允许在 `src/shared/lib/mock-adapters/*` 建立 mock 适配器 - - 必须先定义 Adapter 接口,再提供真实实现与 mock 实现(业务模块只能依赖接口,不可直接依赖某个具体实现) - - 该 mock 仅用于外部服务交互层,禁止承载内部业务数据 - ---- - -## 10. 表单规范(react-hook-form + zod) - -- 表单统一使用 `react-hook-form` + `@hookform/resolvers` + `zod` -- 错误提示放在输入框下方: - - 字号 `text-xs` - - 颜色 `text-destructive` -- 破坏性操作必须二次确认(`AlertDialog`) -- 提交中按钮禁用并展示 loading(可使用 `useFormStatus` 或本地 state) - ---- - -## 11. 质量门禁与评审清单(PR 必须过) - -### 11.1 本地必须通过 - -- `npm run lint` -- `npm run typecheck` -- `npm run build` - -### 11.2 代码评审清单(Reviewer 逐项检查) - -- 目录结构是否符合 Vertical Slice(路由层是否保持“薄”) -- 页面是否只做拼装(功能 UI 是否全部下沉到模块组件) -- Server/Client 边界是否最小化(是否把整页误标 client) -- 是否复用 `src/shared/components/ui/*`,是否重复实现基础交互 -- 是否使用语义化 token(颜色/圆角/间距),是否引入硬编码颜色与大量 arbitrary values -- Loading/Empty/Error 是否齐全(Skeleton/EmptyState/error.tsx) -- 列表页筛选是否 URL 化(nuqs),是否支持刷新/分享 -- 写操作是否通过 Server Action 且正确 `revalidatePath` -- 是否避免 Mock(数据是否通过迁移 + seed 补齐,且 docs/db 与模块文档已同步) -- 是否引入不必要的依赖与重型客户端逻辑 - -### 11.3 Commit 规范(Git History) - -- 推荐遵循 Conventional Commits: - - `feat:` 新功能 - - `fix:` 修复 bug - - `docs:` 文档更新 - - `refactor:` 重构(无功能变化) - - `chore:` 工程杂项 -- 约束: - - 单次提交必须聚焦一个意图,避免把大范围格式化与功能修改混在一起 - - 涉及 DB 迁移与 seed 变更时,commit message 必须包含模块/领域关键词,便于追溯 - ---- - -## 12. 文档同步规则(Docs Sync) - -以下情况必须同步更新文档(就近放在 `docs/design/*` 或 `docs/architecture/*`): - -- 新增“全局交互模式”(例如:新的工作台/拖拽范式/跨模块复用交互) -- 新增“全局组件”或改变基础 UI 行为(影响 `src/shared/components/ui/*`) -- 新增关键路由结构或权限/角色策略 - -### 12.1 业务组件可发现性(可选但推荐) - -- 对 `src/modules//components` 内的复杂业务组件(例如:试卷编辑器、排课表、工作台): - - 推荐在对应的 `docs/design/00*_*.md` 增加“用法示例 + 关键 props + 截图” - - 若团队资源允许,可引入 Storybook 作为可视化组件目录(不作为硬性门禁) - ---- - -## 13. Performance Essentials(必须遵守) - -- 图片: - - 强制使用 `next/image` 替代 ``(SVG 或已明确无需优化的极小图标除外) - - 头像等外部域名资源必须配置并明确缓存策略 -- 字体: - - 强制使用 `next/font` 管理字体加载 - - 禁止在 CSS 中 `@import` 外部字体 URL(避免 CLS 与阻塞渲染) -- 依赖: - - 禁止引入重型动画库作为默认方案;复杂动效需按需加载并解释收益 - - **图表**:标准图表库统一使用 `recharts`(通过 `src/shared/components/ui/chart.tsx` 封装),禁止引入其他图表库(如 Chart.js / Highcharts)。 - - 大体积 Client 组件必须拆分与动态加载,并通过 `Suspense` 提供 skeleton fallback - ---- - -## 14. 参考实现(从现有代码学习的路径) - -- 设计系统与 UI 组件清单:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md) -- Auth:路由层 RSC + 表单 client 拆分模式:[docs/design/001_auth_ui_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/001_auth_ui_implementation.md) -- 教师端班级模块:URL state + client 交互组件 + server actions 的组合:[docs/design/002_teacher_dashboard_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/002_teacher_dashboard_implementation.md) -- 教材工作台:RSC 拉初始数据 + client 工作台容器接管交互:[docs/design/003_textbooks_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/003_textbooks_module_implementation.md) -- 题库:nuqs 驱动筛选 + TanStack Table + CRUD actions:[docs/design/004_question_bank_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/004_question_bank_implementation.md) -- 考试组卷:拖拽编辑器(@dnd-kit)+ structure JSON 模型:[docs/design/005_exam_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/005_exam_module_implementation.md) -- 作业:冻结 exam → assignment 的域模型 + 学生作答/教师批改闭环:[docs/design/006_homework_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/006_homework_module_implementation.md) diff --git a/docs/db/seed-data.md b/docs/db/seed-data.md deleted file mode 100644 index 472fbd6..0000000 --- a/docs/db/seed-data.md +++ /dev/null @@ -1,81 +0,0 @@ -# Database Seeding Strategy - -**Status**: Implemented -**Script Location**: [`scripts/seed.ts`](../../scripts/seed.ts) -**Command**: `npm run db:seed` - ---- - -## 1. Overview -The seed script is designed to populate the database with a **representative set of data** that covers all core business scenarios. It serves two purposes: -1. **Development**: Provides a consistent baseline for developers. -2. **Validation**: Verifies complex schema relationships (e.g., recursive trees, JSON structures). - -## 2. Seed Data Topology - -### 2.1 Identity & Access Management (IAM) -We strictly follow the RBAC model defined in `docs/architecture/001_database_schema_design.md`. - -* **Roles**: - * `admin`: System Administrator. - * `teacher`: Academic Instructor. - * `student`: Learner. - * `grade_head`: Head of Grade Year (Demonstrates multi-role capability). -* **Users**: - * `admin@next-edu.com` (Role: Admin) - * `math@next-edu.com` (Role: Teacher + Grade Head) - * `alice@next-edu.com` (Role: Student) - -### 2.2 Knowledge Graph -Generates a hierarchical structure to test recursive queries (`parentId`). -* **Math** (Level 0) - * └── **Algebra** (Level 1) - * └── **Linear Equations** (Level 2) - -### 2.3 Question Bank -Includes rich content and nested structures. -1. **Simple Single Choice**: "What is 2 + 2?" -2. **Composite Question (Reading Comprehension)**: - * **Parent**: A reading passage. - * **Child 1**: Single Choice question about the passage. - * **Child 2**: Open-ended text question. - -### 2.4 Exams -Demonstrates the new **JSON Structure** field (`exams.structure`). -* **Title**: "Algebra Mid-Term 2025" -* **Structure**: - ```json - [ - { - "type": "group", - "title": "Part 1: Basics", - "children": [{ "type": "question", "questionId": "...", "score": 10 }] - }, - { - "type": "group", - "title": "Part 2: Reading", - "children": [{ "type": "question", "questionId": "...", "score": 20 }] - } - ] - ``` - -### 2.5 Classes / Enrollment / Schedule -Seeds the teacher class management domain. -* **Classes**: Creates at least one class owned by a teacher user. -* **Enrollments**: Links students to classes via `class_enrollments` (default status: `active`). -* **Schedule**: Populates `class_schedule` with weekday + start/end times for timetable validation. - -## 3. How to Run - -### Prerequisites -Ensure your `.env` file contains a valid `DATABASE_URL`. - -### Execution -Run the following command in the project root: - -```bash -npm run db:seed -``` - -### Reset Behavior -**WARNING**: The script currently performs a **TRUNCATE** on all core tables before seeding. This ensures a clean state but will **WIPE EXISTING DATA**. diff --git a/docs/design/001_auth_ui_implementation.md b/docs/design/001_auth_ui_implementation.md deleted file mode 100644 index c4a8e21..0000000 --- a/docs/design/001_auth_ui_implementation.md +++ /dev/null @@ -1,76 +0,0 @@ -# Auth UI Implementation Details - -**Date**: 2025-12-23 -**Author**: Senior Frontend Engineer -**Module**: Auth (`src/modules/auth`) - ---- - -## 1. 概述 (Overview) - -本文档记录了登录 (`/login`) 和注册 (`/register`) 页面的前端实现细节。遵循 Vertical Slice Architecture 和 Pixel-Perfect UI 规范。 - -## 2. 架构设计 (Architecture) - -### 2.1 目录结构 -所有认证相关的业务逻辑和组件均封装在 `src/modules/auth` 下,保持了高内聚。 - -``` -src/ -├── app/ -│ └── (auth)/ # 路由层 (Server Components) -│ ├── layout.tsx # 统一的 AuthLayout 容器 -│ ├── login/page.tsx -│ └── register/page.tsx -│ -├── modules/ -│ └── auth/ # 业务模块 -│ └── components/ # 模块私有组件 -│ ├── auth-layout.tsx # 左右分屏布局 -│ ├── login-form.tsx # 登录表单 (Client Component) -│ └── register-form.tsx # 注册表单 (Client Component) -``` - -### 2.2 渲染策略 -* **Server Components**: 页面入口 (`page.tsx`) 和布局 (`layout.tsx`) 默认为服务端组件,负责元数据 (`Metadata`) 和静态结构渲染。 -* **Client Components**: 表单组件 (`*-form.tsx`) 标记为 `'use client'`,处理交互逻辑(状态管理、表单提交、Loading 状态)。 - -## 3. UI/UX 细节 - -### 3.1 布局 (Layout) -采用 **Split Screen (分屏)** 设计: -* **左侧 (Desktop Only)**: - * 深色背景 (`bg-zinc-900`),强调品牌沉浸感。 - * 包含 Logo (`Next_Edu`) 和用户证言 (`Blockquote`)。 - * 使用 `hidden lg:flex` 实现响应式显隐。 -* **右侧**: - * 居中对齐的表单容器。 - * 移动端优先 (`w-full`),桌面端限制最大宽度 (`sm:w-[350px]`)。 - -### 3.2 交互 (Interactions) -* **Loading State**: 表单提交时按钮进入 `disabled` 状态并显示 `Loader2` 旋转动画。 -* **Micro-animations**: - * 按钮 Hover 效果。 - * 链接 Hover 下划线 (`hover:underline`). -* **Feedback**: 模拟了 3 秒的异步请求延迟,用于演示加载状态。 - -## 4. 错误处理 (Error Handling) - -### 4.1 模块级错误边界 -* **Scoped Error Boundary**: `src/app/(auth)/error.tsx` 仅处理 Auth 模块内的运行时错误。 - * 显示友好的 "Authentication Error" 提示。 - * 提供 "Try again" 按钮重置状态。 - -### 4.2 模块级 404 -* **Scoped Not Found**: `src/app/(auth)/not-found.tsx` 处理 Auth 模块内的无效路径。 - * 引导用户返回 `/login` 页面,防止用户迷失。 - -## 5. 组件复用 -* 使用了 `src/shared/components/ui` 中的标准 Shadcn 组件: - * `Button`, `Input`, `Label` (新增). -* 图标库统一使用 `lucide-react`. - -## 5. 后续计划 (Next Steps) -* [ ] 集成 `next-auth` (Auth.js) 进行实际的身份验证。 -* [ ] 添加 Zod Schema 进行前端表单验证。 -* [ ] 对接后端 API (`src/modules/auth/actions.ts`). diff --git a/docs/design/007_school_module_implementation.md b/docs/design/007_school_module_implementation.md deleted file mode 100644 index 440897e..0000000 --- a/docs/design/007_school_module_implementation.md +++ /dev/null @@ -1,164 +0,0 @@ -# 学校基础数据模块(School)实现文档与更新记录 - -**日期**: 2026-01-07 -**作者**: Frontend Team -**状态**: 已实现 - -## 1. 范围 - -本文档覆盖管理端「School」域的基础数据维护页面(Schools / Departments / Academic Year / Grades),并记录相关实现约束与关键更新,遵循 [003_frontend_engineering_standards.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/003_frontend_engineering_standards.md) 的工程规范(Vertical Slice、Server/Client 边界、质量门禁)。 - -## 2. 路由入口(Admin) - -School 域路由位于 `src/app/(dashboard)/admin/school/*`,均显式声明 `export const dynamic = "force-dynamic"` 以避免构建期预渲染触发数据库访问。 - -- `/admin/school`:入口重定向到 Classes(当前落点不在 `school` 模块内) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/page.tsx) -- `/admin/school/schools`:学校维护(增删改) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/schools/page.tsx) -- `/admin/school/departments`:部门维护(增删改) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/departments/page.tsx) -- `/admin/school/academic-year`:学年维护(增删改 + 设为当前学年) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/academic-year/page.tsx) -- `/admin/school/grades`:年级维护(增删改 + 指派年级组长/教研组长) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/page.tsx) -- `/admin/school/grades/insights`:年级维度作业统计(跨班级聚合) - 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx) - -## 3. 模块结构(Vertical Slice) - -School 模块位于 `src/modules/school`: - -``` -src/modules/school/ -├── components/ -│ ├── schools-view.tsx -│ ├── departments-view.tsx -│ ├── academic-year-view.tsx -│ └── grades-view.tsx -├── actions.ts -├── data-access.ts -├── schema.ts -└── types.ts -``` - -边界约束: -- `data-access.ts` 包含 `import "server-only"`,仅用于服务端查询与 DTO 组装。 -- `actions.ts` 包含 `"use server"`,写操作统一通过 Server Actions 并 `revalidatePath`。 -- `components/*` 为 Client 交互层(表单、Dialog、筛选、行级操作),调用 Server Actions 并用 `sonner` toast 反馈。 - -## 4. 数据访问(data-access.ts) - -实现:[data-access.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/data-access.ts) - -- `getSchools(): Promise` -- `getDepartments(): Promise` -- `getAcademicYears(): Promise` -- `getGrades(): Promise` - - join `schools` 获取 `school.name` - - 收集 `gradeHeadId/teachingHeadId` 并批量查询 `users` 以组装 `StaffOption` -- `getStaffOptions(): Promise` - - 角色过滤 `teacher/admin` - - 排序 `name/email`,用于 Select 列表可用性 -- `getGradesForStaff(staffId: string): Promise` - - 用于按负责人(年级组长/教研组长)反查关联年级 - -返回 DTO 类型定义位于:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/types.ts) - -## 5. 写操作(actions.ts) - -实现:[actions.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/actions.ts) - -通用约束: -- 输入校验:统一使用 [schema.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/schema.ts) 的 Zod schema -- 返回结构:统一使用 [ActionState](file:///c:/Users/xiner/Desktop/CICD/src/shared/types/action-state.ts) -- 刷新策略:对目标页面路径执行 `revalidatePath` - -Departments: -- `createDepartmentAction(formData)` -- `updateDepartmentAction(departmentId, formData)` -- `deleteDepartmentAction(departmentId)` - -Academic Year: -- `createAcademicYearAction(formData)` -- `updateAcademicYearAction(academicYearId, formData)` -- `deleteAcademicYearAction(academicYearId)` - - 当 `isActive=true` 时,通过事务把其它学年置为非激活,保证唯一激活学年 - -Schools: -- `createSchoolAction(formData)` -- `updateSchoolAction(schoolId, formData)` -- `deleteSchoolAction(schoolId)` - - 删除后会同时刷新 `/admin/school/schools` 与 `/admin/school/grades` - -Grades: -- `createGradeAction(formData)` -- `updateGradeAction(gradeId, formData)` -- `deleteGradeAction(gradeId)` - -## 6. UI 组件(components/*) - -Schools: -- 实现:[schools-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/schools-view.tsx) -- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog) - -Departments: -- 实现:[departments-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/departments-view.tsx) -- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog) - -Academic Year: -- 实现:[academic-year-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/academic-year-view.tsx) -- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog)+ 设为当前学年(isActive) - -Grades: -- 实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx) -- 交互:列表展示 + URL 驱动筛选(搜索/学校/负责人/排序)+ Dialog 表单(新增/编辑)+ 删除确认(AlertDialog) -- 负责人指派: - - 年级组长(gradeHeadId) - - 教研组长(teachingHeadId) - -## 7. 关键交互与规则(Grades) - -页面入口(RSC 组装)在服务端并发拉取三类数据: -- 年级列表:`getGrades()` -- 学校选项:`getSchools()` -- 负责人候选:`getStaffOptions()` - -实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/page.tsx) - -### 7.1 URL State(筛选/排序) - -Grades 列表页的筛选状态 URL 化(`nuqs`): -- `q`:关键字(匹配 grade/school) -- `school`:学校过滤(`all` 或具体 schoolId) -- `head`:负责人过滤(全部 / 两者缺失 / 缺年级组长 / 缺教研组长) -- `sort`:排序(默认/名称/更新时间等) - -实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx) - -### 7.2 表单校验 - -Grades 的新增/编辑表单在客户端做轻量校验: -- 必填:`schoolId`、`name` -- `order`:非负整数 -- 去重:同一学校下年级名称不允许重复(忽略大小写 + 规范化空格) - -说明: -- 服务端写入前仍会经过 `UpsertGradeSchema` 校验(schema.ts),避免仅依赖客户端校验。 - -### 7.3 负责人选择(Radix Select) - -Radix Select 约束:`SelectItem` 的 `value` 不能为 `""`(空字符串),否则会触发运行时错误。 - -当前实现策略: -- UI 中 “未设置” 选项使用占位值 `__none__` -- 在 `onValueChange` 中将 `__none__` 映射回 `""` 存入本地表单 state -- 提交时依旧传递空字符串,由 `UpsertGradeSchema` 将其归一为 `null` - -实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx) - -## 2. 更新记录(2026-01-07) - -- 修复 Add Grades 弹窗报错:将 4 处 `` 替换为占位值 `__none__`,并在 `onValueChange` 中映射回 `\"\"`,保持“可清空选择/显示 placeholder”的行为不变。 -- 修复新建年级按钮不可用:创建/编辑表单在状态变化时触发实时校验更新,避免校验状态滞后导致提交被禁用。 -- 质量门禁:本地通过 `npm run lint` 与 `npm run typecheck`。 diff --git a/docs/design/008_teacher_pages_implementation.md b/docs/design/008_teacher_pages_implementation.md index 0379cde..e3dac5b 100644 --- a/docs/design/008_teacher_pages_implementation.md +++ b/docs/design/008_teacher_pages_implementation.md @@ -242,6 +242,35 @@ - **目的**: 创建考试基础信息。 - **关键组件**: `ExamForm` +- **AI 生成执行逻辑**: + - **入口**: + - 选择 `Assembly Mode` 为 `AI Generation` + - 表单增加 `aiQuestionCount` 与 `aiPrompt`,提交时走 `createAiExamAction` + - **请求数据组装**: + - 复用基础字段:`title`、`subject`、`grade`、`difficulty`、`totalScore`、`durationMin`、`scheduledAt` + - 可选字段:`aiQuestionCount`、`aiPrompt` + - **服务端校验**: + - `AiExamCreateSchema` 校验基础字段与 AI 字段 + - 解析失败直接返回 `Invalid form data` + - **AI 调用**: + - 通过 `createAiChatCompletion` 发送系统提示与用户输入 + - 使用 `env.AI_MODEL`,默认 `gpt-4o-mini` + - 期望输出 JSON,仅包含 `sections` 或 `questions` + - **响应解析**: + - `extractJson` 支持从纯 JSON 或代码块中提取 + - `AiExamResponseSchema` 校验题型与字段结构 + - 无题目返回 `AI returned no questions` + - **题目裁剪与分值归一化**: + - 若设置题量,按顺序裁剪题目或分组 + - `normalizeScores` 按 `totalScore` 归一化各题分值 + - **题目落库**: + - 将 AI 题目转换为题库格式 `{ text, options }` + - 写入 `questions` 表,并记录 `authorId` + - **结构与关联写入**: + - 生成 `structure`(按分组或平铺题目) + - 写入 `exams` 表,同时写入 `exam_questions` + - **后续跳转**: + - 创建成功后跳转 `/teacher/exams/[id]/build` 继续编辑 ### 3.17 组卷 `/teacher/exams/[id]/build` 实现:[exams/[id]/build/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/exams/%5Bid%5D/build/page.tsx) diff --git a/docs/design/010_qa_test_plan_and_feedback.md b/docs/design/010_qa_test_plan_and_feedback.md new file mode 100644 index 0000000..b3c761e --- /dev/null +++ b/docs/design/010_qa_test_plan_and_feedback.md @@ -0,0 +1,154 @@ +# 项目全量测试方案与执行反馈 + +**日期**: 2026-03-18 +**角色**: 首席测试师 +**范围**: `src/app` 页面路由、`src/modules` 业务模块、`src/app/api` 接口路由、工程质量门禁 + +--- + +## 1. 测试目标 + +- 建立一份可复用的全量测试方案,覆盖核心业务与工程质量门禁。 +- 对当前代码库执行可落地的全量检查,输出可追溯反馈。 +- 明确已验证范围、未验证范围、风险与后续建议。 + +--- + +## 2. 测试对象与覆盖边界 + +### 2.1 业务域 + +- 认证与会话:登录/注册、会话注入、路由守卫 +- 教师端:工作台、班级、作业、考试、题库、教材 +- 学生端:仪表盘、课程、作业、教材、课表 +- 管理端:学校、年级、班级、部门、学年、洞察 +- 家长端:仪表盘 +- 设置与个人资料 + +### 2.2 技术域 + +- 页面路由编译与渲染入口(`src/app/**/page.tsx`) +- API 路由编译与入口(`src/app/api/**/route.ts`) +- 业务模块(`src/modules/**`) +- 类型系统与静态检查(TypeScript + ESLint) +- 生产构建可通过性(Next.js build) + +### 2.3 本次可执行边界说明 + +- 已执行:静态与构建级全量验证(lint、typecheck、build)。 +- 已执行:页面路由与 API 路由清单级覆盖核对(基于代码库现状)。 +- 未执行:依赖真实数据库与外部 AI 服务的端到端交互验证(当前环境未提供专用测试库与固定测试账号)。 + +--- + +## 3. 测试策略 + +### 3.1 质量门禁(必须通过) + +1. ESLint 静态规范检查 +2. TypeScript 类型检查 +3. Next.js 生产构建 + +### 3.2 全量覆盖策略(按层) + +1. 路由层:统计并核对全部页面与 API 路由入口文件是否可参与编译 +2. 业务层:通过构建期依赖解析与类型系统覆盖模块间调用链 +3. 集成层:以 `next build` 验证服务端组件、路由结构、导入关系、打包一致性 +4. 回归层:输出缺口与风险,给出后续补测清单 + +### 3.3 通过标准 + +- `npm run lint` 退出码为 0 +- `npm run typecheck` 退出码为 0 +- `npm run build` 退出码为 0 +- 无新增阻断级缺陷(Blocker/Critical) + +--- + +## 4. 测试用例总表(主干) + +| 用例ID | 级别 | 用例名称 | 执行方式 | 通过标准 | +|---|---|---|---|---| +| QA-GATE-001 | P0 | 代码规范检查 | `npm run lint` | 命令成功退出 | +| QA-GATE-002 | P0 | 类型系统检查 | `npm run typecheck` | 命令成功退出 | +| QA-GATE-003 | P0 | 生产构建检查 | `npm run build` | 构建成功 | +| QA-ROUTE-001 | P0 | 页面路由入口覆盖 | 路由文件清单核对 | 页面入口均存在 | +| QA-API-001 | P0 | API 路由入口覆盖 | 路由文件清单核对 | API 入口均存在 | + +--- + +## 5. 路由覆盖清单(本次统计) + +### 5.1 页面路由入口 + +- 共统计 `46` 个页面入口文件(`src/app/**/page.tsx`)。 +- 覆盖角色:认证、教师、学生、管理端、家长、通用页面。 + +### 5.2 API 路由入口 + +- 共统计 `4` 个 API 入口文件(`src/app/api/**/route.ts`)。 +- 覆盖接口:`auth`、`ai/chat`、`onboarding/status`、`onboarding/complete`。 + +--- + +## 6. 执行记录与反馈 + +### 6.1 执行环境 + +- 操作系统:Windows +- 项目目录:`E:\Desktop\CICD` +- 包管理器:npm + +### 6.2 结果总览 + +| 检查项 | 命令 | 结果 | 备注 | +|---|---|---|---| +| 代码规范 | `npm run lint` | 通过 | 退出码 0 | +| 类型检查 | `npm run typecheck` | 通过 | 退出码 0 | +| 生产构建 | `npm run build` | 通过 | 退出码 0,构建期间出现 baseline-browser-mapping 数据过期提示 | +| 接口集成测试 | `npm run test:integration` | 通过 | 3 个测试文件,10 条用例通过 | +| E2E 冒烟测试 | `npm run test:e2e:smoke` | 通过 | 本地优先使用系统 Chrome,2 条用例通过 | +| E2E 全路由回归 | `npm run test:e2e:full-routes` | 通过 | 38 条用例通过,覆盖静态页面路由守卫与可达性 | + +### 6.3 缺陷与风险 + +- 阻断级缺陷:0 +- 严重缺陷:0 +- 主要风险:跨角色真实业务数据写入链路仍需测试库与固定账号支撑,当前已完成路由级全覆盖回归。 +- 次要风险:构建日志出现 `baseline-browser-mapping` 数据过期提示,建议在依赖维护窗口升级该依赖数据包。 + +### 6.4 结论 + +- 当前代码库在静态检查、类型系统与生产构建三道门禁均通过,可进入下一阶段联调或发布候选流程。 +- 本次已完成企业级测试基线的第一阶段落地:集成测试框架、E2E 框架、CI 质量门禁已接入。 +- 本次已完成“代码级全量可构建性 + API 集成验证”并通过,未发现新增回归错误。 +- 若要达到“发布级全链路验收”,仍需补充测试库与固定账号下的完整业务 E2E 场景。 + +--- + +## 7. 后续补测计划 + +- 增加端到端测试(Playwright)覆盖关键教师流:创建考试 → 派发作业 → 提交批改。 +- 增加 API 集成测试(鉴权、参数校验、错误码)。 +- 增加数据层测试(测试库 + 回滚策略)验证查询过滤与 RBAC 边界。 + +--- + +## 8. 企业级实现落地清单(本次新增) + +- 测试基础设施: + - 新增 Vitest 配置:`vitest.config.ts` + - 新增 Playwright 配置:`playwright.config.ts` + - 新增集成测试初始化:`tests/setup/integration.setup.ts` +- 集成测试用例: + - `tests/integration/api-ai-chat.route.test.ts` + - `tests/integration/api-onboarding-status.route.test.ts` + - `tests/integration/api-onboarding-complete.route.test.ts` +- E2E 冒烟用例: + - `tests/e2e/smoke-auth.spec.ts` +- E2E 全路由回归用例: + - `tests/e2e/full-route-regression.spec.ts` +- 工程脚本: + - `package.json` 新增 `test`、`test:ci`、`test:integration`、`test:e2e`、`test:e2e:smoke`、`test:e2e:full-routes` +- CI 门禁: + - `.gitea/workflows/ci.yml` 新增 Playwright Chromium 安装、集成测试、E2E 全量回归测试步骤 diff --git a/docs/design/design_system.md b/docs/design/design_system.md deleted file mode 100644 index b802c85..0000000 --- a/docs/design/design_system.md +++ /dev/null @@ -1,261 +0,0 @@ -# Next_Edu Design System Specs - -**Version**: 1.4.0 (Updated) -**Status**: ACTIVE -**Role**: Chief Creative Director -**Philosophy**: "Data as Art" - Clean, Minimalist, Information-Dense. - ---- - -## 1. 核心理念 (Core Philosophy) - -Next_Edu 旨在对抗教育系统常见的信息过载。我们的设计风格深受 **International Typographic Style (国际主义设计风格)** 影响。 - -* **Precision (精准)**: 每一个像素的留白都有其目的。 -* **Clarity (清晰)**: 通过排版和对比度区分层级,而非装饰性的色块。 -* **Efficiency (效率)**: 专为高密度数据操作优化,减少视觉噪音。 - ---- - -## 2. 视觉基础 (Visual Foundation) - -### 2.1 色彩系统 (Color System) - -我们放弃 Hex 直选,全面采用 **HSL 语义化变量** 以支持完美的多主题适配。 - -#### **Base (基调)** -* **Neutral**: Zinc (锌色). 冷峻、纯净,无偏色。 -* **Brand**: Deep Indigo (深靛蓝). - * `Primary`: 专业、权威,避免幼稚的高饱和蓝。 - * 语义: `hsl(var(--primary))` - -#### **Functional (功能色)** -| 语义 | 色系 | 用途 | -| :--- | :--- | :--- | -| **Destructive** | Red | 删除、危险操作、系统错误 | -| **Warning** | Amber | 需注意的状态、非阻断性警告 | -| **Success** | Emerald | 操作成功、状态正常 | -| **Info** | Blue | 一般性提示、帮助信息 | - -#### **Surface Hierarchy (层级)** -1. **Background**: 应用底层背景。 -2. **Card**: 内容承载容器,轻微提升层级。 -3. **Popover**: 悬浮层、下拉菜单,最高层级。 -4. **Muted**: 用于次级信息或禁用状态背景。 - -### 2.2 排版 (Typography) - -* **Font Family**: `Geist Sans` > `Inter` > `System UI`. - * *Requirement*: 必须开启 `tabular-nums` (等宽数字) 特性,确保表格数据对齐。 -* **Scale**: - * **Base Body**: 14px (0.875rem) - 提升信息密度。 - * **H1 (Page Title)**: 24px, Tracking -0.02em, Weight 600. - * **H2 (Section Title)**: 20px, Tracking -0.01em, Weight 600. - * **H3 (Card Title)**: 16px, Weight 600. - * **Tiny/Caption**: 12px, Text-Muted-Foreground. - -### 2.3 质感 (Look & Feel) - -* **Borders**: `1px solid var(--border)`. 界面骨架,以此分割区域而非背景色块。 -* **Radius**: - * `sm` (4px): Badges, Checkboxes. - * `md` (8px): **Default**. Buttons, Inputs, Cards. - * `lg` (12px): Modals, Dialogs. -* **Shadows**: - * Default: None (Flat). - * Hover: `shadow-sm` (仅用于可交互元素). - * Dropdown/Popover: `shadow-md`. - * *Ban*: 禁止使用大面积弥散阴影。 - ---- - -## 3. 核心布局 (App Shell) - -### 3.1 架构 (Architecture) -我们采用了 `SidebarProvider` + `AppSidebar` 的组合模式,确保了布局的灵活性和移动端的完美适配。 - -* **Provider**: `SidebarProvider` (src/modules/layout/components/sidebar-provider.tsx) - * 管理侧边栏状态 (`expanded`, `isMobile`). - * 负责在移动端渲染 Sheet (Drawer)。 - * 负责在桌面端渲染 Sticky Sidebar。 - * **Key Prop**: `sidebar` (显式传递侧边栏组件)。 - -### 3.2 布局结构 -``` -+-------------------------------------------------------+ -| Sidebar | Header (Sticky) | -| |-------------------------------------------| -| (Collap- | Main Content | -| sible) | | -| | +-------------------------------------+ | -| | | Card | | -| | | +---------------------------------+ | | -| | | | Data Table | | | -| | | +---------------------------------+ | | -| | +-------------------------------------+ | -| | | -+-------------------------------------------------------+ -``` - -### 3.3 详细规范 - -#### **Sidebar (侧边栏)** -* **Width**: Expanded `260px` | Collapsed `64px` | Mobile `Sheet (Drawer)`. -* **Behavior**: - * Desktop: 固定左侧,支持折叠。 - * Mobile: 默认隐藏,点击汉堡菜单从左侧滑出。 -* **Navigation Item**: - * Height: `36px` (Compact). - * State: - * `Inactive`: `text-muted-foreground hover:text-foreground`. - * `Active`: `bg-sidebar-accent text-sidebar-accent-foreground font-medium`. - -#### **Header (顶栏)** -* **Height**: `64px` (h-16). -* **Layout**: `flex items-center justify-between px-6 border-b`. -* **Components**: - 1. **Breadcrumb**: 动态路径导航 (Dynamic Breadcrumb). - * **Implementation**: 基于 `usePathname()` 自动解析路由段。 - * **Mapping**: 通过 `NAV_CONFIG` 或 `BREADCRUMB_MAP` 映射路径到友好标题 (e.g., `/teacher/textbooks` -> "Textbooks"). - * **Filtering**: 自动过滤根角色路径 (e.g., `/teacher`) 以保持简洁。 - 2. **Global Search**: `Cmd+K` 触发,居中或靠右。 - 3. **User Nav**: 头像 + 下拉菜单。 - -#### **Main Content (内容区)** -* **Padding**: `p-6` (Desktop) / `p-4` (Mobile). -* **Max Width**: `max-w-[1600px]` (默认) 或 `w-full` (针对超宽报表)。 - ---- - -## 4. 导航与角色系统 (Navigation & Roles) - -Next_Edu 支持多角色(Multi-Tenant / Role-Based)导航系统。 - -### 4.1 配置文件 -导航结构已从 UI 组件中解耦,统一配置在: -`src/modules/layout/config/navigation.ts` - -### 4.2 支持的角色 (Roles) -系统内置支持以下角色,每个角色拥有独立的侧边栏菜单结构: -* **Admin**: 系统管理员,拥有所有管理权限 (School Management, User Management, Finance)。 -* **Teacher**: 教师,关注班级管理 (My Classes)、成绩录入 (Gradebook) 和日程。 -* **Student**: 学生,关注课程学习 (My Learning) 和作业提交。 -* **Parent**: 家长,关注子女动态和学费缴纳。 - -### 4.3 开发与调试 -* **View As (Dev Mode)**: 在开发环境下,侧边栏顶部提供 "View As" 下拉菜单,允许开发者实时切换角色视角,预览不同角色的导航结构。 -* **Implementation**: `AppSidebar` 组件通过读取 `NAV_CONFIG[currentRole]` 动态渲染菜单项。 - ---- - -## 5. 错误处理与边界情况 (Error Handling & Boundaries) - -系统必须优雅地处理错误和边缘情况,避免白屏或无反馈。 - -### 5.1 全局错误边界 (Global Error Boundary) -* **Scope**: 捕获渲染期间的未处理异常。 -* **UI**: 显示友好的错误页面(非技术堆栈信息),提供 "Try Again" 按钮重置状态。 -* **Implementation**: 使用 React `ErrorBoundary` 或 Next.js `error.tsx`。 - -### 5.2 404 Not Found -* **Design**: 必须保留 App Shell (Sidebar + Header),仅在 Main Content 区域显示 404 提示。 -* **Content**: "Page not found" 文案 + 返回 Dashboard 的主操作按钮。 - -### 5.3 空状态 (Empty States) -当列表或表格无数据时,**严禁**只显示空白。 -* **Component**: `EmptyState`。 -* **Composition**: - 1. **Icon**: 线性风格图标 (muted foreground). - 2. **Title**: 简短说明 (e.g., "No students found"). - 3. **Description**: 解释原因或下一步操作 (e.g., "Add a student to get started"). - 4. **Action**: (可选) "Create New" 按钮。 - -### 5.4 加载状态 (Loading States) -* **Initial Load**: 使用 `Skeleton` 骨架屏,模拟内容布局,避免 CLS (Content Layout Shift)。禁止使用全屏 Spinner。 -* **Action Loading**: 按钮点击后进入 `disabled` + `spinner` 状态。 -* **Table Loading**: 表格内容区域显示 3-5 行 Skeleton Rows。 - -### 5.5 表单验证 (Form Validation) -* **Style**: 错误信息显示在输入框下方,字号 `text-xs`,颜色 `text-destructive`。 -* **Input**: 边框变红 (`border-destructive`)。 - ---- - -## 6. 职责边界与协作 (Responsibility Boundaries) - -**[IMPORTANT] 严禁越界修改 (Strict No-Modification Policy)** - -为了维护大型项目的可维护性,UI 工程师和开发人员必须遵守以下边界规则: - -### 6.1 模块化原则 (Modularity) -* **Scope**: 开发者仅应对分配给自己的模块负责。例如,负责 "Dashboard" 的开发者**不应**修改 "Sidebar" 或 "Auth" 模块的代码。 -* **Dependencies**: 如果你的模块依赖于其他模块的变更,**必须**先与该模块的负责人沟通,或在 PR 中明确标注。 - -### 6.2 共享组件 (Shared Components) -* **Immutable Core**: `src/shared/components/ui` 下的基础组件(如 Button, Card)视为**核心库**。 -* **Extension**: 如果基础组件不能满足需求,优先考虑组合(Composition)或创建新的业务组件,而不是修改核心组件的源码。 -* **Modification Request**: 只有在发现严重 Bug 或需要全局样式调整时,才允许修改核心组件,且必须经过 Design Lead 审批。 - -### 6.3 样式一致性 (Consistency) -* **Global CSS**: `globals.css` 定义了系统的物理法则。严禁在局部组件中随意覆盖全局 CSS 变量。 -* **Tailwind Config**: 禁止随意在组件中添加任意值(Arbitrary Values, e.g., `w-[123px]`),必须使用 Design Token。 - ---- - -## 7. 组件设计规范 (Component Specs) - -### 7.1 Card (卡片) -卡片是信息组织的基本单元。 -* **Class**: `bg-card text-card-foreground border rounded-lg shadow-none`. -* **Header**: `p-6 pb-2`. Title (`font-semibold leading-none tracking-tight`). -* **Content**: `p-6 pt-0`. - -### 7.2 Data Table (数据表格) -教务系统的核心组件。 -* **Density**: - * `Default`: Row Height `48px` (h-12). - * `Compact`: Row Height `36px` (h-9). -* **Header**: `bg-muted/50 text-muted-foreground text-xs uppercase font-medium`. -* **Stripes**: 默认关闭。仅在列数 > 8 时开启 `even:bg-muted/50`。 -* **Actions**: 行操作按钮应默认隐形 (`opacity-0`),Hover 时显示 (`group-hover:opacity-100`),减少视觉干扰。 - -### 7.3 Feedback (反馈与通知) -* **Toast**: 使用 `Sonner` 组件。 - * 位置: 默认右下角 (Bottom Right). - * 样式: 极简黑白风格 (跟随主题),支持撤销操作。 - * 调用: `toast("Event has been created", { description: "Sunday, December 03, 2023 at 9:00 AM" })`. -* **Skeleton**: 加载状态必须使用 Skeleton 骨架屏,禁止使用全屏 Spinner。 -* **Badge**: 状态指示器。 - * `default`: 主要状态 (Primary). - * `secondary`: 次要状态 (Neutral). - * `destructive`: 错误/警告状态 (Error). - * `outline`: 描边风格 (Subtle). - ---- - -## 8. 开发指南 (Developer Guide) - -### 8.1 CSS Variables -所有颜色和圆角均通过 CSS 变量控制,定义在 `globals.css` 中。禁止在代码中 Hardcode 颜色值 (如 `#FFFFFF`, `rgb(0,0,0)` )。 - -### 8.2 Tailwind Utility 优先 -优先使用 Tailwind Utility Classes。 -* ✅ `text-sm text-muted-foreground` -* ❌ `.custom-text-class { font-size: 14px; color: #666; }` - -### 8.3 Dark Mode -设计系统原生支持深色模式。只要正确使用语义化颜色变量(如 `bg-background`, `text-foreground`),Dark Mode 将自动完美适配,无需额外编写 `dark:` 修饰符(除非为了特殊调整)。 - -### 8.4 组件库引用 -所有 UI 组件位于 `src/shared/components/ui`。 -* `Button`: 基础按钮 -* `Input`: 输入框 -* `Select`: 下拉选择器 (New) -* `Sheet`: 侧边栏/抽屉 -* `Sonner`: Toast 通知 -* `Badge`: 徽章/标签 -* `Skeleton`: 加载占位符 -* `DropdownMenu`: 下拉菜单 -* `Avatar`: 头像 -* `Label`: 表单标签 -* `EmptyState`: 空状态占位 (New) diff --git a/docs/product_requirements.md b/docs/product_requirements.md deleted file mode 100644 index 063c6f0..0000000 --- a/docs/product_requirements.md +++ /dev/null @@ -1,180 +0,0 @@ -# Next_Edu 产品需求文档 (PRD) - K12 智慧教学管理系统 - -**版本**: 2.0.0 (K12 Enterprise Edition) -**状态**: 规划中 -**最后更新**: 2025-12-22 -**作者**: Senior EdTech Product Manager -**适用范围**: 全校级教学管理 (教-考-练-评) - ---- - -## 1. 角色与权限矩阵 (Complex Role Matrix) - -本系统采用基于 RBAC (Role-Based Access Control) 的多维权限设计,并结合 **行级安全 (Row-Level Security, RLS)** 策略,确保数据隔离与行政管理的精确匹配。 - -### 1.1 角色定义与核心职责 - -| 角色 | 核心职责 | 权限特征 (Scope) | -| :--- | :--- | :--- | -| **系统管理员 (Admin)** | 基础数据维护、账号管理、学期设置 | 全局系统配置,不可触碰教学业务数据内容(隐私保护)。 | -| **校长 (Principal)** | 全校教学概况监控、宏观统计报表 | **全校可见**。查看所有年级、学科的统计数据(平均分、作业完成率),无修改具体的题目/作业权限。 | -| **年级主任 (Grade Head)** | 本年级行政管理、班级均衡度分析 | **年级可见**。管理本年级所有行政班级;查看本年级跨学科对比;无权干涉其他年级。 | -| **教研组长 (Subject Head)** | 学科资源建设、命题质量把控 | **学科可见**。管理本学科公共题库、教案模板;查看全校该学科教学质量;无权查看其他学科详情。 | -| **班主任 (Class Teacher)** | 班级学生管理、家校通知、综合评价 | **行政班可见**。查看本班所有学生的跨学科成绩、考勤;发布班级公告。 | -| **任课老师 (Teacher)** | 备课、出卷、批改、个别辅导 | **教学班可见**。仅能操作自己所教班级的该学科作业/考试;私有题库管理。 | -| **学生 (Student)** | 完成作业、参加考试、查看错题本 | **个人可见**。仅能访问分配给自己的任务;查看个人成长档案。 | - -### 1.2 关键权限辨析:年级主任 vs 教研组长 - -* **维度差异**: - * **年级主任 (横向管理)**: 关注的是 **"人" (People & Administration)**。例如:高一(3)班的整体纪律如何?高一年级整体是否在期中考试中达标?他们需要跨学科的数据视图(如:某学生是否偏科)。 - * **教研组长 (纵向管理)**: 关注的是 **"内容" (Content & Pedagogy)**。例如:英语科目的“阅读理解”题型得分率全校是否偏低?公共题库的题目质量如何?他们需要跨年级但单学科的深度视图。 - -* **数据可见性 (RLS 策略)**: - * `GradeHead_View`: `WHERE class.grade_id = :current_user_grade_id` - * `SubjectHead_View`: `WHERE course.subject_id = :current_user_subject_id` (可能跨所有年级) - ---- - -## 2. 核心功能模块深度拆解 - -### 2.1 智能题库中心 (Smart Question Bank) - -这是系统的核心资产库,必须支持高复杂度的题目结构。 - -* **多层嵌套题目结构 (Nested Questions)**: - * **场景**: 英语完形填空、语文现代文阅读、理综大题。 - * **逻辑**: 引入 **"题干 (Stem)"** 与 **"子题 (Sub-question)"** 的概念。 - * **父题 (Parent)**: 承载公共题干(如一篇 500 字的文章、一张物理实验图表)。通常不直接设分,或者设总分。 - * **子题 (Child)**: 依附于父题,是具体的答题点(选择、填空、简答)。每个子题有独立的分值、答案和解析。 - * **交互**: 组卷时,拖动“父题”,所有“子题”必须作为一个原子整体跟随移动,不可拆分。 - -* **知识点图谱 (Knowledge Graph)**: - * **结构**: 树状结构 (Tree)。例如:`数学 -> 代数 -> 函数 -> 二次函数 -> 二次函数的图像`。 - * **关联**: - * **多对多 (Many-to-Many)**: 一道题可能考察多个知识点(综合题)。 - * **权重**: (可选高级需求) 标记主要考点与次要考点。 - -### 2.2 课本与大纲映射 (Textbook & Curriculum) - -* **课本数字化**: - * 系统预置主流教材版本 (如人教版、北师大版)。 - * **核心映射**: `Textbook Chapter` (课本章节) <--> `Knowledge Point` (知识点)。 - * **价值**: 老师备课时,只需选择“必修一 第一章”,系统自动推荐关联的“集合”相关题目,无需手动去海量题库搜索。 - -### 2.3 试卷/作业组装引擎 (Assembly Engine) - -* **智能筛选**: 支持交集筛选(同时包含“力学”和“三角函数”的题目)。 -* **AB 卷生成**: 针对防作弊场景,支持题目乱序或选项乱序(Shuffle)。 -* **作业分层**: 支持“必做题”与“选做题”设置,满足分层教学需求。 - -### 2.4 消息通知中心 (Notification System) - -分级分策略的消息分发: - -* **强提醒 (High Priority)**: 系统公告、考试开始提醒。通过站内信 + 弹窗 + (集成)短信/微信模板消息。 -* **业务流 (Medium Priority)**: 作业发布、成绩出炉。站内红点 + 列表推送。 -* **弱提醒 (Low Priority)**: 错题本更新、周报生成。仅在进入相关模块时提示。 - ---- - -## 3. 数据实体关系推演 (Data Entity Relationships) - -基于 MySQL 关系型数据库的设计方案。 - -### 3.1 核心实体模型 (ER Draft) - -1. **SysUser**: `id`, `username`, `role`, `school_id` -2. **TeacherProfile**: `user_id`, `is_grade_head`, `is_subject_head` -3. **Class**: `id`, `grade_level` (e.g., 10), `class_name` (e.g., "3班"), `homeroom_teacher_id` -4. **Subject**: `id`, `name` (e.g., "Math") -5. **Course**: `id`, `class_id`, `subject_id`, `teacher_id` (核心教学关系表: 谁教哪个班的哪门课) - -### 3.2 题库与知识点设计 (关键难点) - -#### Table: `knowledge_points` (知识点树) -* `id`: UUID -* `subject_id`: FK -* `name`: VARCHAR -* `parent_id`: UUID (Self-reference, Root is NULL) -* `level`: INT (1, 2, 3...) -* `code`: VARCHAR (e.g., "M-ALG-01-02" 用于快速检索) - -#### Table: `questions` (支持嵌套) -* `id`: UUID -* `content`: TEXT (HTML/Markdown, store images as URLs) -* `type`: ENUM ('SINGLE', 'MULTI', 'FILL', 'ESSAY', 'COMPOSITE') -* `parent_id`: UUID (Self-reference, **核心设计**) - * If `NULL`: 这是一道独立题目 OR 复合题的大题干。 - * If `NOT NULL`: 这是一个子题目,属于 `parent_id` 对应的题干。 -* `difficulty`: INT (1-5) -* `answer`: TEXT (JSON structure for structured answers) -* `analysis`: TEXT (解析) -* `created_by`: FK (Teacher) -* `scope`: ENUM ('PUBLIC', 'PRIVATE') - -#### Table: `question_knowledge` (题目-知识点关联) -* `question_id`: FK -* `knowledge_point_id`: FK -* **Primary Key**: (`question_id`, `knowledge_point_id`) - -### 3.3 课本映射设计 - -#### Table: `textbooks` -* `id`: UUID -* `name`: VARCHAR -* `grade_level`: INT -* `subject_id`: FK - -#### Table: `textbook_chapters` -* `id`: UUID -* `textbook_id`: FK -* `name`: VARCHAR -* `parent_id`: UUID (Sections within Chapters) -* `content`: TEXT (Rich text content of the chapter/section) -- [ADDED for Content Viewing] -* `order`: INT - -#### Table: `chapter_knowledge_mapping` -* `chapter_id`: FK -* `knowledge_point_id`: FK -* *解释*: 这张表是连接“教学进度”与“底层知识”的桥梁。 - ---- - -## 4. 关键业务流程 (User Flows) - -### 4.1 智能组卷与发布流程 (Exam Creation Flow) - -这是一个高频且复杂的路径,需要极高的流畅度。 - -1. **启动组卷**: - * 老师进入 [教学工作台] -> 点击 [新建试卷/作业]。 - * 输入基本信息(名称、考试时长、总分)。 - -2. **设定范围 (锚定课本)**: - * 老师选择教材版本:`人教版高中数学必修一`。 - * 选择章节:勾选 `第一章 集合` 和 `第二章 函数概念`。 - * *系统动作*: 后台查询 `chapter_knowledge_mapping`,提取出这几章对应的所有 `knowledge_points`。 - -3. **筛选题目**: - * 系统展示题目列表,默认过滤条件为上述提取的知识点。 - * 老师增加筛选:`难度: 中等`, `题型: 选择题`。 - * **处理嵌套题**: 如果筛选结果包含一个“完形填空”的子题,系统在 UI 上必须**强制展示**其对应的父题干,并提示老师“需整体添加”。 - -4. **加入试题篮 (Cart)**: - * 老师点击“+”号。 - * 试题篮动态更新:`当前题目数: 15, 预计总分: 85`。 - -5. **试卷精修 (Refine)**: - * 进入“试卷预览”模式。 - * 调整题目顺序 (Drag & drop)。 - * 修改某道题的分值(覆盖默认分值)。 - -6. **发布设置**: - * 选择发布对象:`高一(3)班`, `高一(5)班` (基于 `Course` 表权限)。 - * 设置时间:`开始时间`, `截止时间`。 - * 发布模式:`在线作答` 或 `线下答题卡` (若线下,系统生成 PDF 和答题卡样式)。 - -7. **完成**: - * 学生端收到 `Notifications` 推送。 - * `Exams` 表生成记录,`ExamAllocations` 表为每个班级/学生生成状态记录。 diff --git a/docs/scripts/baseline-migrations.js b/docs/scripts/baseline-migrations.js deleted file mode 100644 index 76e01f0..0000000 --- a/docs/scripts/baseline-migrations.js +++ /dev/null @@ -1,90 +0,0 @@ -require("dotenv/config"); - -const fs = require("node:fs"); -const crypto = require("node:crypto"); -const path = require("node:path"); -const mysql = require("mysql2/promise"); - -const JOURNAL = { - "0000_aberrant_cobalt_man": 1766460456274, - "0001_flawless_texas_twister": 1767004087964, - "0002_equal_wolfpack": 1767145757594, -}; - -function sha256Hex(input) { - return crypto.createHash("sha256").update(input).digest("hex"); -} - -async function main() { - const url = process.env.DATABASE_URL; - if (!url) { - throw new Error("DATABASE_URL is not set"); - } - - const conn = await mysql.createConnection(url); - - await conn.query( - "CREATE TABLE IF NOT EXISTS `__drizzle_migrations` (id serial primary key, hash text not null, created_at bigint)" - ); - - const [existing] = await conn.query( - "SELECT id, hash, created_at FROM `__drizzle_migrations` ORDER BY created_at DESC LIMIT 1" - ); - if (Array.isArray(existing) && existing.length > 0) { - console.log("✅ __drizzle_migrations already has entries. Skip baselining."); - await conn.end(); - return; - } - - const [[accountsRow]] = await conn.query( - "SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='accounts'" - ); - const accountsExists = Number(accountsRow?.cnt ?? 0) > 0; - if (!accountsExists) { - console.log("ℹ️ No existing tables detected (accounts missing). Skip baselining."); - await conn.end(); - return; - } - - const [[structureRow]] = await conn.query( - "SELECT COUNT(*) AS cnt FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='exams' AND column_name='structure'" - ); - const examsStructureExists = Number(structureRow?.cnt ?? 0) > 0; - - const [[homeworkRow]] = await conn.query( - "SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='homework_assignments'" - ); - const homeworkExists = Number(homeworkRow?.cnt ?? 0) > 0; - - const baselineTags = []; - baselineTags.push("0000_aberrant_cobalt_man"); - if (examsStructureExists) baselineTags.push("0001_flawless_texas_twister"); - if (homeworkExists) baselineTags.push("0002_equal_wolfpack"); - - const drizzleDir = path.resolve(__dirname, "..", "..", "drizzle"); - for (const tag of baselineTags) { - const sqlPath = path.join(drizzleDir, `${tag}.sql`); - if (!fs.existsSync(sqlPath)) { - throw new Error(`Missing migration file: ${sqlPath}`); - } - const sqlText = fs.readFileSync(sqlPath).toString(); - const hash = sha256Hex(sqlText); - const createdAt = JOURNAL[tag]; - if (typeof createdAt !== "number") { - throw new Error(`Missing journal timestamp for: ${tag}`); - } - await conn.query( - "INSERT INTO `__drizzle_migrations` (`hash`, `created_at`) VALUES (?, ?)", - [hash, createdAt] - ); - } - - console.log(`✅ Baselined __drizzle_migrations: ${baselineTags.join(", ")}`); - await conn.end(); -} - -main().catch((err) => { - console.error("❌ Baseline failed:", err); - process.exit(1); -}); - diff --git a/docs/scripts/reset-db.ts b/docs/scripts/reset-db.ts deleted file mode 100644 index 19067eb..0000000 --- a/docs/scripts/reset-db.ts +++ /dev/null @@ -1,45 +0,0 @@ -import "dotenv/config" -import { db } from "@/shared/db" -import { sql } from "drizzle-orm" - -async function reset() { - console.log("🔥 Resetting database...") - - // Disable foreign key checks - await db.execute(sql`SET FOREIGN_KEY_CHECKS = 0;`) - - // Get all table names - const tables = await db.execute(sql` - SELECT table_name - FROM information_schema.tables - WHERE table_schema = DATABASE(); - `) - - // Drop each table - const rows = (tables as unknown as [unknown])[0] - if (!Array.isArray(rows)) return - - for (const row of rows) { - const record = row as Record - const tableName = - typeof record.TABLE_NAME === "string" - ? record.TABLE_NAME - : typeof record.table_name === "string" - ? record.table_name - : null - if (!tableName) continue - console.log(`Dropping table: ${tableName}`) - await db.execute(sql.raw(`DROP TABLE IF EXISTS \`${tableName}\`;`)) - } - - // Re-enable foreign key checks - await db.execute(sql`SET FOREIGN_KEY_CHECKS = 1;`) - - console.log("✅ Database reset complete.") - process.exit(0) -} - -reset().catch((err) => { - console.error("❌ Reset failed:", err) - process.exit(1) -}) diff --git a/docs/work_log.md b/docs/work_log.md index eb983b9..352278a 100644 --- a/docs/work_log.md +++ b/docs/work_log.md @@ -1,5 +1,59 @@ # Work Log +## 2026-03-19 + +### 1. 作业与权限测试覆盖补齐(第二阶段) +- 新增角色路由与代理守卫集成测试: + - `tests/integration/dashboard-routing.test.ts` + - `tests/integration/proxy-guard.test.ts` +- 扩展 onboarding 完成接口集成测试,覆盖班级-学科映射与教师分配逻辑: + - `tests/integration/api-onboarding-complete.route.test.ts` +- 新增认证业务流 E2E(注册 -> 登录 -> 受保护区域): + - `tests/e2e/auth-business-flow.spec.ts` +- 新增并补齐作业流程集成测试,覆盖创建、开始作答、提交、批改、保存答案: + - `tests/integration/homework-create-assignment.test.ts` + - `tests/integration/homework-actions.test.ts` +- `saveHomeworkAnswerAction` 增加关键分支用例: + - started 状态首次保存答案(insert) + - started 状态更新已有答案(update) + +### 2. 验证 +- `npm run test:integration`:通过(7 文件,38 用例) +- `npm run lint`:通过 +- `npm run typecheck`:通过 +- `npm run test:e2e`:通过(40 通过,1 跳过) +- 语言诊断:无错误 + +## 2026-03-18 + +### 1. 企业级测试体系落地(第一阶段) +- 新增集成测试与 E2E 基础设施: + - `vitest.config.ts` + - `playwright.config.ts` + - `tests/setup/integration.setup.ts` +- 新增接口集成测试: + - `tests/integration/api-ai-chat.route.test.ts` + - `tests/integration/api-onboarding-status.route.test.ts` + - `tests/integration/api-onboarding-complete.route.test.ts` +- 新增认证冒烟 E2E: + - `tests/e2e/smoke-auth.spec.ts` +- 新增全路由回归 E2E: + - `tests/e2e/full-route-regression.spec.ts` +- 新增工程脚本: + - `test`、`test:ci`、`test:integration`、`test:e2e`、`test:e2e:smoke`、`test:e2e:full-routes` +- 更新 CI 质量门禁: + - 增加 Playwright Chromium 安装 + - 增加集成测试执行 + - 增加 E2E 全量回归测试执行 + +### 2. 验证 +- `npm run lint`:通过 +- `npm run typecheck`:通过 +- `npm run test:integration`:通过(3 文件,10 用例) +- `npm run build`:通过 +- `npm run test:e2e:smoke`:通过(本地使用系统 Chrome 通道,2 用例通过) +- `npm run test:e2e:full-routes`:通过(38 用例通过) + ## 2026-03-03 ### 1. 教师加入班级学科分配逻辑修复 diff --git a/drizzle.config.ts b/drizzle.config.ts index 948ee0b..f1637c9 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,3 +1,4 @@ +import "dotenv/config" import { defineConfig } from "drizzle-kit"; export default defineConfig({ diff --git a/drizzle/0000_aberrant_cobalt_man.sql b/drizzle/0000_aberrant_cobalt_man.sql index 2eb7b8a..578838d 100644 --- a/drizzle/0000_aberrant_cobalt_man.sql +++ b/drizzle/0000_aberrant_cobalt_man.sql @@ -1,183 +1 @@ -CREATE TABLE `accounts` ( - `userId` varchar(128) NOT NULL, - `type` varchar(255) NOT NULL, - `provider` varchar(255) NOT NULL, - `providerAccountId` varchar(255) NOT NULL, - `refresh_token` text, - `access_token` text, - `expires_at` int, - `token_type` varchar(255), - `scope` varchar(255), - `id_token` text, - `session_state` varchar(255), - CONSTRAINT `accounts_provider_providerAccountId_pk` PRIMARY KEY(`provider`,`providerAccountId`) -); ---> statement-breakpoint -CREATE TABLE `chapters` ( - `id` varchar(128) NOT NULL, - `textbook_id` varchar(128) NOT NULL, - `title` varchar(255) NOT NULL, - `order` int DEFAULT 0, - `parent_id` varchar(128), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `chapters_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `exam_questions` ( - `exam_id` varchar(128) NOT NULL, - `question_id` varchar(128) NOT NULL, - `score` int DEFAULT 0, - `order` int DEFAULT 0, - CONSTRAINT `exam_questions_exam_id_question_id_pk` PRIMARY KEY(`exam_id`,`question_id`) -); ---> statement-breakpoint -CREATE TABLE `exam_submissions` ( - `id` varchar(128) NOT NULL, - `exam_id` varchar(128) NOT NULL, - `student_id` varchar(128) NOT NULL, - `score` int, - `status` varchar(50) DEFAULT 'started', - `submitted_at` timestamp, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `exam_submissions_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `exams` ( - `id` varchar(128) NOT NULL, - `title` varchar(255) NOT NULL, - `description` text, - `creator_id` varchar(128) NOT NULL, - `start_time` timestamp, - `end_time` timestamp, - `status` varchar(50) DEFAULT 'draft', - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `exams_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `knowledge_points` ( - `id` varchar(128) NOT NULL, - `name` varchar(255) NOT NULL, - `description` text, - `parent_id` varchar(128), - `level` int DEFAULT 0, - `order` int DEFAULT 0, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `knowledge_points_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `questions` ( - `id` varchar(128) NOT NULL, - `content` json NOT NULL, - `type` enum('single_choice','multiple_choice','text','judgment','composite') NOT NULL, - `difficulty` int DEFAULT 1, - `parent_id` varchar(128), - `author_id` varchar(128) NOT NULL, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `questions_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `questions_to_knowledge_points` ( - `question_id` varchar(128) NOT NULL, - `knowledge_point_id` varchar(128) NOT NULL, - CONSTRAINT `questions_to_knowledge_points_question_id_knowledge_point_id_pk` PRIMARY KEY(`question_id`,`knowledge_point_id`) -); ---> statement-breakpoint -CREATE TABLE `roles` ( - `id` varchar(128) NOT NULL, - `name` varchar(50) NOT NULL, - `description` varchar(255), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `roles_id` PRIMARY KEY(`id`), - CONSTRAINT `roles_name_unique` UNIQUE(`name`) -); ---> statement-breakpoint -CREATE TABLE `sessions` ( - `sessionToken` varchar(255) NOT NULL, - `userId` varchar(128) NOT NULL, - `expires` timestamp NOT NULL, - CONSTRAINT `sessions_sessionToken` PRIMARY KEY(`sessionToken`) -); ---> statement-breakpoint -CREATE TABLE `submission_answers` ( - `id` varchar(128) NOT NULL, - `submission_id` varchar(128) NOT NULL, - `question_id` varchar(128) NOT NULL, - `answer_content` json, - `score` int, - `feedback` text, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `submission_answers_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `textbooks` ( - `id` varchar(128) NOT NULL, - `title` varchar(255) NOT NULL, - `subject` varchar(100) NOT NULL, - `grade` varchar(50), - `publisher` varchar(100), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `textbooks_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `users` ( - `id` varchar(128) NOT NULL, - `name` varchar(255), - `email` varchar(255) NOT NULL, - `emailVerified` timestamp, - `image` varchar(255), - `role` varchar(50) DEFAULT 'student', - `password` varchar(255), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `users_id` PRIMARY KEY(`id`), - CONSTRAINT `users_email_unique` UNIQUE(`email`) -); ---> statement-breakpoint -CREATE TABLE `users_to_roles` ( - `user_id` varchar(128) NOT NULL, - `role_id` varchar(128) NOT NULL, - CONSTRAINT `users_to_roles_user_id_role_id_pk` PRIMARY KEY(`user_id`,`role_id`) -); ---> statement-breakpoint -CREATE TABLE `verificationTokens` ( - `identifier` varchar(255) NOT NULL, - `token` varchar(255) NOT NULL, - `expires` timestamp NOT NULL, - CONSTRAINT `verificationTokens_identifier_token_pk` PRIMARY KEY(`identifier`,`token`) -); ---> statement-breakpoint -ALTER TABLE `accounts` ADD CONSTRAINT `accounts_userId_users_id_fk` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `chapters` ADD CONSTRAINT `chapters_textbook_id_textbooks_id_fk` FOREIGN KEY (`textbook_id`) REFERENCES `textbooks`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exam_questions` ADD CONSTRAINT `exam_questions_exam_id_exams_id_fk` FOREIGN KEY (`exam_id`) REFERENCES `exams`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exam_questions` ADD CONSTRAINT `exam_questions_question_id_questions_id_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exam_submissions` ADD CONSTRAINT `exam_submissions_exam_id_exams_id_fk` FOREIGN KEY (`exam_id`) REFERENCES `exams`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exam_submissions` ADD CONSTRAINT `exam_submissions_student_id_users_id_fk` FOREIGN KEY (`student_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exams` ADD CONSTRAINT `exams_creator_id_users_id_fk` FOREIGN KEY (`creator_id`) REFERENCES `users`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `questions` ADD CONSTRAINT `questions_author_id_users_id_fk` FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `questions_to_knowledge_points` ADD CONSTRAINT `questions_to_knowledge_points_question_id_questions_id_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `questions_to_knowledge_points` ADD CONSTRAINT `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk` FOREIGN KEY (`knowledge_point_id`) REFERENCES `knowledge_points`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `sessions` ADD CONSTRAINT `sessions_userId_users_id_fk` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `submission_answers` ADD CONSTRAINT `submission_answers_submission_id_exam_submissions_id_fk` FOREIGN KEY (`submission_id`) REFERENCES `exam_submissions`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `submission_answers` ADD CONSTRAINT `submission_answers_question_id_questions_id_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `users_to_roles` ADD CONSTRAINT `users_to_roles_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `users_to_roles` ADD CONSTRAINT `users_to_roles_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `account_userId_idx` ON `accounts` (`userId`);--> statement-breakpoint -CREATE INDEX `textbook_idx` ON `chapters` (`textbook_id`);--> statement-breakpoint -CREATE INDEX `parent_id_idx` ON `chapters` (`parent_id`);--> statement-breakpoint -CREATE INDEX `exam_student_idx` ON `exam_submissions` (`exam_id`,`student_id`);--> statement-breakpoint -CREATE INDEX `parent_id_idx` ON `knowledge_points` (`parent_id`);--> statement-breakpoint -CREATE INDEX `parent_id_idx` ON `questions` (`parent_id`);--> statement-breakpoint -CREATE INDEX `author_id_idx` ON `questions` (`author_id`);--> statement-breakpoint -CREATE INDEX `kp_idx` ON `questions_to_knowledge_points` (`knowledge_point_id`);--> statement-breakpoint -CREATE INDEX `session_userId_idx` ON `sessions` (`userId`);--> statement-breakpoint -CREATE INDEX `submission_idx` ON `submission_answers` (`submission_id`);--> statement-breakpoint -CREATE INDEX `email_idx` ON `users` (`email`);--> statement-breakpoint -CREATE INDEX `user_id_idx` ON `users_to_roles` (`user_id`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0001_flawless_texas_twister.sql b/drizzle/0001_flawless_texas_twister.sql index 530ef40..578838d 100644 --- a/drizzle/0001_flawless_texas_twister.sql +++ b/drizzle/0001_flawless_texas_twister.sql @@ -1 +1 @@ -ALTER TABLE `exams` ADD `structure` json; +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0002_equal_wolfpack.sql b/drizzle/0002_equal_wolfpack.sql index e9a8017..578838d 100644 --- a/drizzle/0002_equal_wolfpack.sql +++ b/drizzle/0002_equal_wolfpack.sql @@ -1,274 +1 @@ -CREATE TABLE IF NOT EXISTS `homework_answers` ( - `id` varchar(128) NOT NULL, - `submission_id` varchar(128) NOT NULL, - `question_id` varchar(128) NOT NULL, - `answer_content` json, - `score` int, - `feedback` text, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `homework_answers_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `homework_assignment_questions` ( - `assignment_id` varchar(128) NOT NULL, - `question_id` varchar(128) NOT NULL, - `score` int DEFAULT 0, - `order` int DEFAULT 0, - CONSTRAINT `homework_assignment_questions_assignment_id_question_id_pk` PRIMARY KEY(`assignment_id`,`question_id`) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `homework_assignment_targets` ( - `assignment_id` varchar(128) NOT NULL, - `student_id` varchar(128) NOT NULL, - `created_at` timestamp NOT NULL DEFAULT (now()), - CONSTRAINT `homework_assignment_targets_assignment_id_student_id_pk` PRIMARY KEY(`assignment_id`,`student_id`) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `homework_assignments` ( - `id` varchar(128) NOT NULL, - `source_exam_id` varchar(128) NOT NULL, - `title` varchar(255) NOT NULL, - `description` text, - `structure` json, - `status` varchar(50) DEFAULT 'draft', - `creator_id` varchar(128) NOT NULL, - `available_at` timestamp, - `due_at` timestamp, - `allow_late` boolean NOT NULL DEFAULT false, - `late_due_at` timestamp, - `max_attempts` int NOT NULL DEFAULT 1, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `homework_assignments_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `homework_submissions` ( - `id` varchar(128) NOT NULL, - `assignment_id` varchar(128) NOT NULL, - `student_id` varchar(128) NOT NULL, - `attempt_no` int NOT NULL DEFAULT 1, - `score` int, - `status` varchar(50) DEFAULT 'started', - `started_at` timestamp NOT NULL DEFAULT (now()), - `submitted_at` timestamp, - `is_late` boolean NOT NULL DEFAULT false, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `homework_submissions_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -SET @__qkp_drop_qid := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.referential_constraints - WHERE constraint_schema = DATABASE() - AND constraint_name = 'questions_to_knowledge_points_question_id_questions_id_fk' - ), - 'ALTER TABLE `questions_to_knowledge_points` DROP FOREIGN KEY `questions_to_knowledge_points_question_id_questions_id_fk`;', - 'SELECT 1;' - ) -);--> statement-breakpoint -PREPARE __stmt FROM @__qkp_drop_qid;--> statement-breakpoint -EXECUTE __stmt;--> statement-breakpoint -DEALLOCATE PREPARE __stmt;--> statement-breakpoint -SET @__qkp_drop_kpid := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.referential_constraints - WHERE constraint_schema = DATABASE() - AND constraint_name = 'questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk' - ), - 'ALTER TABLE `questions_to_knowledge_points` DROP FOREIGN KEY `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk`;', - 'SELECT 1;' - ) -);--> statement-breakpoint -PREPARE __stmt2 FROM @__qkp_drop_kpid;--> statement-breakpoint -EXECUTE __stmt2;--> statement-breakpoint -DEALLOCATE PREPARE __stmt2;--> statement-breakpoint -ALTER TABLE `homework_answers` ADD CONSTRAINT `hw_ans_sub_fk` FOREIGN KEY (`submission_id`) REFERENCES `homework_submissions`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_answers` ADD CONSTRAINT `hw_ans_q_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignment_questions` ADD CONSTRAINT `hw_aq_a_fk` FOREIGN KEY (`assignment_id`) REFERENCES `homework_assignments`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignment_questions` ADD CONSTRAINT `hw_aq_q_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignment_targets` ADD CONSTRAINT `hw_at_a_fk` FOREIGN KEY (`assignment_id`) REFERENCES `homework_assignments`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignment_targets` ADD CONSTRAINT `hw_at_s_fk` FOREIGN KEY (`student_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignments` ADD CONSTRAINT `hw_asg_exam_fk` FOREIGN KEY (`source_exam_id`) REFERENCES `exams`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_assignments` ADD CONSTRAINT `hw_asg_creator_fk` FOREIGN KEY (`creator_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_submissions` ADD CONSTRAINT `hw_sub_a_fk` FOREIGN KEY (`assignment_id`) REFERENCES `homework_assignments`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `homework_submissions` ADD CONSTRAINT `hw_sub_student_fk` FOREIGN KEY (`student_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -SET @__idx_hw_answer_submission := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_answers' - AND index_name = 'hw_answer_submission_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_answer_submission_idx` ON `homework_answers` (`submission_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt3 FROM @__idx_hw_answer_submission;--> statement-breakpoint -EXECUTE __stmt3;--> statement-breakpoint -DEALLOCATE PREPARE __stmt3;--> statement-breakpoint -SET @__idx_hw_answer_submission_question := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_answers' - AND index_name = 'hw_answer_submission_question_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_answer_submission_question_idx` ON `homework_answers` (`submission_id`,`question_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt4 FROM @__idx_hw_answer_submission_question;--> statement-breakpoint -EXECUTE __stmt4;--> statement-breakpoint -DEALLOCATE PREPARE __stmt4;--> statement-breakpoint -SET @__idx_hw_assignment_questions_assignment := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignment_questions' - AND index_name = 'hw_assignment_questions_assignment_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_questions_assignment_idx` ON `homework_assignment_questions` (`assignment_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt5 FROM @__idx_hw_assignment_questions_assignment;--> statement-breakpoint -EXECUTE __stmt5;--> statement-breakpoint -DEALLOCATE PREPARE __stmt5;--> statement-breakpoint -SET @__idx_hw_assignment_targets_assignment := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignment_targets' - AND index_name = 'hw_assignment_targets_assignment_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_targets_assignment_idx` ON `homework_assignment_targets` (`assignment_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt6 FROM @__idx_hw_assignment_targets_assignment;--> statement-breakpoint -EXECUTE __stmt6;--> statement-breakpoint -DEALLOCATE PREPARE __stmt6;--> statement-breakpoint -SET @__idx_hw_assignment_targets_student := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignment_targets' - AND index_name = 'hw_assignment_targets_student_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_targets_student_idx` ON `homework_assignment_targets` (`student_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt7 FROM @__idx_hw_assignment_targets_student;--> statement-breakpoint -EXECUTE __stmt7;--> statement-breakpoint -DEALLOCATE PREPARE __stmt7;--> statement-breakpoint -SET @__idx_hw_assignment_creator := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignments' - AND index_name = 'hw_assignment_creator_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_creator_idx` ON `homework_assignments` (`creator_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt8 FROM @__idx_hw_assignment_creator;--> statement-breakpoint -EXECUTE __stmt8;--> statement-breakpoint -DEALLOCATE PREPARE __stmt8;--> statement-breakpoint -SET @__idx_hw_assignment_source_exam := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignments' - AND index_name = 'hw_assignment_source_exam_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_source_exam_idx` ON `homework_assignments` (`source_exam_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt9 FROM @__idx_hw_assignment_source_exam;--> statement-breakpoint -EXECUTE __stmt9;--> statement-breakpoint -DEALLOCATE PREPARE __stmt9;--> statement-breakpoint -SET @__idx_hw_assignment_status := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_assignments' - AND index_name = 'hw_assignment_status_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_status_idx` ON `homework_assignments` (`status`);' - ) -);--> statement-breakpoint -PREPARE __stmt10 FROM @__idx_hw_assignment_status;--> statement-breakpoint -EXECUTE __stmt10;--> statement-breakpoint -DEALLOCATE PREPARE __stmt10;--> statement-breakpoint -SET @__idx_hw_assignment_student := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'homework_submissions' - AND index_name = 'hw_assignment_student_idx' - ), - 'SELECT 1;', - 'CREATE INDEX `hw_assignment_student_idx` ON `homework_submissions` (`assignment_id`,`student_id`);' - ) -);--> statement-breakpoint -PREPARE __stmt11 FROM @__idx_hw_assignment_student;--> statement-breakpoint -EXECUTE __stmt11;--> statement-breakpoint -DEALLOCATE PREPARE __stmt11;--> statement-breakpoint -SET @__qkp_add_qid := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.referential_constraints - WHERE constraint_schema = DATABASE() - AND constraint_name = 'q_kp_qid_fk' - ), - 'SELECT 1;', - 'ALTER TABLE `questions_to_knowledge_points` ADD CONSTRAINT `q_kp_qid_fk` FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE cascade ON UPDATE no action;' - ) -);--> statement-breakpoint -PREPARE __stmt12 FROM @__qkp_add_qid;--> statement-breakpoint -EXECUTE __stmt12;--> statement-breakpoint -DEALLOCATE PREPARE __stmt12;--> statement-breakpoint -SET @__qkp_add_kpid := ( - SELECT IF( - EXISTS( - SELECT 1 - FROM information_schema.referential_constraints - WHERE constraint_schema = DATABASE() - AND constraint_name = 'q_kp_kpid_fk' - ), - 'SELECT 1;', - 'ALTER TABLE `questions_to_knowledge_points` ADD CONSTRAINT `q_kp_kpid_fk` FOREIGN KEY (`knowledge_point_id`) REFERENCES `knowledge_points`(`id`) ON DELETE cascade ON UPDATE no action;' - ) -);--> statement-breakpoint -PREPARE __stmt13 FROM @__qkp_add_kpid;--> statement-breakpoint -EXECUTE __stmt13;--> statement-breakpoint -DEALLOCATE PREPARE __stmt13; +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0003_petite_newton_destine.sql b/drizzle/0003_petite_newton_destine.sql index 43646f0..578838d 100644 --- a/drizzle/0003_petite_newton_destine.sql +++ b/drizzle/0003_petite_newton_destine.sql @@ -1,43 +1 @@ -CREATE TABLE `class_enrollments` ( - `class_id` varchar(128) NOT NULL, - `student_id` varchar(128) NOT NULL, - `class_enrollment_status` enum('active','inactive') NOT NULL DEFAULT 'active', - `created_at` timestamp NOT NULL DEFAULT (now()), - CONSTRAINT `class_enrollments_class_id_student_id_pk` PRIMARY KEY(`class_id`,`student_id`) -); ---> statement-breakpoint -CREATE TABLE `class_schedule` ( - `id` varchar(128) NOT NULL, - `class_id` varchar(128) NOT NULL, - `weekday` int NOT NULL, - `start_time` varchar(5) NOT NULL, - `end_time` varchar(5) NOT NULL, - `course` varchar(255) NOT NULL, - `location` varchar(100), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `class_schedule_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `classes` ( - `id` varchar(128) NOT NULL, - `name` varchar(255) NOT NULL, - `grade` varchar(50) NOT NULL, - `homeroom` varchar(50), - `room` varchar(50), - `teacher_id` varchar(128) NOT NULL, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `classes_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -ALTER TABLE `class_enrollments` ADD CONSTRAINT `ce_c_fk` FOREIGN KEY (`class_id`) REFERENCES `classes`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `class_enrollments` ADD CONSTRAINT `ce_s_fk` FOREIGN KEY (`student_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `class_schedule` ADD CONSTRAINT `cs_c_fk` FOREIGN KEY (`class_id`) REFERENCES `classes`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `classes` ADD CONSTRAINT `classes_teacher_id_users_id_fk` FOREIGN KEY (`teacher_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `class_enrollments_class_idx` ON `class_enrollments` (`class_id`);--> statement-breakpoint -CREATE INDEX `class_enrollments_student_idx` ON `class_enrollments` (`student_id`);--> statement-breakpoint -CREATE INDEX `class_schedule_class_idx` ON `class_schedule` (`class_id`);--> statement-breakpoint -CREATE INDEX `class_schedule_class_day_idx` ON `class_schedule` (`class_id`,`weekday`);--> statement-breakpoint -CREATE INDEX `classes_teacher_idx` ON `classes` (`teacher_id`);--> statement-breakpoint -CREATE INDEX `classes_grade_idx` ON `classes` (`grade`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0004_add_chapter_content_and_kp_chapter.sql b/drizzle/0004_add_chapter_content_and_kp_chapter.sql index d9b23e0..578838d 100644 --- a/drizzle/0004_add_chapter_content_and_kp_chapter.sql +++ b/drizzle/0004_add_chapter_content_and_kp_chapter.sql @@ -1,3 +1 @@ -ALTER TABLE `chapters` ADD `content` text;--> statement-breakpoint -ALTER TABLE `knowledge_points` ADD `chapter_id` varchar(128);--> statement-breakpoint -CREATE INDEX `kp_chapter_id_idx` ON `knowledge_points` (`chapter_id`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0005_add_class_school_subject_teachers.sql b/drizzle/0005_add_class_school_subject_teachers.sql index 746a9ca..578838d 100644 --- a/drizzle/0005_add_class_school_subject_teachers.sql +++ b/drizzle/0005_add_class_school_subject_teachers.sql @@ -1,52 +1 @@ -CREATE TABLE `academic_years` ( - `id` varchar(128) NOT NULL, - `name` varchar(100) NOT NULL, - `start_date` timestamp NOT NULL, - `end_date` timestamp NOT NULL, - `is_active` boolean NOT NULL DEFAULT false, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `academic_years_id` PRIMARY KEY(`id`), - CONSTRAINT `academic_years_name_unique` UNIQUE(`name`) -); ---> statement-breakpoint -CREATE TABLE `class_subject_teachers` ( - `class_id` varchar(128) NOT NULL, - `subject` enum('语文','数学','英语','美术','体育','科学','社会','音乐') NOT NULL, - `teacher_id` varchar(128), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `class_subject_teachers_class_id_subject_pk` PRIMARY KEY(`class_id`,`subject`) -); ---> statement-breakpoint -CREATE TABLE `classrooms` ( - `id` varchar(128) NOT NULL, - `name` varchar(255) NOT NULL, - `building` varchar(100), - `floor` int, - `capacity` int, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `classrooms_id` PRIMARY KEY(`id`), - CONSTRAINT `classrooms_name_unique` UNIQUE(`name`) -); ---> statement-breakpoint -CREATE TABLE `departments` ( - `id` varchar(128) NOT NULL, - `name` varchar(255) NOT NULL, - `description` text, - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `departments_id` PRIMARY KEY(`id`), - CONSTRAINT `departments_name_unique` UNIQUE(`name`) -); ---> statement-breakpoint -ALTER TABLE `classes` ADD `school_name` varchar(255);--> statement-breakpoint -ALTER TABLE `class_subject_teachers` ADD CONSTRAINT `class_subject_teachers_teacher_id_users_id_fk` FOREIGN KEY (`teacher_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `class_subject_teachers` ADD CONSTRAINT `cst_c_fk` FOREIGN KEY (`class_id`) REFERENCES `classes`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `academic_years_name_idx` ON `academic_years` (`name`);--> statement-breakpoint -CREATE INDEX `academic_years_active_idx` ON `academic_years` (`is_active`);--> statement-breakpoint -CREATE INDEX `class_subject_teachers_class_idx` ON `class_subject_teachers` (`class_id`);--> statement-breakpoint -CREATE INDEX `class_subject_teachers_teacher_idx` ON `class_subject_teachers` (`teacher_id`);--> statement-breakpoint -CREATE INDEX `classrooms_name_idx` ON `classrooms` (`name`);--> statement-breakpoint -CREATE INDEX `departments_name_idx` ON `departments` (`name`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0006_faithful_king_bedlam.sql b/drizzle/0006_faithful_king_bedlam.sql index 691861e..578838d 100644 --- a/drizzle/0006_faithful_king_bedlam.sql +++ b/drizzle/0006_faithful_king_bedlam.sql @@ -1,38 +1 @@ -CREATE TABLE `grades` ( - `id` varchar(128) NOT NULL, - `school_id` varchar(128) NOT NULL, - `name` varchar(100) NOT NULL, - `order` int NOT NULL DEFAULT 0, - `grade_head_id` varchar(128), - `teaching_head_id` varchar(128), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `grades_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -CREATE TABLE `schools` ( - `id` varchar(128) NOT NULL, - `name` varchar(255) NOT NULL, - `code` varchar(50), - `created_at` timestamp NOT NULL DEFAULT (now()), - `updated_at` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT `schools_id` PRIMARY KEY(`id`), - CONSTRAINT `schools_name_unique` UNIQUE(`name`), - CONSTRAINT `schools_code_unique` UNIQUE(`code`) -); ---> statement-breakpoint -ALTER TABLE `classes` ADD `school_id` varchar(128);--> statement-breakpoint -ALTER TABLE `classes` ADD `grade_id` varchar(128);--> statement-breakpoint -ALTER TABLE `grades` ADD CONSTRAINT `g_s_fk` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `grades` ADD CONSTRAINT `g_gh_fk` FOREIGN KEY (`grade_head_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `grades` ADD CONSTRAINT `g_th_fk` FOREIGN KEY (`teaching_head_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `grades_school_idx` ON `grades` (`school_id`);--> statement-breakpoint -CREATE INDEX `grades_school_name_uniq` ON `grades` (`school_id`,`name`);--> statement-breakpoint -CREATE INDEX `grades_grade_head_idx` ON `grades` (`grade_head_id`);--> statement-breakpoint -CREATE INDEX `grades_teaching_head_idx` ON `grades` (`teaching_head_id`);--> statement-breakpoint -CREATE INDEX `schools_name_idx` ON `schools` (`name`);--> statement-breakpoint -CREATE INDEX `schools_code_idx` ON `schools` (`code`);--> statement-breakpoint -ALTER TABLE `classes` ADD CONSTRAINT `c_s_fk` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `classes` ADD CONSTRAINT `c_g_fk` FOREIGN KEY (`grade_id`) REFERENCES `grades`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `classes_school_idx` ON `classes` (`school_id`);--> statement-breakpoint -CREATE INDEX `classes_grade_id_idx` ON `classes` (`grade_id`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0007_talented_bromley.sql b/drizzle/0007_talented_bromley.sql index e6d0af1..578838d 100644 --- a/drizzle/0007_talented_bromley.sql +++ b/drizzle/0007_talented_bromley.sql @@ -1,6 +1 @@ -ALTER TABLE `exams` ADD `subject_id` varchar(128);--> statement-breakpoint -ALTER TABLE `exams` ADD `grade_id` varchar(128);--> statement-breakpoint -ALTER TABLE `exams` ADD CONSTRAINT `exams_subject_id_subjects_id_fk` FOREIGN KEY (`subject_id`) REFERENCES `subjects`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE `exams` ADD CONSTRAINT `exams_grade_id_grades_id_fk` FOREIGN KEY (`grade_id`) REFERENCES `grades`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX `exams_subject_idx` ON `exams` (`subject_id`);--> statement-breakpoint -CREATE INDEX `exams_grade_idx` ON `exams` (`grade_id`); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0008_thin_madrox.sql b/drizzle/0008_thin_madrox.sql index 9519df1..578838d 100644 --- a/drizzle/0008_thin_madrox.sql +++ b/drizzle/0008_thin_madrox.sql @@ -1 +1 @@ -ALTER TABLE `knowledge_points` ADD `anchor_text` varchar(255); \ No newline at end of file +SELECT 1;--> statement-breakpoint diff --git a/drizzle/0011_ai_providers.sql b/drizzle/0011_ai_providers.sql new file mode 100644 index 0000000..e889c2d --- /dev/null +++ b/drizzle/0011_ai_providers.sql @@ -0,0 +1,46 @@ +SET @has_ai_providers := ( + SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'ai_providers' +);--> statement-breakpoint +SET @sql := IF(@has_ai_providers = 0, ' + CREATE TABLE `ai_providers` ( + `id` varchar(128) NOT NULL, + `provider` enum(''zhipu'',''openai'',''gemini'',''custom'') NOT NULL, + `base_url` varchar(512), + `model` varchar(128) NOT NULL, + `api_key_encrypted` text NOT NULL, + `api_key_last4` varchar(4), + `is_default` boolean NOT NULL DEFAULT false, + `created_by` varchar(128), + `updated_by` varchar(128), + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) + ); +', 'SELECT 1');--> statement-breakpoint +PREPARE stmt FROM @sql;--> statement-breakpoint +EXECUTE stmt;--> statement-breakpoint +DEALLOCATE PREPARE stmt;--> statement-breakpoint + +SET @has_ai_provider_idx := ( + SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'ai_providers' + AND INDEX_NAME = 'ai_provider_idx' +);--> statement-breakpoint +SET @sql := IF(@has_ai_provider_idx = 0, 'CREATE INDEX `ai_provider_idx` ON `ai_providers` (`provider`);', 'SELECT 1');--> statement-breakpoint +PREPARE stmt FROM @sql;--> statement-breakpoint +EXECUTE stmt;--> statement-breakpoint +DEALLOCATE PREPARE stmt;--> statement-breakpoint + +SET @has_ai_provider_default_idx := ( + SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'ai_providers' + AND INDEX_NAME = 'ai_provider_default_idx' +);--> statement-breakpoint +SET @sql := IF(@has_ai_provider_default_idx = 0, 'CREATE INDEX `ai_provider_default_idx` ON `ai_providers` (`is_default`);', 'SELECT 1');--> statement-breakpoint +PREPARE stmt FROM @sql;--> statement-breakpoint +EXECUTE stmt;--> statement-breakpoint +DEALLOCATE PREPARE stmt;--> statement-breakpoint diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index 7df477c..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,1286 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "43c3b7a0-a45f-4305-aa2c-c548dd09afcf", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_to_knowledge_points_question_id_questions_id_fk": { - "name": "questions_to_knowledge_points_question_id_questions_id_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk": { - "name": "questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 50aa8ab..0000000 --- a/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,1293 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "c1afed29-ad52-484d-a6a1-272b6dec6a24", - "prevId": "43c3b7a0-a45f-4305-aa2c-c548dd09afcf", - "tables": { - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_to_knowledge_points_question_id_questions_id_fk": { - "name": "questions_to_knowledge_points_question_id_questions_id_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk": { - "name": "questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json deleted file mode 100644 index d5869f3..0000000 --- a/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "e6118000-4093-4c16-a01c-e33a2a5f0875", - "prevId": "c1afed29-ad52-484d-a6a1-272b6dec6a24", - "tables": { - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json deleted file mode 100644 index 6bc4088..0000000 --- a/drizzle/meta/0003_snapshot.json +++ /dev/null @@ -1,2192 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "b020bc0f-36bb-45fc-aad9-6cc6e6c99426", - "prevId": "e6118000-4093-4c16-a01c-e33a2a5f0875", - "tables": { - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_enrollments": { - "name": "class_enrollments", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_enrollment_status": { - "name": "class_enrollment_status", - "type": "enum('active','inactive')", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "class_enrollments_class_idx": { - "name": "class_enrollments_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_enrollments_student_idx": { - "name": "class_enrollments_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "ce_c_fk": { - "name": "ce_c_fk", - "tableFrom": "class_enrollments", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ce_s_fk": { - "name": "ce_s_fk", - "tableFrom": "class_enrollments", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_enrollments_class_id_student_id_pk": { - "name": "class_enrollments_class_id_student_id_pk", - "columns": [ - "class_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_schedule": { - "name": "class_schedule", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "weekday": { - "name": "weekday", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "course": { - "name": "course", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_schedule_class_idx": { - "name": "class_schedule_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_schedule_class_day_idx": { - "name": "class_schedule_class_day_idx", - "columns": [ - "class_id", - "weekday" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cs_c_fk": { - "name": "cs_c_fk", - "tableFrom": "class_schedule", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_schedule_id": { - "name": "class_schedule_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classes": { - "name": "classes", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "homeroom": { - "name": "homeroom", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room": { - "name": "room", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classes_teacher_idx": { - "name": "classes_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - }, - "classes_grade_idx": { - "name": "classes_grade_idx", - "columns": [ - "grade" - ], - "isUnique": false - } - }, - "foreignKeys": { - "classes_teacher_id_users_id_fk": { - "name": "classes_teacher_id_users_id_fk", - "tableFrom": "classes", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "classes_id": { - "name": "classes_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json deleted file mode 100644 index 4623d70..0000000 --- a/drizzle/meta/0004_snapshot.json +++ /dev/null @@ -1,2213 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "97232350-6c6c-4c3e-98d6-c34d33d55a4d", - "prevId": "b020bc0f-36bb-45fc-aad9-6cc6e6c99426", - "tables": { - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_enrollments": { - "name": "class_enrollments", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_enrollment_status": { - "name": "class_enrollment_status", - "type": "enum('active','inactive')", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "class_enrollments_class_idx": { - "name": "class_enrollments_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_enrollments_student_idx": { - "name": "class_enrollments_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "ce_c_fk": { - "name": "ce_c_fk", - "tableFrom": "class_enrollments", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ce_s_fk": { - "name": "ce_s_fk", - "tableFrom": "class_enrollments", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_enrollments_class_id_student_id_pk": { - "name": "class_enrollments_class_id_student_id_pk", - "columns": [ - "class_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_schedule": { - "name": "class_schedule", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "weekday": { - "name": "weekday", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "course": { - "name": "course", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_schedule_class_idx": { - "name": "class_schedule_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_schedule_class_day_idx": { - "name": "class_schedule_class_day_idx", - "columns": [ - "class_id", - "weekday" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cs_c_fk": { - "name": "cs_c_fk", - "tableFrom": "class_schedule", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_schedule_id": { - "name": "class_schedule_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classes": { - "name": "classes", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "homeroom": { - "name": "homeroom", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room": { - "name": "room", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classes_teacher_idx": { - "name": "classes_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - }, - "classes_grade_idx": { - "name": "classes_grade_idx", - "columns": [ - "grade" - ], - "isUnique": false - } - }, - "foreignKeys": { - "classes_teacher_id_users_id_fk": { - "name": "classes_teacher_id_users_id_fk", - "tableFrom": "classes", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "classes_id": { - "name": "classes_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "chapter_id": { - "name": "chapter_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "kp_chapter_id_idx": { - "name": "kp_chapter_id_idx", - "columns": [ - "chapter_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json deleted file mode 100644 index 62ca73a..0000000 --- a/drizzle/meta/0005_snapshot.json +++ /dev/null @@ -1,2564 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "abd9c192-f000-4024-a017-3d662125141b", - "prevId": "97232350-6c6c-4c3e-98d6-c34d33d55a4d", - "tables": { - "academic_years": { - "name": "academic_years", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_date": { - "name": "start_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_date": { - "name": "end_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "academic_years_name_idx": { - "name": "academic_years_name_idx", - "columns": [ - "name" - ], - "isUnique": false - }, - "academic_years_active_idx": { - "name": "academic_years_active_idx", - "columns": [ - "is_active" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "academic_years_id": { - "name": "academic_years_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "academic_years_name_unique": { - "name": "academic_years_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_enrollments": { - "name": "class_enrollments", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_enrollment_status": { - "name": "class_enrollment_status", - "type": "enum('active','inactive')", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "class_enrollments_class_idx": { - "name": "class_enrollments_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_enrollments_student_idx": { - "name": "class_enrollments_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "ce_c_fk": { - "name": "ce_c_fk", - "tableFrom": "class_enrollments", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ce_s_fk": { - "name": "ce_s_fk", - "tableFrom": "class_enrollments", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_enrollments_class_id_student_id_pk": { - "name": "class_enrollments_class_id_student_id_pk", - "columns": [ - "class_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_schedule": { - "name": "class_schedule", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "weekday": { - "name": "weekday", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "course": { - "name": "course", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_schedule_class_idx": { - "name": "class_schedule_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_schedule_class_day_idx": { - "name": "class_schedule_class_day_idx", - "columns": [ - "class_id", - "weekday" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cs_c_fk": { - "name": "cs_c_fk", - "tableFrom": "class_schedule", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_schedule_id": { - "name": "class_schedule_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_subject_teachers": { - "name": "class_subject_teachers", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "enum('语文','数学','英语','美术','体育','科学','社会','音乐')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_subject_teachers_class_idx": { - "name": "class_subject_teachers_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_subject_teachers_teacher_idx": { - "name": "class_subject_teachers_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "class_subject_teachers_teacher_id_users_id_fk": { - "name": "class_subject_teachers_teacher_id_users_id_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "cst_c_fk": { - "name": "cst_c_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_subject_teachers_class_id_subject_pk": { - "name": "class_subject_teachers_class_id_subject_pk", - "columns": [ - "class_id", - "subject" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classes": { - "name": "classes", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "school_name": { - "name": "school_name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "homeroom": { - "name": "homeroom", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room": { - "name": "room", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classes_teacher_idx": { - "name": "classes_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - }, - "classes_grade_idx": { - "name": "classes_grade_idx", - "columns": [ - "grade" - ], - "isUnique": false - } - }, - "foreignKeys": { - "classes_teacher_id_users_id_fk": { - "name": "classes_teacher_id_users_id_fk", - "tableFrom": "classes", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "classes_id": { - "name": "classes_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classrooms": { - "name": "classrooms", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "building": { - "name": "building", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "floor": { - "name": "floor", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "capacity": { - "name": "capacity", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classrooms_name_idx": { - "name": "classrooms_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "classrooms_id": { - "name": "classrooms_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "classrooms_name_unique": { - "name": "classrooms_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "departments": { - "name": "departments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "departments_name_idx": { - "name": "departments_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "departments_id": { - "name": "departments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "departments_name_unique": { - "name": "departments_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "chapter_id": { - "name": "chapter_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "kp_chapter_id_idx": { - "name": "kp_chapter_id_idx", - "columns": [ - "chapter_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json deleted file mode 100644 index 8973e5b..0000000 --- a/drizzle/meta/0006_snapshot.json +++ /dev/null @@ -1,2848 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "3b23e056-3d79-4ea9-a03e-d1b5d56bafda", - "prevId": "abd9c192-f000-4024-a017-3d662125141b", - "tables": { - "academic_years": { - "name": "academic_years", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_date": { - "name": "start_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_date": { - "name": "end_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "academic_years_name_idx": { - "name": "academic_years_name_idx", - "columns": [ - "name" - ], - "isUnique": false - }, - "academic_years_active_idx": { - "name": "academic_years_active_idx", - "columns": [ - "is_active" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "academic_years_id": { - "name": "academic_years_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "academic_years_name_unique": { - "name": "academic_years_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_enrollments": { - "name": "class_enrollments", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_enrollment_status": { - "name": "class_enrollment_status", - "type": "enum('active','inactive')", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "class_enrollments_class_idx": { - "name": "class_enrollments_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_enrollments_student_idx": { - "name": "class_enrollments_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "ce_c_fk": { - "name": "ce_c_fk", - "tableFrom": "class_enrollments", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ce_s_fk": { - "name": "ce_s_fk", - "tableFrom": "class_enrollments", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_enrollments_class_id_student_id_pk": { - "name": "class_enrollments_class_id_student_id_pk", - "columns": [ - "class_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_schedule": { - "name": "class_schedule", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "weekday": { - "name": "weekday", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "course": { - "name": "course", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_schedule_class_idx": { - "name": "class_schedule_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_schedule_class_day_idx": { - "name": "class_schedule_class_day_idx", - "columns": [ - "class_id", - "weekday" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cs_c_fk": { - "name": "cs_c_fk", - "tableFrom": "class_schedule", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_schedule_id": { - "name": "class_schedule_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_subject_teachers": { - "name": "class_subject_teachers", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "enum('语文','数学','英语','美术','体育','科学','社会','音乐')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_subject_teachers_class_idx": { - "name": "class_subject_teachers_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_subject_teachers_teacher_idx": { - "name": "class_subject_teachers_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "class_subject_teachers_teacher_id_users_id_fk": { - "name": "class_subject_teachers_teacher_id_users_id_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "cst_c_fk": { - "name": "cst_c_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_subject_teachers_class_id_subject_pk": { - "name": "class_subject_teachers_class_id_subject_pk", - "columns": [ - "class_id", - "subject" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classes": { - "name": "classes", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "school_name": { - "name": "school_name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "school_id": { - "name": "school_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade_id": { - "name": "grade_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "homeroom": { - "name": "homeroom", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room": { - "name": "room", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classes_teacher_idx": { - "name": "classes_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - }, - "classes_grade_idx": { - "name": "classes_grade_idx", - "columns": [ - "grade" - ], - "isUnique": false - }, - "classes_school_idx": { - "name": "classes_school_idx", - "columns": [ - "school_id" - ], - "isUnique": false - }, - "classes_grade_id_idx": { - "name": "classes_grade_id_idx", - "columns": [ - "grade_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "classes_teacher_id_users_id_fk": { - "name": "classes_teacher_id_users_id_fk", - "tableFrom": "classes", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "c_s_fk": { - "name": "c_s_fk", - "tableFrom": "classes", - "tableTo": "schools", - "columnsFrom": [ - "school_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "c_g_fk": { - "name": "c_g_fk", - "tableFrom": "classes", - "tableTo": "grades", - "columnsFrom": [ - "grade_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "classes_id": { - "name": "classes_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classrooms": { - "name": "classrooms", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "building": { - "name": "building", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "floor": { - "name": "floor", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "capacity": { - "name": "capacity", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classrooms_name_idx": { - "name": "classrooms_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "classrooms_id": { - "name": "classrooms_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "classrooms_name_unique": { - "name": "classrooms_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "departments": { - "name": "departments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "departments_name_idx": { - "name": "departments_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "departments_id": { - "name": "departments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "departments_name_unique": { - "name": "departments_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "grades": { - "name": "grades", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "school_id": { - "name": "school_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "grade_head_id": { - "name": "grade_head_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teaching_head_id": { - "name": "teaching_head_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "grades_school_idx": { - "name": "grades_school_idx", - "columns": [ - "school_id" - ], - "isUnique": false - }, - "grades_school_name_uniq": { - "name": "grades_school_name_uniq", - "columns": [ - "school_id", - "name" - ], - "isUnique": false - }, - "grades_grade_head_idx": { - "name": "grades_grade_head_idx", - "columns": [ - "grade_head_id" - ], - "isUnique": false - }, - "grades_teaching_head_idx": { - "name": "grades_teaching_head_idx", - "columns": [ - "teaching_head_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "g_s_fk": { - "name": "g_s_fk", - "tableFrom": "grades", - "tableTo": "schools", - "columnsFrom": [ - "school_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "g_gh_fk": { - "name": "g_gh_fk", - "tableFrom": "grades", - "tableTo": "users", - "columnsFrom": [ - "grade_head_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "g_th_fk": { - "name": "g_th_fk", - "tableFrom": "grades", - "tableTo": "users", - "columnsFrom": [ - "teaching_head_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "grades_id": { - "name": "grades_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "chapter_id": { - "name": "chapter_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "kp_chapter_id_idx": { - "name": "kp_chapter_id_idx", - "columns": [ - "chapter_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "schools": { - "name": "schools", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "code": { - "name": "code", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "schools_name_idx": { - "name": "schools_name_idx", - "columns": [ - "name" - ], - "isUnique": false - }, - "schools_code_idx": { - "name": "schools_code_idx", - "columns": [ - "code" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "schools_id": { - "name": "schools_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "schools_name_unique": { - "name": "schools_name_unique", - "columns": [ - "name" - ] - }, - "schools_code_unique": { - "name": "schools_code_unique", - "columns": [ - "code" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json deleted file mode 100644 index 3c5305c..0000000 --- a/drizzle/meta/0007_snapshot.json +++ /dev/null @@ -1,3064 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "a6d95d47-4400-464e-bc53-45735dd6e3e3", - "prevId": "5eaf9185-8a1e-4e35-8144-553aec7ff31f", - "tables": { - "academic_years": { - "name": "academic_years", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_date": { - "name": "start_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_date": { - "name": "end_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "academic_years_name_idx": { - "name": "academic_years_name_idx", - "columns": [ - "name" - ], - "isUnique": false - }, - "academic_years_active_idx": { - "name": "academic_years_active_idx", - "columns": [ - "is_active" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "academic_years_id": { - "name": "academic_years_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "academic_years_name_unique": { - "name": "academic_years_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "accounts": { - "name": "accounts", - "columns": { - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "accounts_userId_users_id_fk": { - "name": "accounts_userId_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accounts_provider_providerAccountId_pk": { - "name": "accounts_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "chapters": { - "name": "chapters", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "textbook_id": { - "name": "textbook_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "textbook_idx": { - "name": "textbook_idx", - "columns": [ - "textbook_id" - ], - "isUnique": false - }, - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "chapters_textbook_id_textbooks_id_fk": { - "name": "chapters_textbook_id_textbooks_id_fk", - "tableFrom": "chapters", - "tableTo": "textbooks", - "columnsFrom": [ - "textbook_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "chapters_id": { - "name": "chapters_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_enrollments": { - "name": "class_enrollments", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_enrollment_status": { - "name": "class_enrollment_status", - "type": "enum('active','inactive')", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "class_enrollments_class_idx": { - "name": "class_enrollments_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_enrollments_student_idx": { - "name": "class_enrollments_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "ce_c_fk": { - "name": "ce_c_fk", - "tableFrom": "class_enrollments", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ce_s_fk": { - "name": "ce_s_fk", - "tableFrom": "class_enrollments", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_enrollments_class_id_student_id_pk": { - "name": "class_enrollments_class_id_student_id_pk", - "columns": [ - "class_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_schedule": { - "name": "class_schedule", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "weekday": { - "name": "weekday", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "varchar(5)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "course": { - "name": "course", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_schedule_class_idx": { - "name": "class_schedule_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_schedule_class_day_idx": { - "name": "class_schedule_class_day_idx", - "columns": [ - "class_id", - "weekday" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cs_c_fk": { - "name": "cs_c_fk", - "tableFrom": "class_schedule", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_schedule_id": { - "name": "class_schedule_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "class_subject_teachers": { - "name": "class_subject_teachers", - "columns": { - "class_id": { - "name": "class_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "enum('语文','数学','英语','美术','体育','科学','社会','音乐')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "class_subject_teachers_class_idx": { - "name": "class_subject_teachers_class_idx", - "columns": [ - "class_id" - ], - "isUnique": false - }, - "class_subject_teachers_teacher_idx": { - "name": "class_subject_teachers_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "class_subject_teachers_teacher_id_users_id_fk": { - "name": "class_subject_teachers_teacher_id_users_id_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "cst_c_fk": { - "name": "cst_c_fk", - "tableFrom": "class_subject_teachers", - "tableTo": "classes", - "columnsFrom": [ - "class_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "class_subject_teachers_class_id_subject_pk": { - "name": "class_subject_teachers_class_id_subject_pk", - "columns": [ - "class_id", - "subject" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "classes": { - "name": "classes", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "school_name": { - "name": "school_name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "school_id": { - "name": "school_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade_id": { - "name": "grade_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "homeroom": { - "name": "homeroom", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room": { - "name": "room", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "invitation_code": { - "name": "invitation_code", - "type": "varchar(6)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teacher_id": { - "name": "teacher_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classes_teacher_idx": { - "name": "classes_teacher_idx", - "columns": [ - "teacher_id" - ], - "isUnique": false - }, - "classes_grade_idx": { - "name": "classes_grade_idx", - "columns": [ - "grade" - ], - "isUnique": false - }, - "classes_school_idx": { - "name": "classes_school_idx", - "columns": [ - "school_id" - ], - "isUnique": false - }, - "classes_grade_id_idx": { - "name": "classes_grade_id_idx", - "columns": [ - "grade_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "classes_teacher_id_users_id_fk": { - "name": "classes_teacher_id_users_id_fk", - "tableFrom": "classes", - "tableTo": "users", - "columnsFrom": [ - "teacher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "c_s_fk": { - "name": "c_s_fk", - "tableFrom": "classes", - "tableTo": "schools", - "columnsFrom": [ - "school_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "c_g_fk": { - "name": "c_g_fk", - "tableFrom": "classes", - "tableTo": "grades", - "columnsFrom": [ - "grade_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "classes_id": { - "name": "classes_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "classes_invitation_code_unique": { - "name": "classes_invitation_code_unique", - "columns": [ - "invitation_code" - ] - } - }, - "checkConstraint": {} - }, - "classrooms": { - "name": "classrooms", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "building": { - "name": "building", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "floor": { - "name": "floor", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "capacity": { - "name": "capacity", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "classrooms_name_idx": { - "name": "classrooms_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "classrooms_id": { - "name": "classrooms_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "classrooms_name_unique": { - "name": "classrooms_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "departments": { - "name": "departments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "departments_name_idx": { - "name": "departments_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "departments_id": { - "name": "departments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "departments_name_unique": { - "name": "departments_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "exam_questions": { - "name": "exam_questions", - "columns": { - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "exam_questions_exam_id_exams_id_fk": { - "name": "exam_questions_exam_id_exams_id_fk", - "tableFrom": "exam_questions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_questions_question_id_questions_id_fk": { - "name": "exam_questions_question_id_questions_id_fk", - "tableFrom": "exam_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_questions_exam_id_question_id_pk": { - "name": "exam_questions_exam_id_question_id_pk", - "columns": [ - "exam_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exam_submissions": { - "name": "exam_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "exam_id": { - "name": "exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exam_student_idx": { - "name": "exam_student_idx", - "columns": [ - "exam_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exam_submissions_exam_id_exams_id_fk": { - "name": "exam_submissions_exam_id_exams_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "exams", - "columnsFrom": [ - "exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "exam_submissions_student_id_users_id_fk": { - "name": "exam_submissions_student_id_users_id_fk", - "tableFrom": "exam_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exam_submissions_id": { - "name": "exam_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "exams": { - "name": "exams", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject_id": { - "name": "subject_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "grade_id": { - "name": "grade_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "start_time": { - "name": "start_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "end_time": { - "name": "end_time", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "exams_subject_idx": { - "name": "exams_subject_idx", - "columns": [ - "subject_id" - ], - "isUnique": false - }, - "exams_grade_idx": { - "name": "exams_grade_idx", - "columns": [ - "grade_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "exams_creator_id_users_id_fk": { - "name": "exams_creator_id_users_id_fk", - "tableFrom": "exams", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "exams_subject_id_subjects_id_fk": { - "name": "exams_subject_id_subjects_id_fk", - "tableFrom": "exams", - "tableTo": "subjects", - "columnsFrom": [ - "subject_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "exams_grade_id_grades_id_fk": { - "name": "exams_grade_id_grades_id_fk", - "tableFrom": "exams", - "tableTo": "grades", - "columnsFrom": [ - "grade_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "exams_id": { - "name": "exams_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "grades": { - "name": "grades", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "school_id": { - "name": "school_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "grade_head_id": { - "name": "grade_head_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "teaching_head_id": { - "name": "teaching_head_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "grades_school_idx": { - "name": "grades_school_idx", - "columns": [ - "school_id" - ], - "isUnique": false - }, - "grades_school_name_uniq": { - "name": "grades_school_name_uniq", - "columns": [ - "school_id", - "name" - ], - "isUnique": false - }, - "grades_grade_head_idx": { - "name": "grades_grade_head_idx", - "columns": [ - "grade_head_id" - ], - "isUnique": false - }, - "grades_teaching_head_idx": { - "name": "grades_teaching_head_idx", - "columns": [ - "teaching_head_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "g_s_fk": { - "name": "g_s_fk", - "tableFrom": "grades", - "tableTo": "schools", - "columnsFrom": [ - "school_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "g_gh_fk": { - "name": "g_gh_fk", - "tableFrom": "grades", - "tableTo": "users", - "columnsFrom": [ - "grade_head_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "g_th_fk": { - "name": "g_th_fk", - "tableFrom": "grades", - "tableTo": "users", - "columnsFrom": [ - "teaching_head_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "grades_id": { - "name": "grades_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_answers": { - "name": "homework_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_answer_submission_idx": { - "name": "hw_answer_submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - }, - "hw_answer_submission_question_idx": { - "name": "hw_answer_submission_question_idx", - "columns": [ - "submission_id", - "question_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_ans_sub_fk": { - "name": "hw_ans_sub_fk", - "tableFrom": "homework_answers", - "tableTo": "homework_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_ans_q_fk": { - "name": "hw_ans_q_fk", - "tableFrom": "homework_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_answers_id": { - "name": "homework_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_questions": { - "name": "homework_assignment_questions", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "hw_assignment_questions_assignment_idx": { - "name": "hw_assignment_questions_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_aq_a_fk": { - "name": "hw_aq_a_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_aq_q_fk": { - "name": "hw_aq_q_fk", - "tableFrom": "homework_assignment_questions", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_questions_assignment_id_question_id_pk": { - "name": "homework_assignment_questions_assignment_id_question_id_pk", - "columns": [ - "assignment_id", - "question_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignment_targets": { - "name": "homework_assignment_targets", - "columns": { - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_targets_assignment_idx": { - "name": "hw_assignment_targets_assignment_idx", - "columns": [ - "assignment_id" - ], - "isUnique": false - }, - "hw_assignment_targets_student_idx": { - "name": "hw_assignment_targets_student_idx", - "columns": [ - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_at_a_fk": { - "name": "hw_at_a_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_at_s_fk": { - "name": "hw_at_s_fk", - "tableFrom": "homework_assignment_targets", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignment_targets_assignment_id_student_id_pk": { - "name": "homework_assignment_targets_assignment_id_student_id_pk", - "columns": [ - "assignment_id", - "student_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_assignments": { - "name": "homework_assignments", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_exam_id": { - "name": "source_exam_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "structure": { - "name": "structure", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'draft'" - }, - "creator_id": { - "name": "creator_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "due_at": { - "name": "due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "allow_late": { - "name": "allow_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "late_due_at": { - "name": "late_due_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "max_attempts": { - "name": "max_attempts", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_creator_idx": { - "name": "hw_assignment_creator_idx", - "columns": [ - "creator_id" - ], - "isUnique": false - }, - "hw_assignment_source_exam_idx": { - "name": "hw_assignment_source_exam_idx", - "columns": [ - "source_exam_id" - ], - "isUnique": false - }, - "hw_assignment_status_idx": { - "name": "hw_assignment_status_idx", - "columns": [ - "status" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_asg_exam_fk": { - "name": "hw_asg_exam_fk", - "tableFrom": "homework_assignments", - "tableTo": "exams", - "columnsFrom": [ - "source_exam_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_asg_creator_fk": { - "name": "hw_asg_creator_fk", - "tableFrom": "homework_assignments", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_assignments_id": { - "name": "homework_assignments_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "homework_submissions": { - "name": "homework_submissions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "assignment_id": { - "name": "assignment_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "student_id": { - "name": "student_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempt_no": { - "name": "attempt_no", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'started'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "submitted_at": { - "name": "submitted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_late": { - "name": "is_late", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "hw_assignment_student_idx": { - "name": "hw_assignment_student_idx", - "columns": [ - "assignment_id", - "student_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "hw_sub_a_fk": { - "name": "hw_sub_a_fk", - "tableFrom": "homework_submissions", - "tableTo": "homework_assignments", - "columnsFrom": [ - "assignment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "hw_sub_student_fk": { - "name": "hw_sub_student_fk", - "tableFrom": "homework_submissions", - "tableTo": "users", - "columnsFrom": [ - "student_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "homework_submissions_id": { - "name": "homework_submissions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "knowledge_points": { - "name": "knowledge_points", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "chapter_id": { - "name": "chapter_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "kp_chapter_id_idx": { - "name": "kp_chapter_id_idx", - "columns": [ - "chapter_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "knowledge_points_id": { - "name": "knowledge_points_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions": { - "name": "questions", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "content": { - "name": "content", - "type": "json", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "enum('single_choice','multiple_choice','text','judgment','composite')", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "difficulty": { - "name": "difficulty", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "parent_id": { - "name": "parent_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "author_id": { - "name": "author_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "parent_id_idx": { - "name": "parent_id_idx", - "columns": [ - "parent_id" - ], - "isUnique": false - }, - "author_id_idx": { - "name": "author_id_idx", - "columns": [ - "author_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "questions_author_id_users_id_fk": { - "name": "questions_author_id_users_id_fk", - "tableFrom": "questions", - "tableTo": "users", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_id": { - "name": "questions_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "questions_to_knowledge_points": { - "name": "questions_to_knowledge_points", - "columns": { - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "knowledge_point_id": { - "name": "knowledge_point_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "kp_idx": { - "name": "kp_idx", - "columns": [ - "knowledge_point_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "q_kp_qid_fk": { - "name": "q_kp_qid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "q_kp_kpid_fk": { - "name": "q_kp_kpid_fk", - "tableFrom": "questions_to_knowledge_points", - "tableTo": "knowledge_points", - "columnsFrom": [ - "knowledge_point_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "questions_to_knowledge_points_question_id_knowledge_point_id_pk": { - "name": "questions_to_knowledge_points_question_id_knowledge_point_id_pk", - "columns": [ - "question_id", - "knowledge_point_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "roles": { - "name": "roles", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "roles_id": { - "name": "roles_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "columns": [ - "name" - ] - } - }, - "checkConstraint": {} - }, - "schools": { - "name": "schools", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "code": { - "name": "code", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "schools_name_idx": { - "name": "schools_name_idx", - "columns": [ - "name" - ], - "isUnique": false - }, - "schools_code_idx": { - "name": "schools_code_idx", - "columns": [ - "code" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "schools_id": { - "name": "schools_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "schools_name_unique": { - "name": "schools_name_unique", - "columns": [ - "name" - ] - }, - "schools_code_unique": { - "name": "schools_code_unique", - "columns": [ - "code" - ] - } - }, - "checkConstraint": {} - }, - "sessions": { - "name": "sessions", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_userId_users_id_fk": { - "name": "sessions_userId_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sessions_sessionToken": { - "name": "sessions_sessionToken", - "columns": [ - "sessionToken" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "subjects": { - "name": "subjects", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "code": { - "name": "code", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "order": { - "name": "order", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "subjects_name_idx": { - "name": "subjects_name_idx", - "columns": [ - "name" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "subjects_id": { - "name": "subjects_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "subjects_name_unique": { - "name": "subjects_name_unique", - "columns": [ - "name" - ] - }, - "subjects_code_unique": { - "name": "subjects_code_unique", - "columns": [ - "code" - ] - } - }, - "checkConstraint": {} - }, - "submission_answers": { - "name": "submission_answers", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "submission_id": { - "name": "submission_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question_id": { - "name": "question_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer_content": { - "name": "answer_content", - "type": "json", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "score": { - "name": "score", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "submission_idx": { - "name": "submission_idx", - "columns": [ - "submission_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "submission_answers_submission_id_exam_submissions_id_fk": { - "name": "submission_answers_submission_id_exam_submissions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "exam_submissions", - "columnsFrom": [ - "submission_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "submission_answers_question_id_questions_id_fk": { - "name": "submission_answers_question_id_questions_id_fk", - "tableFrom": "submission_answers", - "tableTo": "questions", - "columnsFrom": [ - "question_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "submission_answers_id": { - "name": "submission_answers_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "textbooks": { - "name": "textbooks", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subject": { - "name": "subject", - "type": "varchar(100)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "grade": { - "name": "grade", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "publisher": { - "name": "publisher", - "type": "varchar(100)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "textbooks_id": { - "name": "textbooks_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'student'" - }, - "password": { - "name": "password", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "phone": { - "name": "phone", - "type": "varchar(30)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "address": { - "name": "address", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "gender": { - "name": "gender", - "type": "varchar(20)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "age": { - "name": "age", - "type": "int", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "grade_id": { - "name": "grade_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "department_id": { - "name": "department_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "onboarded_at": { - "name": "onboarded_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(now())" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "onUpdate": true, - "default": "(now())" - } - }, - "indexes": { - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": false - }, - "users_grade_id_idx": { - "name": "users_grade_id_idx", - "columns": [ - "grade_id" - ], - "isUnique": false - }, - "users_department_id_idx": { - "name": "users_department_id_idx", - "columns": [ - "department_id" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "users_id": { - "name": "users_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ] - } - }, - "checkConstraint": {} - }, - "users_to_roles": { - "name": "users_to_roles", - "columns": { - "user_id": { - "name": "user_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role_id": { - "name": "role_id", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_id_idx": { - "name": "user_id_idx", - "columns": [ - "user_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "users_to_roles_user_id_users_id_fk": { - "name": "users_to_roles_user_id_users_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_to_roles_role_id_roles_id_fk": { - "name": "users_to_roles_role_id_roles_id_fk", - "tableFrom": "users_to_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_to_roles_user_id_role_id_pk": { - "name": "users_to_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - }, - "verificationTokens": { - "name": "verificationTokens", - "columns": { - "identifier": { - "name": "identifier", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationTokens_identifier_token_pk": { - "name": "verificationTokens_identifier_token_pk", - "columns": [ - "identifier", - "token" - ] - } - }, - "uniqueConstraints": {}, - "checkConstraint": {} - } - }, - "views": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "tables": {}, - "indexes": {} - } -} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 9ba0063..4f48560 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,22 @@ const eslintConfig = defineConfig([ "react-hooks/incompatible-library": "off", }, }, + { + files: ["tests/**/*.ts"], + languageOptions: { + globals: { + describe: "readonly", + it: "readonly", + test: "readonly", + expect: "readonly", + beforeAll: "readonly", + afterAll: "readonly", + beforeEach: "readonly", + afterEach: "readonly", + vi: "readonly", + }, + }, + }, // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: @@ -18,6 +34,8 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", "docs/scripts/**", + "playwright-report/**", + "test-results/**", ]), ]); diff --git a/package-lock.json b/package-lock.json index f1926cd..22402c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,8 @@ "next-auth": "^5.0.0-beta.30", "next-themes": "^0.4.6", "nuqs": "^2.8.5", + "openai": "^6.25.0", + "p-queue": "^9.1.0", "react": "19.2.1", "react-dom": "19.2.1", "react-hook-form": "^7.69.0", @@ -62,12 +64,14 @@ }, "devDependencies": { "@faker-js/faker": "^10.1.0", + "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4", "@tailwindcss/typography": "^0.5.16", "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^4.1.0", "dotenv": "^17.2.3", "drizzle-kit": "^0.31.8", "eslint": "^9", @@ -75,7 +79,8 @@ "prettier": "^3.7.4", "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.0" } }, "node_modules/@alloc/quick-lru": { @@ -297,13 +302,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -347,9 +352,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -360,6 +365,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -2327,6 +2342,26 @@ "node": ">=12.4.0" } }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -2350,6 +2385,22 @@ "cuid2": "bin/cuid2.js" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -4515,6 +4566,285 @@ "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", "license": "MIT" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", + "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5406,6 +5736,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -5478,6 +5819,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6137,6 +6485,130 @@ "win32" ] }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", + "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.0", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.0", + "vitest": "4.1.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -6381,6 +6853,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -6388,6 +6870,25 @@ "dev": true, "license": "MIT" }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -6640,6 +7141,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7461,6 +7972,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -8032,6 +8550,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8048,6 +8576,16 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8204,6 +8742,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -8567,6 +9120,13 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -9161,6 +9721,45 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -9697,6 +10296,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -11024,6 +11664,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/openai": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.25.0.tgz", + "integrity": "sha512-mEh6VZ2ds2AGGokWARo18aPISI1OhlgdEIC1ewhkZr8pSIT31dec0ecr9Nhxx0JlybyOgoAT1sWeKtwPZzJyww==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -11098,6 +11770,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11163,6 +11863,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11182,6 +11889,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -11193,9 +11932,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -12006,6 +12745,40 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", + "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + } + }, "node_modules/rope-sequence": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", @@ -12324,6 +13097,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -12390,6 +13170,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -12666,6 +13460,23 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -12714,6 +13525,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tiptap-markdown": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.9.0.tgz", @@ -13265,6 +14086,995 @@ "d3-timer": "^3.0.1" } }, + "node_modules/vitest": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/vitest/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/vitest/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", + "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -13376,6 +15186,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 8136db0..836dc57 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,14 @@ "start": "next start", "lint": "eslint", "typecheck": "tsc --noEmit", + "test": "npm run test:integration && npm run test:e2e", + "test:ci": "npm run test:integration && npm run test:e2e", + "test:integration": "vitest run --config vitest.config.ts", + "test:integration:watch": "vitest --config vitest.config.ts", + "test:integration:coverage": "vitest run --config vitest.config.ts --coverage", + "test:e2e": "playwright test", + "test:e2e:smoke": "playwright test tests/e2e/smoke-auth.spec.ts", + "test:e2e:full-routes": "playwright test tests/e2e/full-route-regression.spec.ts", "db:seed": "npx tsx scripts/seed.ts", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate" @@ -41,9 +49,9 @@ "@tiptap/pm": "^3.15.3", "@tiptap/react": "^3.15.3", "@tiptap/starter-kit": "^3.15.3", + "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "bcryptjs": "^2.4.3", "drizzle-orm": "^0.45.1", "lucide-react": "^0.562.0", "mysql2": "^3.16.0", @@ -51,6 +59,8 @@ "next-auth": "^5.0.0-beta.30", "next-themes": "^0.4.6", "nuqs": "^2.8.5", + "openai": "^6.25.0", + "p-queue": "^9.1.0", "react": "19.2.1", "react-dom": "19.2.1", "react-hook-form": "^7.69.0", @@ -67,12 +77,14 @@ }, "devDependencies": { "@faker-js/faker": "^10.1.0", + "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4", "@tailwindcss/typography": "^0.5.16", "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^4.1.0", "dotenv": "^17.2.3", "drizzle-kit": "^0.31.8", "eslint": "^9", @@ -80,6 +92,7 @@ "prettier": "^3.7.4", "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.0" } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..b39a770 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,37 @@ +import { defineConfig, devices } from "@playwright/test" + +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 2 : undefined, + reporter: [["html", { open: "never" }], ["list"]], + use: { + baseURL: "http://127.0.0.1:3000", + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: process.env.CI ? "retain-on-failure" : "off", + }, + webServer: { + command: "npm run dev", + port: 3000, + reuseExistingServer: !process.env.CI, + timeout: 180000, + env: { + SKIP_ENV_VALIDATION: "1", + NEXTAUTH_SECRET: "test-nextauth-secret", + NEXTAUTH_URL: "http://127.0.0.1:3000", + DATABASE_URL: "mysql://test:test@127.0.0.1:3306/test_db", + }, + }, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + channel: process.env.CI ? undefined : "chrome", + }, + }, + ], +}) diff --git a/src/app/(dashboard)/student/learning/assignments/page.tsx b/src/app/(dashboard)/student/learning/assignments/page.tsx index af27f9d..bb5805d 100644 --- a/src/app/(dashboard)/student/learning/assignments/page.tsx +++ b/src/app/(dashboard)/student/learning/assignments/page.tsx @@ -3,14 +3,7 @@ import Link from "next/link" import { EmptyState } from "@/shared/components/ui/empty-state" import { Badge } from "@/shared/components/ui/badge" import { Button } from "@/shared/components/ui/button" -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/shared/components/ui/table" +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { formatDate } from "@/shared/lib/utils" import { getDemoStudentUser, getStudentHomeworkAssignments } from "@/modules/homework/data-access" import { Inbox } from "lucide-react" @@ -43,18 +36,14 @@ const getActionVariant = (status: string): "default" | "secondary" | "outline" = return "default" } +const isAnswered = (status: string) => status === "submitted" || status === "graded" + export default async function StudentAssignmentsPage() { const student = await getDemoStudentUser() if (!student) { return (
-
-
-

Assignments

-

Your homework assignments.

-
-
) @@ -62,63 +51,115 @@ export default async function StudentAssignmentsPage() { const assignments = await getStudentHomeworkAssignments(student.id) const hasAssignments = assignments.length > 0 + const assignmentsBySubject = assignments.reduce((acc, assignment) => { + const subject = assignment.subjectName?.trim() || "Other" + const existing = acc.get(subject) + if (existing) { + existing.push(assignment) + } else { + acc.set(subject, [assignment]) + } + return acc + }, new Map()) + const subjectEntries = Array.from(assignmentsBySubject.entries()).sort((a, b) => a[0].localeCompare(b[0])) return (
-
-
-

Assignments

-

Your homework assignments.

-
- -
- {!hasAssignments ? ( ) : ( -
- - - - Title - Status - Due - Attempts - Score - Action - - - - {assignments.map((a) => ( - - - - {a.title} - - - - - {getStatusLabel(a.progressStatus)} - - - {a.dueAt ? formatDate(a.dueAt) : "-"} - - {a.attemptsUsed}/{a.maxAttempts} - - {a.latestScore ?? "-"} - - - - - ))} - -
+
+ {subjectEntries.map(([subject, items]) => { + const answeredItems = items.filter((a) => isAnswered(a.progressStatus)) + const unansweredItems = items.filter((a) => !isAnswered(a.progressStatus)) + + return ( +
+
{subject}
+ {unansweredItems.length > 0 && ( +
+
未答题
+
+ {unansweredItems.map((a) => ( + + +
+ + + {a.title} + + + + {getStatusLabel(a.progressStatus)} + +
+
+ Due {a.dueAt ? formatDate(a.dueAt) : "-"} + + + Attempts {a.attemptsUsed}/{a.maxAttempts} + +
+
+ +
+
Score
+
{a.latestScore ?? "-"}
+
+ +
+
+ ))} +
+
+ )} + {answeredItems.length > 0 && ( +
+
已答题
+
+ {answeredItems.map((a) => ( + + +
+ + + {a.title} + + + + {getStatusLabel(a.progressStatus)} + +
+
+ Due {a.dueAt ? formatDate(a.dueAt) : "-"} + + + Attempts {a.attemptsUsed}/{a.maxAttempts} + +
+
+ +
+
Score
+
{a.latestScore ?? "-"}
+
+ +
+
+ ))} +
+
+ )} +
+ )})}
)}
diff --git a/src/app/(dashboard)/teacher/exams/create/page.tsx b/src/app/(dashboard)/teacher/exams/create/page.tsx index da1e91a..c15ad12 100644 --- a/src/app/(dashboard)/teacher/exams/create/page.tsx +++ b/src/app/(dashboard)/teacher/exams/create/page.tsx @@ -3,7 +3,7 @@ import { ExamForm } from "@/modules/exams/components/exam-form" export default function CreateExamPage() { return ( -
+
) diff --git a/src/app/api/ai/chat/route.ts b/src/app/api/ai/chat/route.ts new file mode 100644 index 0000000..f2134ff --- /dev/null +++ b/src/app/api/ai/chat/route.ts @@ -0,0 +1,28 @@ +import { NextResponse } from "next/server" +import { auth } from "@/auth" +import { createAiChatCompletion, getAiErrorMessage, parseAiChatPayload } from "@/shared/lib/ai" + +export const dynamic = "force-dynamic" + +const getStatusFromError = (message: string) => { + if (message === "Invalid payload" || message === "Messages are required") return 400 + if (message === "AI API key missing") return 500 + if (message === "Empty response") return 502 + return 502 +} + +export async function POST(req: Request) { + const session = await auth() + const userId = String(session?.user?.id ?? "").trim() + if (!userId) return NextResponse.json({ success: false, message: "Unauthorized" }, { status: 401 }) + + try { + const body = await req.json().catch(() => null) + const input = parseAiChatPayload(body) + const result = await createAiChatCompletion(input) + return NextResponse.json({ success: true, content: result.content, usage: result.usage }) + } catch (e) { + const message = getAiErrorMessage(e) + return NextResponse.json({ success: false, message }, { status: getStatusFromError(message) }) + } +} diff --git a/src/app/globals.css b/src/app/globals.css index dee0cc6..8a81f34 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -172,7 +172,7 @@ @apply border-border; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground h-screen overflow-hidden; font-feature-settings: "rlig" 1, "calt" 1; } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c0f1520..d26d192 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -20,6 +20,7 @@ export default function RootLayout({ { + const value = formData.get(key) + return typeof value === "string" ? value : undefined +} + +const failState = (message: string, errors?: Record): ActionState => ({ + success: false, + message, + errors, +}) + +const successState = (data: T, message?: string): ActionState => ({ + success: true, + message, + data, +}) + +const invalidFormState = ( + error: z.ZodError, + options?: { fallbackMessage?: string; useFirstMessage?: boolean } +): ActionState => { + const errors = error.flatten().fieldErrors + const fallbackMessage = options?.fallbackMessage ?? "Invalid form data" + const useFirstMessage = options?.useFirstMessage ?? true + const messages = Object.values(errors).flatMap((items) => items ?? []) + const firstMessage = messages.find((msg): msg is string => typeof msg === "string" && msg.length > 0) + return failState(useFirstMessage ? (firstMessage ?? fallbackMessage) : fallbackMessage, errors) +} + +const prepareExamCreateContext = async (input: { + subject: string + grade: string + difficulty: number + totalScore: number + durationMin: number + scheduledAt?: string | null +}) => { + const examId = createId() + const scheduled = input.scheduledAt || undefined + const resolvedNames = await resolveSubjectGradeNames({ + subjectId: input.subject, + gradeId: input.grade, + }) + const subjectName = resolvedNames.subjectName ?? input.subject + const gradeName = resolvedNames.gradeName ?? input.grade + const buildDescription = (options?: { questionCount?: number }) => buildExamDescription({ + subject: subjectName, + grade: gradeName, + difficulty: input.difficulty, + totalScore: input.totalScore, + durationMin: input.durationMin, + scheduledAt: scheduled, + questionCount: options?.questionCount, + }) + return { examId, scheduled, subjectName, gradeName, buildDescription } +} + +const loadAiDraftQuestionsAndStructure = async (input: { + rawAiQuestions: string | null + rawStructure: string | null + title: string + subject: string + grade: string + difficulty: number + totalScore: number + durationMin: number + aiSourceText?: string + aiQuestionCount?: number + aiProviderId?: string +}): Promise< + | { ok: true; generated: AiGeneratedQuestion[]; structure: AiGeneratedStructureNode[] } + | { ok: false; message: string } +> => { + if (input.rawAiQuestions) { + let parsedQuestions: unknown = null + try { + parsedQuestions = JSON.parse(input.rawAiQuestions) + } catch { + return { ok: false, message: "Invalid AI preview payload" } + } + const validated = z.array(AiInsertQuestionSchema).safeParse(parsedQuestions) + if (!validated.success || validated.data.length === 0) { + return { ok: false, message: "Invalid AI preview payload" } + } + const generated = validated.data.map((q) => ({ + id: q.id, + type: q.type, + difficulty: q.difficulty, + content: q.content, + score: q.score, + })) + let structure: AiGeneratedStructureNode[] = [] + if (input.rawStructure) { + try { + const parsedStructure = JSON.parse(input.rawStructure) + const validatedStructure = AiGeneratedStructureSchema.safeParse(parsedStructure) + if (validatedStructure.success) { + structure = validatedStructure.data + } else { + return { ok: false, message: "Invalid preview structure" } + } + } catch { + return { ok: false, message: "Invalid preview structure" } + } + } + if (structure.length === 0) { + structure = generated.map((q) => ({ + id: createId(), + type: "question", + questionId: q.id, + score: q.score, + })) + } + return { ok: true, generated, structure } + } + + const sourceText = input.aiSourceText?.trim() + if (!sourceText) { + return { ok: false, message: "Please analyze and preview before creating" } + } + const aiDraft = await generateAiCreateDraftFromSource({ + title: input.title, + subject: input.subject, + grade: input.grade, + difficulty: input.difficulty, + totalScore: input.totalScore, + durationMin: input.durationMin, + questionCount: input.aiQuestionCount, + sourceText, + aiProviderId: input.aiProviderId, + }) + if (!aiDraft.ok) { + return { ok: false, message: aiDraft.message } + } + return { ok: true, generated: aiDraft.generated, structure: aiDraft.structure } +} + +const prepareAiPreviewRequest = async (input: { + title?: string + subject?: string + grade?: string + difficulty?: number + totalScore?: number + durationMin?: number + aiSourceText: string + aiQuestionCount?: number + aiProviderId?: string +}) => { + const resolvedNames = await resolveSubjectGradeNames({ + subjectId: input.subject, + gradeId: input.grade, + }) + const title = input.title && input.title.trim().length > 0 ? input.title : "AI Exam" + const subjectName = input.subject ? resolvedNames.subjectName ?? input.subject : undefined + const gradeName = input.grade ? resolvedNames.gradeName ?? input.grade : undefined + return { + title, + subject: subjectName, + grade: gradeName, + difficulty: input.difficulty ?? 3, + totalScore: input.totalScore ?? 100, + durationMin: input.durationMin ?? 90, + questionCount: input.aiQuestionCount, + sourceText: input.aiSourceText, + aiProviderId: input.aiProviderId, + } +} + +const parseRegenerateAiQuestionInput = ( + formData: FormData +): + | { + ok: true + instruction: string + aiProviderId?: string + sourceText?: string + originalQuestion: z.infer + } + | { ok: false; state: ActionState } => { + const instruction = getStringValue(formData, "instruction")?.trim() + const aiProviderId = getStringValue(formData, "aiProviderId")?.trim() + const sourceText = getStringValue(formData, "sourceText")?.trim() + const questionJson = getStringValue(formData, "questionJson") + if (!instruction) { + return { ok: false, state: failState("Please enter rewrite instruction") } + } + if (!questionJson) { + return { ok: false, state: failState("No selected question data") } + } + try { + const parsedQuestion = JSON.parse(questionJson) as unknown + const validatedQuestion = AiQuestionSchema.safeParse(parsedQuestion) + if (!validatedQuestion.success) { + return { ok: false, state: failState("Selected question format invalid") } + } + return { + ok: true, + instruction, + aiProviderId, + sourceText, + originalQuestion: validatedQuestion.data, + } + } catch { + return { ok: false, state: failState("Selected question format invalid") } + } +} + export async function createExamAction( prevState: ActionState | null, formData: FormData @@ -34,72 +256,235 @@ export async function createExamAction( const rawQuestions = formData.get("questionsJson") as string | null const parsed = ExamCreateSchema.safeParse({ - title: formData.get("title"), - subject: formData.get("subject"), - grade: formData.get("grade"), - difficulty: formData.get("difficulty"), - totalScore: formData.get("totalScore"), - durationMin: formData.get("durationMin"), - scheduledAt: formData.get("scheduledAt"), + title: getStringValue(formData, "title"), + subject: getStringValue(formData, "subject"), + grade: getStringValue(formData, "grade"), + difficulty: getStringValue(formData, "difficulty"), + totalScore: getStringValue(formData, "totalScore"), + durationMin: getStringValue(formData, "durationMin"), + scheduledAt: getStringValue(formData, "scheduledAt") ?? null, questions: rawQuestions ? JSON.parse(rawQuestions) : [], }) if (!parsed.success) { - return { - success: false, - message: "Invalid form data", - errors: parsed.error.flatten().fieldErrors, - } + return invalidFormState(parsed.error, { useFirstMessage: false }) } const input = parsed.data - - const examId = createId() - const scheduled = input.scheduledAt || undefined - - // Retrieve names for JSON description (to maintain compatibility) - const subjectRecord = await db.query.subjects.findFirst({ - where: eq(subjects.id, input.subject), - }) - const gradeRecord = await db.query.grades.findFirst({ - where: eq(grades.id, input.grade), - }) - - const meta = { - subject: subjectRecord?.name ?? input.subject, - grade: gradeRecord?.name ?? input.grade, + const context = await prepareExamCreateContext({ + subject: input.subject, + grade: input.grade, difficulty: input.difficulty, totalScore: input.totalScore, durationMin: input.durationMin, - scheduledAt: scheduled ?? undefined, - } + scheduledAt: input.scheduledAt, + }) + const description = context.buildDescription() try { const user = await getCurrentUser() - await db.insert(exams).values({ - id: examId, + await persistExamDraft({ + examId: context.examId, title: input.title, - description: JSON.stringify(meta), creatorId: user?.id ?? "user_teacher_math", subjectId: input.subject, gradeId: input.grade, - startTime: scheduled ? new Date(scheduled) : null, - status: "draft", + scheduledAt: context.scheduled, + description, }) } catch (error) { console.error("Failed to create exam:", error) - return { - success: false, - message: "Database error: Failed to create exam", - } + return failState("Database error: Failed to create exam") } revalidatePath("/teacher/exams/all") - return { - success: true, - message: "Exam created successfully.", - data: examId, + return successState(context.examId, "Exam created successfully.") +} + +const AiExamCreateSchema = ExamCreateSchema.extend({ + aiSourceText: z.string().optional(), + aiQuestionCount: z.coerce.number().int().min(1).max(200).optional(), + aiProviderId: z.string().min(1).optional(), +}) + +const AiExamPreviewSchema = z.object({ + title: z.string().optional(), + subject: z.string().optional(), + grade: z.string().optional(), + difficulty: z.coerce.number().int().min(1).max(5).optional(), + totalScore: z.coerce.number().int().min(1).optional(), + durationMin: z.coerce.number().int().min(1).optional(), + aiSourceText: z.string().min(1), + aiQuestionCount: z.coerce.number().int().min(1).max(200).optional(), + aiProviderId: z.string().min(1).optional(), +}) + +export async function createAiExamAction( + prevState: ActionState | null, + formData: FormData +): Promise> { + const rawQuestions = formData.get("questionsJson") as string | null + const rawAiQuestions = formData.get("aiQuestionsJson") as string | null + const rawStructure = formData.get("structureJson") as string | null + const aiSourceTextRaw = formData.get("aiSourceText") + const aiQuestionCountRaw = formData.get("aiQuestionCount") + const aiProviderIdRaw = formData.get("aiProviderId") + + const parsed = AiExamCreateSchema.safeParse({ + title: getStringValue(formData, "title"), + subject: getStringValue(formData, "subject"), + grade: getStringValue(formData, "grade"), + difficulty: getStringValue(formData, "difficulty"), + totalScore: getStringValue(formData, "totalScore"), + durationMin: getStringValue(formData, "durationMin"), + scheduledAt: getStringValue(formData, "scheduledAt") ?? null, + questions: rawQuestions ? JSON.parse(rawQuestions) : [], + aiSourceText: typeof aiSourceTextRaw === "string" ? aiSourceTextRaw.trim() : undefined, + aiQuestionCount: typeof aiQuestionCountRaw === "string" && aiQuestionCountRaw.trim().length > 0 + ? aiQuestionCountRaw + : undefined, + aiProviderId: typeof aiProviderIdRaw === "string" && aiProviderIdRaw.trim().length > 0 + ? aiProviderIdRaw + : undefined, + }) + + if (!parsed.success) { + return invalidFormState(parsed.error) + } + + const input = parsed.data + if (!rawAiQuestions && !input.aiSourceText) { + return failState("Please analyze and preview before creating") + } + const context = await prepareExamCreateContext({ + subject: input.subject, + grade: input.grade, + difficulty: input.difficulty, + totalScore: input.totalScore, + durationMin: input.durationMin, + scheduledAt: input.scheduledAt, + }) + + const user = await getCurrentUser() + const aiDraftResult = await loadAiDraftQuestionsAndStructure({ + rawAiQuestions, + rawStructure, + title: input.title, + subject: context.subjectName, + grade: context.gradeName, + difficulty: input.difficulty, + totalScore: input.totalScore, + durationMin: input.durationMin, + aiSourceText: input.aiSourceText, + aiQuestionCount: input.aiQuestionCount, + aiProviderId: input.aiProviderId, + }) + if (!aiDraftResult.ok) { + return failState(aiDraftResult.message) + } + const { generated, structure } = aiDraftResult + + const questionCount = generated.length + const description = context.buildDescription({ questionCount }) + + try { + await persistAiGeneratedExamDraft({ + examId: context.examId, + title: input.title, + creatorId: user?.id ?? "user_teacher_math", + subjectId: input.subject, + gradeId: input.grade, + scheduledAt: context.scheduled, + description, + structure, + generated, + }) + } catch (error) { + console.error("Failed to create exam:", error) + return failState("Database error: Failed to create exam") + } + + revalidatePath("/teacher/exams/all") + + return successState(context.examId, "Exam created successfully.") +} + +export async function previewAiExamAction( + prevState: ActionState | null, + formData: FormData +): Promise> { + const aiSourceTextRaw = formData.get("aiSourceText") + const aiQuestionCountRaw = formData.get("aiQuestionCount") + const aiProviderIdRaw = formData.get("aiProviderId") + + const sourceText = typeof aiSourceTextRaw === "string" ? aiSourceTextRaw.trim() : "" + if (!sourceText) { + return failState("Please paste the full exam text first", { + aiSourceText: ["Please paste the full exam text first"], + }) + } + + const parsed = AiExamPreviewSchema.safeParse({ + title: getStringValue(formData, "title"), + subject: getStringValue(formData, "subject"), + grade: getStringValue(formData, "grade"), + difficulty: getStringValue(formData, "difficulty"), + totalScore: getStringValue(formData, "totalScore"), + durationMin: getStringValue(formData, "durationMin"), + aiSourceText: sourceText, + aiQuestionCount: typeof aiQuestionCountRaw === "string" && aiQuestionCountRaw.trim().length > 0 + ? aiQuestionCountRaw + : undefined, + aiProviderId: typeof aiProviderIdRaw === "string" && aiProviderIdRaw.trim().length > 0 + ? aiProviderIdRaw + : undefined, + }) + + if (!parsed.success) { + return invalidFormState(parsed.error) + } + + const input = parsed.data + const previewRequest = await prepareAiPreviewRequest(input) + const aiDraft = await generateAiPreviewData(previewRequest) + if (!aiDraft.ok) { + return failState(aiDraft.message) + } + return successState({ ...aiDraft.data, rawOutput: aiDraft.rawOutput }) +} + +export async function regenerateAiQuestionAction( + prevState: ActionState | null, + formData: FormData +): Promise> { + const parsedInput = parseRegenerateAiQuestionInput(formData) + if (!parsedInput.ok) { + return parsedInput.state + } + const { instruction, aiProviderId, sourceText, originalQuestion } = parsedInput + + const originalDifficulty = originalQuestion.difficulty ?? 3 + const originalScore = originalQuestion.score ?? 0 + + try { + const result = await regenerateAiQuestionByInstruction({ + instruction, + originalQuestion, + sourceText, + aiProviderId, + }) + if (!result.ok) { + return failState(result.message) + } + return successState({ + type: result.data.type, + difficulty: result.data.difficulty ?? originalDifficulty, + score: result.data.score ?? originalScore, + content: result.data.content, + }) + } catch { + return failState("AI question format invalid") } } @@ -134,11 +519,10 @@ export async function updateExamAction( }) if (!parsed.success) { - return { - success: false, - message: "Invalid update data", - errors: parsed.error.flatten().fieldErrors, - } + return invalidFormState(parsed.error, { + fallbackMessage: "Invalid update data", + useFirstMessage: false, + }) } const { examId, questions, structure, status } = parsed.data @@ -168,19 +552,12 @@ export async function updateExamAction( } } catch { - return { - success: false, - message: "Database error: Failed to update exam", - } + return failState("Database error: Failed to update exam") } revalidatePath("/teacher/exams/all") - return { - success: true, - message: "Exam updated", - data: examId, - } + return successState(examId, "Exam updated") } const ExamDeleteSchema = z.object({ @@ -196,11 +573,10 @@ export async function deleteExamAction( }) if (!parsed.success) { - return { - success: false, - message: "Invalid delete data", - errors: parsed.error.flatten().fieldErrors, - } + return invalidFormState(parsed.error, { + fallbackMessage: "Invalid delete data", + useFirstMessage: false, + }) } const { examId } = parsed.data @@ -208,19 +584,12 @@ export async function deleteExamAction( try { await db.delete(exams).where(eq(exams.id, examId)) } catch { - return { - success: false, - message: "Database error: Failed to delete exam", - } + return failState("Database error: Failed to delete exam") } revalidatePath("/teacher/exams/all") - return { - success: true, - message: "Exam deleted", - data: examId, - } + return successState(examId, "Exam deleted") } const ExamDuplicateSchema = z.object({ @@ -236,11 +605,10 @@ export async function duplicateExamAction( }) if (!parsed.success) { - return { - success: false, - message: "Invalid duplicate data", - errors: parsed.error.flatten().fieldErrors, - } + return invalidFormState(parsed.error, { + fallbackMessage: "Invalid duplicate data", + useFirstMessage: false, + }) } const { examId } = parsed.data @@ -255,10 +623,7 @@ export async function duplicateExamAction( }) if (!source) { - return { - success: false, - message: "Exam not found", - } + return failState("Exam not found") } const newExamId = createId() @@ -289,22 +654,17 @@ export async function duplicateExamAction( } }) } catch { - return { - success: false, - message: "Database error: Failed to duplicate exam", - } + return failState("Database error: Failed to duplicate exam") } revalidatePath("/teacher/exams/all") - return { - success: true, - message: "Exam duplicated", - data: newExamId, - } + return successState(newExamId, "Exam duplicated") } -export async function getExamPreviewAction(examId: string) { +export async function getExamPreviewAction( + examId: string +): Promise }>> { try { const exam = await db.query.exams.findFirst({ where: eq(exams.id, examId), @@ -319,23 +679,17 @@ export async function getExamPreviewAction(examId: string) { }) if (!exam) { - return { success: false, message: "Exam not found" } + return failState<{ structure: unknown; questions: Array<{ id: string }> }>("Exam not found") } - - // Extract questions from the relation - const questions = exam.questions.map(eq => eq.question) - - return { - success: true, - data: { + const questions = exam.questions.map((eq) => eq.question) + return successState({ structure: exam.structure, - questions: questions - } + questions, + }) + } catch (error) { + console.error(error) + return failState<{ structure: unknown; questions: Array<{ id: string }> }>("Failed to load exam preview") } -} catch (error) { - console.error(error) - return { success: false, message: "Failed to load exam preview" } -} } export async function getSubjectsAction(): Promise> { @@ -344,16 +698,10 @@ export async function getSubjectsAction(): Promise [asc(subjects.order), asc(subjects.name)], }) - return { - success: true, - data: allSubjects.map((s) => ({ id: s.id, name: s.name })), - } + return successState(allSubjects.map((s) => ({ id: s.id, name: s.name }))) } catch (error) { console.error("Failed to fetch subjects:", error) - return { - success: false, - message: "Failed to load subjects", - } + return failState<{ id: string; name: string }[]>("Failed to load subjects") } } @@ -363,16 +711,10 @@ export async function getGradesAction(): Promise [asc(grades.order), asc(grades.name)], }) - return { - success: true, - data: allGrades.map((g) => ({ id: g.id, name: g.name })), - } + return successState(allGrades.map((g) => ({ id: g.id, name: g.name }))) } catch (error) { console.error("Failed to fetch grades:", error) - return { - success: false, - message: "Failed to load grades", - } + return failState<{ id: string; name: string }[]>("Failed to load grades") } } diff --git a/src/modules/exams/ai-pipeline.ts b/src/modules/exams/ai-pipeline.ts new file mode 100644 index 0000000..d7f95d2 --- /dev/null +++ b/src/modules/exams/ai-pipeline.ts @@ -0,0 +1,912 @@ +import { createId } from "@paralleldrive/cuid2" +import { z } from "zod" +import { createAiChatCompletion, getAiErrorMessage } from "@/shared/lib/ai" +import { env } from "@/env.mjs" + +const AiSubQuestionSchema = z.object({ + id: z.string().min(1).optional(), + text: z.string().min(1), + answer: z.string().min(1).optional(), + score: z.coerce.number().int().min(0).optional(), +}) + +const AiQuestionContentSchema = z.object({ + text: z.string().min(1), + options: z + .array( + z.object({ + id: z.string().min(1).optional(), + text: z.string().min(1), + isCorrect: z.boolean().optional(), + }) + ) + .optional(), + subQuestions: z.array(AiSubQuestionSchema).optional(), +}) + +export const AiQuestionSchema = z.object({ + type: z.enum(["single_choice", "multiple_choice", "text", "judgment"]), + difficulty: z.coerce.number().int().min(1).max(5).optional(), + score: z.coerce.number().int().min(0).optional(), + content: AiQuestionContentSchema, +}) + +export const AiInsertQuestionSchema = z.object({ + id: z.string().min(1), + type: z.enum(["single_choice", "multiple_choice", "text", "judgment"]), + difficulty: z.coerce.number().int().min(1).max(5), + score: z.coerce.number().int().min(0), + content: AiQuestionContentSchema.extend({ + options: z + .array( + z.object({ + id: z.string().min(1), + text: z.string().min(1), + isCorrect: z.boolean().optional(), + }) + ) + .optional(), + subQuestions: z.array( + AiSubQuestionSchema.extend({ + id: z.string().min(1), + }) + ).optional(), + }), +}) + +const AiSectionSchema = z.object({ + title: z.string().min(1), + questions: z.array(AiQuestionSchema).min(1), +}) + +const AiExamResponseSchema = z.object({ + title: z.string().optional(), + questions: z.array(AiQuestionSchema).optional(), + sections: z.array(AiSectionSchema).optional(), +}) + +const sanitizeJsonCandidate = (value: string) => value + .replace(/\[\s*\.\.\.\s*\]/g, "[]") + .replace(/\{\s*\.\.\.\s*\}/g, "{}") + .trim() + +const tryParseJson = (value: string): unknown | null => { + const sanitized = sanitizeJsonCandidate(value) + if (!sanitized) return null + try { + return JSON.parse(sanitized) + } catch { + return null + } +} + +const extractBalancedJsonSegment = (value: string): string | null => { + const startBrace = value.indexOf("{") + const startBracket = value.indexOf("[") + const start = + startBrace === -1 + ? startBracket + : startBracket === -1 + ? startBrace + : Math.min(startBrace, startBracket) + if (start === -1) return null + const opening = value[start] + const closing = opening === "{" ? "}" : "]" + let depth = 0 + let inString = false + let escaped = false + for (let i = start; i < value.length; i += 1) { + const char = value[i] + if (inString) { + if (escaped) { + escaped = false + } else if (char === "\\") { + escaped = true + } else if (char === "\"") { + inString = false + } + continue + } + if (char === "\"") { + inString = true + continue + } + if (char === opening) { + depth += 1 + continue + } + if (char === closing) { + depth -= 1 + if (depth === 0) { + return value.slice(start, i + 1) + } + } + } + return null +} + +const extractJson = (raw: string): unknown => { + const trimmed = raw.trim() + const candidates: string[] = [] + const fencedMatches = [...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)```/ig)] + if (fencedMatches.length > 0) { + candidates.push(...fencedMatches.map((match) => (match[1] ?? "").trim())) + } + candidates.push(trimmed) + for (const candidate of candidates) { + const direct = tryParseJson(candidate) + if (direct !== null) return direct + const segment = extractBalancedJsonSegment(candidate) + if (!segment) continue + const parsed = tryParseJson(segment) + if (parsed !== null) return parsed + } + throw new Error("Invalid AI response") +} + +const AI_JSON_REPAIR_PROMPT = [ + "You are a JSON repair engine.", + "Fix the provided invalid JSON into valid JSON only.", + "Keep the original structure and values as much as possible.", + "Do not use placeholders such as ... or [...].", + "Return JSON only without markdown.", +].join("\n") + +const repairJson = async (raw: string, providerId?: string) => { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId, + messages: [ + { role: "system" as const, content: AI_JSON_REPAIR_PROMPT }, + { role: "user" as const, content: raw }, + ], + temperature: 0, + maxTokens: 4000, + }) + return extractJson(aiResult.content) +} + +const parseAiResponse = async (raw: string, providerId?: string) => { + try { + return extractJson(raw) + } catch { + return repairJson(raw, providerId) + } +} + +const normalizeScores = (scores: number[], totalScore: number) => { + if (scores.length === 0) return [] + const sum = scores.reduce((acc, s) => acc + s, 0) + if (sum <= 0) { + const base = Math.floor(totalScore / scores.length) + const remainder = totalScore - base * scores.length + return scores.map((_, idx) => base + (idx < remainder ? 1 : 0)) + } + const scaled = scores.map((s) => Math.max(0, Math.round((s / sum) * totalScore))) + let diff = totalScore - scaled.reduce((acc, s) => acc + s, 0) + let i = 0 + while (diff !== 0 && i < scaled.length * 2) { + const idx = i % scaled.length + if (diff > 0) { + scaled[idx] += 1 + diff -= 1 + } else if (scaled[idx] > 0) { + scaled[idx] -= 1 + diff += 1 + } + i += 1 + } + return scaled +} + +const AI_EXAM_SYSTEM_PROMPT = [ + "You are an exam parsing engine.", + "Parse the provided exam text and output JSON only.", + "Allowed question types: single_choice, multiple_choice, judgment, text.", + "Preserve the original order and sectioning if present.", + "Escape double quotes inside string values.", + "Output schema:", + "{", + ' "sections": [', + ' { "title": "Section Title", "questions": [', + ' { "type": "single_choice", "difficulty": 1, "score": 5, "content": { "text": "...", "options": [ { "id": "A", "text": "...", "isCorrect": true } ] } }', + " ] }", + " ]", + "}", + "For grouped blanks or one prompt with multiple small questions, keep one parent question and place each child item into content.subQuestions.", + 'content.subQuestions item schema: { "id": "1", "text": "lǎn duò( )", "answer": "懒惰", "score": 1 }', + "If you do not need sections, return { \"questions\": [] } or include real question items.", + "Never output placeholders like ..., [...], or {...}.", + "Return JSON only without markdown.", +].join("\n") + +const AI_REWRITE_QUESTION_SYSTEM_PROMPT = [ + "You are a question rewriting engine.", + "Rewrite exactly one question based on teacher instruction.", + "Return JSON only without markdown.", + "Allowed question types: single_choice, multiple_choice, judgment, text.", + "Output schema:", + "{", + ' "type": "single_choice | multiple_choice | judgment | text",', + ' "difficulty": 1,', + ' "score": 5,', + ' "content": { "text": "...", "options": [ { "id": "A", "text": "...", "isCorrect": true } ], "subQuestions": [ { "id": "1", "text": "...", "answer": "...", "score": 1 } ] }', + "}", + "For judgment/text, options can be omitted. Keep subQuestions when original question has multiple child items.", + "Never output placeholders like ..., [...], or {...}.", +].join("\n") + +const AiStructureQuestionSchema = z.object({ + text: z.string().min(1), + score: z.coerce.number().int().min(0).optional(), +}) + +const AiStructureSectionSchema = z.object({ + title: z.string().min(1), + questions: z.array(AiStructureQuestionSchema).min(1), +}) + +const AiStructureResponseSchema = z.object({ + title: z.string().optional(), + sections: z.array(AiStructureSectionSchema).optional(), + questions: z.array(AiStructureQuestionSchema).optional(), +}) + +const AiSourceValidationSchema = z.object({ + valid: z.boolean(), + reason: z.string().optional(), +}) + +const AI_EXAM_STRUCTURE_SYSTEM_PROMPT = [ + "You are an exam splitter engine.", + "Split the provided exam text into ordered question units quickly.", + "Do not deeply analyze choices or answers in this step.", + "Keep original sectioning and question order.", + "If one stem contains multiple numbered sub-items, keep them in one question unit and include all sub-items in the same text.", + "Do not split one parent question into several child-only units.", + "Output JSON only.", + "Output schema:", + "{", + ' "title": "Optional title",', + ' "sections": [', + ' { "title": "Section Title", "questions": [', + ' { "text": "Original full question text", "score": 5 }', + " ] }", + " ]", + "}", + "If no sections, return:", + '{ "questions": [ { "text": "Original full question text", "score": 5 } ] }', + "Never output placeholders like ..., [...], or {...}.", +].join("\n") + +const AI_EXAM_SOURCE_VALIDATION_SYSTEM_PROMPT = [ + "You are an exam text validator.", + "Judge whether the input text is readable and likely a normal exam/question text.", + "Reject garbled text, random symbols, severely disordered fragments, or meaningless content.", + "Do not require strict section formatting. Focus only on readability and whether it resembles exam questions.", + "Return JSON only without markdown.", + "Output schema:", + '{ "valid": true, "reason": "short reason" }', +].join("\n") + +const AI_QUESTION_DETAIL_SYSTEM_PROMPT = [ + "You are an exam question detail parser.", + "Given one split question text, output one structured question JSON only.", + "Allowed question types: single_choice, multiple_choice, judgment, text.", + "For one stem with multiple child sub-items, keep one parent content.text and place child items in content.subQuestions.", + "Use exact key name content.subQuestions (camelCase).", + "Output schema:", + "{", + ' "type": "single_choice | multiple_choice | judgment | text",', + ' "difficulty": 1,', + ' "score": 5,', + ' "content": { "text": "...", "options": [ { "id": "A", "text": "...", "isCorrect": true } ], "subQuestions": [ { "id": "1", "text": "...", "answer": "...", "score": 1 } ] }', + "}", + "For judgment/text, options can be omitted.", + "Never output placeholders like ..., [...], or {...}.", +].join("\n") + +const buildAiMessages = (input: { + title?: string + subject?: string + grade?: string + difficulty?: number + totalScore?: number + durationMin?: number + questionCount?: number + sourceText: string +}) => { + const userLines = [ + input.title ? `Title: ${input.title}` : "", + input.subject ? `Subject: ${input.subject}` : "", + input.grade ? `Grade: ${input.grade}` : "", + typeof input.difficulty === "number" ? `Difficulty: ${input.difficulty}` : "", + typeof input.totalScore === "number" ? `Total Score: ${input.totalScore}` : "", + typeof input.durationMin === "number" ? `Duration (min): ${input.durationMin}` : "", + input.questionCount ? `Question Count: ${input.questionCount}` : "", + `Source Exam Text:\n${input.sourceText}`, + ] + const userContent = userLines.filter((l) => l.length > 0).join("\n") + return [ + { role: "system" as const, content: AI_EXAM_SYSTEM_PROMPT }, + { role: "user" as const, content: userContent }, + ] +} + +type AiDraftResult = + | { ok: true; data: z.infer; rawOutput: string } + | { ok: false; message: string } + +type AiStructureDraftResult = + | { ok: true; data: z.infer; rawOutput: string } + | { ok: false; message: string } + +const requestAiExamDraft = async (input: { + title?: string + subject?: string + grade?: string + difficulty?: number + totalScore?: number + durationMin?: number + questionCount?: number + sourceText: string + aiProviderId?: string +}): Promise => { + try { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId: input.aiProviderId, + messages: buildAiMessages(input), + temperature: 0.7, + maxTokens: 4000, + }) + const rawOutput = aiResult.content + const data = await parseAiResponse(rawOutput, input.aiProviderId) + const validated = AiExamResponseSchema.safeParse(data) + if (!validated.success) { + return { ok: false, message: "AI response format invalid" } + } + return { ok: true, data: validated.data, rawOutput } + } catch (error) { + return { ok: false, message: getAiErrorMessage(error) } + } +} + +const requestAiExamStructureDraft = async (input: { + title?: string + subject?: string + grade?: string + difficulty?: number + totalScore?: number + durationMin?: number + questionCount?: number + sourceText: string + aiProviderId?: string +}): Promise => { + try { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId: input.aiProviderId, + messages: [ + { role: "system" as const, content: AI_EXAM_STRUCTURE_SYSTEM_PROMPT }, + { role: "user" as const, content: buildAiMessages(input)[1].content }, + ], + temperature: 0.2, + maxTokens: 4000, + }) + const rawOutput = aiResult.content + const data = await parseAiResponse(rawOutput, input.aiProviderId) + const validated = AiStructureResponseSchema.safeParse(data) + if (!validated.success) { + return { ok: false, message: "AI response format invalid" } + } + return { ok: true, data: validated.data, rawOutput } + } catch (error) { + return { ok: false, message: getAiErrorMessage(error) } + } +} + +type SplitQuestionItem = { + sectionIndex: number | null + sectionTitle?: string + text: string + score?: number +} + +const validateExamSourceText = async (input: { sourceText: string; aiProviderId?: string }) => { + const text = input.sourceText.trim() + if (!text) { + return { ok: false as const, message: "请先粘贴试卷文本" } + } + const userContent = [ + "请判断下面文本是否是可读、正常的题目/试卷内容(不是乱码、随机字符或混乱文本)。", + `文本内容:\n${text}`, + ].join("\n\n") + try { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId: input.aiProviderId, + messages: [ + { role: "system" as const, content: AI_EXAM_SOURCE_VALIDATION_SYSTEM_PROMPT }, + { role: "user" as const, content: userContent }, + ], + temperature: 0, + maxTokens: 300, + }) + const parsed = await parseAiResponse(aiResult.content, input.aiProviderId) + const validated = AiSourceValidationSchema.safeParse(parsed) + if (!validated.success) { + return { ok: false as const, message: "试卷文本校验失败,请重试" } + } + if (!validated.data.valid) { + return { + ok: false as const, + message: validated.data.reason?.trim() || "识别为乱码或混乱文本,请粘贴清晰完整的题目内容", + } + } + return { ok: true as const } + } catch (error) { + return { ok: false as const, message: getAiErrorMessage(error) } + } +} + +const splitStructureItems = (draft: z.infer) => { + const hasSections = Array.isArray(draft.sections) && draft.sections.length > 0 + if (!hasSections) { + return (draft.questions ?? []).map((q) => ({ + sectionIndex: null, + sectionTitle: undefined, + text: q.text, + score: q.score, + } satisfies SplitQuestionItem)) + } + const rows: SplitQuestionItem[] = [] + draft.sections!.forEach((section, sectionIndex) => { + section.questions.forEach((q) => { + rows.push({ + sectionIndex, + sectionTitle: section.title, + text: q.text, + score: q.score, + }) + }) + }) + return rows +} + +const mapWithConcurrency = async ( + items: T[], + concurrency: number, + worker: (item: T, index: number) => Promise +) => { + const results = new Array(items.length) + let cursor = 0 + const runWorker = async () => { + while (cursor < items.length) { + const index = cursor + cursor += 1 + results[index] = await worker(items[index], index) + } + } + const workers = Array.from({ length: Math.max(1, Math.min(concurrency, items.length)) }, () => runWorker()) + await Promise.all(workers) + return results +} + +const parseQuestionDetail = async (input: { + item: SplitQuestionItem + subject?: string + grade?: string + difficulty: number + aiProviderId?: string +}) => { + const normalizeQuestionCandidate = (value: unknown): unknown => { + if (!value || typeof value !== "object") return value + const record = value as Record + const contentRaw = record.content + if (!contentRaw || typeof contentRaw !== "object") return value + const content = contentRaw as Record + const normalizedSubQuestions = Array.isArray(content.subQuestions) + ? content.subQuestions + : Array.isArray(content.subquestions) + ? content.subquestions + : Array.isArray(content.sub_questions) + ? content.sub_questions + : undefined + if (!normalizedSubQuestions) return value + return { + ...record, + content: { + ...content, + subQuestions: normalizedSubQuestions, + }, + } + } + + const userContent = [ + input.subject ? `Subject: ${input.subject}` : "", + input.grade ? `Grade: ${input.grade}` : "", + `Question Text:\n${input.item.text}`, + ].filter((line) => line.length > 0).join("\n\n") + + try { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId: input.aiProviderId, + messages: [ + { role: "system" as const, content: AI_QUESTION_DETAIL_SYSTEM_PROMPT }, + { role: "user" as const, content: userContent }, + ], + temperature: 0.4, + maxTokens: 1200, + }) + const parsed = await parseAiResponse(aiResult.content, input.aiProviderId) + const candidate = parsed && typeof parsed === "object" && "question" in parsed + ? (parsed as { question: unknown }).question + : parsed + const validated = AiQuestionSchema.safeParse(normalizeQuestionCandidate(candidate)) + if (validated.success) { + const q = validated.data + return { + type: q.type, + difficulty: q.difficulty ?? input.difficulty, + score: q.score ?? input.item.score ?? 0, + content: q.content, + } satisfies z.infer + } + } catch { + } + + return { + type: "text", + difficulty: input.difficulty, + score: input.item.score ?? 0, + content: { text: input.item.text }, + } satisfies z.infer +} + +const buildQuestionContent = (q: z.infer) => { + const base = { text: q.content.text } + const subQuestions = Array.isArray(q.content.subQuestions) + ? q.content.subQuestions.map((item, index) => ({ + id: item.id ?? String(index + 1), + text: item.text, + answer: item.answer, + score: item.score, + })) + : [] + if (q.type === "single_choice" || q.type === "multiple_choice") { + const options = (q.content.options ?? []).map((opt, idx) => ({ + id: opt.id ?? String.fromCharCode(65 + idx), + text: opt.text, + isCorrect: opt.isCorrect ?? false, + })) + if (options.length > 0 && subQuestions.length > 0) return { ...base, options, subQuestions } + if (options.length > 0) return { ...base, options } + if (subQuestions.length > 0) return { ...base, subQuestions } + return base + } + if (subQuestions.length > 0) return { ...base, subQuestions } + return base +} + +type AiPreviewQuestion = { + id: string + type: z.infer["type"] + difficulty: number + score: number + content: ReturnType +} + +export type AiPreviewData = { + title: string + rawOutput?: string + sections?: Array<{ + id: string + title: string + questions: AiPreviewQuestion[] + }> + questions?: AiPreviewQuestion[] +} + +export type AiRewriteQuestionData = { + type: z.infer["type"] + difficulty: number + score: number + content: ReturnType +} + +export type AiGeneratedQuestion = { + id: string + type: z.infer["type"] + difficulty: number + score: number + content: ReturnType +} + +export type AiGeneratedStructureNode = { + id: string + type: "group" | "question" + title?: string + questionId?: string + score?: number + children?: AiGeneratedStructureNode[] +} + +export const AiGeneratedStructureNodeSchema: z.ZodType = z.lazy(() => z.object({ + id: z.string().min(1), + type: z.enum(["group", "question"]), + title: z.string().optional(), + questionId: z.string().optional(), + score: z.coerce.number().int().min(0).optional(), + children: z.array(AiGeneratedStructureNodeSchema).optional(), +})) + +export const AiGeneratedStructureSchema = z.array(AiGeneratedStructureNodeSchema) + +const buildPreviewPayload = ( + aiParsed: z.infer, + input: { + title: string + difficulty: number + totalScore: number + questionCount?: number + } +): AiPreviewData => { + const hasSections = Array.isArray(aiParsed.sections) && aiParsed.sections.length > 0 + const baseQuestions = hasSections ? aiParsed.sections!.flatMap((s) => s.questions) : aiParsed.questions ?? [] + const limit = input.questionCount + let sections = aiParsed.sections + let flatQuestions = baseQuestions + + if (typeof limit === "number" && limit > 0) { + if (hasSections) { + let remaining = limit + sections = aiParsed.sections!.map((s) => { + if (remaining <= 0) return { ...s, questions: [] } + const sliced = s.questions.slice(0, remaining) + remaining -= sliced.length + return { ...s, questions: sliced } + }).filter((s) => s.questions.length > 0) + flatQuestions = sections.flatMap((s) => s.questions) + } else { + flatQuestions = baseQuestions.slice(0, limit) + } + } + + const scores = normalizeScores( + flatQuestions.map((q) => q.score ?? 0), + input.totalScore + ) + + let scoreIndex = 0 + const toPreviewQuestion = (q: z.infer): AiPreviewQuestion => ({ + id: createId(), + type: q.type, + difficulty: q.difficulty ?? input.difficulty, + score: scores[scoreIndex++] ?? 0, + content: buildQuestionContent(q), + }) + + if (hasSections && sections && sections.length > 0) { + return { + title: aiParsed.title ?? input.title, + sections: sections.map((section) => ({ + id: createId(), + title: section.title, + questions: section.questions.map((q) => toPreviewQuestion(q)), + })), + } + } + + return { + title: aiParsed.title ?? input.title, + questions: flatQuestions.map((q) => toPreviewQuestion(q)), + } +} + +const previewToDraft = (preview: AiPreviewData) => { + const generated: AiGeneratedQuestion[] = [] + const structure: AiGeneratedStructureNode[] = [] + if (Array.isArray(preview.sections) && preview.sections.length > 0) { + for (const section of preview.sections) { + const children: AiGeneratedStructureNode[] = [] + for (const question of section.questions) { + generated.push({ + id: question.id, + type: question.type, + difficulty: question.difficulty, + score: question.score, + content: question.content, + }) + children.push({ + id: createId(), + type: "question", + questionId: question.id, + score: question.score, + }) + } + structure.push({ + id: section.id || createId(), + type: "group", + title: section.title, + children, + }) + } + return { generated, structure } + } + for (const question of preview.questions ?? []) { + generated.push({ + id: question.id, + type: question.type, + difficulty: question.difficulty, + score: question.score, + content: question.content, + }) + structure.push({ + id: createId(), + type: "question", + questionId: question.id, + score: question.score, + }) + } + return { generated, structure } +} + +export async function generateAiPreviewData(input: { + title: string + subject?: string + grade?: string + difficulty: number + totalScore: number + durationMin: number + questionCount?: number + sourceText: string + aiProviderId?: string +}) { + const sourceValidation = await validateExamSourceText({ + sourceText: input.sourceText, + aiProviderId: input.aiProviderId, + }) + if (!sourceValidation.ok) { + return { ok: false as const, message: sourceValidation.message } + } + const structureDraft = await requestAiExamStructureDraft(input) + if (!structureDraft.ok) return structureDraft + const splitItems = splitStructureItems(structureDraft.data) + const limitedItems = typeof input.questionCount === "number" && input.questionCount > 0 + ? splitItems.slice(0, input.questionCount) + : splitItems + if (limitedItems.length === 0) { + return { ok: false as const, message: "AI returned no questions" } + } + const detailedQuestions = await mapWithConcurrency(limitedItems, 6, (item) => parseQuestionDetail({ + item, + subject: input.subject, + grade: input.grade, + difficulty: input.difficulty, + aiProviderId: input.aiProviderId, + })) + const hasSectionStructure = limitedItems.some((item) => item.sectionIndex !== null) + const aiParsed: z.infer = hasSectionStructure + ? { + title: structureDraft.data.title ?? input.title, + sections: (() => { + const sectionMap = new Map[] }>() + limitedItems.forEach((item, index) => { + if (item.sectionIndex === null) return + const existed = sectionMap.get(item.sectionIndex) + const question = detailedQuestions[index] + if (existed) { + existed.questions.push(question) + return + } + sectionMap.set(item.sectionIndex, { + title: item.sectionTitle || `Section ${item.sectionIndex + 1}`, + questions: [question], + }) + }) + return Array.from(sectionMap.entries()) + .sort((a, b) => a[0] - b[0]) + .map(([, section]) => section) + })(), + questions: undefined, + } + : { + title: structureDraft.data.title ?? input.title, + questions: detailedQuestions, + sections: undefined, + } + const payload = buildPreviewPayload(aiParsed, input) + return { + ok: true as const, + data: payload, + rawOutput: structureDraft.rawOutput, + } +} + +export async function generateAiCreateDraftFromSource(input: { + title: string + subject?: string + grade?: string + difficulty: number + totalScore: number + durationMin: number + questionCount?: number + sourceText: string + aiProviderId?: string +}) { + const preview = await generateAiPreviewData(input) + if (!preview.ok) { + return preview + } + const draft = previewToDraft(preview.data) + return { + ok: true as const, + generated: draft.generated, + structure: draft.structure, + rawOutput: preview.rawOutput, + } +} + +export async function regenerateAiQuestionByInstruction(input: { + instruction: string + originalQuestion: z.infer + sourceText?: string + aiProviderId?: string +}) { + const originalDifficulty = input.originalQuestion.difficulty ?? 3 + const originalScore = input.originalQuestion.score ?? 0 + const contextLines = [ + `Instruction:\n${input.instruction}`, + `Original Question JSON:\n${JSON.stringify(input.originalQuestion, null, 2)}`, + input.sourceText ? `Source Exam Text:\n${input.sourceText}` : "", + ] + const userContent = contextLines.filter((line) => line.length > 0).join("\n\n") + try { + const aiResult = await createAiChatCompletion({ + model: String(env.AI_MODEL ?? "gpt-4o-mini"), + providerId: input.aiProviderId && input.aiProviderId.length > 0 ? input.aiProviderId : undefined, + messages: [ + { role: "system" as const, content: AI_REWRITE_QUESTION_SYSTEM_PROMPT }, + { role: "user" as const, content: userContent }, + ], + temperature: 0.7, + maxTokens: 2000, + }) + const parsed = await parseAiResponse(aiResult.content, input.aiProviderId) + const candidate = parsed && typeof parsed === "object" && "question" in parsed + ? (parsed as { question: unknown }).question + : parsed + const validated = AiQuestionSchema.safeParse(candidate) + if (!validated.success) { + return { ok: false as const, message: "AI question format invalid" } + } + const question = validated.data + return { + ok: true as const, + data: { + type: question.type, + difficulty: question.difficulty ?? originalDifficulty, + score: question.score ?? originalScore, + content: buildQuestionContent(question), + } satisfies AiRewriteQuestionData, + } + } catch (error) { + return { ok: false as const, message: getAiErrorMessage(error) } + } +} + +export async function generateAiExamDraft(input: { + title?: string + subject?: string + grade?: string + difficulty?: number + totalScore?: number + durationMin?: number + questionCount?: number + sourceText: string + aiProviderId?: string +}) { + return requestAiExamDraft(input) +} diff --git a/src/modules/exams/components/exam-form.tsx b/src/modules/exams/components/exam-form.tsx index 841abaa..842df94 100644 --- a/src/modules/exams/components/exam-form.tsx +++ b/src/modules/exams/components/exam-form.tsx @@ -1,12 +1,13 @@ "use client" -import { useTransition, useEffect, useState } from "react" +import { useTransition, useEffect, useRef, useState, type FormEvent, type ReactNode } from "react" import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { toast } from "sonner" -import { Loader2, Sparkles, BookOpen } from "lucide-react" +import { Loader2, Sparkles, BookOpen, Settings, Wand2, Plus, Trash2 } from "lucide-react" +import PQueue from "p-queue" import { cn } from "@/shared/lib/utils" import { Button } from "@/shared/components/ui/button" @@ -20,6 +21,18 @@ import { FormMessage, } from "@/shared/components/ui/form" import { Input } from "@/shared/components/ui/input" +import { Textarea } from "@/shared/components/ui/textarea" +import { Label } from "@/shared/components/ui/label" +import { Checkbox } from "@/shared/components/ui/checkbox" +import { ScrollArea } from "@/shared/components/ui/scroll-area" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/shared/components/ui/dialog" import { Select, SelectContent, @@ -35,20 +48,124 @@ import { CardHeader, CardTitle, } from "@/shared/components/ui/card" -import { createExamAction, getSubjectsAction, getGradesAction } from "../actions" +import { createAiExamAction, createExamAction, getSubjectsAction, getGradesAction, previewAiExamAction, regenerateAiQuestionAction, type AiPreviewData, type AiRewriteQuestionData } from "../actions" +import { getAiProviderSummaries, type AiProviderSummary } from "@/modules/settings/actions" +import { AiProviderSettingsCard } from "@/modules/settings/components/ai-provider-settings-card" +import type { ExamNode } from "./assembly/selected-question-list" +import type { Question } from "@/modules/questions/types" +import { createId } from "@paralleldrive/cuid2" export const formSchema = z.object({ - title: z.string().min(2, "Title must be at least 2 characters."), - subject: z.string().min(1, "Subject is required."), - grade: z.string().min(1, "Grade is required."), - difficulty: z.string(), - totalScore: z.coerce.number().min(1, "Total score must be at least 1."), - durationMin: z.coerce.number().min(10, "Duration must be at least 10 minutes."), + title: z.string().optional(), + subject: z.string().optional(), + grade: z.string().optional(), + difficulty: z.string().optional(), + totalScore: z.coerce.number().min(1, "Total score must be at least 1.").optional(), + durationMin: z.coerce.number().min(10, "Duration must be at least 10 minutes.").optional(), scheduledAt: z.string().optional(), mode: z.enum(["manual", "ai"]), + aiSourceText: z.string().optional(), + aiQuestionCount: z.coerce.number().min(1).max(200).optional(), + aiProviderId: z.string().optional(), +}).superRefine((data, ctx) => { + if (data.mode === "ai") { + if (!data.aiSourceText?.trim()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["aiSourceText"], + message: "Source exam text is required for AI generation.", + }) + } + return + } + if (!data.title?.trim()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["title"], + message: "Title must be at least 2 characters.", + }) + } + if (!data.subject?.trim()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["subject"], + message: "Subject is required.", + }) + } + if (!data.grade?.trim()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["grade"], + message: "Grade is required.", + }) + } + if (!data.difficulty?.trim()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["difficulty"], + message: "Difficulty is required.", + }) + } + if (typeof data.totalScore !== "number") { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["totalScore"], + message: "Total score must be at least 1.", + }) + } + if (typeof data.durationMin !== "number") { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["durationMin"], + message: "Duration must be at least 10 minutes.", + }) + } }) type ExamFormValues = z.infer +type PreviewQuestion = { + id: string + type: Question["type"] + difficulty: number + score: number + content: Question["content"] +} + +type EditableQuestionContent = { + text: string + options: Array<{ id: string; text: string; isCorrect: boolean }> + subQuestions: Array<{ id: string; text: string; answer?: string; score?: number }> +} + +type PreviewSnapshotMeta = { + subject: string + grade: string + durationMin: number + totalScore: number +} + +type PreviewBackgroundTask = { + id: string + createdAt: number + status: "queued" | "running" | "success" | "failed" + title: string + signature: string + message?: string + result?: { + title: string + nodes: ExamNode[] + rawOutput: string + meta: PreviewSnapshotMeta + formValues: Pick + } +} + +const aiProviderLabels: Record = { + zhipu: "智谱", + openai: "OpenAI", + gemini: "Gemini", + custom: "Custom", +} const defaultValues: Partial = { title: "", @@ -59,66 +176,181 @@ const defaultValues: Partial = { durationMin: 90, mode: "manual", scheduledAt: "", + aiSourceText: "", + aiQuestionCount: undefined, + aiProviderId: "", } +const previewTaskStorageKey = "exam-preview-background-tasks:v1" export function ExamForm() { const router = useRouter() const [isPending, startTransition] = useTransition() + const [providerDialogOpen, setProviderDialogOpen] = useState(false) + const [providerDialogKey, setProviderDialogKey] = useState(0) const [subjects, setSubjects] = useState<{ id: string; name: string }[]>([]) const [loadingSubjects, setLoadingSubjects] = useState(true) const [grades, setGrades] = useState<{ id: string; name: string }[]>([]) const [loadingGrades, setLoadingGrades] = useState(true) + const [aiProviders, setAiProviders] = useState([]) + const [loadingAiProviders, setLoadingAiProviders] = useState(true) + const [previewOpen, setPreviewOpen] = useState(false) + const [previewLoading, setPreviewLoading] = useState(false) + const [previewNodes, setPreviewNodes] = useState([]) + const [previewTitle, setPreviewTitle] = useState("") + const [previewRawOutput, setPreviewRawOutput] = useState("") + const [previewSignature, setPreviewSignature] = useState("") + const [previewMeta, setPreviewMeta] = useState(null) + const [previewTasks, setPreviewTasks] = useState([]) + const [selectedQuestionId, setSelectedQuestionId] = useState("") + const [rewriteInstruction, setRewriteInstruction] = useState("") + const [rewritingQuestion, setRewritingQuestion] = useState(false) const form = useForm({ // eslint-disable-next-line @typescript-eslint/no-explicit-any resolver: zodResolver(formSchema) as any, defaultValues: defaultValues as unknown as ExamFormValues, }) + const previewQueueRef = useRef(null) + if (!previewQueueRef.current) { + previewQueueRef.current = new PQueue({ concurrency: 3 }) + } + const previewQueue = previewQueueRef.current + const persistPreviewTasks = (tasks: PreviewBackgroundTask[]) => { + try { + window.localStorage.setItem(previewTaskStorageKey, JSON.stringify(tasks.slice(0, 20))) + } catch (error) { + console.error(error) + } + } + useEffect(() => { + try { + const raw = window.localStorage.getItem(previewTaskStorageKey) + if (!raw) return + const parsed = JSON.parse(raw) as PreviewBackgroundTask[] + if (!Array.isArray(parsed)) return + const restoredTasks = parsed + .filter((task) => task && typeof task.id === "string") + .map((task) => { + if (task.status === "queued" || task.status === "running") { + return { + ...task, + status: "failed" as const, + message: "页面刷新后任务已中断,请重新生成", + } + } + return task + }) + setPreviewTasks(restoredTasks) + if (restoredTasks.length > 0) { + form.setValue("mode", "ai") + } + } catch (error) { + console.error(error) + setPreviewTasks([]) + } + }, [form]) useEffect(() => { const fetchMetadata = async () => { try { - const [subjectsResult, gradesResult] = await Promise.all([ + const [subjectsResult, gradesResult, aiProvidersResult] = await Promise.all([ getSubjectsAction(), - getGradesAction() + getGradesAction(), + getAiProviderSummaries(), ]) if (subjectsResult.success && subjectsResult.data) { setSubjects(subjectsResult.data) + if (!form.getValues("subject") && subjectsResult.data.length > 0) { + form.setValue("subject", subjectsResult.data[0].id) + } } else { toast.error("Failed to load subjects") } if (gradesResult.success && gradesResult.data) { setGrades(gradesResult.data) + if (!form.getValues("grade") && gradesResult.data.length > 0) { + form.setValue("grade", gradesResult.data[0].id) + } } else { toast.error("Failed to load grades") } + if (Array.isArray(aiProvidersResult)) { + setAiProviders(aiProvidersResult) + const current = form.getValues("aiProviderId") + if (!current) { + const preferred = aiProvidersResult.find((item) => item.isDefault) ?? aiProvidersResult[0] + if (preferred) { + form.setValue("aiProviderId", preferred.id) + } + } + } } catch (error) { console.error(error) toast.error("Failed to load form data") } finally { setLoadingSubjects(false) setLoadingGrades(false) + setLoadingAiProviders(false) } } - fetchMetadata() - }, []) + void fetchMetadata() + }, [form]) + useEffect(() => { + return () => { + previewQueue.clear() + } + }, [previewQueue]) + useEffect(() => { + persistPreviewTasks(previewTasks) + }, [previewTasks]) function onSubmit(data: ExamFormValues) { + const resolvedTitle = data.mode === "ai" && !data.title?.trim() ? "AI Exam" : data.title?.trim() ?? "" + const resolvedSubject = data.subject?.trim() || subjects[0]?.id || "" + const resolvedGrade = data.grade?.trim() || grades[0]?.id || "" + const resolvedDifficulty = Number.parseInt(String(data.difficulty ?? "3"), 10) + const resolvedTotalScore = typeof data.totalScore === "number" && data.totalScore > 0 ? data.totalScore : 100 + const resolvedDurationMin = typeof data.durationMin === "number" && data.durationMin > 0 ? data.durationMin : 90 + + if (data.mode === "ai" && (!resolvedSubject || !resolvedGrade)) { + toast.error("Missing subject or grade configuration") + return + } + if (data.mode === "ai") { + const signature = buildPreviewSignature(data) + if (!previewSignature || signature !== previewSignature || previewNodes.length === 0) { + toast.error("Please preview and confirm before creating") + return + } + } + const formData = new FormData() - formData.append("title", data.title) - formData.append("subject", data.subject) - formData.append("grade", data.grade) - formData.append("difficulty", data.difficulty) - formData.append("totalScore", data.totalScore.toString()) - formData.append("durationMin", data.durationMin.toString()) - if (data.scheduledAt) { + formData.append("title", resolvedTitle) + formData.append("subject", resolvedSubject) + formData.append("grade", resolvedGrade) + formData.append("difficulty", Number.isFinite(resolvedDifficulty) && resolvedDifficulty >= 1 && resolvedDifficulty <= 5 ? String(resolvedDifficulty) : "3") + formData.append("totalScore", String(resolvedTotalScore)) + formData.append("durationMin", String(resolvedDurationMin)) + if (data.mode === "manual" && data.scheduledAt) { formData.append("scheduledAt", data.scheduledAt) } + if (data.mode === "ai") { + if (data.aiSourceText) formData.append("aiSourceText", data.aiSourceText) + if (data.aiQuestionCount) formData.append("aiQuestionCount", data.aiQuestionCount.toString()) + if (data.aiProviderId) formData.append("aiProviderId", data.aiProviderId) + const payload = buildPreviewPayload(previewNodes) + if (payload.questions.length > 0) { + formData.append("aiQuestionsJson", JSON.stringify(payload.questions)) + formData.append("structureJson", JSON.stringify(payload.structure)) + } + } startTransition(async () => { - const result = await createExamAction(null, formData) + const result = data.mode === "ai" + ? await createAiExamAction(null, formData) + : await createExamAction(null, formData) if (result.success && result.data) { toast.success("Exam draft created", { @@ -131,227 +363,1261 @@ export function ExamForm() { }) } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleSubmit = (e: any) => { + const handleCreateClick = () => { + if (isAiMode) { + handleBackgroundPreview() + return + } // eslint-disable-next-line @typescript-eslint/no-explicit-any - form.handleSubmit(onSubmit as any)(e); + form.handleSubmit(onSubmit as any)() + } + + const handleConfirmCreate = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form.handleSubmit(onSubmit as any)() + } + + const handleFormSubmit = (event: FormEvent) => { + event.preventDefault() + handleCreateClick() + } + + const mode = form.watch("mode") + const isAiMode = mode === "ai" + const previewSubject = form.watch("subject") + const previewGrade = form.watch("grade") + const previewDuration = form.watch("durationMin") ?? 90 + const previewTotalScore = form.watch("totalScore") ?? 100 + const previewTitleValue = form.watch("title") + + const buildPreviewNodes = (data: AiPreviewData): ExamNode[] => { + const now = new Date() + const toQuestionNode = (q: PreviewQuestion): ExamNode => ({ + id: q.id, + type: "question", + questionId: q.id, + score: q.score, + question: { + id: q.id, + content: q.content, + type: q.type, + difficulty: q.difficulty, + createdAt: now, + updatedAt: now, + author: null, + knowledgePoints: [], + } satisfies Question, + }) + + if (data.sections && data.sections.length > 0) { + return data.sections.map((section) => ({ + id: section.id || createId(), + type: "group", + title: section.title, + children: section.questions.map((q) => toQuestionNode(q)), + })) + } + + return (data.questions ?? []).map((q) => toQuestionNode(q)) + } + + const parseEditableContent = (raw: unknown): EditableQuestionContent => { + const parseFromObject = (value: unknown): EditableQuestionContent => { + if (!value || typeof value !== "object") return { text: "", options: [], subQuestions: [] } + const record = value as { text?: unknown; options?: unknown; subQuestions?: unknown } + const text = typeof record.text === "string" ? record.text : "" + const options = Array.isArray(record.options) + ? record.options.map((opt, index) => { + const item = opt && typeof opt === "object" ? opt as { id?: unknown; text?: unknown; isCorrect?: unknown } : {} + return { + id: typeof item.id === "string" && item.id.trim().length > 0 ? item.id : String.fromCharCode(65 + index), + text: typeof item.text === "string" ? item.text : "", + isCorrect: typeof item.isCorrect === "boolean" ? item.isCorrect : false, + } + }) + : [] + const subQuestions = Array.isArray(record.subQuestions) + ? record.subQuestions.map((item, index) => { + const row = item && typeof item === "object" + ? item as { id?: unknown; text?: unknown; answer?: unknown; score?: unknown } + : {} + const rawScore = typeof row.score === "number" ? row.score : Number.parseInt(String(row.score ?? ""), 10) + return { + id: typeof row.id === "string" && row.id.trim().length > 0 ? row.id : String(index + 1), + text: typeof row.text === "string" ? row.text : "", + answer: typeof row.answer === "string" ? row.answer : "", + score: Number.isFinite(rawScore) ? rawScore : undefined, + } + }) + : [] + return { text, options, subQuestions } + } + + if (typeof raw === "string") { + try { + return parseFromObject(JSON.parse(raw)) + } catch { + return { text: raw, options: [], subQuestions: [] } + } + } + return parseFromObject(raw) + } + + const flattenPreviewQuestions = (nodes: ExamNode[]) => { + const rows: Array<{ node: ExamNode; sectionTitle?: string }> = [] + const walk = (items: ExamNode[], sectionTitle?: string) => { + items.forEach((node) => { + if (node.type === "question" && node.questionId && node.question) { + rows.push({ node, sectionTitle }) + return + } + if (node.type === "group" && node.children) { + walk(node.children, node.title || sectionTitle) + } + }) + } + walk(nodes) + return rows + } + + const findPreviewQuestionNode = (nodes: ExamNode[], questionId: string): ExamNode | null => { + for (const node of nodes) { + if (node.type === "question" && node.questionId === questionId && node.question) { + return node + } + if (node.type === "group" && node.children) { + const found = findPreviewQuestionNode(node.children, questionId) + if (found) return found + } + } + return null + } + + const updatePreviewQuestionNode = (questionId: string, updater: (node: ExamNode) => ExamNode) => { + const updateList = (items: ExamNode[]): ExamNode[] => { + return items.map((node) => { + if (node.type === "question" && node.questionId === questionId && node.question) { + return updater(node) + } + if (node.type === "group" && node.children) { + return { ...node, children: updateList(node.children) } + } + return node + }) + } + setPreviewNodes((prev) => updateList(prev)) + } + + const updateSelectedQuestionFromAi = (questionId: string, data: AiRewriteQuestionData) => { + updatePreviewQuestionNode(questionId, (node) => { + if (!node.question) return node + return { + ...node, + score: data.score, + question: { + ...node.question, + type: data.type, + difficulty: data.difficulty, + content: data.content, + updatedAt: new Date(), + }, + } + }) + } + + const handleRewriteSelectedQuestion = async () => { + if (!selectedQuestionId) { + toast.error("请先选择一个题目") + return + } + const selected = findPreviewQuestionNode(previewNodes, selectedQuestionId) + if (!selected?.question) { + toast.error("未找到选中的题目") + return + } + const instruction = rewriteInstruction.trim() + if (!instruction) { + toast.error("请输入重写指令") + return + } + setRewritingQuestion(true) + try { + const content = parseEditableContent(selected.question.content) + const questionPayload = { + type: selected.question.type, + difficulty: selected.question.difficulty ?? 3, + score: selected.score ?? 0, + content: { + text: content.text, + options: content.options.map((opt) => ({ + id: opt.id, + text: opt.text, + isCorrect: opt.isCorrect, + })), + subQuestions: content.subQuestions.map((item) => ({ + id: item.id, + text: item.text, + answer: item.answer, + score: item.score, + })), + }, + } + const formData = new FormData() + formData.append("instruction", instruction) + formData.append("questionJson", JSON.stringify(questionPayload)) + const providerId = form.getValues("aiProviderId") + const sourceText = form.getValues("aiSourceText") + if (providerId) formData.append("aiProviderId", providerId) + if (sourceText) formData.append("sourceText", sourceText) + const result = await regenerateAiQuestionAction(null, formData) + if (!result.success || !result.data) { + toast.error(result.message || "AI 重写失败") + return + } + updateSelectedQuestionFromAi(selectedQuestionId, result.data) + setRewriteInstruction("") + toast.success("题目已按指令重写") + } catch { + toast.error("AI 重写失败") + } finally { + setRewritingQuestion(false) + } + } + + const buildPreviewSignature = (values: ExamFormValues) => { + return JSON.stringify({ + sourceText: values.aiSourceText?.trim() || "", + questionCount: values.aiQuestionCount ?? null, + providerId: values.aiProviderId ?? "", + title: values.title?.trim() || "", + subject: values.subject?.trim() || "", + grade: values.grade?.trim() || "", + difficulty: values.difficulty ?? "", + totalScore: values.totalScore ?? "", + durationMin: values.durationMin ?? "", + }) + } + + const buildPreviewPayload = (nodes: ExamNode[]) => { + const questions: Array<{ + id: string + type: Question["type"] + difficulty: number + score: number + content: Question["content"] + }> = [] + const seen = new Set() + const collect = (items: ExamNode[]) => { + items.forEach((node) => { + if (node.type === "question" && node.questionId && node.question) { + if (!seen.has(node.questionId)) { + seen.add(node.questionId) + questions.push({ + id: node.questionId, + type: node.question.type, + difficulty: node.question.difficulty ?? 3, + score: node.score ?? 0, + content: node.question.content, + }) + } + return + } + if (node.type === "group" && node.children) collect(node.children) + }) + } + collect(nodes) + + const cleanStructure = (items: ExamNode[]): Array & { children?: unknown[] }> => { + return items.map((node) => { + const { question, ...rest } = node + void question + if (node.type === "group") { + return { ...rest, children: cleanStructure(node.children || []) } + } + return rest + }) + } + + return { + questions, + structure: cleanStructure(nodes), + } + } + + const buildPreviewRequestData = (values: ExamFormValues) => { + const sourceText = values.aiSourceText?.trim() + if (!sourceText) return null + const formData = new FormData() + if (values.title?.trim()) formData.append("title", values.title.trim()) + if (values.subject?.trim()) formData.append("subject", values.subject.trim()) + if (values.grade?.trim()) formData.append("grade", values.grade.trim()) + const previewDifficulty = Number.parseInt(String(values.difficulty ?? "3"), 10) + const previewTotalScore = typeof values.totalScore === "number" && values.totalScore > 0 ? values.totalScore : 100 + const previewDurationMin = typeof values.durationMin === "number" && values.durationMin > 0 ? values.durationMin : 90 + formData.append("difficulty", Number.isFinite(previewDifficulty) && previewDifficulty >= 1 && previewDifficulty <= 5 ? String(previewDifficulty) : "3") + formData.append("totalScore", String(previewTotalScore)) + formData.append("durationMin", String(previewDurationMin)) + formData.append("aiSourceText", sourceText) + if (values.aiQuestionCount) formData.append("aiQuestionCount", values.aiQuestionCount.toString()) + if (values.aiProviderId) formData.append("aiProviderId", values.aiProviderId) + const meta: PreviewSnapshotMeta = { + subject: values.subject?.trim() || "—", + grade: values.grade?.trim() || "—", + durationMin: previewDurationMin, + totalScore: previewTotalScore, + } + return { formData, meta, signature: buildPreviewSignature(values) } + } + + const applyPreviewResult = (input: { data: AiPreviewData; signature: string; meta: PreviewSnapshotMeta }) => { + setPreviewTitle(input.data.title) + const nextNodes = buildPreviewNodes(input.data) + setPreviewNodes(nextNodes) + const firstQuestion = flattenPreviewQuestions(nextNodes)[0] + setSelectedQuestionId(firstQuestion?.node.questionId ?? "") + setPreviewRawOutput(input.data.rawOutput ?? "") + setPreviewSignature(input.signature) + setPreviewMeta(input.meta) + setRewriteInstruction("") + setPreviewOpen(true) + } + + const handlePreview = async () => { + const values = form.getValues() + const requestData = buildPreviewRequestData(values) + if (!requestData) { + toast.error("Please paste the full exam text first") + return + } + + setPreviewOpen(false) + setPreviewLoading(true) + setPreviewNodes([]) + setPreviewRawOutput("") + setPreviewSignature("") + setSelectedQuestionId("") + setRewriteInstruction("") + try { + const result = await previewAiExamAction(null, requestData.formData) + if (result.success && result.data) { + applyPreviewResult({ + data: result.data, + signature: requestData.signature, + meta: requestData.meta, + }) + } else { + toast.error(result.message || "Failed to generate preview") + } + } catch { + toast.error("Failed to generate preview") + } finally { + setPreviewLoading(false) + } + } + + const handleBackgroundPreview = () => { + const values = form.getValues() + const requestData = buildPreviewRequestData(values) + if (!requestData) { + toast.error("Please paste the full exam text first") + return + } + const taskId = createId() + const taskTitle = values.title?.trim() || "未命名试卷" + setPreviewTasks((prev) => { + const next = [ + { + id: taskId, + createdAt: Date.now(), + status: "queued" as const, + title: taskTitle, + signature: requestData.signature, + }, + ...prev, + ] + persistPreviewTasks(next) + return next + }) + toast.success("已加入后台队列,可继续编辑页面") + void previewQueue.add(async () => { + setPreviewTasks((prev) => prev.map((task) => task.id === taskId + ? { + ...task, + status: "running", + } + : task)) + try { + const result = await previewAiExamAction(null, requestData.formData) + const data = result.data + if (result.success && data) { + const nextNodes = buildPreviewNodes(data) + setPreviewTasks((prev) => prev.map((task) => task.id === taskId + ? { + ...task, + status: "success", + result: { + title: data.title, + nodes: nextNodes, + rawOutput: data.rawOutput ?? "", + meta: requestData.meta, + formValues: { + title: values.title, + subject: values.subject, + grade: values.grade, + difficulty: values.difficulty, + totalScore: values.totalScore, + durationMin: values.durationMin, + aiSourceText: values.aiSourceText, + aiQuestionCount: values.aiQuestionCount, + aiProviderId: values.aiProviderId, + }, + }, + } + : task)) + toast.success(`后台生成完成:${taskTitle}`) + return + } + setPreviewTasks((prev) => prev.map((task) => task.id === taskId + ? { + ...task, + status: "failed", + message: result.message || "Failed to generate preview", + } + : task)) + toast.error(`后台生成失败:${taskTitle}`) + } catch { + setPreviewTasks((prev) => prev.map((task) => task.id === taskId + ? { + ...task, + status: "failed", + message: "Failed to generate preview", + } + : task)) + toast.error(`后台生成失败:${taskTitle}`) + } + }) + } + + const handleOpenPreviewTask = (taskId: string) => { + const task = previewTasks.find((item) => item.id === taskId) + if (!task || task.status !== "success" || !task.result) return + const taskValues = task.result.formValues + if (typeof taskValues.title !== "undefined") form.setValue("title", taskValues.title) + if (typeof taskValues.subject !== "undefined") form.setValue("subject", taskValues.subject) + if (typeof taskValues.grade !== "undefined") form.setValue("grade", taskValues.grade) + if (typeof taskValues.difficulty !== "undefined") form.setValue("difficulty", taskValues.difficulty) + if (typeof taskValues.totalScore !== "undefined") form.setValue("totalScore", taskValues.totalScore) + if (typeof taskValues.durationMin !== "undefined") form.setValue("durationMin", taskValues.durationMin) + if (typeof taskValues.aiSourceText !== "undefined") form.setValue("aiSourceText", taskValues.aiSourceText) + if (typeof taskValues.aiQuestionCount !== "undefined") form.setValue("aiQuestionCount", taskValues.aiQuestionCount) + if (typeof taskValues.aiProviderId !== "undefined") form.setValue("aiProviderId", taskValues.aiProviderId) + setPreviewTitle(task.result.title) + setPreviewNodes(task.result.nodes) + setPreviewRawOutput(task.result.rawOutput) + setPreviewSignature(task.signature) + setPreviewMeta(task.result.meta) + const firstQuestion = flattenPreviewQuestions(task.result.nodes)[0] + setSelectedQuestionId(firstQuestion?.node.questionId ?? "") + setRewriteInstruction("") + setPreviewOpen(true) + } + + const previewQuestionRows = flattenPreviewQuestions(previewNodes) + const selectedPreviewQuestion = selectedQuestionId + ? findPreviewQuestionNode(previewNodes, selectedQuestionId) + : null + const selectedPreviewContent = selectedPreviewQuestion?.question + ? parseEditableContent(selectedPreviewQuestion.question.content) + : null + const runningPreviewTaskCount = previewTasks.filter((task) => task.status === "running").length + const queuedPreviewTaskCount = previewTasks.filter((task) => task.status === "queued").length + const activePreviewTaskCount = runningPreviewTaskCount + queuedPreviewTaskCount + const activePreviewMeta = previewMeta ?? { + subject: previewSubject || "—", + grade: previewGrade || "—", + durationMin: previewDuration, + totalScore: previewTotalScore, + } + const formatTaskTime = (value: number) => new Date(value).toLocaleString("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }) + const renderSelectablePreview = (nodes: ExamNode[]) => { + let questionCounter = 0 + const renderNode = (node: ExamNode, depth: number = 0): ReactNode => { + if (node.type === "group") { + return ( +
+

+ {node.title || "Section"} +

+
+ {(node.children ?? []).map((child) => renderNode(child, depth + 1))} +
+
+ ) + } + if (node.type === "question" && node.question && node.questionId) { + questionCounter += 1 + const content = parseEditableContent(node.question.content) + const active = node.questionId === selectedQuestionId + return ( + + ) + } + return null + } + return ( +
+ {nodes.map((node) => renderNode(node))} +
+ ) } return ( -
- - {/* Left Column: Exam Details */} -
- - - Exam Details - - Define the core information for your exam. - - - - ( - - Title - - - - - - )} - /> - -
+
+ + + {!isAiMode && ( + + + Exam Details + + Define the core information for your exam. + + + ( - Subject - + Title + + + )} /> - ( - - Grade Level - - - - )} - /> -
- -
+ +
( + + Subject + + + + )} + /> + ( + + Grade Level + + + + )} + /> +
+ +
+ ( + + Difficulty + + + + )} + /> + ( + + Total Score + + + + + + )} + /> + ( + + Duration (min) + + + + + + )} + /> +
+ + ( - Difficulty - + + + If set, this exam will be scheduled for a specific time. + + + + )} + /> + + + )} + {isAiMode && ( + + + AI Generation + + Paste the exam text and generate a structured preview. + + + + ( + +
+ AI Provider + { + setProviderDialogOpen(open) + if (open) { + setProviderDialogKey((value) => value + 1) + } + }} + > + + + + + + AI Provider Settings + + Create a new provider or update existing configuration. + + + { + setAiProviders(rows) + const preferred = rows.find((item) => item.isDefault) ?? rows[0] + if (preferred) { + form.setValue("aiProviderId", preferred.id) + } + }} + /> + + +
+ + + Select the AI configuration for this generation. +
)} /> +
+ + +
( - Total Score + Source Exam Text - - - - - )} - /> - ( - - Duration (min) - - +