109 lines
5.5 KiB
JavaScript
Executable File
109 lines
5.5 KiB
JavaScript
Executable File
const Database = require('better-sqlite3');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'cpas.db');
|
|
const dir = path.dirname(dbPath);
|
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
|
|
const db = new Database(dbPath);
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8');
|
|
db.exec(schema);
|
|
|
|
// ── Migrations for existing DBs ──────────────────────────────────────────────
|
|
const cols = db.prepare('PRAGMA table_info(violations)').all().map(c => c.name);
|
|
if (!cols.includes('negated')) db.exec("ALTER TABLE violations ADD COLUMN negated INTEGER NOT NULL DEFAULT 0");
|
|
if (!cols.includes('negated_at')) db.exec("ALTER TABLE violations ADD COLUMN negated_at DATETIME");
|
|
if (!cols.includes('prior_active_points')) db.exec("ALTER TABLE violations ADD COLUMN prior_active_points INTEGER");
|
|
if (!cols.includes('prior_tier_label')) db.exec("ALTER TABLE violations ADD COLUMN prior_tier_label TEXT");
|
|
if (!cols.includes('acknowledged_by')) db.exec("ALTER TABLE violations ADD COLUMN acknowledged_by TEXT");
|
|
if (!cols.includes('acknowledged_date')) db.exec("ALTER TABLE violations ADD COLUMN acknowledged_date TEXT");
|
|
// Financial amount in question (record-keeping / repayment for chargeback, receipt negligence, etc.)
|
|
if (!cols.includes('amount')) db.exec("ALTER TABLE violations ADD COLUMN amount TEXT");
|
|
|
|
// Employee notes column (free-text, does not affect scoring)
|
|
const empCols = db.prepare('PRAGMA table_info(employees)').all().map(c => c.name);
|
|
if (!empCols.includes('notes')) db.exec("ALTER TABLE employees ADD COLUMN notes TEXT");
|
|
|
|
// Ensure resolutions table exists
|
|
db.exec(`CREATE TABLE IF NOT EXISTS violation_resolutions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
violation_id INTEGER NOT NULL REFERENCES violations(id) ON DELETE CASCADE,
|
|
resolution_type TEXT NOT NULL,
|
|
details TEXT,
|
|
resolved_by TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// ── Feature: Violation Amendments ────────────────────────────────────────────
|
|
// Stores a field-level diff every time a violation's editable fields are changed.
|
|
db.exec(`CREATE TABLE IF NOT EXISTS violation_amendments (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
violation_id INTEGER NOT NULL REFERENCES violations(id) ON DELETE CASCADE,
|
|
changed_by TEXT,
|
|
field_name TEXT NOT NULL,
|
|
old_value TEXT,
|
|
new_value TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// ── Feature: Audit Log ───────────────────────────────────────────────────────
|
|
// Append-only record of every write action across the system.
|
|
db.exec(`CREATE TABLE IF NOT EXISTS audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
action TEXT NOT NULL,
|
|
entity_type TEXT NOT NULL,
|
|
entity_id INTEGER,
|
|
performed_by TEXT,
|
|
details TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// ── Feature: Custom Violation Types ──────────────────────────────────────────
|
|
// Persisted violation type definitions created via the UI. type_key is prefixed
|
|
// with 'custom_' to prevent collisions with hardcoded violation keys.
|
|
db.exec(`CREATE TABLE IF NOT EXISTS violation_types (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type_key TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
category TEXT NOT NULL DEFAULT 'Custom',
|
|
chapter TEXT,
|
|
description TEXT,
|
|
min_points INTEGER NOT NULL DEFAULT 1,
|
|
max_points INTEGER NOT NULL DEFAULT 1,
|
|
fields TEXT NOT NULL DEFAULT '["description"]',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// ── Feature: Authentication ──────────────────────────────────────────────────
|
|
// User accounts and login sessions. Passwords are stored as scrypt hashes
|
|
// (see auth.js). The bootstrap admin account is created/synced from the
|
|
// ADMIN_USERNAME / ADMIN_PASSWORD environment variables on startup.
|
|
db.exec(`CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
|
password_hash TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'user',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
db.exec(`CREATE TABLE IF NOT EXISTS sessions (
|
|
token TEXT PRIMARY KEY,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at DATETIME NOT NULL
|
|
)`);
|
|
|
|
// The old `active_cpas_scores` view implemented a naive per-violation 90-day
|
|
// window. CPAS standing now follows the clean-cycle roll-off model (see
|
|
// lib/rolloff.js), which is order-dependent and computed in JS, so the view is
|
|
// retired here to keep a single source of truth.
|
|
db.exec('DROP VIEW IF EXISTS active_cpas_scores;');
|
|
|
|
console.log('[DB] Connected:', dbPath);
|
|
module.exports = db;
|