188 lines
5.9 KiB
JavaScript
188 lines
5.9 KiB
JavaScript
const initSqlJs = require('sql.js');
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
|
||
const DATA_DIR = path.join(__dirname, '..', 'data');
|
||
const DB_PATH = path.join(DATA_DIR, 'exams.db');
|
||
|
||
let db = null;
|
||
|
||
async function initDb() {
|
||
if (!fs.existsSync(DATA_DIR)) {
|
||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||
}
|
||
|
||
const SQL = await initSqlJs();
|
||
|
||
if (fs.existsSync(DB_PATH)) {
|
||
const buffer = fs.readFileSync(DB_PATH);
|
||
db = new SQL.Database(buffer);
|
||
} else {
|
||
db = new SQL.Database();
|
||
}
|
||
|
||
db.run('PRAGMA foreign_keys = ON');
|
||
|
||
db.run(`
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
login TEXT UNIQUE NOT NULL,
|
||
password TEXT,
|
||
name TEXT NOT NULL,
|
||
email TEXT,
|
||
role TEXT DEFAULT 'teacher',
|
||
auth_type TEXT DEFAULT 'local',
|
||
groups TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
last_login DATETIME
|
||
)
|
||
`);
|
||
|
||
db.run(`
|
||
CREATE TABLE IF NOT EXISTS students (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
last_name TEXT NOT NULL,
|
||
first_name TEXT NOT NULL,
|
||
patronymic TEXT DEFAULT '',
|
||
class TEXT DEFAULT '',
|
||
school_code TEXT DEFAULT '',
|
||
passport_series TEXT DEFAULT '',
|
||
passport_number TEXT DEFAULT '',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(last_name, first_name, patronymic, class)
|
||
)
|
||
`);
|
||
|
||
db.run(`
|
||
CREATE TABLE IF NOT EXISTS subjects (
|
||
code TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
exam_date TEXT DEFAULT '',
|
||
is_mandatory INTEGER DEFAULT 0
|
||
)
|
||
`);
|
||
|
||
db.run(`
|
||
CREATE TABLE IF NOT EXISTS exam_results (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
student_id INTEGER NOT NULL,
|
||
subject_code TEXT NOT NULL,
|
||
primary_score INTEGER,
|
||
grade INTEGER,
|
||
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
|
||
FOREIGN KEY (subject_code) REFERENCES subjects(code),
|
||
UNIQUE(student_id, subject_code)
|
||
)
|
||
`);
|
||
|
||
db.run('INSERT OR IGNORE INTO subjects (code, name, is_mandatory) VALUES (?, ?, 1)', ['01', 'Русский язык']);
|
||
db.run('INSERT OR IGNORE INTO subjects (code, name, is_mandatory) VALUES (?, ?, 1)', ['02', 'Математика']);
|
||
|
||
saveDb();
|
||
|
||
// Миграция: перенос Русского/Математики в обязательные слоты 01/02, если они пусты
|
||
migrateMandatorySubjects();
|
||
|
||
console.log('БД инициализирована');
|
||
return db;
|
||
}
|
||
|
||
function saveDb() {
|
||
if (!db) return;
|
||
const data = db.export();
|
||
const buffer = Buffer.from(data);
|
||
fs.writeFileSync(DB_PATH, buffer);
|
||
}
|
||
|
||
function migrateMandatorySubjects() {
|
||
if (!db) return;
|
||
|
||
// Найти русский язык с кодом ≠ 01, где слот 01 пуст (нет grade)
|
||
const rusRows = dbAll(`
|
||
SELECT DISTINCT er.student_id, er.subject_code, er.primary_score, er.grade
|
||
FROM exam_results er
|
||
JOIN subjects s ON s.code = er.subject_code
|
||
WHERE s.name = 'Русский язык' AND er.subject_code != '01'
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM exam_results er2
|
||
WHERE er2.student_id = er.student_id AND er2.subject_code = '01' AND er2.grade IS NOT NULL
|
||
)
|
||
`);
|
||
for (const row of rusRows) {
|
||
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [row.student_id, '01', row.primary_score, row.grade]);
|
||
dbRun('DELETE FROM exam_results WHERE student_id = ? AND subject_code = ?', [row.student_id, row.subject_code]);
|
||
}
|
||
|
||
// Найти математику с кодом ≠ 02, где слот 02 пуст
|
||
const mathRows = dbAll(`
|
||
SELECT DISTINCT er.student_id, er.subject_code, er.primary_score, er.grade
|
||
FROM exam_results er
|
||
JOIN subjects s ON s.code = er.subject_code
|
||
WHERE s.name = 'Математика' AND er.subject_code != '02'
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM exam_results er2
|
||
WHERE er2.student_id = er.student_id AND er2.subject_code = '02' AND er2.grade IS NOT NULL
|
||
)
|
||
`);
|
||
for (const row of mathRows) {
|
||
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [row.student_id, '02', row.primary_score, row.grade]);
|
||
dbRun('DELETE FROM exam_results WHERE student_id = ? AND subject_code = ?', [row.student_id, row.subject_code]);
|
||
}
|
||
|
||
// Удалить лишние subjects (не 01/02) для русского и математики, если они без результатов
|
||
dbRun(`DELETE FROM subjects WHERE (name = 'Русский язык' AND code != '01') OR (name = 'Математика' AND code != '02')`);
|
||
|
||
if (rusRows.length > 0 || mathRows.length > 0) {
|
||
console.log(`Миграция: перенесено в обязательные — русский: ${rusRows.length}, математика: ${mathRows.length}`);
|
||
saveDb();
|
||
}
|
||
}
|
||
|
||
function getDb() {
|
||
if (!db) throw new Error('БД не инициализирована.');
|
||
return db;
|
||
}
|
||
|
||
// Wrappers emulating better-sqlite3 API
|
||
function dbGet(sql, params = []) {
|
||
const st = db.prepare(sql);
|
||
st.bind(params);
|
||
const hasRow = st.step();
|
||
const row = hasRow ? st.getAsObject() : null;
|
||
st.free();
|
||
return row;
|
||
}
|
||
|
||
function dbAll(sql, params = []) {
|
||
const st = db.prepare(sql);
|
||
st.bind(params);
|
||
const rows = [];
|
||
while (st.step()) {
|
||
rows.push(st.getAsObject());
|
||
}
|
||
st.free();
|
||
return rows;
|
||
}
|
||
|
||
function dbRun(sql, params = []) {
|
||
const before = db.getRowsModified();
|
||
db.run(sql, params);
|
||
const after = db.getRowsModified();
|
||
return { changes: after - before };
|
||
}
|
||
|
||
function dbLastInsertId() {
|
||
const r = db.exec('SELECT last_insert_rowid() as id');
|
||
return r.length ? r[0].values[0][0] : null;
|
||
}
|
||
|
||
function dbExec(sql) {
|
||
db.run(sql);
|
||
}
|
||
|
||
function dbBegin() { db.run('BEGIN'); }
|
||
function dbCommit() { db.run('COMMIT'); }
|
||
function dbRollback() { try { db.run('ROLLBACK'); } catch (e) {} }
|
||
|
||
module.exports = { initDb, getDb, saveDb, dbGet, dbAll, dbRun, dbLastInsertId, dbExec, dbBegin, dbCommit, dbRollback };
|