Files
gia-gve-certificates/src/db.js
2026-07-01 11:17:17 +05:00

188 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 };