import { getDb } from './db' import bcrypt from 'bcryptjs' export function initDatabase(): void { const db = getDb() const schema = [ "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, display_name TEXT NOT NULL, email TEXT, role TEXT NOT NULL DEFAULT 'viewer', is_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 roles (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, permissions TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS tickets (id INTEGER PRIMARY KEY, device_ip TEXT, device_sn TEXT, device_name TEXT, content TEXT, assign_time TEXT, close_time TEXT, duration_minutes INTEGER, availability REAL, process_summary TEXT, conclusion TEXT, fault_category TEXT, fault_subcategory TEXT, parts_replaced TEXT, parts_name TEXT, current_status TEXT NOT NULL DEFAULT 'open', counted_in_sla INTEGER NOT NULL DEFAULT 1, responsibility TEXT, created_by INTEGER REFERENCES users(id), updated_by INTEGER REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS ticket_steps (id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_id INTEGER NOT NULL REFERENCES tickets(id) ON DELETE CASCADE, step_order INTEGER NOT NULL, time_node TEXT, handler TEXT, description TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS reports (id INTEGER PRIMARY KEY AUTOINCREMENT, report_type TEXT NOT NULL, period_start TEXT, period_end TEXT, format TEXT NOT NULL DEFAULT 'pdf', file_path TEXT, file_name TEXT, status TEXT NOT NULL DEFAULT 'pending', error_message TEXT, created_by INTEGER REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS audit_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id), action TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id INTEGER, details TEXT, ip_address TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')));", "CREATE TABLE IF NOT EXISTS api_keys (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, key_hash TEXT NOT NULL, permissions TEXT NOT NULL DEFAULT '[\"tickets:read\"]', last_used_at TEXT, expires_at TEXT, is_active INTEGER NOT NULL DEFAULT 1, created_by INTEGER REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (datetime('now')));" ] for (const sql of schema) db.exec(sql) // 迁移:添加 parts_name 列 try { db.exec('ALTER TABLE tickets ADD COLUMN parts_name TEXT') } catch { /* 列已存在 */ } // 迁移:添加 ticket_type 列 try { db.exec('ALTER TABLE tickets ADD COLUMN ticket_type TEXT') } catch { /* 列已存在 */ } // 迁移:将已有 OEM 分类迁移到 ticket_type try { db.prepare("UPDATE tickets SET ticket_type = 'OEM诊断' WHERE fault_category = 'OEM诊断' AND ticket_type IS NULL").run() db.prepare("UPDATE tickets SET ticket_type = 'OEM维修' WHERE fault_category = 'OEM维修' AND ticket_type IS NULL").run() db.prepare("UPDATE tickets SET ticket_type = 'OEM诊断' WHERE ticket_type IS NULL AND fault_category = '无故障'").run() db.prepare("UPDATE tickets SET ticket_type = 'OEM维修' WHERE ticket_type IS NULL AND fault_category IN ('硬件故障', '网络故障', '存储故障', '电源故障')").run() } catch { /* 迁移失败则保持原样 */ } // 迁移:metadata 列(报告元数据 JSON) try { db.exec('ALTER TABLE reports ADD COLUMN metadata TEXT') } catch { /* 已存在 */ } // 迁移:修正 reports 表 created_at 为北京时间(UTC+8),并修正 format 默认值 try { const info = db.prepare('SELECT sql FROM sqlite_master WHERE type=? AND name=?').get('table', 'reports') as any if (info && info.sql && !info.sql.includes("+8 hours")) { db.exec('DROP TABLE IF EXISTS reports') db.exec("CREATE TABLE reports (id INTEGER PRIMARY KEY AUTOINCREMENT, report_type TEXT NOT NULL, period_start TEXT, period_end TEXT, format TEXT NOT NULL DEFAULT 'docx', file_path TEXT, file_name TEXT, status TEXT NOT NULL DEFAULT 'pending', error_message TEXT, created_by INTEGER REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (datetime('now', '+8 hours')), metadata TEXT)") } } catch { /* 迁移失败则保持原样 */ } const existing = db.prepare('SELECT id FROM users WHERE username = ?').get('admin') if (!existing) { const defaultPassword = process.env.ADMIN_PASSWORD || 'admin123' const hash = bcrypt.hashSync(defaultPassword, 10) db.prepare('INSERT INTO users (username, password_hash, display_name, role) VALUES (?, ?, ?, ?)').run('admin', hash, '系统管理员', 'admin') } const roles = [ { name: 'admin', display_name: '管理员', permissions: '["*"]' }, { name: 'operator', display_name: '运维人员', permissions: '["tickets:read","tickets:write","reports:read"]' }, { name: 'viewer', display_name: '查看者', permissions: '["tickets:read","reports:read"]' }, ] for (const r of roles) { const ex = db.prepare('SELECT id FROM roles WHERE name = ?').get(r.name) if (!ex) db.prepare('INSERT INTO roles (name, display_name, permissions) VALUES (?, ?, ?)').run(r.name, r.display_name, r.permissions) } }