const mysql = require('mysql2'); // Database Config from Env const dbConfig = { host: process.env.DB_HOST || '192.168.1.102', user: process.env.DB_USER || 'obsidian_user', password: process.env.DB_PASS || 'obsidian_pass', database: process.env.DB_NAME || 'obsidian_db', waitForConnections: true, connectionLimit: 10, queueLimit: 0 }; let pool; // Mock Data for Seeding const SEED_PLAYERS = [ { uuid: '80301bff-74df-4579-bcfc-082ac8d26b5b', username: 'kaiwastoshort', isOnline: 1, isNpc: 0, isAdmin: 0, tags: JSON.stringify(['#Bürger', '#Händler']), stats: JSON.stringify({ playtimeHours: 482, level: 45, role: 'Bürger' }), minecraftStats: null, inventory: JSON.stringify([]), storyMarkdown: '# Der Bauplan von V\n\n> "Stein erinnert sich..."', organizationId: 'org-3' }, { uuid: '8984c0b5-d912-4462-b189-c864fba4a1af', username: 'DrKButz', isOnline: 0, isNpc: 0, isAdmin: 1, // DrKButz is admin for testing tags: JSON.stringify(['#Bauunternehmer']), stats: JSON.stringify({ playtimeHours: 120, level: 12, role: 'Unternehmer' }), minecraftStats: null, inventory: JSON.stringify([]), storyMarkdown: '# Forschungslogbuch:\n\nSpezialisiert auf...', organizationId: 'org-4' } ]; const SEED_ORGS = [ { id: 'org-3', name: 'Provisorium Null', type: 'City', description: 'Die erste Siedlung...', memberCount: 6, status: 'active', mayor: '', establishedYear: 'Day 0', bannerUrl: 'images/screenshots/2025-12-28_01.11.10.png', gallery: JSON.stringify(['images/screenshots/2025-12-28_01.12.07.png']), cityStats: JSON.stringify({ taxRate: 3.5, biome: 'Ebene', defenseRating: 8, government: 'Feudal', specialty: 'Handel' }) }, { id: 'org-4', name: 'Sakura', type: 'City', description: 'Eine dunkle, biolumineszente Hafenstadt...', memberCount: 2, status: 'active', mayor: 'Kampfzwerk', establishedYear: 'Ära 2', bannerUrl: 'images/screenshots/2025-12-28_01.11.32.png', gallery: JSON.stringify([]), cityStats: JSON.stringify({ taxRate: 15.0, biome: 'Deep Dark', defenseRating: 4, government: 'Syndikat', specialty: 'Schmuggel' }) } ]; const SEED_PROJECTS = [ { id: 'ven-1', title: 'DrkButz Architektur', description: 'Führendes Architekturbüro...', category: 'Enterprise', status: 'active', progress: 85, owner: 'DrKButz', employees: JSON.stringify([]), hiring: 0, foundedDate: 'Zyklus 12', associatedOrgId: 'org-4', bannerImageId: null, shopCatalog: JSON.stringify([]) } ]; const SEED_MAP_LAYERS = [ { id: 'layer-1', name: 'Städte', description: 'Alle Städte und Siedlungen', order_index: 1 }, { id: 'layer-2', name: 'Points of Interest', description: 'Besondere Orte und Sehenswürdigkeiten', order_index: 2 }, { id: 'layer-3', name: 'Spieler-Häuser', description: 'Spieler-Wohnsitze und Häuser', order_index: 3 } ]; const SEED_MAP_MARKERS = [ { id: 'marker-1', name: 'Provisorium Null', type: 'city', x_coord: -2560, z_coord: 512, description: 'Die erste Siedlung der neuen Ära', linked_entity_type: 'organization', linked_entity_id: 'org-3', icon_type: 'city', color: '#2563eb', is_public: 1 }, { id: 'marker-2', name: 'Sakura', type: 'city', x_coord: 1536, z_coord: -512, description: 'Eine dunkle, biolumineszente Hafenstadt', linked_entity_type: 'organization', linked_entity_id: 'org-4', icon_type: 'city', color: '#dc2626', is_public: 1 } ]; // Retry connection logic for Docker function init() { const tryConnect = () => { console.log("Attempting to connect to MySQL..."); const tempPool = mysql.createPool(dbConfig); tempPool.getConnection((err, connection) => { if (err) { console.error("Database connection failed. Retrying in 5s...", err.code); setTimeout(tryConnect, 5000); } else { console.log("MySQL Connected!"); pool = tempPool; connection.release(); setupTables(); } }); }; tryConnect(); } function setupTables() { const queries = [ `CREATE TABLE IF NOT EXISTS players ( uuid VARCHAR(36) PRIMARY KEY, username VARCHAR(255), isOnline TINYINT, isNpc TINYINT DEFAULT 0, isAdmin TINYINT DEFAULT 0, tags JSON, stats JSON, minecraftStats JSON, inventory JSON, storyMarkdown TEXT, discordId VARCHAR(255), organizationId VARCHAR(50) )`, `CREATE TABLE IF NOT EXISTS orgs ( id VARCHAR(50) PRIMARY KEY, name VARCHAR(255), type VARCHAR(50), description TEXT, memberCount INTEGER, status VARCHAR(50), mayor VARCHAR(255), establishedYear VARCHAR(100), bannerImageId VARCHAR(50), logoImageId VARCHAR(50), gallery JSON, cityStats JSON )`, `CREATE TABLE IF NOT EXISTS images ( id VARCHAR(50) PRIMARY KEY, filename VARCHAR(255) NOT NULL, originalName VARCHAR(255), mimeType VARCHAR(100), size INTEGER, uploadDate DATETIME DEFAULT CURRENT_TIMESTAMP, altText VARCHAR(255), entityType VARCHAR(50), -- 'project', 'org', 'player' entityId VARCHAR(50), -- ID of the owning entity imageType VARCHAR(50) -- 'banner', 'gallery' )`, `CREATE TABLE IF NOT EXISTS projects ( id VARCHAR(50) PRIMARY KEY, title VARCHAR(255), description TEXT, category VARCHAR(50), status VARCHAR(50), progress INTEGER, owner VARCHAR(255), employees JSON, hiring TINYINT, foundedDate VARCHAR(100), associatedOrgId VARCHAR(50), bannerImageId VARCHAR(50), logoImageId VARCHAR(50), shopCatalog JSON, gallery JSON )`, `CREATE TABLE IF NOT EXISTS map_markers ( id VARCHAR(50) PRIMARY KEY, name VARCHAR(255), type VARCHAR(50), -- 'city', 'poi', 'player_home', 'waypoint' x_coord INTEGER, z_coord INTEGER, description TEXT, linked_entity_type VARCHAR(50), -- 'city', 'organization', 'player' linked_entity_id VARCHAR(50), icon_type VARCHAR(50), -- 'city', 'house', 'chest', 'flag', etc. color VARCHAR(7), -- hex color code is_public TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )`, `CREATE TABLE IF NOT EXISTS map_layers ( id VARCHAR(50) PRIMARY KEY, name VARCHAR(255), description TEXT, is_active TINYINT DEFAULT 1, order_index INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, `CREATE TABLE IF NOT EXISTS map_metadata ( key VARCHAR(100) PRIMARY KEY, value TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )` ]; queries.forEach(q => { pool.query(q, (err) => { if (err) console.error("Error creating table:", err); }); }); seedData(); } function seedData() { // Check if players exist pool.query("SELECT COUNT(*) as count FROM players", (err, rows) => { if (!err && rows[0].count === 0) { console.log("Seeding Players..."); SEED_PLAYERS.forEach(p => { pool.query("INSERT INTO players VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [p.uuid, p.username, p.isOnline, p.isNpc, p.isAdmin, p.tags, p.stats, p.minecraftStats, p.inventory, p.storyMarkdown, null, p.organizationId]); }); } }); pool.query("SELECT COUNT(*) as count FROM orgs", (err, rows) => { if (!err && rows[0].count === 0) { console.log("Seeding Orgs..."); SEED_ORGS.forEach(o => { pool.query("INSERT INTO orgs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [o.id, o.name, o.type, o.description, o.memberCount, o.status, o.mayor, o.establishedYear, null, null, o.gallery, o.cityStats]); }); } }); pool.query("SELECT COUNT(*) as count FROM projects", (err, rows) => { if (!err && rows[0].count === 0) { console.log("Seeding Projects..."); SEED_PROJECTS.forEach(p => { pool.query("INSERT INTO projects VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [p.id, p.title, p.description, p.category, p.status, p.progress, p.owner, p.employees, p.hiring, p.foundedDate, p.associatedOrgId, p.bannerImageId, null, p.shopCatalog, '[]']); }); } }); // Seed map layers pool.query("SELECT COUNT(*) as count FROM map_layers", (err, rows) => { if (!err && rows[0].count === 0) { console.log("Seeding Map Layers..."); SEED_MAP_LAYERS.forEach(layer => { pool.query("INSERT INTO map_layers VALUES (?, ?, ?, ?, ?, ?)", [layer.id, layer.name, layer.description, 1, layer.order_index, new Date().toISOString().slice(0, 19).replace('T', ' ')]); }); } }); // Seed map markers pool.query("SELECT COUNT(*) as count FROM map_markers", (err, rows) => { if (!err && rows[0].count === 0) { console.log("Seeding Map Markers..."); SEED_MAP_MARKERS.forEach(marker => { pool.query("INSERT INTO map_markers (id, name, type, x_coord, z_coord, description, linked_entity_type, linked_entity_id, icon_type, color, is_public) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [marker.id, marker.name, marker.type, marker.x_coord, marker.z_coord, marker.description, marker.linked_entity_type, marker.linked_entity_id, marker.icon_type, marker.color, marker.is_public]); }); } }); } // Wrapper to mimic SQLite API for easy migration in server.js const dbWrapper = { get: (sql, params, cb) => { if (!pool) return cb(new Error("DB not ready")); pool.query(sql, params, (err, rows) => { if (err) return cb(err); cb(null, rows[0]); }); }, all: (sql, params, cb) => { // Handle case where params is omitted and cb is second parameter if (typeof params === 'function') { cb = params; params = []; } if (!pool) return cb(new Error("DB not ready")); pool.query(sql, params || [], (err, rows) => { cb(err, rows); }); }, run: (sql, params, cb) => { if (!pool) return cb(new Error("DB not ready")); pool.query(sql, params, function(err, result) { // "this" context in sqlite callback contains changes, mock it if needed if (cb) cb(err); }); } }; module.exports = { db: dbWrapper, init };