Files
project_vollidioten_website/backend/database.js
Lars Behrends 065a6e657d feat: add world map functionality and admin map management
- Added world map page with interactive marker display
- Implemented admin map management for marker CRUD operations
- Added map layers and markers seed data to database
- Integrated new routes for map functionality
- Updated database configuration for production environment
- Added documentation page route
- Enhanced package.json with required dependencies for map features
2026-01-02 05:08:07 +01:00

332 lines
11 KiB
JavaScript

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 };