version: 1
id: database_schema
title: SQLite Database Schema
sources:
  init_sql:
    path: api/database/init.sql
  init_code:
    path: api/database/index.ts
    notes: "仅在 users 表不存在时执行 init.sql"
  models_dir:
    path: api/models

database:
  engine: sqlite3
  file_path:
    env: DB_PATH
    default: data/survey.db
  pragmas:
    foreign_keys: true
    evidence:
      - api/database/index.ts:36

tables:
  users:
    source:
      init_sql_lines: "1-12"
      model: api/models/user.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      name:
        type: TEXT
        nullable: false
        checks:
          - "length(name) >= 2 AND length(name) <= 20"
      phone:
        type: TEXT
        nullable: false
        unique: true
        checks:
          - "length(phone) = 11"
          - "phone LIKE '1%'"
          - "substr(phone, 2, 1) BETWEEN '3' AND '9'"
      password:
        type: TEXT
        nullable: false
        default: "''"
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    indexes:
      - name: idx_users_phone
        columns: [phone]
      - name: idx_users_created_at
        columns: [created_at]

  questions:
    source:
      init_sql_lines: "14-29"
      model: api/models/question.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      content:
        type: TEXT
        nullable: false
      type:
        type: TEXT
        nullable: false
        checks:
          - "type IN ('single', 'multiple', 'judgment', 'text')"
      options:
        type: TEXT
        nullable: true
        notes: "JSON 字符串，存储选项数组"
      answer:
        type: TEXT
        nullable: false
        notes: "多选题答案可能为 JSON 字符串或可解析为数组的字符串"
      analysis:
        type: TEXT
        nullable: false
        default: "''"
        notes: "题目解析（0~255 字符串）"
      score:
        type: INTEGER
        nullable: false
        checks:
          - "score > 0"
      category:
        type: TEXT
        nullable: false
        default: "'通用'"
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    indexes:
      - name: idx_questions_type
        columns: [type]
      - name: idx_questions_score
        columns: [score]
      - name: idx_questions_category
        columns: [category]

  question_categories:
    source:
      init_sql_lines: "31-38"
      model: api/models/questionCategory.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      name:
        type: TEXT
        nullable: false
        unique: true
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    seed:
      - id: default
        name: 通用

  exam_subjects:
    source:
      init_sql_lines: "40-50"
      model: api/models/examSubject.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      name:
        type: TEXT
        nullable: false
        unique: true
      type_ratios:
        type: TEXT
        nullable: false
        notes: "JSON 字符串"
      category_ratios:
        type: TEXT
        nullable: false
        notes: "JSON 字符串"
      total_score:
        type: INTEGER
        nullable: false
      duration_minutes:
        type: INTEGER
        nullable: false
        default: 60
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
      updated_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP

  exam_tasks:
    source:
      init_sql_lines: "52-63"
      model: api/models/examTask.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      name:
        type: TEXT
        nullable: false
      subject_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: exam_subjects
          column: id
      start_at:
        type: DATETIME
        nullable: false
      end_at:
        type: DATETIME
        nullable: false
      selection_config:
        type: TEXT
        nullable: true
        notes: "JSON 字符串；用于记录用户/用户组选择原始配置"
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    indexes:
      - name: idx_exam_tasks_subject_id
        columns: [subject_id]

  exam_task_users:
    source:
      init_sql_lines: "65-77"
      model: api/models/examTask.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      task_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: exam_tasks
          column: id
          on_delete: CASCADE
      user_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: users
          column: id
          on_delete: CASCADE
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    uniques:
      - columns: [task_id, user_id]
    indexes:
      - name: idx_exam_task_users_task_id
        columns: [task_id]
      - name: idx_exam_task_users_user_id
        columns: [user_id]

  quiz_records:
    source:
      init_sql_lines: "79-98"
      model: api/models/quiz.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      user_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: users
          column: id
      subject_id:
        type: TEXT
        nullable: true
        foreign_key:
          table: exam_subjects
          column: id
      task_id:
        type: TEXT
        nullable: true
        foreign_key:
          table: exam_tasks
          column: id
      total_score:
        type: INTEGER
        nullable: false
      correct_count:
        type: INTEGER
        nullable: false
      total_count:
        type: INTEGER
        nullable: false
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    indexes:
      - name: idx_quiz_records_user_id
        columns: [user_id]
      - name: idx_quiz_records_created_at
        columns: [created_at]
      - name: idx_quiz_records_subject_id
        columns: [subject_id]
      - name: idx_quiz_records_task_id
        columns: [task_id]

  quiz_answers:
    source:
      init_sql_lines: "100-115"
      model: api/models/quiz.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      record_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: quiz_records
          column: id
      question_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: questions
          column: id
      user_answer:
        type: TEXT
        nullable: false
        notes: "字符串或 JSON 字符串"
      score:
        type: INTEGER
        nullable: false
      is_correct:
        type: BOOLEAN
        nullable: false
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    indexes:
      - name: idx_quiz_answers_record_id
        columns: [record_id]
      - name: idx_quiz_answers_question_id
        columns: [question_id]

  system_configs:
    source:
      init_sql_lines: "117-131"
      model: api/models/systemConfig.ts
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      config_type:
        type: TEXT
        nullable: false
        unique: true
      config_value:
        type: TEXT
        nullable: false
        notes: "JSON 字符串或普通字符串"
      updated_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP
    seed:
      - id: "1"
        config_type: quiz_config
        config_value: "{\"singleRatio\":40,\"multipleRatio\":30,\"judgmentRatio\":20,\"textRatio\":10,\"totalScore\":100}"
      - id: "2"
        config_type: admin_user
        config_value: "{\"username\":\"admin\",\"password\":\"admin123\"}"

  user_groups:
    source:
      inferred_from_models:
        - api/models/userGroup.ts
      notes: "模型读写该表，但 init.sql 未包含该表"
    columns:
      id:
        type: TEXT
        primary_key: true
        nullable: false
      name:
        type: TEXT
        nullable: false
        unique: true
      description:
        type: TEXT
        nullable: false
        default: "''"
      is_system:
        type: INTEGER
        nullable: false
        default: 0
        notes: "0/1 标记系统内置用户组"
      created_at:
        type: DATETIME
        nullable: false
        default: CURRENT_TIMESTAMP

  user_group_members:
    source:
      inferred_from_models:
        - api/models/userGroup.ts
      notes: "模型读写该表，但 init.sql 未包含该表"
    columns:
      group_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: user_groups
          column: id
      user_id:
        type: TEXT
        nullable: false
        foreign_key:
          table: users
          column: id
      created_at:
        type: DATETIME
        nullable: true
        notes: "模型查询中使用 m.created_at 排序，推断该列存在"
    uniques:
      - columns: [group_id, user_id]

constraints:
  - id: init_sql_missing_user_group_tables
    status: implemented_in_models_not_in_init_sql
    evidence:
      - api/models/userGroup.ts:1
      - api/database/init.sql:1
    details: |
      `init.sql` 未创建 `user_groups` / `user_group_members`，但后端模型与路由已使用该表。
      新初始化数据库时可能导致相关接口运行失败或功能不可用。

  - id: init_sql_missing_exam_tasks_selection_config
    status: implemented_in_models_not_in_init_sql
    evidence:
      - api/models/examTask.ts:201
      - api/database/init.sql:52
    details: |
      `exam_tasks.selection_config` 在模型中读写，但 init.sql 的 exam_tasks 建表语句未包含该列。

