mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
- 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
332 lines
11 KiB
JavaScript
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 };
|