Initial commit — Mek-Tech AI Consultancy Framework v2.0
This commit is contained in:
@@ -0,0 +1,555 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const DB_PATH = process.env.DB_PATH || path.join(__dirname, 'data', 'consulting.db');
|
||||
|
||||
let db;
|
||||
|
||||
function getDb() {
|
||||
if (!db) {
|
||||
const fs = require('fs');
|
||||
const dir = path.dirname(DB_PATH);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
db = new Database(DB_PATH);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
initSchema();
|
||||
migrateSchema();
|
||||
seedDefaults();
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
function migrateSchema() {
|
||||
const existingColumns = db.prepare("PRAGMA table_info('users')").all().map(r => r.name);
|
||||
const newCols = { email: "TEXT DEFAULT ''", role: "TEXT DEFAULT 'admin'", reset_token: "TEXT DEFAULT ''", reset_expires: "TEXT DEFAULT ''" };
|
||||
for (const [col, def] of Object.entries(newCols)) {
|
||||
if (!existingColumns.includes(col)) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN ${col} ${def}`);
|
||||
}
|
||||
}
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ai_providers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
provider_type TEXT NOT NULL DEFAULT 'openrouter',
|
||||
api_key TEXT DEFAULT '',
|
||||
base_url TEXT DEFAULT '',
|
||||
default_model TEXT DEFAULT '',
|
||||
is_active INTEGER DEFAULT 1,
|
||||
is_default INTEGER DEFAULT 0,
|
||||
extra_headers TEXT DEFAULT '{}',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS integrations (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT DEFAULT '',
|
||||
api_key TEXT DEFAULT '',
|
||||
webhook_url TEXT DEFAULT '',
|
||||
config TEXT DEFAULT '{}',
|
||||
status TEXT DEFAULT 'inactive',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS reports (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
report_type TEXT DEFAULT 'client_summary',
|
||||
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||
project_id INTEGER,
|
||||
config TEXT DEFAULT '{}',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||
start_date TEXT,
|
||||
end_date TEXT,
|
||||
status TEXT DEFAULT 'planning',
|
||||
priority TEXT DEFAULT 'medium',
|
||||
budget REAL,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
const diagramCols = db.prepare("PRAGMA table_info('data_diagrams')").all().map(r => r.name);
|
||||
if (!diagramCols.includes('engagement_id')) {
|
||||
db.exec('ALTER TABLE data_diagrams ADD COLUMN engagement_id INTEGER REFERENCES engagements(id) ON DELETE SET NULL');
|
||||
}
|
||||
if (!diagramCols.includes('rack_id')) {
|
||||
db.exec('ALTER TABLE data_diagrams ADD COLUMN rack_id INTEGER REFERENCES racks(id) ON DELETE SET NULL');
|
||||
}
|
||||
|
||||
const tblRow = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='data_diagrams'").get();
|
||||
const tblSql = tblRow ? tblRow.sql : '';
|
||||
if (tblSql && tblSql.includes('CHECK') && !tblSql.includes('application_flow')) {
|
||||
const cols = db.prepare("PRAGMA table_info('data_diagrams')").all();
|
||||
const colNames = cols.map(c => c.name);
|
||||
const selectCols = colNames.join(', ');
|
||||
db.exec(`
|
||||
CREATE TABLE data_diagrams_v2 (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
diagram_type TEXT DEFAULT 'data_architecture',
|
||||
nodes TEXT NOT NULL DEFAULT '[]',
|
||||
edges TEXT NOT NULL DEFAULT '[]',
|
||||
engagement_id INTEGER REFERENCES engagements(id) ON DELETE SET NULL,
|
||||
rack_id INTEGER REFERENCES racks(id) ON DELETE SET NULL,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
INSERT INTO data_diagrams_v2 (${selectCols}) SELECT ${selectCols} FROM data_diagrams;
|
||||
DROP TABLE data_diagrams;
|
||||
ALTER TABLE data_diagrams_v2 RENAME TO data_diagrams;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
function initSchema() {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
email TEXT DEFAULT '',
|
||||
role TEXT DEFAULT 'admin' CHECK(role IN ('admin','consultant','viewer')),
|
||||
reset_token TEXT DEFAULT '',
|
||||
reset_expires TEXT DEFAULT '',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
industry TEXT,
|
||||
website TEXT,
|
||||
contact_name TEXT,
|
||||
contact_email TEXT,
|
||||
contact_phone TEXT,
|
||||
status TEXT DEFAULT 'lead' CHECK(status IN ('lead','active','paused','archived')),
|
||||
source TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS engagements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL CHECK(type IN ('assessment','architecture','implementation','monitoring','consulting','training')),
|
||||
status TEXT DEFAULT 'planned' CHECK(status IN ('planned','in_progress','completed','on_hold')),
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
start_date TEXT,
|
||||
end_date TEXT,
|
||||
outcome TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assessments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
engagement_id INTEGER REFERENCES engagements(id) ON DELETE SET NULL,
|
||||
answers TEXT NOT NULL,
|
||||
report TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
engagement_id INTEGER NOT NULL REFERENCES engagements(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT DEFAULT 'todo' CHECK(status IN ('todo','in_progress','done','cancelled')),
|
||||
priority TEXT DEFAULT 'medium' CHECK(priority IN ('low','medium','high','critical')),
|
||||
due_date TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS advice_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
advice_type TEXT NOT NULL,
|
||||
advice_text TEXT NOT NULL,
|
||||
priority TEXT DEFAULT 'medium',
|
||||
dismissed INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS racks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
location TEXT,
|
||||
datacenter TEXT,
|
||||
total_units INTEGER DEFAULT 42,
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rack_devices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rack_id INTEGER NOT NULL REFERENCES racks(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
device_type TEXT DEFAULT 'server',
|
||||
model TEXT,
|
||||
manufacturer TEXT,
|
||||
position_u INTEGER NOT NULL,
|
||||
height_u INTEGER DEFAULT 1,
|
||||
specs TEXT,
|
||||
mgmt_ip TEXT,
|
||||
notes TEXT,
|
||||
serial TEXT DEFAULT '',
|
||||
asset_tag TEXT DEFAULT '',
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_devices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
device_type TEXT NOT NULL CHECK(device_type IN ('switch','router','firewall','loadbalancer','ap','other')),
|
||||
model TEXT,
|
||||
manufacturer TEXT,
|
||||
ip_address TEXT,
|
||||
mgmt_ip TEXT,
|
||||
ports_count INTEGER DEFAULT 24,
|
||||
os_version TEXT,
|
||||
notes TEXT,
|
||||
status TEXT DEFAULT 'active' CHECK(status IN ('active','standby','decommissioned')),
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_connections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
name TEXT,
|
||||
from_type TEXT NOT NULL CHECK(from_type IN ('rack_device','network_device','external')),
|
||||
from_id INTEGER,
|
||||
from_port TEXT,
|
||||
to_type TEXT NOT NULL CHECK(to_type IN ('rack_device','network_device','external')),
|
||||
to_id INTEGER,
|
||||
to_port TEXT,
|
||||
media_type TEXT DEFAULT 'copper' CHECK(media_type IN ('copper','sfp','sfp+','qsfp','dac','fiber')),
|
||||
speed TEXT DEFAULT '1GbE',
|
||||
vlan TEXT,
|
||||
status TEXT DEFAULT 'active' CHECK(status IN ('active','inactive','planned')),
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS data_diagrams (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
diagram_type TEXT DEFAULT 'data_architecture' CHECK(diagram_type IN ('data_architecture','network_topology','system_architecture')),
|
||||
nodes TEXT NOT NULL DEFAULT '[]',
|
||||
edges TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL,
|
||||
created_by TEXT DEFAULT '',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
original_name TEXT NOT NULL,
|
||||
stored_name TEXT NOT NULL,
|
||||
mime_type TEXT DEFAULT '',
|
||||
file_size INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
username TEXT DEFAULT '',
|
||||
action TEXT NOT NULL,
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id INTEGER,
|
||||
details TEXT DEFAULT '',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS time_entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||
engagement_id INTEGER REFERENCES engagements(id) ON DELETE SET NULL,
|
||||
date TEXT NOT NULL,
|
||||
hours REAL NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
billable INTEGER DEFAULT 1,
|
||||
hourly_rate REAL DEFAULT 150.0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS invoices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||
number TEXT UNIQUE NOT NULL,
|
||||
status TEXT DEFAULT 'draft' CHECK(status IN ('draft','sent','paid','overdue','cancelled')),
|
||||
date TEXT NOT NULL,
|
||||
due_date TEXT NOT NULL,
|
||||
subtotal REAL DEFAULT 0,
|
||||
tax REAL DEFAULT 0,
|
||||
total REAL DEFAULT 0,
|
||||
notes TEXT DEFAULT '',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS invoice_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
|
||||
description TEXT NOT NULL,
|
||||
quantity REAL DEFAULT 1,
|
||||
unit_price REAL DEFAULT 0,
|
||||
total REAL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
message TEXT DEFAULT '',
|
||||
channel TEXT DEFAULT 'in-app' CHECK(channel IN ('in-app','email','slack','webhook')),
|
||||
read INTEGER DEFAULT 0,
|
||||
sent INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||
engagement_id INTEGER REFERENCES engagements(id) ON DELETE SET NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
event_type TEXT DEFAULT 'meeting' CHECK(event_type IN ('meeting','call','deadline','workshop','review','other')),
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT NOT NULL,
|
||||
all_day INTEGER DEFAULT 0,
|
||||
color TEXT DEFAULT '#58a6ff',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_settings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL UNIQUE,
|
||||
notify_email INTEGER DEFAULT 1,
|
||||
notify_slack INTEGER DEFAULT 0,
|
||||
notify_webhook INTEGER DEFAULT 0,
|
||||
webhook_url TEXT DEFAULT '',
|
||||
slack_webhook TEXT DEFAULT '',
|
||||
language TEXT DEFAULT 'nl'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
name TEXT DEFAULT '',
|
||||
imap_host TEXT NOT NULL,
|
||||
imap_port INTEGER DEFAULT 993,
|
||||
imap_secure INTEGER DEFAULT 1,
|
||||
imap_user TEXT NOT NULL,
|
||||
imap_pass TEXT NOT NULL,
|
||||
smtp_host TEXT NOT NULL,
|
||||
smtp_port INTEGER DEFAULT 587,
|
||||
smtp_secure INTEGER DEFAULT 0,
|
||||
smtp_user TEXT DEFAULT '',
|
||||
smtp_pass TEXT DEFAULT '',
|
||||
active INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email_messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER NOT NULL REFERENCES email_accounts(id) ON DELETE CASCADE,
|
||||
uid INTEGER NOT NULL,
|
||||
folder TEXT DEFAULT 'INBOX',
|
||||
subject TEXT DEFAULT '',
|
||||
from_name TEXT DEFAULT '',
|
||||
from_addr TEXT DEFAULT '',
|
||||
to_addr TEXT DEFAULT '',
|
||||
cc TEXT DEFAULT '',
|
||||
bcc TEXT DEFAULT '',
|
||||
date TEXT DEFAULT '',
|
||||
body_text TEXT DEFAULT '',
|
||||
body_html TEXT DEFAULT '',
|
||||
attachments INTEGER DEFAULT 0,
|
||||
seen INTEGER DEFAULT 0,
|
||||
flagged INTEGER DEFAULT 0,
|
||||
replied INTEGER DEFAULT 0,
|
||||
size INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS datacenters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT DEFAULT '',
|
||||
city TEXT DEFAULT '',
|
||||
region TEXT DEFAULT '',
|
||||
postal_code TEXT DEFAULT '',
|
||||
phone TEXT DEFAULT '',
|
||||
website TEXT DEFAULT '',
|
||||
type TEXT DEFAULT 'colo',
|
||||
operator TEXT DEFAULT '',
|
||||
tier TEXT DEFAULT 'III',
|
||||
power_mw REAL DEFAULT 0,
|
||||
size_sqm INTEGER DEFAULT 0,
|
||||
network_vendors TEXT DEFAULT '',
|
||||
server_vendors TEXT DEFAULT '',
|
||||
storage_vendors TEXT DEFAULT '',
|
||||
certifications TEXT DEFAULT '',
|
||||
notes TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tech_companies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
industry TEXT DEFAULT '',
|
||||
city TEXT DEFAULT '',
|
||||
address TEXT DEFAULT '',
|
||||
postal_code TEXT DEFAULT '',
|
||||
phone TEXT DEFAULT '',
|
||||
email TEXT DEFAULT '',
|
||||
website TEXT DEFAULT '',
|
||||
linkedin TEXT DEFAULT '',
|
||||
employees INTEGER DEFAULT 0,
|
||||
revenue_range TEXT DEFAULT '',
|
||||
data_warehouse TEXT DEFAULT '',
|
||||
bi_tools TEXT DEFAULT '',
|
||||
cloud_provider TEXT DEFAULT '',
|
||||
data_lake TEXT DEFAULT '',
|
||||
ai_ml TEXT DEFAULT '',
|
||||
database_vendor TEXT DEFAULT '',
|
||||
lead_score INTEGER DEFAULT 0,
|
||||
lead_status TEXT DEFAULT 'new',
|
||||
notes TEXT DEFAULT '',
|
||||
source TEXT DEFAULT ''
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
function seedDefaults() {
|
||||
const existing = db.prepare('SELECT COUNT(*) as cnt FROM users').get();
|
||||
if (existing.cnt === 0) {
|
||||
const pw = process.env.ADMIN_PASSWORD || 'admin';
|
||||
const hash = bcrypt.hashSync(pw, 10);
|
||||
const result = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)').run('admin', hash, 'admin');
|
||||
db.prepare('INSERT INTO user_settings (user_id, language) VALUES (?, ?)').run(result.lastInsertRowid, 'nl');
|
||||
console.log(`Default user created: admin / ${pw}`);
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULTS = {
|
||||
brand_name: 'Mek-Tech Consulting',
|
||||
language: 'nl',
|
||||
theme: 'mek-neon-blue',
|
||||
default_page: '/',
|
||||
auto_refresh: '0',
|
||||
items_per_page: '50',
|
||||
debug_mode: '0',
|
||||
icon_colors: JSON.stringify({
|
||||
'layout-dashboard': '#58a6ff',
|
||||
users: '#3fb950',
|
||||
briefcase: '#d29922',
|
||||
server: '#bc8cff',
|
||||
'check-circle': '#3fb950',
|
||||
lightbulb: '#d29922',
|
||||
clock: '#79c0ff',
|
||||
eye: '#f778ba',
|
||||
phone: '#3fb950',
|
||||
'file-text': '#f778ba',
|
||||
'edit-3': '#79c0ff',
|
||||
folder: '#d29922',
|
||||
'clipboard-list': '#bc8cff'
|
||||
}),
|
||||
theme_colors: '{}',
|
||||
showcase_url: 'http://192.168.1.247:3010'
|
||||
};
|
||||
|
||||
function getSettings() {
|
||||
const d = getDb();
|
||||
const rows = d.prepare('SELECT key, value FROM app_settings').all();
|
||||
const s = { ...DEFAULTS };
|
||||
// Ensure defaults exist in DB
|
||||
const upsert = d.prepare(`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`);
|
||||
for (const [k, v] of Object.entries(DEFAULTS)) {
|
||||
const row = rows.find(r => r.key === k);
|
||||
if (row) {
|
||||
s[k] = row.value;
|
||||
} else {
|
||||
upsert.run(k, v);
|
||||
}
|
||||
}
|
||||
// Also store icon_colors as parsed object
|
||||
if (typeof s.icon_colors === 'string') {
|
||||
try { s.icon_colors = JSON.parse(s.icon_colors); } catch (e) { s.icon_colors = JSON.parse(DEFAULTS.icon_colors); }
|
||||
}
|
||||
if (typeof s.theme_colors === 'string') {
|
||||
try { s.theme_colors = JSON.parse(s.theme_colors || '{}'); } catch (e) { s.theme_colors = {}; }
|
||||
}
|
||||
if (!s.theme_colors || typeof s.theme_colors !== 'object') s.theme_colors = {};
|
||||
return s;
|
||||
}
|
||||
|
||||
function updateSetting(key, value) {
|
||||
const d = getDb();
|
||||
if (typeof value === 'object') value = JSON.stringify(value);
|
||||
d.prepare(`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`).run(key, value);
|
||||
}
|
||||
|
||||
function logAudit(userId, username, action, entityType, entityId, details) {
|
||||
const d = getDb();
|
||||
d.prepare('INSERT INTO audit_log (user_id, username, action, entity_type, entity_id, details) VALUES (?, ?, ?, ?, ?, ?)')
|
||||
.run(userId || null, username || '', action, entityType, entityId || null, details || '');
|
||||
}
|
||||
|
||||
function getUserSettings(userId) {
|
||||
const d = getDb();
|
||||
let s = d.prepare('SELECT * FROM user_settings WHERE user_id = ?').get(userId);
|
||||
if (!s) {
|
||||
d.prepare('INSERT INTO user_settings (user_id) VALUES (?)').run(userId);
|
||||
s = d.prepare('SELECT * FROM user_settings WHERE user_id = ?').get(userId);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
module.exports = { getDb, getSettings, updateSetting, logAudit, getUserSettings, DEFAULTS };
|
||||
Reference in New Issue
Block a user