feat: Add DatabaseManager and LinkPlayer components, implement authentication and linking logic

- Created DatabaseManager component for managing database access via phpMyAdmin.
- Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling.
- Added mock data files for players, organizations, and projects to handle backend unavailability.
- Implemented AuthService for managing user authentication and session checks.
- Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data.
- Added HTML page for handling authentication unavailability.
- Developed a test script for validating Docker setup and required files.
This commit is contained in:
Lars Behrends
2025-12-28 16:46:04 +01:00
parent 6abdffe22a
commit d3d7ec46e6
40 changed files with 5967 additions and 102 deletions

226
backend/database.js Normal file
View File

@@ -0,0 +1,226 @@
const mysql = require('mysql2');
// Database Config from Env
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_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', organizationId: 'org-3' }),
inventory: JSON.stringify([]),
storyMarkdown: '# Der Bauplan von V\n\n> "Stein erinnert sich..."'
},
{
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', organizationId: 'org-4' }),
inventory: JSON.stringify([]),
storyMarkdown: '# Forschungslogbuch:\n\nSpezialisiert auf...'
}
];
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',
bannerUrl: 'images/screenshots/2025-12-28_01.11.49.png',
shopCatalog: JSON.stringify([])
}
];
// 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,
inventory JSON,
storyMarkdown TEXT,
discordId VARCHAR(255)
)`,
`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),
bannerUrl VARCHAR(255),
gallery JSON,
cityStats JSON
)`,
`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),
bannerUrl VARCHAR(255),
shopCatalog JSON,
gallery JSON
)`
];
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.inventory, p.storyMarkdown, null]);
});
}
});
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, o.bannerUrl, 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.bannerUrl, p.shopCatalog, '[]']);
});
}
});
}
// 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 };