feat: 修改部分导入文本的逻辑,添加部署脚本和样式文件,更新数据库迁移逻辑
- 新增部署脚本 `build-deploy-bundle.mjs`,用于构建和部署 web 和 server 目录。 - 新增样式文件 `index-acd65452.css`,包含基础样式和响应式设计。 - 新增脚本 `repro-import-text.mjs`,用于测试文本导入 API。 - 新增测试文件 `db-migration-score-zero.test.ts`,验证历史数据库中 questions.score 约束的迁移逻辑。 - 更新数据库初始化逻辑,允许插入 score=0 的问题。
This commit is contained in:
@@ -164,9 +164,84 @@ const ensureUserGroupSchemaAndAllUsersMembership = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getTableCreateSql = async (tableName: string): Promise<string> => {
|
||||
const row = await get(
|
||||
`SELECT sql FROM sqlite_master WHERE type='table' AND name=? LIMIT 1`,
|
||||
[tableName],
|
||||
);
|
||||
return (row?.sql as string | undefined) ?? '';
|
||||
};
|
||||
|
||||
const tableSqlHasScoreGreaterThanZeroCheck = (tableSql: string): boolean => {
|
||||
if (!tableSql) return false;
|
||||
// 兼容不同空白/大小写写法:CHECK(score > 0) / CHECK ( score>0 )
|
||||
return /check\s*\(\s*score\s*>\s*0\s*\)/i.test(tableSql);
|
||||
};
|
||||
|
||||
const migrateQuestionsScoreCheckToAllowZero = async () => {
|
||||
const questionsSql = await getTableCreateSql('questions');
|
||||
if (!tableSqlHasScoreGreaterThanZeroCheck(questionsSql)) return;
|
||||
|
||||
console.log('检测到旧表约束:questions.score CHECK(score > 0),开始迁移为 >= 0');
|
||||
|
||||
// 迁移方式:重建 questions 表(SQLite 不支持直接修改 CHECK 约束)
|
||||
// 注意:questions 被 quiz_answers 外键引用,因此迁移期间临时关闭 foreign_keys。
|
||||
await exec('PRAGMA foreign_keys = OFF;');
|
||||
try {
|
||||
await exec('BEGIN TRANSACTION;');
|
||||
|
||||
await exec(`
|
||||
CREATE TABLE IF NOT EXISTS questions_new (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('single', 'multiple', 'judgment', 'text')),
|
||||
options TEXT,
|
||||
answer TEXT NOT NULL,
|
||||
analysis TEXT NOT NULL DEFAULT '',
|
||||
score INTEGER NOT NULL CHECK(score >= 0),
|
||||
category TEXT NOT NULL DEFAULT '通用',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
await exec(`
|
||||
INSERT INTO questions_new (id, content, type, options, answer, analysis, score, category, created_at)
|
||||
SELECT
|
||||
id,
|
||||
content,
|
||||
type,
|
||||
options,
|
||||
COALESCE(answer, ''),
|
||||
COALESCE(analysis, ''),
|
||||
score,
|
||||
COALESCE(category, '通用'),
|
||||
created_at
|
||||
FROM questions;
|
||||
`);
|
||||
|
||||
await exec('DROP TABLE questions;');
|
||||
await exec('ALTER TABLE questions_new RENAME TO questions;');
|
||||
|
||||
await exec('CREATE INDEX IF NOT EXISTS idx_questions_type ON questions(type);');
|
||||
await exec('CREATE INDEX IF NOT EXISTS idx_questions_score ON questions(score);');
|
||||
await exec('CREATE INDEX IF NOT EXISTS idx_questions_category ON questions(category);');
|
||||
|
||||
await exec('COMMIT;');
|
||||
console.log('questions 表迁移完成:score 允许 0 分');
|
||||
} catch (error) {
|
||||
try {
|
||||
await exec('ROLLBACK;');
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await exec('PRAGMA foreign_keys = ON;');
|
||||
}
|
||||
};
|
||||
|
||||
const migrateDatabase = async () => {
|
||||
// 跳过迁移,因为数据库连接可能未初始化
|
||||
console.log('跳过数据库迁移');
|
||||
await migrateQuestionsScoreCheckToAllowZero();
|
||||
};
|
||||
|
||||
// 数据库初始化函数
|
||||
@@ -194,6 +269,9 @@ export const initDatabase = async () => {
|
||||
await ensureColumn('quiz_records', "score_percentage REAL", 'score_percentage');
|
||||
await ensureColumn('quiz_records', "status TEXT", 'status');
|
||||
|
||||
// 兼容历史数据库:迁移无法通过 init.sql 修复的约束/结构
|
||||
await migrateDatabase();
|
||||
|
||||
// 用户组(含“全体用户”系统组)
|
||||
await ensureUserGroupSchemaAndAllUsersMembership();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ CREATE TABLE questions (
|
||||
options TEXT, -- JSON格式存储选项
|
||||
answer TEXT NOT NULL,
|
||||
analysis TEXT NOT NULL DEFAULT '',
|
||||
score INTEGER NOT NULL CHECK(score > 0),
|
||||
score INTEGER NOT NULL CHECK(score >= 0),
|
||||
category TEXT NOT NULL DEFAULT '通用',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user