Files
Toolbox/src/server/weekly_db.js
T

186 lines
5.6 KiB
JavaScript

const Database = require('better-sqlite3')
const fs = require('fs')
const path = require('path')
const { ensureDecrypted } = require('./weekly_crypto')
let db = null
const getDB = () => {
if (!db) throw new Error('Database not initialized')
return db
}
const tryOpen = (dbPath) => {
try {
const d = new Database(dbPath)
try {
const row = d.prepare(`PRAGMA integrity_check`).get()
const ok = row && (row.integrity_check === 'ok' || row['integrity_check'] === 'ok' || Object.values(row)[0] === 'ok')
if (!ok) throw new Error('integrity_check_failed')
} catch (e) {
throw e
}
return d
} catch (e) {
return null
}
}
const initDB = (dbPath, sealPath) => {
if (db) return db
// Robust Open
db = tryOpen(dbPath)
if (!db) {
// Attempt recovery
if (fs.existsSync(dbPath)) {
const badName = `${dbPath}.bad-${Date.now()}`
try { fs.renameSync(dbPath, badName) } catch {}
console.warn(`Database corrupt, renamed to ${badName}`)
}
// Try decrypt from seal
if (sealPath) ensureDecrypted(dbPath, sealPath)
db = tryOpen(dbPath)
if (!db) {
// If still fails, create fresh (previous was renamed or didn't exist)
db = new Database(dbPath)
}
}
// Pragma
db.pragma('journal_mode = WAL');
db.pragma('synchronous = NORMAL');
db.pragma('busy_timeout = 4000');
// Users
db.exec(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
pass_salt TEXT,
pass_hash TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)`)
// Reports
db.exec(`CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
week_id TEXT UNIQUE,
title_enc TEXT, title_iv TEXT, title_tag TEXT,
summary_enc TEXT, summary_iv TEXT, summary_tag TEXT,
content_enc TEXT, content_iv TEXT, content_tag TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)`)
// Events
db.exec(`CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER,
parent_event_id INTEGER,
title_enc TEXT, title_iv TEXT, title_tag TEXT,
content_enc TEXT, content_iv TEXT, content_tag TEXT,
status_code TEXT,
status_enc TEXT, status_iv TEXT, status_tag TEXT,
deadline TEXT,
push_required INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
completed_at TEXT,
FOREIGN KEY(report_id) REFERENCES reports(id)
)`)
// Event Reviews
db.exec(`CREATE TABLE IF NOT EXISTS event_reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER,
review_enc TEXT, review_iv TEXT, review_tag TEXT,
rating INTEGER,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY(event_id) REFERENCES events(id)
)`)
// Attachments
db.exec(`CREATE TABLE IF NOT EXISTS attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER,
filename_enc TEXT, filename_iv TEXT, filename_tag TEXT,
mime_enc TEXT, mime_iv TEXT, mime_tag TEXT,
size INTEGER,
chunks INTEGER DEFAULT 1,
store_enc TEXT, store_iv TEXT, store_tag TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY(event_id) REFERENCES events(id)
)`)
// Versions
db.exec(`CREATE TABLE IF NOT EXISTS versions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subject_type TEXT,
subject_id INTEGER,
snapshot_enc TEXT, snapshot_iv TEXT, snapshot_tag TEXT,
created_at TEXT DEFAULT (datetime('now'))
)`)
// Logs
db.exec(`CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT,
details TEXT,
source TEXT,
created_at TEXT DEFAULT (datetime('now'))
)`)
// Email Sends
db.exec(`CREATE TABLE IF NOT EXISTS email_sends (
id INTEGER PRIMARY KEY AUTOINCREMENT,
day TEXT,
report_id INTEGER,
event_id INTEGER,
category TEXT,
created_at TEXT DEFAULT (datetime('now')),
UNIQUE(day, report_id, event_id, category)
)`)
// Event External (Ensure table exists for integrations)
db.exec(`CREATE TABLE IF NOT EXISTS event_external (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
ext_id TEXT NOT NULL,
event_id INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
UNIQUE(source, ext_id),
FOREIGN KEY(event_id) REFERENCES events(id)
);`)
// Event Relations
db.exec(`CREATE TABLE IF NOT EXISTS event_relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER NOT NULL,
target_event_id INTEGER NOT NULL,
relation_type TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY(event_id) REFERENCES events(id),
FOREIGN KEY(target_event_id) REFERENCES events(id)
);`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_reports_week_id ON reports(week_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_report_id ON events(report_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_parent_event_id ON events(parent_event_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_event_reviews_event_id ON event_reviews(event_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_attachments_event_id ON attachments(event_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_versions_subject ON versions(subject_type, subject_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_event_external_event_id ON event_external(event_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_event_relations_event_id ON event_relations(event_id)`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_event_relations_target_event_id ON event_relations(target_event_id)`)
return db
}
module.exports = {
initDB,
getDB
}