import Database from 'better-sqlite3'; import bcrypt from 'bcrypt'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const db = new Database(join(__dirname, 'users.sqlite')); const VALID_ROLES = ['user', 'moderator', 'superadmin']; export function initDb() { // Create users table with role db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, role TEXT DEFAULT 'user' CHECK(role IN ('user', 'moderator', 'superadmin')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Migration: add role column if it doesn't exist const columns = db.prepare("PRAGMA table_info(users)").all(); const hasRole = columns.some(col => col.name === 'role'); if (!hasRole) { db.exec("ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'"); // Upgrade existing admin user to superadmin db.prepare("UPDATE users SET role = 'superadmin' WHERE username = 'admin'").run(); console.log('Migration: Added role column, admin upgraded to superadmin'); } // Create default admin if no users exist const count = db.prepare('SELECT COUNT(*) as count FROM users').get(); if (count.count === 0) { const hash = bcrypt.hashSync('admin', 10); db.prepare('INSERT INTO users (username, password, role) VALUES (?, ?, ?)').run('admin', hash, 'superadmin'); console.log('Default superadmin user created (username: admin, password: admin)'); } } export { db, VALID_ROLES }; // Whitelist cache table export function initWhitelistCache() { db.exec(` CREATE TABLE IF NOT EXISTS whitelist_cache ( server_id TEXT PRIMARY KEY, players TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } export function getCachedWhitelist(serverId) { const row = db.prepare('SELECT players FROM whitelist_cache WHERE server_id = ?').get(serverId); return row ? JSON.parse(row.players) : []; } export function setCachedWhitelist(serverId, players) { db.prepare(` INSERT OR REPLACE INTO whitelist_cache (server_id, players, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) `).run(serverId, JSON.stringify(players)); } // Factorio templates table export function initFactorioTemplates() { db.exec(` CREATE TABLE IF NOT EXISTS factorio_templates ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, settings TEXT NOT NULL, created_by INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (created_by) REFERENCES users(id) ) `); } export function getFactorioTemplates() { return db.prepare(` SELECT t.id, t.name, t.settings, t.created_at, u.username as created_by_name FROM factorio_templates t LEFT JOIN users u ON t.created_by = u.id ORDER BY t.name `).all(); } export function getFactorioTemplate(id) { return db.prepare("SELECT * FROM factorio_templates WHERE id = ?").get(id); } export function createFactorioTemplate(name, settings, userId) { const result = db.prepare( "INSERT INTO factorio_templates (name, settings, created_by) VALUES (?, ?, ?)" ).run(name, JSON.stringify(settings), userId); return result.lastInsertRowid; } export function deleteFactorioTemplate(id) { return db.prepare("DELETE FROM factorio_templates WHERE id = ?").run(id); } // Factorio world settings table (stores settings used when creating worlds) export function initFactorioWorldSettings() { db.exec(` CREATE TABLE IF NOT EXISTS factorio_world_settings ( save_name TEXT PRIMARY KEY, settings TEXT NOT NULL, created_by INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (created_by) REFERENCES users(id) ) `); } export function getFactorioWorldSettings(saveName) { return db.prepare( "SELECT ws.*, u.username as created_by_name FROM factorio_world_settings ws LEFT JOIN users u ON ws.created_by = u.id WHERE ws.save_name = ?" ).get(saveName); } export function saveFactorioWorldSettings(saveName, settings, userId) { return db.prepare( "INSERT OR REPLACE INTO factorio_world_settings (save_name, settings, created_by, created_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)" ).run(saveName, JSON.stringify(settings), userId); } export function deleteFactorioWorldSettings(saveName) { return db.prepare("DELETE FROM factorio_world_settings WHERE save_name = ?").run(saveName); } // Auto-shutdown settings table export function initAutoShutdownSettings() { db.exec(` CREATE TABLE IF NOT EXISTS autoshutdown_settings ( server_id TEXT PRIMARY KEY, enabled INTEGER DEFAULT 0, timeout_minutes INTEGER DEFAULT 15, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } export function getAutoShutdownSettings(serverId) { return db.prepare('SELECT * FROM autoshutdown_settings WHERE server_id = ?').get(serverId); } export function getAllAutoShutdownSettings() { return db.prepare('SELECT * FROM autoshutdown_settings WHERE enabled = 1').all(); } export function setAutoShutdownSettings(serverId, enabled, timeoutMinutes) { return db.prepare(` INSERT OR REPLACE INTO autoshutdown_settings (server_id, enabled, timeout_minutes, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP) `).run(serverId, enabled ? 1 : 0, timeoutMinutes); } // Discord users table export function initDiscordUsers() { db.exec(` CREATE TABLE IF NOT EXISTS discord_users ( id INTEGER PRIMARY KEY AUTOINCREMENT, discord_id TEXT UNIQUE NOT NULL, username TEXT NOT NULL, discriminator TEXT DEFAULT '0', avatar TEXT, role TEXT DEFAULT 'user' CHECK(role IN ('user', 'moderator', 'superadmin')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } // Activity Log export function initActivityLog() { db.exec(` CREATE TABLE IF NOT EXISTS activity_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, username TEXT NOT NULL, discord_id TEXT, avatar TEXT, action TEXT NOT NULL, target TEXT, details TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } export function logActivity(userId, username, action, target = null, details = null, discordId = null, avatar = null) { db.prepare(` INSERT INTO activity_log (user_id, username, discord_id, avatar, action, target, details) VALUES (?, ?, ?, ?, ?, ?, ?) `).run(userId, username, discordId, avatar, action, target, details); } export function getActivityLog(limit = 100) { return db.prepare(` SELECT * FROM activity_log ORDER BY created_at DESC LIMIT ? `).all(limit); } // Server display settings table export function initServerDisplaySettings() { db.exec(` CREATE TABLE IF NOT EXISTS server_display_settings ( server_id TEXT PRIMARY KEY, address TEXT, hint TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } export function getServerDisplaySettings(serverId) { return db.prepare('SELECT * FROM server_display_settings WHERE server_id = ?').get(serverId); } export function getAllServerDisplaySettings() { return db.prepare('SELECT * FROM server_display_settings').all(); } export function setServerDisplaySettings(serverId, address, hint) { return db.prepare(` INSERT OR REPLACE INTO server_display_settings (server_id, address, hint, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP) `).run(serverId, address, hint); } // Guild settings for multi-server Discord bot export function initGuildSettings() { db.exec(` CREATE TABLE IF NOT EXISTS guild_settings ( guild_id TEXT PRIMARY KEY, category_id TEXT, info_channel_id TEXT, status_channel_id TEXT, status_message_id TEXT, alerts_channel_id TEXT, updates_channel_id TEXT, discussion_channel_id TEXT, requests_channel_id TEXT, requests_info_thread_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); } export function getGuildSettings(guildId) { return db.prepare('SELECT * FROM guild_settings WHERE guild_id = ?').get(guildId); } export function getAllGuildSettings() { return db.prepare('SELECT * FROM guild_settings').all(); } export function setGuildSettings(guildId, settings) { const existing = getGuildSettings(guildId); if (existing) { return db.prepare(` UPDATE guild_settings SET category_id = ?, info_channel_id = ?, status_channel_id = ?, status_message_id = ?, alerts_channel_id = ?, updates_channel_id = ?, discussion_channel_id = ?, requests_channel_id = ?, requests_info_thread_id = ?, updated_at = CURRENT_TIMESTAMP WHERE guild_id = ? `).run( settings.category_id, settings.info_channel_id, settings.status_channel_id, settings.status_message_id, settings.alerts_channel_id, settings.updates_channel_id, settings.discussion_channel_id, settings.requests_channel_id, settings.requests_info_thread_id, guildId ); } else { return db.prepare(` INSERT INTO guild_settings (guild_id, category_id, info_channel_id, status_channel_id, status_message_id, alerts_channel_id, updates_channel_id, discussion_channel_id, requests_channel_id, requests_info_thread_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( guildId, settings.category_id, settings.info_channel_id, settings.status_channel_id, settings.status_message_id, settings.alerts_channel_id, settings.updates_channel_id, settings.discussion_channel_id, settings.requests_channel_id, settings.requests_info_thread_id ); } } export function deleteGuildSettings(guildId) { return db.prepare('DELETE FROM guild_settings WHERE guild_id = ?').run(guildId); }