openlearn-next 0.4.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -21
- package/cli-data.mjs +535 -40
- package/cli-doctor.mjs +456 -17
- package/cli.mjs +214 -12
- package/dist/assets/AcademicYearFilter-DUUGpDKt.js +1 -0
- package/dist/assets/ActivityWorkspaceWidget-5PzyxF6a.js +1 -0
- package/dist/assets/AdminDirectoryView-BCtmQTEK.js +5 -0
- package/dist/assets/AssessmentCenter-B43ojHG-.js +1 -0
- package/dist/assets/BatchPickerModal-CbmjInp7.js +1 -0
- package/dist/assets/ClassesView-DEnMaDOt.js +32 -0
- package/dist/assets/ClassroomCountdownWidget-CHq4983w.js +2 -0
- package/dist/assets/CloudDriveModal-D2t3Uyzr.js +1 -0
- package/dist/assets/ComputerLabView-YtnoT5Rb.js +1 -0
- package/dist/assets/CourseManagement-DOXyJ_j-.js +1 -0
- package/dist/assets/CourseWizardModal-Dbi6Caw2.js +3 -0
- package/dist/assets/CoursewareHubPanel-CyxDIJAF.js +1 -0
- package/dist/assets/Dashboard-2rLu_ZBy.js +8 -0
- package/dist/assets/ExportWeightModal-BrmAh6VX.js +1 -0
- package/dist/assets/GLTFLoader-CZJbo_y1.js +1 -0
- package/dist/assets/HelpTour-D_q1z_MI.js +1 -0
- package/dist/assets/HelpView-CVBrKDrd.js +204 -0
- package/dist/assets/ImportLessonsModal-PhHtX31R.js +1 -0
- package/dist/assets/ImportModal-DqjqNeH4.js +2 -0
- package/dist/assets/InteractiveCoursewareViewer-Vi6lLz7a.js +1 -0
- package/dist/assets/InteractiveWhiteboard-lTzeayX5.js +65 -0
- package/dist/assets/LazyCourseware-KIt0rGtW.js +2 -0
- package/dist/assets/LazyWhiteboard-CCN3pYcW.js +2 -0
- package/dist/assets/LessonEditorView-D_GVhHuM.js +1 -0
- package/dist/assets/LiveClassroomView-BRsZ6QEq.js +61 -0
- package/dist/assets/NotificationDetailModal-tY5rxYCN.js +1 -0
- package/dist/assets/OrbitControls-CQjN_uwS.js +1 -0
- package/dist/assets/PluginView-CedpAW6d.js +77 -0
- package/dist/assets/ProcessLogsModal-Cfr-T9Gd.js +1 -0
- package/dist/assets/QuizGeneratorModal-bUA002kP.js +1 -0
- package/dist/assets/StudentAssignmentView-BhAz6Hfk.js +1 -0
- package/dist/assets/StudentLessonView-DC1yX4kG.js +1 -0
- package/dist/assets/StudentView-PHKxIV9T.js +2 -0
- package/dist/assets/SystemResourceLibraryModal-DgXYe6_x.js +1 -0
- package/dist/assets/TeacherView-DscJtOJ9.js +2 -0
- package/dist/assets/TimetableView-D2ozoj_D.js +6 -0
- package/dist/assets/UTIF-FfCsrHhe.js +1 -0
- package/dist/assets/__vite-optional-peer-dep_ai_ooxml-ui-BIHI7g3E.js +1 -0
- package/dist/assets/browser-DEI0QxZC.js +1 -0
- package/dist/assets/html2canvas-pro.esm-Bo50djkB.js +12 -0
- package/dist/assets/index-2676X8YB.js +131 -0
- package/dist/assets/index-C6hlh8pw.css +1 -0
- package/dist/assets/{index.es-C5eWEO4o.js → index.es-DyEF0CZ9.js} +1 -1
- package/dist/assets/pluginParsers-e4PSm2Iy.js +1 -0
- package/dist/assets/react_pptx-react-viewer-BIHI7g3E.js +1 -0
- package/dist/assets/teaching-modes-client-BeVaDdJ-.js +1 -0
- package/dist/assets/three.module-iOYB3rD4.js +4228 -0
- package/dist/assets/timeline-duration-BgdJCSrW.js +1 -0
- package/dist/assets/{vendor-charts-qLbgF1eL.js → vendor-charts-SrpBsvM3.js} +2 -2
- package/dist/assets/{vendor-icons-BRmhY6fg.js → vendor-icons-CXrZ3R2V.js} +2 -2
- package/dist/assets/{vendor-konva-CEOq4dcl.js → vendor-konva-Dne0Z-WC.js} +1 -1
- package/dist/assets/{vendor-pdf-D7rGevI1.js → vendor-pdf-C1Vh2Aqq.js} +56 -56
- package/dist/assets/vendor-pptx-Bzn6whfK.js +11311 -0
- package/dist/assets/vendor-pptx-DIWdXF1L.css +1 -0
- package/dist/assets/{vendor-react-B0t7H3hh.js → vendor-react-Kv_JATwr.js} +1 -1
- package/dist/assets/vendor-utils-aM7aYvPP.js +1 -0
- package/dist/cli-migrator.cjs +310 -0
- package/dist/env-validation.cjs +351 -0
- package/dist/index.html +13 -10
- package/dist/plugin-net-preload.cjs +263 -0
- package/dist/server.cjs +51140 -27229
- package/migrations/000_initial_schema.sql +410 -0
- package/migrations/001_add_execution_mode.sql +6 -0
- package/migrations/002_add_client_session_expiry.sql +6 -0
- package/migrations/003_classroom_runtime.sql +33 -0
- package/migrations/004_courseware_score_config.sql +26 -0
- package/migrations/005_assignment_hub.sql +155 -0
- package/migrations/006_classroom_event_bus.sql +28 -0
- package/migrations/007_interactive_classroom.sql +119 -0
- package/migrations/008_teaching_modes.sql +36 -0
- package/migrations/009_classroom_peer_review.sql +159 -0
- package/migrations/010_class_groups.sql +33 -0
- package/migrations/011_classroom_feed.sql +34 -0
- package/migrations/012_auto_record_score.sql +48 -0
- package/migrations/013_score_source_strategy.sql +81 -0
- package/migrations/014_performance_indexes.sql +21 -0
- package/migrations/015_demo_data_registry.sql +26 -0
- package/migrations/016_preset_polls_and_passcode_expiry.sql +33 -0
- package/migrations/017_pacing_signals_segment.sql +6 -0
- package/migrations/018_processes_plugin_owner.sql +26 -0
- package/migrations/019_plugins_version.sql +61 -0
- package/migrations/020_upload_max_file_mb.sql +14 -0
- package/migrations/021_class_owner_and_password_gate.sql +11 -0
- package/migrations/022_plugin_public_routes.sql +14 -0
- package/migrations/023_encrypt_class_passcodes.sql +18 -0
- package/migrations/024_segment_gates.sql +37 -0
- package/migrations/025_assignment_lookup_indexes.sql +11 -0
- package/migrations/026_plugin_grants.sql +21 -0
- package/migrations/027_academic_year.sql +26 -0
- package/migrations/028_classroom_indexes.sql +14 -0
- package/migrations/029_clean_lesson_think_artifacts.sql +13 -0
- package/migrations/030_backfill_academic_year.sql +30 -0
- package/migrations/031_assessment_core.sql +158 -0
- package/migrations/032_progress_verification.sql +17 -0
- package/migrations/033_student_lock_mode.sql +9 -0
- package/migrations/034_plugin_orphan_data.sql +27 -0
- package/migrations/035_icebreaker_checkins.sql +26 -0
- package/migrations/036_student_read_notifications_created_at.sql +19 -0
- package/migrations/037_plugin_config_scopes.sql +40 -0
- package/migrations/038_assessment_ai_grading_suggestion.sql +10 -0
- package/migrations/039_question_bank_sharing_and_papers.sql +14 -0
- package/migrations/040_assignment_grade_history.sql +37 -0
- package/migrations/041_course_package_idempotency.sql +33 -0
- package/migrations/042_assignment_grade_resubmit_audit.sql +12 -0
- package/migrations/043_mark_sha256_passwords.sql +31 -0
- package/migrations/README.md +61 -0
- package/migrations/schema-golden.json +640 -0
- package/package.json +54 -28
- package/dist/assets/AdminDirectoryView-DY--WM5Z.js +0 -1
- package/dist/assets/BatchPickerModal-BpWu0Lil.js +0 -1
- package/dist/assets/ClassesView-KbHEqJto.js +0 -22
- package/dist/assets/ClassroomCountdownWidget-96WFRO9A.js +0 -2
- package/dist/assets/CloudDriveModal-DEQ1nkNk.js +0 -1
- package/dist/assets/ComputerLabView-1qWDNRQ2.js +0 -1
- package/dist/assets/CourseManagement-D3ZpxY0l.js +0 -1
- package/dist/assets/CourseWizardModal-Ck_-M8pk.js +0 -3
- package/dist/assets/CoursewareHubPanel-CaEizhxl.js +0 -1
- package/dist/assets/Dashboard-CYbpbofV.js +0 -8
- package/dist/assets/ExportWeightModal-DW2bu9Zz.js +0 -1
- package/dist/assets/HelpTour-CMJbbGne.js +0 -1
- package/dist/assets/HelpView-CtzdqhBq.js +0 -204
- package/dist/assets/ImportLessonsModal-jBuIK453.js +0 -1
- package/dist/assets/ImportModal-D7L-KNhZ.js +0 -1
- package/dist/assets/InteractiveCoursewareViewer-Bj7ahXMs.js +0 -1
- package/dist/assets/InteractiveWhiteboard-B7JcJvDu.js +0 -77
- package/dist/assets/LazyCourseware-CuiPxhDg.js +0 -2
- package/dist/assets/LazyWhiteboard-BJwCEZAU.js +0 -2
- package/dist/assets/LessonEditorView-DFrOp2qp.js +0 -1
- package/dist/assets/LiveClassroomView-BZ5nGee3.js +0 -59
- package/dist/assets/NotificationDetailModal-Baqnc5pU.js +0 -1
- package/dist/assets/PluginView-CCbIuZ24.js +0 -66
- package/dist/assets/ProcessLogsModal-DmFXg9mG.js +0 -1
- package/dist/assets/QuizGeneratorModal-BBEDssp7.js +0 -1
- package/dist/assets/StudentAssignmentView-DgIjUsXz.js +0 -1
- package/dist/assets/StudentLessonView-CBJbI5x6.js +0 -1
- package/dist/assets/StudentPreviewModal-Bh8OZs0t.js +0 -1
- package/dist/assets/StudentView--uGxc5TJ.js +0 -2
- package/dist/assets/SystemResourceLibraryModal-BVCclcqA.js +0 -1
- package/dist/assets/TeacherView-D4Zsc0eZ.js +0 -2
- package/dist/assets/TimetableView-BQG-rEb5.js +0 -6
- package/dist/assets/index-BKBCBKvy.js +0 -217
- package/dist/assets/index-CvGu7LSj.css +0 -1
- package/dist/assets/purify.es-CYR4BTuT.js +0 -3
- package/dist/assets/vendor-pptx-C5-U7ieS.js +0 -39
- package/dist/assets/vendor-utils-m0Urvtws.js +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 019_plugins_version.sql
|
|
3
|
+
-- 审计项 H-3(原始编号 L-7):plugins 表补 version 列,让「插件版本」可在 SQL 层查询。
|
|
4
|
+
--
|
|
5
|
+
-- 背景:plugins 表把整个 manifest 存成一列 JSON(`manifest`),版本信息只存在于
|
|
6
|
+
-- `json_extract(manifest, '$.version')` 里面。这带来三个具体问题:
|
|
7
|
+
--
|
|
8
|
+
-- ① 无法用索引。社区市场、依赖解析、版本兼容性检查都需要「按版本筛选/排序」,
|
|
9
|
+
-- `json_extract` 无法走索引,只能全表扫描。plugins 表行数不大但会随生态增长。
|
|
10
|
+
-- ② 无法做 SQL 级 DISTINCT / GROUP BY 版本比较。
|
|
11
|
+
-- 「哪些插件是 1.x」「有没有两个插件声明了同一版本」这类问题写不出来。
|
|
12
|
+
-- ③ 无法对版本做 CHECK 约束。manifest 里的 version 写错了也照样入库,
|
|
13
|
+
-- 只在插件真正激活时才可能在别处炸。
|
|
14
|
+
--
|
|
15
|
+
-- 为什么不是「从 manifest 里读就行」:
|
|
16
|
+
-- 读得到不等于查得起。插件宿主本身已有 `resolvePluginUuid()` 用
|
|
17
|
+
-- `json_extract(manifest, '$.id')` 做主键回查(index.ts),说明这个模式可用;
|
|
18
|
+
-- 但那是一次性 O(1) 点查,而版本筛选是集合查询,代价差异很大。
|
|
19
|
+
-- 同时把 version 提升为独立列后,它才能参与索引与约束 —— 这才是本迁移的目的。
|
|
20
|
+
--
|
|
21
|
+
-- 数据回填:从已有 manifest JSON 抽取。抽不出(manifest 非合法 JSON 或无 version)
|
|
22
|
+
-- 的行留 NULL —— 不猜、不填占位符。NULL 语义明确为「版本未知」。
|
|
23
|
+
--
|
|
24
|
+
-- 维护说明:安装/更新插件的路径(installPlugin / installPluginFromZip /
|
|
25
|
+
-- updatePluginFromZip)需同步写入该列,否则新装插件的 version 仍为 NULL。
|
|
26
|
+
-- 读路径已兼容 NULL:既有代码从 manifest JSON 读 version,本列是**加速索引**,
|
|
27
|
+
-- 不是唯一真源 —— 这样即使某条写入路径漏了也不会造成功能回归。
|
|
28
|
+
|
|
29
|
+
ALTER TABLE plugins ADD COLUMN version TEXT;
|
|
30
|
+
|
|
31
|
+
-- 回填:从 manifest JSON 抽取。
|
|
32
|
+
-- 实测:UPDATE ... WHERE json_valid(manifest) 会先过滤再求值,脏行被跳过、不会中断整表;
|
|
33
|
+
-- 真正会被脏行炸掉的是下面的**表达式索引**(建索引必须逐行求值),故那里用部分索引。
|
|
34
|
+
UPDATE plugins
|
|
35
|
+
SET version = json_extract(manifest, '$.version')
|
|
36
|
+
WHERE json_valid(manifest)
|
|
37
|
+
AND json_extract(manifest, '$.version') IS NOT NULL;
|
|
38
|
+
|
|
39
|
+
-- 索引:版本筛选 + 排序(社区市场「按版本列出」、兼容性检查「找出低于 X 的插件」)
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_plugins_version ON plugins(version);
|
|
41
|
+
|
|
42
|
+
-- manifest.id 是逻辑主键(DB 主键是 UUID),PluginHost.resolvePluginUuid 按它查。
|
|
43
|
+
--
|
|
44
|
+
-- 必须用**部分索引**:`json_extract()` 遇到非法 JSON 会抛 `malformed JSON`,
|
|
45
|
+
-- 而建表达式索引时 SQLite 会对**每一行**求值 —— 一条脏数据就让整条 DDL 失败。
|
|
46
|
+
-- 实测(SQLite 3.53.2):
|
|
47
|
+
-- 无条件表达式索引 → 抛错 "malformed JSON"
|
|
48
|
+
-- 带 WHERE json_valid(manifest) 的部分索引 → 通过
|
|
49
|
+
--
|
|
50
|
+
-- 刻意**不**采用「先把非法 manifest 改写成 {}」的替代方案:那会销毁损坏数据的现场。
|
|
51
|
+
-- manifest 理论上只由本宿主写入(JSON.stringify),脏行意味着另有原因,
|
|
52
|
+
-- 不该被本迁移顺手掩盖。
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_plugins_manifest_id
|
|
54
|
+
ON plugins(json_extract(manifest, '$.id'))
|
|
55
|
+
WHERE json_valid(manifest);
|
|
56
|
+
|
|
57
|
+
-- DOWN
|
|
58
|
+
DROP INDEX IF EXISTS idx_plugins_manifest_id;
|
|
59
|
+
DROP INDEX IF EXISTS idx_plugins_version;
|
|
60
|
+
-- SQLite 3.35+ 支持 DROP COLUMN;旧版走表重建兜底由 migrate 层处理
|
|
61
|
+
ALTER TABLE plugins DROP COLUMN version;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 020_upload_max_file_mb.sql
|
|
3
|
+
-- v0.6.1:课件上传文件大小上限改为管理员可配置(管理后台 → 站点设置)。
|
|
4
|
+
--
|
|
5
|
+
-- site_settings 增加 upload_max_file_mb 列(MB,整数)。NULL = 未配置,
|
|
6
|
+
-- 运行时取默认 10MB(与历史 express.json 10MB 限制下的实际可用值一致)。
|
|
7
|
+
-- 硬上限 90MB:受 express.json 的 110MB 全局 JSON 上限约束(base64 膨胀
|
|
8
|
+
-- ~33%),服务端 /api/upload 在解码后按此列逐请求强制执行。
|
|
9
|
+
|
|
10
|
+
ALTER TABLE site_settings ADD COLUMN upload_max_file_mb INTEGER;
|
|
11
|
+
|
|
12
|
+
-- DOWN
|
|
13
|
+
-- SQLite 3.35+ 支持 DROP COLUMN(Node 24 内置版本满足);旧版需表重建兜底
|
|
14
|
+
ALTER TABLE site_settings DROP COLUMN upload_max_file_mb;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 021_class_owner_and_password_gate.sql
|
|
3
|
+
-- 班级归属:教师只能管理 owner_user_id 指向自己的班级;历史空值仅管理员可访问,需回填。
|
|
4
|
+
-- 种子账号:must_change_password=1 时,除改密/登出/会话查询外的 API(含 GET)一律拒绝。
|
|
5
|
+
|
|
6
|
+
ALTER TABLE classes ADD COLUMN owner_user_id TEXT;
|
|
7
|
+
ALTER TABLE users ADD COLUMN must_change_password INTEGER NOT NULL DEFAULT 0;
|
|
8
|
+
|
|
9
|
+
-- DOWN
|
|
10
|
+
ALTER TABLE classes DROP COLUMN owner_user_id;
|
|
11
|
+
ALTER TABLE users DROP COLUMN must_change_password;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 插件公开路由必须由管理员显式批准。manifest 的 auth:false 不再单独放行。
|
|
3
|
+
-- plugin_key 同时写入 DB UUID 与 manifest.id,网关用任一身份都能命中。
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS plugin_public_routes (
|
|
6
|
+
plugin_key TEXT NOT NULL,
|
|
7
|
+
method TEXT NOT NULL,
|
|
8
|
+
path TEXT NOT NULL,
|
|
9
|
+
approved_at INTEGER NOT NULL,
|
|
10
|
+
PRIMARY KEY (plugin_key, method, path)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- DOWN
|
|
14
|
+
DROP TABLE IF EXISTS plugin_public_routes;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 班级临时口令改为 AES-256-GCM 密文(前缀 olenc1:)。
|
|
3
|
+
-- 纯 SQL 拿不到 ENCRYPTION_KEY,真正的加密在启动时由
|
|
4
|
+
-- encryptStoredClassPasscodes() 完成:已是 olenc1: 密文的行跳过,可重复执行。
|
|
5
|
+
-- 本表只用于在 _migrations 之外留下「这一列已经按密文约定存储」的标记。
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS _class_passcode_encryption (
|
|
8
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
9
|
+
note TEXT NOT NULL
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
INSERT OR IGNORE INTO _class_passcode_encryption (id, note)
|
|
13
|
+
VALUES (1, 'class_passcode is sealed with olenc1: AES-256-GCM. plaintext rows are encrypted on startup');
|
|
14
|
+
|
|
15
|
+
-- DOWN
|
|
16
|
+
-- 不能在 SQL 里把密文还原成明文(需要 ENCRYPTION_KEY,而且会重新暴露口令)。
|
|
17
|
+
-- 回滚应用代码前,请改用迁移前的数据库快照。已加密的 class_passcode 保持不动。
|
|
18
|
+
DROP TABLE IF EXISTS _class_passcode_encryption;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 教学环节门槛:按学生记录在某一环节上的停留,以及教师对「某学生 × 某环节」的放行。
|
|
3
|
+
-- 签到若没有排课行,lesson_checkins 仍留下课节级到课事实,供 checked_in 条件读取。
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS segment_visits (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
lesson_id TEXT NOT NULL,
|
|
8
|
+
segment_id TEXT NOT NULL,
|
|
9
|
+
student_id TEXT NOT NULL,
|
|
10
|
+
entered_at INTEGER NOT NULL,
|
|
11
|
+
left_at INTEGER
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_segment_visits_lookup
|
|
15
|
+
ON segment_visits(lesson_id, student_id, segment_id);
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS segment_gate_overrides (
|
|
18
|
+
lesson_id TEXT NOT NULL,
|
|
19
|
+
segment_id TEXT NOT NULL,
|
|
20
|
+
student_id TEXT NOT NULL,
|
|
21
|
+
actor_id TEXT NOT NULL,
|
|
22
|
+
reason TEXT,
|
|
23
|
+
created_at INTEGER NOT NULL,
|
|
24
|
+
PRIMARY KEY (lesson_id, segment_id, student_id)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS lesson_checkins (
|
|
28
|
+
lesson_id TEXT NOT NULL,
|
|
29
|
+
student_id TEXT NOT NULL,
|
|
30
|
+
checked_in_at INTEGER NOT NULL,
|
|
31
|
+
PRIMARY KEY (lesson_id, student_id)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
-- DOWN
|
|
35
|
+
DROP TABLE IF EXISTS lesson_checkins;
|
|
36
|
+
DROP TABLE IF EXISTS segment_gate_overrides;
|
|
37
|
+
DROP TABLE IF EXISTS segment_visits;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 课节进度按 student_id 聚合该课作业最高分,仪表盘按学生取提交。
|
|
3
|
+
-- assignment_submissions 的主键是 (assignment_id, student_id),不能单独服务 student_id 查找。
|
|
4
|
+
-- assignments 已有 class_id 索引,没有 lesson_id 索引。
|
|
5
|
+
|
|
6
|
+
CREATE INDEX IF NOT EXISTS idx_assignment_submissions_student ON assignment_submissions(student_id);
|
|
7
|
+
CREATE INDEX IF NOT EXISTS idx_assignments_lesson ON assignments(lesson_id);
|
|
8
|
+
|
|
9
|
+
-- DOWN
|
|
10
|
+
DROP INDEX IF EXISTS idx_assignments_lesson;
|
|
11
|
+
DROP INDEX IF EXISTS idx_assignment_submissions_student;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 第三方插件的能力授权与出站域名白名单必须由管理员显式批准写入。
|
|
3
|
+
-- plugin_key 支持 DB UUID 与 manifest.id 双键查询,确保向后兼容与隔离。
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS plugin_capability_grants (
|
|
6
|
+
plugin_key TEXT NOT NULL,
|
|
7
|
+
capability TEXT NOT NULL,
|
|
8
|
+
approved_at INTEGER NOT NULL,
|
|
9
|
+
PRIMARY KEY (plugin_key, capability)
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
CREATE TABLE IF NOT EXISTS plugin_network_grants (
|
|
13
|
+
plugin_key TEXT NOT NULL,
|
|
14
|
+
host TEXT NOT NULL,
|
|
15
|
+
approved_at INTEGER NOT NULL,
|
|
16
|
+
PRIMARY KEY (plugin_key, host)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- DOWN
|
|
20
|
+
DROP TABLE IF EXISTS plugin_capability_grants;
|
|
21
|
+
DROP TABLE IF EXISTS plugin_network_grants;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 学年维度 (academic_year):允许按学年过滤、统计、导出;NULL 表示「未分类」。
|
|
3
|
+
|
|
4
|
+
ALTER TABLE schedules ADD COLUMN academic_year TEXT;
|
|
5
|
+
ALTER TABLE assignments ADD COLUMN academic_year TEXT;
|
|
6
|
+
ALTER TABLE assignment_submissions ADD COLUMN academic_year TEXT;
|
|
7
|
+
ALTER TABLE exams ADD COLUMN academic_year TEXT;
|
|
8
|
+
ALTER TABLE exam_scores ADD COLUMN academic_year TEXT;
|
|
9
|
+
ALTER TABLE attendance ADD COLUMN academic_year TEXT;
|
|
10
|
+
ALTER TABLE student_semester_reports ADD COLUMN academic_year TEXT;
|
|
11
|
+
ALTER TABLE student_lesson_progress ADD COLUMN academic_year TEXT;
|
|
12
|
+
ALTER TABLE classroom_sessions ADD COLUMN academic_year TEXT;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE site_settings ADD COLUMN current_academic_year TEXT;
|
|
15
|
+
ALTER TABLE site_settings ADD COLUMN academic_start_month INTEGER DEFAULT 9;
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_schedules_academic_year ON schedules(academic_year);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_assignments_academic_year ON assignments(academic_year);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_attendance_academic_year ON attendance(academic_year);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_sessions_academic_year ON classroom_sessions(academic_year);
|
|
21
|
+
|
|
22
|
+
-- DOWN
|
|
23
|
+
DROP INDEX IF EXISTS idx_classroom_sessions_academic_year;
|
|
24
|
+
DROP INDEX IF EXISTS idx_attendance_academic_year;
|
|
25
|
+
DROP INDEX IF EXISTS idx_assignments_academic_year;
|
|
26
|
+
DROP INDEX IF EXISTS idx_schedules_academic_year;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 补齐课堂互动与会话高频查询/级联删除索引
|
|
3
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_buzzers_session ON classroom_buzzers(session_id);
|
|
4
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_buzzers_lesson ON classroom_buzzers(lesson_id);
|
|
5
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_sessions_class ON classroom_sessions(class_id);
|
|
6
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_sessions_created_at ON classroom_sessions(created_at);
|
|
7
|
+
CREATE INDEX IF NOT EXISTS idx_classroom_sessions_teacher ON classroom_sessions(teacher_id);
|
|
8
|
+
|
|
9
|
+
-- DOWN
|
|
10
|
+
DROP INDEX IF EXISTS idx_classroom_sessions_teacher;
|
|
11
|
+
DROP INDEX IF EXISTS idx_classroom_sessions_created_at;
|
|
12
|
+
DROP INDEX IF EXISTS idx_classroom_sessions_class;
|
|
13
|
+
DROP INDEX IF EXISTS idx_classroom_buzzers_lesson;
|
|
14
|
+
DROP INDEX IF EXISTS idx_classroom_buzzers_session;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 课程大纲模型思维链残留 (<think>...</think>) 清洗标记表。
|
|
3
|
+
-- 真正的正则文本清洗由启动阶段 cleanStoredLessonThinkArtifacts(db) 幂等执行。
|
|
4
|
+
CREATE TABLE IF NOT EXISTS _lesson_think_cleanup (
|
|
5
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
6
|
+
note TEXT NOT NULL
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
INSERT OR IGNORE INTO _lesson_think_cleanup (id, note)
|
|
10
|
+
VALUES (1, 'lessons.content reasoning artifacts cleaned on startup');
|
|
11
|
+
|
|
12
|
+
-- DOWN
|
|
13
|
+
DROP TABLE IF EXISTS _lesson_think_cleanup;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 学年历史数据向后兼容回填标记与 SQL 级关联回填
|
|
3
|
+
-- 细粒度日期推导由启动阶段 backfillAcademicYearData(db) 幂等执行。
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS _academic_year_backfill (
|
|
6
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
7
|
+
backfilled_at TEXT NOT NULL
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
-- SQL 级关联回填:优先从所属父实体继承已存在的学年
|
|
11
|
+
UPDATE assignment_submissions
|
|
12
|
+
SET academic_year = (SELECT a.academic_year FROM assignments a WHERE a.id = assignment_submissions.assignment_id)
|
|
13
|
+
WHERE academic_year IS NULL
|
|
14
|
+
AND EXISTS (SELECT 1 FROM assignments a WHERE a.id = assignment_submissions.assignment_id AND a.academic_year IS NOT NULL);
|
|
15
|
+
|
|
16
|
+
UPDATE attendance
|
|
17
|
+
SET academic_year = (SELECT s.academic_year FROM schedules s WHERE s.id = attendance.schedule_id)
|
|
18
|
+
WHERE academic_year IS NULL
|
|
19
|
+
AND EXISTS (SELECT 1 FROM schedules s WHERE s.id = attendance.schedule_id AND s.academic_year IS NOT NULL);
|
|
20
|
+
|
|
21
|
+
UPDATE exam_scores
|
|
22
|
+
SET academic_year = (SELECT e.academic_year FROM exams e WHERE e.id = exam_scores.exam_id)
|
|
23
|
+
WHERE academic_year IS NULL
|
|
24
|
+
AND EXISTS (SELECT 1 FROM exams e WHERE e.id = exam_scores.exam_id AND e.academic_year IS NOT NULL);
|
|
25
|
+
|
|
26
|
+
INSERT OR IGNORE INTO _academic_year_backfill (id, backfilled_at)
|
|
27
|
+
VALUES (1, datetime('now'));
|
|
28
|
+
|
|
29
|
+
-- DOWN
|
|
30
|
+
DROP TABLE IF EXISTS _academic_year_backfill;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 测评子系统核心表(Issue #100 / 子 issue #120)。
|
|
3
|
+
--
|
|
4
|
+
-- 设计口径(记录在这里,避免和大纲脱节):
|
|
5
|
+
-- * 组卷即快照:题目加入试卷时就复制进 assessment_items(草稿自包含),
|
|
6
|
+
-- 发布只改 assessments.status;bank_item_id 仅作溯源,删题库不影响已组卷内容。
|
|
7
|
+
-- * 权威答案字段是 answer_key_json,**只在服务端使用**,任何学生端响应都不得包含它
|
|
8
|
+
-- (白名单序列化 + 单测门禁,见 packages/core/assessment/serialize.ts)。
|
|
9
|
+
-- * 匿名问卷:assessment_attempts.student_id 为 NULL,用 anon_key(HMAC)去重;
|
|
10
|
+
-- 教师端响应不含 anon_key / student_id。
|
|
11
|
+
-- * 多次作答的聚合口径(first/last/highest/average)实时计算,不落冗余列。
|
|
12
|
+
-- * 超时用惰性结算:任何读取到 now > deadline_at 的 in_progress attempt 都结算为 submitted。
|
|
13
|
+
-- * 表里没有"总分"冗余列以外的派生数据;assessment_attempts.score 是"已判分部分的合计",
|
|
14
|
+
-- needs_manual_review 指明是否还有待人工批改的题。
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS assessments (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
kind TEXT NOT NULL CHECK (kind IN ('quiz', 'exam', 'survey')),
|
|
19
|
+
title TEXT NOT NULL,
|
|
20
|
+
description TEXT,
|
|
21
|
+
owner_id TEXT NOT NULL,
|
|
22
|
+
-- 计时 / 作答次数 / 乱序 / 匿名 / 结果可见性 / 计分策略,见 packages/core/assessment/settings.ts
|
|
23
|
+
settings_json TEXT NOT NULL DEFAULT '{}',
|
|
24
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'closed', 'archived')),
|
|
25
|
+
academic_year TEXT,
|
|
26
|
+
published_at INTEGER,
|
|
27
|
+
created_at INTEGER NOT NULL,
|
|
28
|
+
updated_at INTEGER NOT NULL
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_assessments_owner ON assessments(owner_id, status);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_assessments_kind ON assessments(kind, status);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS question_bank_items (
|
|
35
|
+
id TEXT PRIMARY KEY,
|
|
36
|
+
owner_id TEXT NOT NULL,
|
|
37
|
+
type TEXT NOT NULL,
|
|
38
|
+
stem TEXT NOT NULL,
|
|
39
|
+
config_json TEXT NOT NULL DEFAULT '{}',
|
|
40
|
+
-- 标准答案,仅服务端可读
|
|
41
|
+
answer_key_json TEXT,
|
|
42
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
43
|
+
difficulty INTEGER,
|
|
44
|
+
created_at INTEGER NOT NULL,
|
|
45
|
+
updated_at INTEGER NOT NULL
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_question_bank_owner ON question_bank_items(owner_id, type);
|
|
49
|
+
|
|
50
|
+
-- 试卷中的题:组卷时物化的快照(改题库不影响已组卷内容)
|
|
51
|
+
CREATE TABLE IF NOT EXISTS assessment_items (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
assessment_id TEXT NOT NULL,
|
|
54
|
+
-- 溯源用;题库条目被删后仍保留快照,因此可为 NULL
|
|
55
|
+
bank_item_id TEXT,
|
|
56
|
+
section TEXT,
|
|
57
|
+
sort_order INTEGER NOT NULL,
|
|
58
|
+
type TEXT NOT NULL,
|
|
59
|
+
stem TEXT NOT NULL,
|
|
60
|
+
config_json TEXT NOT NULL DEFAULT '{}',
|
|
61
|
+
answer_key_json TEXT,
|
|
62
|
+
max_score REAL NOT NULL DEFAULT 1,
|
|
63
|
+
FOREIGN KEY (assessment_id) REFERENCES assessments(id) ON DELETE CASCADE
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_items_order ON assessment_items(assessment_id, sort_order);
|
|
67
|
+
|
|
68
|
+
-- 发布到班级(可选关联课节 / 课表节次 / 课堂会话)
|
|
69
|
+
CREATE TABLE IF NOT EXISTS assessment_assignments (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
assessment_id TEXT NOT NULL,
|
|
72
|
+
class_id TEXT NOT NULL,
|
|
73
|
+
lesson_id TEXT,
|
|
74
|
+
schedule_id TEXT,
|
|
75
|
+
session_id TEXT,
|
|
76
|
+
opens_at INTEGER,
|
|
77
|
+
closes_at INTEGER,
|
|
78
|
+
duration_sec INTEGER,
|
|
79
|
+
-- P1 回写 exams/exam_scores 时的关联
|
|
80
|
+
exam_id TEXT,
|
|
81
|
+
academic_year TEXT,
|
|
82
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'closed')),
|
|
83
|
+
-- 教师「发布成绩」后才允许学生看到分数与答案(exam 类型默认关闭)
|
|
84
|
+
score_released INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
created_by TEXT,
|
|
86
|
+
created_at INTEGER NOT NULL,
|
|
87
|
+
FOREIGN KEY (assessment_id) REFERENCES assessments(id) ON DELETE CASCADE
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_assignments_class ON assessment_assignments(class_id, status);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_assignments_lesson ON assessment_assignments(lesson_id);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_assignments_window
|
|
93
|
+
ON assessment_assignments(opens_at, closes_at);
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS assessment_attempts (
|
|
96
|
+
id TEXT PRIMARY KEY,
|
|
97
|
+
assignment_id TEXT NOT NULL,
|
|
98
|
+
-- 匿名问卷为 NULL;非匿名为学生 id
|
|
99
|
+
student_id TEXT,
|
|
100
|
+
-- 匿名去重用的 HMAC(见 packages/core/assessment/anonymous.ts)
|
|
101
|
+
anon_key TEXT,
|
|
102
|
+
attempt_no INTEGER NOT NULL DEFAULT 1,
|
|
103
|
+
status TEXT NOT NULL DEFAULT 'in_progress'
|
|
104
|
+
CHECK (status IN ('in_progress', 'submitted', 'graded', 'released')),
|
|
105
|
+
started_at INTEGER NOT NULL,
|
|
106
|
+
submitted_at INTEGER,
|
|
107
|
+
deadline_at INTEGER,
|
|
108
|
+
-- 已判分部分的合计;人工批改后由服务端重算
|
|
109
|
+
score REAL,
|
|
110
|
+
max_score REAL,
|
|
111
|
+
-- 1 表示仍有题目在等人工批改(简答 / grader 返回 score=null)
|
|
112
|
+
needs_manual_review INTEGER NOT NULL DEFAULT 0,
|
|
113
|
+
-- 切屏 / 失焦计数等考试监考数据(P1 使用)
|
|
114
|
+
proctor_json TEXT NOT NULL DEFAULT '{}',
|
|
115
|
+
academic_year TEXT,
|
|
116
|
+
FOREIGN KEY (assignment_id) REFERENCES assessment_assignments(id) ON DELETE CASCADE
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
-- 同一身份(学生 id 或匿名 key)在同一份布置下,每个 attempt_no 只允许一条;
|
|
120
|
+
-- 匿名问卷 max_attempts 恒为 1,因此这条索引同时承担「防一人多交」。
|
|
121
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_assessment_attempts_identity ON assessment_attempts(
|
|
122
|
+
assignment_id,
|
|
123
|
+
COALESCE(student_id, ''),
|
|
124
|
+
COALESCE(anon_key, ''),
|
|
125
|
+
attempt_no
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_attempts_status ON assessment_attempts(assignment_id, status);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_attempts_student ON assessment_attempts(student_id);
|
|
130
|
+
|
|
131
|
+
CREATE TABLE IF NOT EXISTS assessment_answers (
|
|
132
|
+
attempt_id TEXT NOT NULL,
|
|
133
|
+
item_id TEXT NOT NULL,
|
|
134
|
+
answer_json TEXT,
|
|
135
|
+
score REAL,
|
|
136
|
+
max_score REAL,
|
|
137
|
+
is_correct INTEGER,
|
|
138
|
+
-- auto | manual | plugin:<id> | ai_suggested
|
|
139
|
+
grader TEXT,
|
|
140
|
+
feedback TEXT,
|
|
141
|
+
graded_by TEXT,
|
|
142
|
+
graded_at INTEGER,
|
|
143
|
+
-- 教师手动改过的行不被自动判分覆盖(判分幂等的一部分)
|
|
144
|
+
manual_override INTEGER NOT NULL DEFAULT 0,
|
|
145
|
+
updated_at INTEGER NOT NULL,
|
|
146
|
+
PRIMARY KEY (attempt_id, item_id),
|
|
147
|
+
FOREIGN KEY (attempt_id) REFERENCES assessment_attempts(id) ON DELETE CASCADE
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_assessment_answers_item ON assessment_answers(item_id);
|
|
151
|
+
|
|
152
|
+
-- DOWN
|
|
153
|
+
DROP TABLE IF EXISTS assessment_answers;
|
|
154
|
+
DROP TABLE IF EXISTS assessment_attempts;
|
|
155
|
+
DROP TABLE IF EXISTS assessment_assignments;
|
|
156
|
+
DROP TABLE IF EXISTS assessment_items;
|
|
157
|
+
DROP TABLE IF EXISTS question_bank_items;
|
|
158
|
+
DROP TABLE IF EXISTS assessments;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 032_progress_verification.sql
|
|
3
|
+
-- 学习进度区分「学生自报」与「已验证」:成绩只计入已验证进度。
|
|
4
|
+
-- verified = 1 由教师/管理员、课件自动录入或受信插件写入
|
|
5
|
+
-- verified_progress_percent 该次验证对应的进度值(学生自报只改展示值,不覆盖此列)
|
|
6
|
+
--
|
|
7
|
+
-- 历史数据:修复前所有进度都直接计入成绩,为保持既有成绩口径,全部回填为已验证。
|
|
8
|
+
|
|
9
|
+
ALTER TABLE student_lesson_progress ADD COLUMN verified INTEGER NOT NULL DEFAULT 0;
|
|
10
|
+
ALTER TABLE student_lesson_progress ADD COLUMN verified_progress_percent INTEGER;
|
|
11
|
+
|
|
12
|
+
UPDATE student_lesson_progress
|
|
13
|
+
SET verified = 1, verified_progress_percent = progress_percent;
|
|
14
|
+
|
|
15
|
+
-- DOWN
|
|
16
|
+
ALTER TABLE student_lesson_progress DROP COLUMN verified_progress_percent;
|
|
17
|
+
ALTER TABLE student_lesson_progress DROP COLUMN verified;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 学生端锁定模式 (lock_mode) 与锁定目标 (lock_target)(Issue #110)
|
|
3
|
+
-- lock_mode: 'full' (完全锁定/只读跟随) | 'navigation' (答题锁定/放开输入作答,禁止离开)
|
|
4
|
+
-- lock_target: JSON 字符串,例如 {"tab":"whiteboard"|"assignment"|"courseware","assignmentId":"...","elementId":"..."}
|
|
5
|
+
|
|
6
|
+
ALTER TABLE students ADD COLUMN lock_mode TEXT DEFAULT 'full';
|
|
7
|
+
ALTER TABLE students ADD COLUMN lock_target TEXT DEFAULT NULL;
|
|
8
|
+
|
|
9
|
+
-- DOWN
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 插件卸载的数据语义(Issue #141):卸载默认保留数据,purge 需先导出归档。
|
|
3
|
+
--
|
|
4
|
+
-- `keep`(默认):插件私有表、`plugin_migrations` 版本记录与 `plugin_storage`
|
|
5
|
+
-- 键值原样保留,本表登记一条「孤立插件数据」;
|
|
6
|
+
-- 重装同 manifest.id 时复用 plugin_id(原安装 UUID),表前缀 / 迁移版本 / 存储键
|
|
7
|
+
-- 三者都会天然命中旧数据,无需搬迁。
|
|
8
|
+
CREATE TABLE IF NOT EXISTS plugin_orphan_data (
|
|
9
|
+
manifest_id TEXT NOT NULL,
|
|
10
|
+
plugin_id TEXT NOT NULL,
|
|
11
|
+
plugin_name TEXT,
|
|
12
|
+
plugin_version TEXT,
|
|
13
|
+
table_prefixes TEXT NOT NULL DEFAULT '[]',
|
|
14
|
+
table_names TEXT NOT NULL DEFAULT '[]',
|
|
15
|
+
row_count INTEGER NOT NULL DEFAULT 0,
|
|
16
|
+
storage_keys INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
migration_version INTEGER,
|
|
18
|
+
last_export_path TEXT,
|
|
19
|
+
uninstalled_at INTEGER NOT NULL,
|
|
20
|
+
PRIMARY KEY (manifest_id, plugin_id)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_orphan_data_manifest ON plugin_orphan_data (manifest_id);
|
|
24
|
+
|
|
25
|
+
-- DOWN
|
|
26
|
+
DROP INDEX IF EXISTS idx_plugin_orphan_data_manifest;
|
|
27
|
+
DROP TABLE IF EXISTS plugin_orphan_data;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 课前破冰心情打卡持久化与去重(Issue #131)
|
|
3
|
+
-- 记录班级、课节、学生的心情打卡状态,支持当日/当堂重复打卡幂等更新
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS icebreaker_checkins (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
class_id TEXT NOT NULL,
|
|
8
|
+
lesson_id TEXT,
|
|
9
|
+
scope_key TEXT NOT NULL,
|
|
10
|
+
student_id TEXT NOT NULL,
|
|
11
|
+
mood TEXT NOT NULL,
|
|
12
|
+
checkin_date TEXT NOT NULL,
|
|
13
|
+
created_at INTEGER NOT NULL,
|
|
14
|
+
updated_at INTEGER NOT NULL
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_icebreaker_scope_student
|
|
18
|
+
ON icebreaker_checkins(class_id, scope_key, student_id);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_icebreaker_lookup
|
|
21
|
+
ON icebreaker_checkins(class_id, lesson_id, checkin_date);
|
|
22
|
+
|
|
23
|
+
-- DOWN
|
|
24
|
+
DROP INDEX IF EXISTS idx_icebreaker_lookup;
|
|
25
|
+
DROP INDEX IF EXISTS idx_icebreaker_scope_student;
|
|
26
|
+
DROP TABLE IF EXISTS icebreaker_checkins;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 学生已读通知保留策略支持:添加 created_at 时间戳列(Issue #145)
|
|
3
|
+
-- 注意:SQLite 在非空表上执行 ALTER TABLE ADD COLUMN 时,禁止使用非字面常量的 DEFAULT 表达式(如 strftime)。
|
|
4
|
+
-- 因此遵循 SQLite 标准做法,通过「新建临时表 + 迁移存量数据 + 替换表」实现幂等安全迁移。
|
|
5
|
+
CREATE TABLE IF NOT EXISTS student_read_notifications_new (
|
|
6
|
+
student_id TEXT NOT NULL,
|
|
7
|
+
notification_id TEXT NOT NULL,
|
|
8
|
+
created_at INTEGER DEFAULT (strftime('%s','now')*1000),
|
|
9
|
+
PRIMARY KEY (student_id, notification_id)
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
INSERT OR IGNORE INTO student_read_notifications_new (student_id, notification_id, created_at)
|
|
13
|
+
SELECT student_id, notification_id, CAST(strftime('%s','now')*1000 AS INTEGER) FROM student_read_notifications;
|
|
14
|
+
|
|
15
|
+
DROP TABLE IF EXISTS student_read_notifications;
|
|
16
|
+
ALTER TABLE student_read_notifications_new RENAME TO student_read_notifications;
|
|
17
|
+
|
|
18
|
+
-- DOWN
|
|
19
|
+
ALTER TABLE student_read_notifications DROP COLUMN created_at;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Issue #151:插件配置分层(平台 → 学校 → 班级 → 插件默认值)。
|
|
2
|
+
--
|
|
3
|
+
-- 旧实现把配置值塞在 plugin_storage 的 `config:<key>` 里,只有一层(等价于平台层)。
|
|
4
|
+
-- 新增独立表承载 scope,避免把作用域编码进 key 而无法按 scope 查询/清理。
|
|
5
|
+
--
|
|
6
|
+
-- 兼容性:
|
|
7
|
+
-- * 建表幂等;
|
|
8
|
+
-- * 旧值在迁移时原样搬到平台层(scope_type='platform', scope_id='');
|
|
9
|
+
-- * ConfigService 仍会在新表缺行时回退读 plugin_storage,双重保险。
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS plugin_config (
|
|
12
|
+
plugin_id TEXT NOT NULL,
|
|
13
|
+
scope_type TEXT NOT NULL DEFAULT 'platform',
|
|
14
|
+
scope_id TEXT NOT NULL DEFAULT '',
|
|
15
|
+
key TEXT NOT NULL,
|
|
16
|
+
value TEXT NOT NULL,
|
|
17
|
+
updated_at INTEGER NOT NULL,
|
|
18
|
+
PRIMARY KEY (plugin_id, scope_type, scope_id, key)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_config_scope
|
|
22
|
+
ON plugin_config (plugin_id, scope_type, scope_id);
|
|
23
|
+
|
|
24
|
+
-- 迁移旧值:plugin_storage.key = 'config:<key>' → plugin_config(platform, '', <key>)
|
|
25
|
+
INSERT INTO plugin_config (plugin_id, scope_type, scope_id, key, value, updated_at)
|
|
26
|
+
SELECT
|
|
27
|
+
plugin_id,
|
|
28
|
+
'platform',
|
|
29
|
+
'',
|
|
30
|
+
substr(key, 8),
|
|
31
|
+
value,
|
|
32
|
+
COALESCE(updated_at, 0)
|
|
33
|
+
FROM plugin_storage
|
|
34
|
+
WHERE key LIKE 'config:%'
|
|
35
|
+
ON CONFLICT (plugin_id, scope_type, scope_id, key) DO NOTHING;
|
|
36
|
+
|
|
37
|
+
-- DOWN
|
|
38
|
+
-- 回滚只删新表/索引:旧值仍在 plugin_storage,回退到旧版 ConfigService 后可继续读取。
|
|
39
|
+
DROP INDEX IF EXISTS idx_plugin_config_scope;
|
|
40
|
+
DROP TABLE IF EXISTS plugin_config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Issue #187:AI 辅助批改建议(教师确认后生效)。
|
|
2
|
+
--
|
|
3
|
+
-- 建议与分数分开存:ai_suggestion_json 只放「建议分 + 理由 + 置信度」,
|
|
4
|
+
-- 绝不直接写 score。教师确认走既有 manualGrade(manual_override = 1)。
|
|
5
|
+
-- grader 列在未判分时记 'ai_suggested' 作为来源标记;已有分数的行不改 grader。
|
|
6
|
+
|
|
7
|
+
ALTER TABLE assessment_answers ADD COLUMN ai_suggestion_json TEXT;
|
|
8
|
+
|
|
9
|
+
-- DOWN
|
|
10
|
+
ALTER TABLE assessment_answers DROP COLUMN ai_suggestion_json;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Issue #186:题库共享与按规则随机抽题(每人一卷)。
|
|
2
|
+
--
|
|
3
|
+
-- * question_bank_items.visibility:'private'(默认,仅 owner)| 'school'(同实例教师可见)。
|
|
4
|
+
-- 共享不复制条目:读取时按 owner_id + visibility 过滤。
|
|
5
|
+
-- * assessment_attempts.paper_json:本次作答实际抽到的题目集合与种子({ seed, itemIds })。
|
|
6
|
+
-- 同一学生多次进入复用同一条 attempt/paper,保证题目一致;不同学生种子不同 → 卷不同。
|
|
7
|
+
-- 未配置抽题规则的试卷为 NULL(沿用整卷)。
|
|
8
|
+
|
|
9
|
+
ALTER TABLE question_bank_items ADD COLUMN visibility TEXT NOT NULL DEFAULT 'private';
|
|
10
|
+
ALTER TABLE assessment_attempts ADD COLUMN paper_json TEXT;
|
|
11
|
+
|
|
12
|
+
-- DOWN
|
|
13
|
+
ALTER TABLE assessment_attempts DROP COLUMN paper_json;
|
|
14
|
+
ALTER TABLE question_bank_items DROP COLUMN visibility;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- UP
|
|
2
|
+
-- 成绩审计与手动分保护(P1 审计修复):
|
|
3
|
+
-- 1. assignment_grade_history:每次成绩变更一行,记录旧/新分数、状态、来源与操作者,
|
|
4
|
+
-- 用于回答「这个分数是谁、什么时候、为什么改的」。
|
|
5
|
+
-- 2. assignment_submissions.graded_by:最后一次成绩写入的操作者
|
|
6
|
+
-- (`user:<id>:<role>` 或 `system`),学期成绩页可据此展示录入人。
|
|
7
|
+
-- 3. source 补齐:历史遗留 NULL(手动按钮、插件投影、学期成绩同步)按 manual 处理。
|
|
8
|
+
-- 自动录入只覆盖 source='auto' 的行,NULL 若不回填会被当成「未保护」直接冲掉。
|
|
9
|
+
-- 4. 缺考行(status='absent')继续保持 source='manual',自动规则不得覆盖。
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS assignment_grade_history (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
assignment_id TEXT NOT NULL,
|
|
14
|
+
student_id TEXT NOT NULL,
|
|
15
|
+
old_score REAL,
|
|
16
|
+
new_score REAL,
|
|
17
|
+
old_status TEXT,
|
|
18
|
+
new_status TEXT,
|
|
19
|
+
old_source TEXT,
|
|
20
|
+
new_source TEXT,
|
|
21
|
+
actor_id TEXT,
|
|
22
|
+
actor_role TEXT,
|
|
23
|
+
reason TEXT,
|
|
24
|
+
created_at INTEGER NOT NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_assignment_grade_history_target
|
|
28
|
+
ON assignment_grade_history(assignment_id, student_id, created_at);
|
|
29
|
+
|
|
30
|
+
ALTER TABLE assignment_submissions ADD COLUMN graded_by TEXT;
|
|
31
|
+
|
|
32
|
+
UPDATE assignment_submissions SET source = 'manual' WHERE source IS NULL;
|
|
33
|
+
|
|
34
|
+
-- DOWN
|
|
35
|
+
DROP INDEX IF EXISTS idx_assignment_grade_history_target;
|
|
36
|
+
DROP TABLE IF EXISTS assignment_grade_history;
|
|
37
|
+
ALTER TABLE assignment_submissions DROP COLUMN graded_by;
|