140 lines
3.5 KiB
JavaScript
140 lines
3.5 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();
|
|
console.log('БД инициализирована');
|
|
return db;
|
|
}
|
|
|
|
function saveDb() {
|
|
if (!db) return;
|
|
const data = db.export();
|
|
const buffer = Buffer.from(data);
|
|
fs.writeFileSync(DB_PATH, buffer);
|
|
}
|
|
|
|
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 };
|