asset_browser/auth/db.js
2026-03-27 09:18:39 -04:00

110 lines
3.9 KiB
JavaScript
Executable file

'use strict';
const Database = require('better-sqlite3');
const path = require('path');
let _db;
function getDb() {
if (!_db) {
_db = new Database(path.join(__dirname, '..', 'data', 'app.db'));
_db.pragma('journal_mode = WAL');
_db.pragma('foreign_keys = ON');
}
return _db;
}
function initDb() {
const db = getDb();
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL COLLATE NOCASE,
password_hash TEXT NOT NULL,
name TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('superduperadmin','admin','tech','client')),
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sessions (
sid TEXT PRIMARY KEY NOT NULL,
sess TEXT NOT NULL,
expired INTEGER NOT NULL
);
`);
// Migrations
const cols = db.prepare('PRAGMA table_info(users)').all().map(c => c.name);
if (!cols.includes('company')) {
db.exec("ALTER TABLE users ADD COLUMN company TEXT NOT NULL DEFAULT ''");
}
if (!cols.includes('syncro_customer_id')) {
db.exec('ALTER TABLE users ADD COLUMN syncro_customer_id INTEGER DEFAULT NULL');
}
// Label queue migrations
const lqCols = db.prepare('PRAGMA table_info(label_queue)').all().map(c => c.name);
if (lqCols.length && !lqCols.includes('custom_line')) {
db.exec("ALTER TABLE label_queue ADD COLUMN custom_line TEXT DEFAULT NULL");
}
// Label Center queue — cross-device label batching
db.exec(`
CREATE TABLE IF NOT EXISTS label_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
asset_id INTEGER NOT NULL,
asset_name TEXT NOT NULL,
asset_serial TEXT,
customer_name TEXT NOT NULL,
customer_phone TEXT,
custom_line TEXT,
sheet_position INTEGER DEFAULT NULL,
added_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, asset_id),
UNIQUE(user_id, sheet_position)
);
CREATE INDEX IF NOT EXISTS idx_label_queue_user ON label_queue(user_id);
`);
}
// ── User queries ──────────────────────────────────────────────────────────────
function getUserByUsername(username) {
return getDb()
.prepare('SELECT * FROM users WHERE username = ? AND active = 1')
.get(username);
}
function getUserById(id) {
return getDb()
.prepare('SELECT id, username, name, company, syncro_customer_id, role, active, created_at FROM users WHERE id = ?')
.get(id);
}
function getAllUsers() {
return getDb()
.prepare('SELECT id, username, name, company, syncro_customer_id, role, active, created_at FROM users ORDER BY company COLLATE NOCASE, username COLLATE NOCASE')
.all();
}
function createUser(username, passwordHash, name, role, company = '', syncroCustomerId = null) {
return getDb()
.prepare('INSERT INTO users (username, password_hash, name, role, company, syncro_customer_id) VALUES (?, ?, ?, ?, ?, ?)')
.run(username, passwordHash, name, role, company, syncroCustomerId);
}
function updateUser(id, fields) {
const allowed = ['name', 'company', 'syncro_customer_id', 'role', 'active', 'password_hash'];
const filtered = Object.fromEntries(
Object.entries(fields).filter(([k]) => allowed.includes(k))
);
if (!Object.keys(filtered).length) return;
const sets = Object.keys(filtered).map(k => `${k} = ?`).join(', ');
return getDb()
.prepare(`UPDATE users SET ${sets}, updated_at = datetime('now') WHERE id = ?`)
.run(...Object.values(filtered), id);
}
module.exports = { initDb, getDb, getUserByUsername, getUserById, getAllUsers, createUser, updateUser };