110 lines
3.9 KiB
JavaScript
Executable file
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 };
|