This commit is contained in:
2026-07-21 12:49:59 +08:00 Unverified
parent 97bbe07789
commit ae528b20e9
24 changed files with 770 additions and 30 deletions
+35 -9
View File
@@ -32,6 +32,8 @@ export function createSqliteAdapter(context) {
ensureColumns('users', [
['admin_level', 'TEXT'], ['school_id', 'TEXT'], ['class_id', 'TEXT'], ['active', 'INTEGER NOT NULL DEFAULT 1'],
['candidate_number', 'TEXT'], ['must_change_password', 'INTEGER NOT NULL DEFAULT 0'],
['totp_enabled', 'INTEGER NOT NULL DEFAULT 0'], ['totp_secret_encrypted', 'TEXT'],
['totp_recovery_codes', "TEXT NOT NULL DEFAULT '[]'"], ['totp_last_used_step', 'INTEGER'],
['archived_at', 'TEXT'], ['archived_by', 'TEXT']
]);
ensureColumns('schema_metadata', [['self_registration_enabled', 'INTEGER NOT NULL DEFAULT 0']]);
@@ -39,7 +41,8 @@ export function createSqliteAdapter(context) {
['school_id', 'TEXT'], ['class_id', 'TEXT'], ['native_place', 'TEXT'], ['birth_date', 'TEXT'], ['ethnicity', 'TEXT'],
['postal_code', 'TEXT'], ['guardian_name', 'TEXT'], ['guardian_phone', 'TEXT'], ['profile_completed', 'INTEGER NOT NULL DEFAULT 0'],
['province_code', 'TEXT'], ['province_name', 'TEXT'], ['city_code', 'TEXT'], ['city_name', 'TEXT'],
['district_code', 'TEXT'], ['district_name', 'TEXT']
['district_code', 'TEXT'], ['district_name', 'TEXT'], ['specialty_types', "TEXT NOT NULL DEFAULT '[]'"],
['specialty_certificate', 'TEXT'], ['policy_eligibility', 'TEXT']
]);
ensureColumns('registrations', [['registration_number', 'TEXT'], ['number_rule_id', 'TEXT']]);
ensureColumns('exams', [
@@ -68,6 +71,32 @@ export function createSqliteAdapter(context) {
ensureColumns('admit_card_subjects', [
['building', "TEXT NOT NULL DEFAULT ''"], ['floor', "TEXT NOT NULL DEFAULT ''"]
]);
if (tableExists('users')) {
const usersSql = connection.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'users'").get()?.sql || '';
if (!usersSql.includes('admission_school')) {
connection.exec(`
PRAGMA foreign_keys = OFF;
BEGIN IMMEDIATE;
CREATE TABLE users_v18 (
id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, candidate_number TEXT UNIQUE, password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('admin', 'candidate', 'admission_school')),
admin_level TEXT CHECK (admin_level IN ('super', 'school', 'class')),
school_id TEXT REFERENCES schools(id) ON DELETE SET NULL, class_id TEXT REFERENCES school_classes(id) ON DELETE SET NULL,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)), must_change_password INTEGER NOT NULL DEFAULT 0 CHECK (must_change_password IN (0, 1)),
totp_enabled INTEGER NOT NULL DEFAULT 0 CHECK (totp_enabled IN (0, 1)), totp_secret_encrypted TEXT,
totp_recovery_codes TEXT NOT NULL DEFAULT '[]', totp_last_used_step INTEGER, archived_at TEXT,
archived_by TEXT REFERENCES users_v18(id) ON DELETE RESTRICT, display_name TEXT NOT NULL, created_at TEXT NOT NULL
) STRICT;
INSERT INTO users_v18 SELECT id, username, candidate_number, password_hash, role, admin_level, school_id, class_id,
active, must_change_password, COALESCE(totp_enabled, 0), totp_secret_encrypted, COALESCE(totp_recovery_codes, '[]'),
totp_last_used_step, archived_at, archived_by, display_name, created_at FROM users;
DROP TABLE users;
ALTER TABLE users_v18 RENAME TO users;
COMMIT;
PRAGMA foreign_keys = ON;
`);
}
}
if (tableExists('workflow_definitions')) {
const definitionSql = connection.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'workflow_definitions'").get()?.sql || '';
if (!definitionSql.includes('candidate_account_batch')) {
@@ -246,13 +275,10 @@ export function createSqliteAdapter(context) {
connection.prepare('UPDATE schema_metadata SET schema_version = 16 WHERE id = 1').run();
}
if (existingSystem && Number(existingSystem.schema_version || 1) < 17) {
connection.exec(`
ALTER TABLE users ADD COLUMN totp_enabled INTEGER NOT NULL DEFAULT 0 CHECK (totp_enabled IN (0, 1));
ALTER TABLE users ADD COLUMN totp_secret_encrypted TEXT;
ALTER TABLE users ADD COLUMN totp_recovery_codes TEXT NOT NULL DEFAULT '[]';
ALTER TABLE users ADD COLUMN totp_last_used_step INTEGER;
UPDATE schema_metadata SET schema_version = 17 WHERE id = 1;
`);
connection.prepare('UPDATE schema_metadata SET schema_version = 17 WHERE id = 1').run();
}
if (existingSystem && Number(existingSystem.schema_version || 1) < 18) {
connection.prepare('UPDATE schema_metadata SET schema_version = 18, app_version = 18 WHERE id = 1').run();
}
if (existingSystem && Number(existingSystem.app_version || 1) < 2) {
const extension = seed();
@@ -414,7 +440,7 @@ export function createSqliteAdapter(context) {
try {
connection.prepare(`
INSERT INTO schema_metadata (id, schema_version, app_version, self_registration_enabled, created_at)
VALUES (1, 17, ?, ?, ?)
VALUES (1, 18, ?, ?, ?)
`).run(Number(initialState.meta?.version || 1), initialState.settings?.selfRegistrationEnabled ? 1 : 0, initialState.meta?.createdAt || new Date().toISOString());
for (const item of buildSeedOperations(initialState)) connection.prepare(item.sql).run(...item.params);
connection.exec('COMMIT');