const express = require('express'); const session = require('express-session'); const passport = require('passport'); const DiscordStrategy = require('passport-discord').Strategy; const cors = require('cors'); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { db, init } = require('./database'); const app = express(); const PORT = 3000; // Env variables (provided via Docker/Environment) const CLIENT_ID = process.env.DISCORD_CLIENT_ID || 'mock_id'; const CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET || 'mock_secret'; const CALLBACK_URL = process.env.CALLBACK_URL || 'http://localhost:3000/auth/discord/callback'; const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:8000'; const BACKEND_URL = process.env.BACKEND_URL || 'https://vollidioten.ceraticsoft.de'; // File upload configuration const UPLOAD_DIR = path.join(__dirname, 'uploads'); if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR, { recursive: true }); } // Configure multer for file uploads const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, UPLOAD_DIR); }, filename: (req, file, cb) => { // Generate unique filename with timestamp const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1E9) + path.extname(file.originalname); cb(null, uniqueName); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024, // 10MB limit }, fileFilter: (req, file, cb) => { // Allow only image files if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Nur Bilddateien sind erlaubt'), false); } } }); init(); // Initialize DB // Middleware app.use(express.json()); app.use(cors({ origin: FRONTEND_URL, credentials: true })); app.use(session({ secret: process.env.SESSION_SECRET || 'dev_secret', resave: false, saveUninitialized: false, cookie: { secure: false } // Set true if using https })); app.use(passport.initialize()); app.use(passport.session()); // Passport Setup passport.serializeUser((user, done) => done(null, user)); passport.deserializeUser((obj, done) => { // If user has a linked player, fetch their Minecraft username and admin status from database if (obj.linkedPlayerUuid) { db.get("SELECT username, isAdmin FROM players WHERE uuid = ?", [obj.linkedPlayerUuid], (err, row) => { if (!err && row) { // Replace Discord username with Minecraft username for easier API usage obj.username = row.username; obj.isAdmin = !!row.isAdmin; } done(null, obj); }); } else { obj.isAdmin = false; done(null, obj); } }); passport.use(new DiscordStrategy({ clientID: CLIENT_ID, clientSecret: CLIENT_SECRET, callbackURL: CALLBACK_URL, scope: ['identify'] }, function(accessToken, refreshToken, profile, cb) { // In a real app, you might map the Discord ID to a player in the DB here // For this demo, we just pass the profile through // We try to find a player who has this discordId linked, or return the raw profile db.get("SELECT uuid FROM players WHERE discordId = ?", [profile.id], (err, row) => { const user = { id: profile.id, username: profile.username, discriminator: profile.discriminator, avatarUrl: `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.png`, linkedPlayerUuid: row ? row.uuid : null }; // Hack for demo: If user is "DrKButz" (or you), make them admin of that player // For testing, we just link the first login to DrKButz if not taken if (!row && profile.username === 'YourDiscordUsername') { // Replace logic as needed user.linkedPlayerUuid = '8984c0b5-d912-4462-b189-c864fba4a1af'; } return cb(null, user); }); } )); app.get('/api/status', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), version: '1.0.0', database: 'connected' }); }); // --- ROUTES --- // Auth app.get('/auth/discord', passport.authenticate('discord')); app.get('/auth/discord/callback', passport.authenticate('discord', { failureRedirect: FRONTEND_URL + '?error=login_failed' }), (req, res) => { res.redirect(FRONTEND_URL); }); app.get('/auth/me', (req, res) => { if (req.isAuthenticated()) { res.json(req.user); } else { res.status(401).json({ error: 'Not authenticated' }); } }); app.get('/auth/logout', (req, res) => { req.logout(() => { res.redirect(FRONTEND_URL); }); }); // API: Players app.get('/api/players', (req, res) => { db.all("SELECT * FROM players", (err, rows) => { if (err) return res.status(500).json({error: err.message}); // Parse JSON fields const parsed = rows.map(r => ({ ...r, tags: JSON.parse(r.tags), stats: JSON.parse(r.stats), inventory: JSON.parse(r.inventory), isOnline: !!r.isOnline })); res.json(parsed); }); }); // API: Unlinked Players (for Discord account linking) app.get('/api/unlinked-players', (req, res) => { db.all("SELECT uuid, username, tags, stats FROM players WHERE discordId IS NULL OR discordId = ''", (err, rows) => { if (err) return res.status(500).json({error: err.message}); // Parse JSON fields const parsed = rows.map(r => ({ ...r, tags: JSON.parse(r.tags), stats: JSON.parse(r.stats) })); res.json(parsed); }); }); app.put('/api/players/:uuid', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user owns this player profile OR if it's an admin creating/updating NPCs const isOwner = req.user.linkedPlayerUuid === req.params.uuid; const isAdmin = req.user.isAdmin; if (!isOwner && !isAdmin) { return res.status(403).json({error: 'Keine Berechtigung zum Bearbeiten'}); } const updates = req.body; const allowedFields = ['storyMarkdown', 'tags']; const updateFields = []; const values = []; // Handle organizationId specially - it goes into stats JSON if (updates.organizationId !== undefined) { // First get current player data db.get("SELECT stats FROM players WHERE uuid = ?", [req.params.uuid], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Spieler nicht gefunden'}); try { const currentStats = JSON.parse(row.stats || '{}'); const updatedStats = { ...currentStats, organizationId: updates.organizationId }; updateFields.push('stats = ?'); values.push(JSON.stringify(updatedStats)); } catch (e) { return res.status(500).json({error: 'Fehler beim Verarbeiten der Stats'}); } // Continue with other fields for (const field of allowedFields) { if (updates[field] !== undefined) { if (field === 'tags') { // Tags are stored as JSON updateFields.push(`${field} = ?`); values.push(JSON.stringify(updates[field])); } else { updateFields.push(`${field} = ?`); values.push(updates[field]); } } } if (updateFields.length === 0) { return res.status(400).json({error: 'Keine gültigen Felder zum Aktualisieren'}); } const query = `UPDATE players SET ${updateFields.join(', ')} WHERE uuid = ?`; values.push(req.params.uuid); db.run(query, values, function(err) { if (err) { console.error('Error updating player:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren'}); } res.json({success: true, message: 'Spieler erfolgreich aktualisiert'}); }); }); return; // Exit early since we're handling this asynchronously } // Handle other fields normally for (const field of allowedFields) { if (updates[field] !== undefined) { if (field === 'tags') { // Tags are stored as JSON updateFields.push(`${field} = ?`); values.push(JSON.stringify(updates[field])); } else { updateFields.push(`${field} = ?`); values.push(updates[field]); } } } if (updateFields.length === 0) { return res.status(400).json({error: 'Keine gültigen Felder zum Aktualisieren'}); } const query = `UPDATE players SET ${updateFields.join(', ')} WHERE uuid = ?`; values.push(req.params.uuid); db.run(query, values, function(err) { if (err) { console.error('Error updating player:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren'}); } res.json({success: true, message: 'Spieler erfolgreich aktualisiert'}); }); }); // API: Orgs app.get('/api/orgs', (req, res) => { db.all("SELECT * FROM orgs", (err, rows) => { if (err) return res.status(500).json({error: err.message}); const parsed = rows.map(r => ({ ...r, gallery: JSON.parse(r.gallery || '[]'), bannerUrl: getImageUrl(r.bannerImageId), logoUrl: getImageUrl(r.logoImageId), cityStats: r.cityStats ? JSON.parse(r.cityStats) : undefined })); res.json(parsed); }); }); // Helper function to get full image URL function getImageUrl(imageId) { if (!imageId) return null; return `${BACKEND_URL}/api/images/${imageId}`; } // Helper function to resolve gallery image IDs to URLs function resolveGalleryImages(gallery) { if (!gallery) return []; try { const imageIds = JSON.parse(gallery); return imageIds.map(id => ({ id, url: getImageUrl(id) })); } catch (e) { return []; } } // API: Projects app.get('/api/projects', (req, res) => { db.all("SELECT * FROM projects", (err, rows) => { if (err) return res.status(500).json({error: err.message}); const parsed = rows.map(r => ({ ...r, employees: JSON.parse(r.employees), shopCatalog: JSON.parse(r.shopCatalog), gallery: resolveGalleryImages(r.gallery), bannerUrl: getImageUrl(r.bannerImageId), logoUrl: getImageUrl(r.logoImageId), hiring: !!r.hiring })); res.json(parsed); }); }); // Create new project app.post('/api/projects', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); console.log('Received project creation request'); console.log('Request body:', req.body); console.log('Authenticated user:', req.user); const { title, description, category, associatedOrgId, owner } = req.body; if (!title || !description) { return res.status(400).json({ error: 'Titel und Beschreibung sind erforderlich' }); } // Use the owner from request body (Minecraft character name) or fallback to Discord username const projectOwner = owner || req.user.username; console.log('Using project owner:', projectOwner); // Generate unique ID const projectId = 'proj_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); db.run(`INSERT INTO projects (id, title, description, category, status, progress, owner, employees, hiring, foundedDate, associatedOrgId, bannerImageId, shopCatalog, gallery) VALUES (?, ?, ?, ?, 'active', 0, ?, '[]', 0, ?, ?, null, '[]', '[]')`, [projectId, title, description, category || 'Enterprise', projectOwner, new Date().toISOString().split('T')[0], associatedOrgId || null], function(err) { if (err) { console.error('Error creating project:', err); return res.status(500).json({error: 'Fehler beim Erstellen des Projekts'}); } res.json({ success: true, projectId: projectId, message: 'Projekt erfolgreich erstellt' }); } ); }); // Update project (with ownership check) app.put('/api/projects/:id', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const projectId = req.params.id; const updates = req.body; // First check ownership db.get("SELECT owner FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username) { return res.status(403).json({error: 'Keine Berechtigung zum Bearbeiten'}); } // Build dynamic update query const allowedFields = ['title', 'description', 'category', 'status', 'progress', 'hiring', 'bannerUrl']; const updateFields = []; const values = []; for (const field of allowedFields) { if (updates[field] !== undefined) { if (field === 'hiring') { updateFields.push(`${field} = ?`); values.push(updates[field] ? 1 : 0); } else { updateFields.push(`${field} = ?`); values.push(updates[field]); } } } if (updateFields.length === 0) { return res.status(400).json({error: 'Keine gültigen Felder zum Aktualisieren'}); } const query = `UPDATE projects SET ${updateFields.join(', ')} WHERE id = ?`; values.push(projectId); db.run(query, values, function(err) { if (err) { console.error('Error updating project:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren'}); } res.json({success: true, message: 'Projekt erfolgreich aktualisiert'}); }); }); }); // Delete project (with ownership check) app.delete('/api/projects/:id', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const projectId = req.params.id; // Check ownership db.get("SELECT owner FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username) { return res.status(403).json({error: 'Keine Berechtigung zum Löschen'}); } db.run("DELETE FROM projects WHERE id = ?", [projectId], function(err) { if (err) { console.error('Error deleting project:', err); return res.status(500).json({error: 'Fehler beim Löschen'}); } res.json({success: true, message: 'Projekt erfolgreich gelöscht'}); }); }); }); // Link Discord to Player (Admin/Dev helper) app.post('/api/link-user', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { playerUuid } = req.body; db.run("UPDATE players SET discordId = ? WHERE uuid = ?", [req.user.id, playerUuid], (err) => { if (err) return res.status(500).json({error: err}); res.json({success: true}); }); }); // === NPC MANAGEMENT API (Admin Only) === // Create NPC Citizen app.post('/api/admin/npc-citizen', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { username, tags, role, organizationId, storyMarkdown } = req.body; if (!username) { return res.status(400).json({error: 'NPC-Name erforderlich'}); } // Generate UUID for NPC const uuid = 'npc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); const npcData = { uuid, username, isOnline: 0, isNpc: 1, tags: JSON.stringify(tags || []), stats: JSON.stringify({ playtimeHours: Math.floor(Math.random() * 1000), level: Math.floor(Math.random() * 50) + 1, role: role || 'Bürger', organizationId: organizationId || null }), inventory: JSON.stringify([]), storyMarkdown: storyMarkdown || `# ${username}\n\nEin NPC-Bürger im Obsidian-Tal.`, discordId: null }; db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, tags, stats, inventory, storyMarkdown, discordId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [npcData.uuid, npcData.username, npcData.isOnline, npcData.isNpc, npcData.tags, npcData.stats, npcData.inventory, npcData.storyMarkdown, npcData.discordId], function(err) { if (err) { console.error('Error creating NPC citizen:', err); return res.status(500).json({error: 'Fehler beim Erstellen des NPC-Bürgers'}); } res.json({ success: true, npc: { ...npcData, tags: JSON.parse(npcData.tags), stats: JSON.parse(npcData.stats) }, message: 'NPC-Bürger erfolgreich erstellt' }); } ); }); // Create NPC Company app.post('/api/admin/npc-company', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { title, description, category, owner, associatedOrgId, shopCatalog } = req.body; if (!title || !owner) { return res.status(400).json({error: 'Titel und NPC-Eigentümer erforderlich'}); } // Generate unique ID const projectId = 'npc_proj_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); db.run(`INSERT INTO projects (id, title, description, category, status, progress, owner, employees, hiring, foundedDate, associatedOrgId, bannerImageId, shopCatalog, gallery) VALUES (?, ?, ?, ?, 'active', ?, ?, '[]', 0, ?, ?, null, ?, '[]')`, [projectId, title, description || `${title} - Eine NPC-Firma im Obsidian-Tal.`, category || 'Enterprise', Math.floor(Math.random() * 100), owner, new Date().toISOString().split('T')[0], associatedOrgId || null, JSON.stringify(shopCatalog || [])], function(err) { if (err) { console.error('Error creating NPC company:', err); return res.status(500).json({error: 'Fehler beim Erstellen der NPC-Firma'}); } res.json({ success: true, projectId: projectId, message: 'NPC-Firma erfolgreich erstellt' }); } ); }); // Grant admin rights to a player (Admin only) app.post('/api/admin/grant-admin/:uuid', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const playerUuid = req.params.uuid; db.run("UPDATE players SET isAdmin = 1 WHERE uuid = ?", [playerUuid], function(err) { if (err) { console.error('Error granting admin rights:', err); return res.status(500).json({error: 'Fehler beim Vergeben der Admin-Rechte'}); } if (this.changes === 0) { return res.status(404).json({error: 'Spieler nicht gefunden'}); } res.json({success: true, message: 'Admin-Rechte erfolgreich vergeben'}); }); }); // Revoke admin rights from a player (Admin only) app.post('/api/admin/revoke-admin/:uuid', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const playerUuid = req.params.uuid; // Prevent revoking own admin rights if (req.user.linkedPlayerUuid === playerUuid) { return res.status(400).json({error: 'Sie können Ihre eigenen Admin-Rechte nicht entziehen'}); } db.run("UPDATE players SET isAdmin = 0 WHERE uuid = ?", [playerUuid], function(err) { if (err) { console.error('Error revoking admin rights:', err); return res.status(500).json({error: 'Fehler beim Entziehen der Admin-Rechte'}); } if (this.changes === 0) { return res.status(404).json({error: 'Spieler nicht gefunden'}); } res.json({success: true, message: 'Admin-Rechte erfolgreich entzogen'}); }); }); // Get all NPCs (for admin overview) app.get('/api/admin/npcs', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } // First get all players with isNpc flag db.all("SELECT * FROM players WHERE isNpc = 1", (err, npcPlayers) => { if (err) return res.status(500).json({error: err.message}); // Get all projects where owner is an NPC (starts with npc_) db.all("SELECT * FROM projects WHERE owner LIKE 'npc_%'", (err, npcOwnedProjects) => { if (err) return res.status(500).json({error: err.message}); // Also get all projects with NPC-like IDs (npc_proj_ prefix) db.all("SELECT * FROM projects WHERE id LIKE 'npc_proj_%'", (err, npcCreatedProjects) => { if (err) return res.status(500).json({error: err.message}); // Combine and deduplicate projects const allNpcProjects = [...npcOwnedProjects, ...npcCreatedProjects]; const uniqueProjects = allNpcProjects.filter((project, index, self) => index === self.findIndex(p => p.id === project.id) ); console.log('NPC Debug Info:'); console.log('NPC Players found:', npcPlayers.length); console.log('NPC Owned Projects:', npcOwnedProjects.length); console.log('NPC Created Projects:', npcCreatedProjects.length); console.log('Total Unique Projects:', uniqueProjects.length); // Parse JSON fields for players const parsedPlayers = npcPlayers.map(r => ({ ...r, tags: JSON.parse(r.tags || '[]'), stats: JSON.parse(r.stats || '{}'), inventory: JSON.parse(r.inventory || '[]'), isOnline: !!r.isOnline, isNpc: !!r.isNpc })); // Parse JSON fields for projects const parsedProjects = uniqueProjects.map(r => ({ ...r, employees: JSON.parse(r.employees || '[]'), shopCatalog: JSON.parse(r.shopCatalog || '[]'), gallery: JSON.parse(r.gallery || '[]'), hiring: !!r.hiring })); res.json({ citizens: parsedPlayers, companies: parsedProjects, debug: { npcPlayersCount: npcPlayers.length, npcOwnedProjectsCount: npcOwnedProjects.length, npcCreatedProjectsCount: npcCreatedProjects.length, totalUniqueProjects: uniqueProjects.length } }); }); }); }); }); // === SHOP MANAGEMENT API === // Get shop items for a project app.get('/api/projects/:projectId/shop', (req, res) => { const projectId = req.params.projectId; db.get("SELECT shopCatalog FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); try { const shopCatalog = JSON.parse(row.shopCatalog || '[]'); res.json(shopCatalog); } catch (e) { res.status(500).json({error: 'Fehler beim Laden des Shops'}); } }); }); // Add shop item app.post('/api/projects/:projectId/shop', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const projectId = req.params.projectId; const { name, description, price, currency, stock, type, materialsRequired } = req.body; // Check ownership db.get("SELECT owner, shopCatalog FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const shopCatalog = JSON.parse(row.shopCatalog || '[]'); const newItem = { id: `item_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, name, description, price: parseFloat(price), currency: currency || 'Gold', stock: parseInt(stock) || 0, type: type || 'item', materialsRequired: materialsRequired || null }; shopCatalog.push(newItem); db.run("UPDATE projects SET shopCatalog = ? WHERE id = ?", [JSON.stringify(shopCatalog), projectId], function(err) { if (err) { console.error('Error adding shop item:', err); return res.status(500).json({error: 'Fehler beim Hinzufügen'}); } res.json({success: true, item: newItem}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // Update shop item app.put('/api/projects/:projectId/shop/:itemId', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { projectId, itemId } = req.params; const updates = req.body; // Check ownership db.get("SELECT owner, shopCatalog FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const shopCatalog = JSON.parse(row.shopCatalog || '[]'); const itemIndex = shopCatalog.findIndex(item => item.id === itemId); if (itemIndex === -1) { return res.status(404).json({error: 'Artikel nicht gefunden'}); } // Update item shopCatalog[itemIndex] = { ...shopCatalog[itemIndex], ...updates }; db.run("UPDATE projects SET shopCatalog = ? WHERE id = ?", [JSON.stringify(shopCatalog), projectId], function(err) { if (err) { console.error('Error updating shop item:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren'}); } res.json({success: true, item: shopCatalog[itemIndex]}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // Delete shop item app.delete('/api/projects/:projectId/shop/:itemId', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { projectId, itemId } = req.params; // Check ownership db.get("SELECT owner, shopCatalog FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const shopCatalog = JSON.parse(row.shopCatalog || '[]'); const filteredCatalog = shopCatalog.filter(item => item.id !== itemId); if (filteredCatalog.length === shopCatalog.length) { return res.status(404).json({error: 'Artikel nicht gefunden'}); } db.run("UPDATE projects SET shopCatalog = ? WHERE id = ?", [JSON.stringify(filteredCatalog), projectId], function(err) { if (err) { console.error('Error deleting shop item:', err); return res.status(500).json({error: 'Fehler beim Löschen'}); } res.json({success: true}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // === EMPLOYEE MANAGEMENT API === // Get project employees app.get('/api/projects/:projectId/employees', (req, res) => { const projectId = req.params.projectId; db.get("SELECT employees FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); try { const employees = JSON.parse(row.employees || '[]'); res.json(employees); } catch (e) { res.status(500).json({error: 'Fehler beim Laden der Mitarbeiter'}); } }); }); // Add employee app.post('/api/projects/:projectId/employees', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const projectId = req.params.projectId; const { employeeName } = req.body; if (!employeeName) { return res.status(400).json({error: 'Mitarbeiter-Name erforderlich'}); } // Check ownership db.get("SELECT owner, employees FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const employees = JSON.parse(row.employees || '[]'); if (employees.includes(employeeName)) { return res.status(400).json({error: 'Mitarbeiter bereits hinzugefügt'}); } employees.push(employeeName); db.run("UPDATE projects SET employees = ? WHERE id = ?", [JSON.stringify(employees), projectId], function(err) { if (err) { console.error('Error adding employee:', err); return res.status(500).json({error: 'Fehler beim Hinzufügen'}); } res.json({success: true, employees}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // Remove employee app.delete('/api/projects/:projectId/employees/:employeeName', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { projectId, employeeName } = req.params; // Check ownership db.get("SELECT owner, employees FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const employees = JSON.parse(row.employees || '[]'); const filteredEmployees = employees.filter(emp => emp !== employeeName); if (filteredEmployees.length === employees.length) { return res.status(404).json({error: 'Mitarbeiter nicht gefunden'}); } db.run("UPDATE projects SET employees = ? WHERE id = ?", [JSON.stringify(filteredEmployees), projectId], function(err) { if (err) { console.error('Error removing employee:', err); return res.status(500).json({error: 'Fehler beim Entfernen'}); } res.json({success: true, employees: filteredEmployees}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // === GALLERY MANAGEMENT API === // Get project gallery app.get('/api/projects/:projectId/gallery', (req, res) => { const projectId = req.params.projectId; db.get("SELECT gallery FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); try { const gallery = JSON.parse(row.gallery || '[]'); res.json(gallery); } catch (e) { res.status(500).json({error: 'Fehler beim Laden der Galerie'}); } }); }); // Add gallery image (for now just URL-based) app.post('/api/projects/:projectId/gallery', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const projectId = req.params.projectId; const { imageUrl } = req.body; if (!imageUrl) { return res.status(400).json({error: 'Bild-URL erforderlich'}); } // Check ownership db.get("SELECT owner, gallery FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const gallery = JSON.parse(row.gallery || '[]'); gallery.push(imageUrl); db.run("UPDATE projects SET gallery = ? WHERE id = ?", [JSON.stringify(gallery), projectId], function(err) { if (err) { console.error('Error adding gallery image:', err); return res.status(500).json({error: 'Fehler beim Hinzufügen'}); } res.json({success: true, gallery}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); // Delete gallery image app.delete('/api/projects/:projectId/gallery/:imageId', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { projectId, imageId } = req.params; // Check ownership db.get("SELECT owner, gallery FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } try { const gallery = JSON.parse(row.gallery || '[]'); const imageIndex = gallery.indexOf(imageId); if (imageIndex === -1) { return res.status(404).json({error: 'Bild nicht in Galerie gefunden'}); } // Remove from gallery array gallery.splice(imageIndex, 1); // Update project gallery db.run("UPDATE projects SET gallery = ? WHERE id = ?", [JSON.stringify(gallery), projectId], function(err) { if (err) { console.error('Error updating gallery:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren der Galerie'}); } // Delete image record and file db.get("SELECT filename FROM images WHERE id = ?", [imageId], (err, imageRow) => { if (err) { console.error('Error finding image record:', err); return res.status(500).json({error: 'Fehler beim Löschen des Bildes'}); } if (imageRow) { // Delete file from filesystem const filePath = path.join(UPLOAD_DIR, imageRow.filename); try { if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } } catch (fileErr) { console.error('Error deleting file:', fileErr); } // Delete image record from database db.run("DELETE FROM images WHERE id = ?", [imageId], function(err) { if (err) { console.error('Error deleting image record:', err); return res.status(500).json({error: 'Fehler beim Löschen des Bildes'}); } res.json({ success: true, gallery: gallery, message: 'Bild erfolgreich gelöscht' }); }); } else { // Image record not found, but gallery was updated res.json({ success: true, gallery: gallery, message: 'Bild aus Galerie entfernt' }); } }); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Galerie-Daten'}); } }); }); // === IMAGE MANAGEMENT API === // Get image by ID app.get('/api/images/:imageId', (req, res) => { const { imageId } = req.params; db.get("SELECT * FROM images WHERE id = ?", [imageId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Bild nicht gefunden'}); // Serve the actual image file const filePath = path.join(UPLOAD_DIR, row.filename); if (fs.existsSync(filePath)) { res.sendFile(filePath); } else { res.status(404).json({error: 'Bild-Datei nicht gefunden'}); } }); }); // Get image metadata by ID app.get('/api/images/:imageId/meta', (req, res) => { const { imageId } = req.params; db.get("SELECT * FROM images WHERE id = ?", [imageId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Bild nicht gefunden'}); res.json({ id: row.id, filename: row.filename, originalName: row.originalName, mimeType: row.mimeType, size: row.size, uploadDate: row.uploadDate, altText: row.altText, url: `${BACKEND_URL}/api/images/${row.id}` }); }); }); // Upload banner image app.post('/api/projects/:projectId/banner/upload', upload.single('banner'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); const { projectId } = req.params; // Check ownership db.get("SELECT owner FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || null, entityType: 'project', entityId: projectId, imageType: 'banner' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Bildes'}); } // Update project banner db.run("UPDATE projects SET bannerImageId = ? WHERE id = ?", [imageId, projectId], function(err) { if (err) { console.error('Error updating banner:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren des Banners'}); } res.json({ success: true, imageId: imageId, imageUrl: `${BACKEND_URL}/api/images/${imageId}`, message: 'Banner erfolgreich hochgeladen' }); }); }); }); }); // Upload gallery image app.post('/api/projects/:projectId/gallery/upload', upload.single('gallery'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); const { projectId } = req.params; // Check ownership db.get("SELECT owner, gallery FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || null, entityType: 'project', entityId: projectId, imageType: 'gallery' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Bildes'}); } try { // Add image ID to gallery array const gallery = JSON.parse(row.gallery || '[]'); gallery.push(imageId); db.run("UPDATE projects SET gallery = ? WHERE id = ?", [JSON.stringify(gallery), projectId], function(err) { if (err) { console.error('Error updating gallery:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren der Galerie'}); } res.json({ success: true, imageId: imageId, imageUrl: `${BACKEND_URL}/api/images/${imageId}`, gallery: gallery, message: 'Bild erfolgreich zur Galerie hinzugefügt' }); }); } catch (e) { console.error('Error parsing gallery:', e); res.status(500).json({error: 'Fehler beim Verarbeiten der Galerie-Daten'}); } }); }); }); // Upload logo image app.post('/api/projects/:projectId/logo/upload', upload.single('logo'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); const { projectId } = req.params; // Check ownership db.get("SELECT owner FROM projects WHERE id = ?", [projectId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Projekt nicht gefunden'}); if (row.owner !== req.user.username && req.user.username !== 'admin') { return res.status(403).json({error: 'Keine Berechtigung'}); } if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || `${row.title} Logo`, entityType: 'project', entityId: projectId, imageType: 'logo' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating logo image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Logos'}); } // Update project logo db.run("UPDATE projects SET logoImageId = ? WHERE id = ?", [imageId, projectId], function(err) { if (err) { console.error('Error updating logo:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren des Logos'}); } res.json({ success: true, imageId: imageId, logoUrl: `${BACKEND_URL}/api/images/${imageId}`, message: 'Logo erfolgreich hochgeladen' }); }); }); }); }); // === CITY MANAGEMENT API (Admin Only) === // Create new city app.post('/api/admin/cities', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { name, description, mayor, establishedYear, cityStats } = req.body; if (!name) { return res.status(400).json({error: 'Stadt-Name erforderlich'}); } // Generate unique ID const cityId = 'city_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); const cityData = { id: cityId, name: name.trim(), type: 'City', description: description || `${name} - Eine Stadt im Obsidian-Tal.`, memberCount: 0, status: 'active', mayor: mayor || '', establishedYear: establishedYear || new Date().getFullYear().toString(), bannerImageId: null, logoImageId: null, gallery: '[]', cityStats: cityStats ? JSON.stringify(cityStats) : JSON.stringify({ taxRate: 5.0, biome: 'Ebene', defenseRating: 5, government: 'Demokratie', specialty: 'Handel' }) }; db.run(`INSERT INTO orgs (id, name, type, description, memberCount, status, mayor, establishedYear, bannerImageId, logoImageId, gallery, cityStats) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [cityData.id, cityData.name, cityData.type, cityData.description, cityData.memberCount, cityData.status, cityData.mayor, cityData.establishedYear, cityData.bannerImageId, cityData.logoImageId, cityData.gallery, cityData.cityStats], function(err) { if (err) { console.error('Error creating city:', err); return res.status(500).json({error: 'Fehler beim Erstellen der Stadt'}); } res.json({ success: true, cityId: cityId, message: 'Stadt erfolgreich erstellt' }); } ); }); // Update city app.put('/api/admin/cities/:cityId', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { cityId } = req.params; const updates = req.body; // Build dynamic update query const allowedFields = ['name', 'description', 'mayor', 'establishedYear', 'status', 'cityStats']; const updateFields = []; const values = []; for (const field of allowedFields) { if (updates[field] !== undefined) { if (field === 'cityStats') { updateFields.push(`${field} = ?`); values.push(JSON.stringify(updates[field])); } else { updateFields.push(`${field} = ?`); values.push(updates[field]); } } } if (updateFields.length === 0) { return res.status(400).json({error: 'Keine gültigen Felder zum Aktualisieren'}); } const query = `UPDATE orgs SET ${updateFields.join(', ')} WHERE id = ? AND type = 'City'`; values.push(cityId); db.run(query, values, function(err) { if (err) { console.error('Error updating city:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren'}); } if (this.changes === 0) { return res.status(404).json({error: 'Stadt nicht gefunden'}); } res.json({success: true, message: 'Stadt erfolgreich aktualisiert'}); }); }); // Delete city app.delete('/api/admin/cities/:cityId', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { cityId } = req.params; db.run("DELETE FROM orgs WHERE id = ? AND type = 'City'", [cityId], function(err) { if (err) { console.error('Error deleting city:', err); return res.status(500).json({error: 'Fehler beim Löschen'}); } if (this.changes === 0) { return res.status(404).json({error: 'Stadt nicht gefunden'}); } res.json({success: true, message: 'Stadt erfolgreich gelöscht'}); }); }); // Upload city banner app.post('/api/admin/cities/:cityId/banner/upload', upload.single('banner'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { cityId } = req.params; // Check if city exists db.get("SELECT name FROM orgs WHERE id = ? AND type = 'City'", [cityId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Stadt nicht gefunden'}); if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || `${row.name} Banner`, entityType: 'org', entityId: cityId, imageType: 'banner' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating city banner image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Banners'}); } // Update city banner db.run("UPDATE orgs SET bannerImageId = ? WHERE id = ? AND type = 'City'", [imageId, cityId], function(err) { if (err) { console.error('Error updating city banner:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren des Banners'}); } res.json({ success: true, imageId: imageId, bannerUrl: `${BACKEND_URL}/api/images/${imageId}`, message: 'Banner erfolgreich hochgeladen' }); }); }); }); }); // Upload city logo app.post('/api/admin/cities/:cityId/logo/upload', upload.single('logo'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { cityId } = req.params; // Check if city exists db.get("SELECT name FROM orgs WHERE id = ? AND type = 'City'", [cityId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Stadt nicht gefunden'}); if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || `${row.name} Logo`, entityType: 'org', entityId: cityId, imageType: 'logo' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating city logo image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Logos'}); } // Update city logo db.run("UPDATE orgs SET logoImageId = ? WHERE id = ? AND type = 'City'", [imageId, cityId], function(err) { if (err) { console.error('Error updating city logo:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren des Logos'}); } res.json({ success: true, imageId: imageId, logoUrl: `${BACKEND_URL}/api/images/${imageId}`, message: 'Logo erfolgreich hochgeladen' }); }); }); }); }); // Upload city gallery image app.post('/api/admin/cities/:cityId/gallery/upload', upload.single('gallery'), (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({error: 'Nicht authentifiziert'}); // Check if user is admin if (!req.user.isAdmin) { return res.status(403).json({error: 'Admin-Berechtigung erforderlich'}); } const { cityId } = req.params; // Check if city exists db.get("SELECT name, gallery FROM orgs WHERE id = ? AND type = 'City'", [cityId], (err, row) => { if (err) return res.status(500).json({error: err.message}); if (!row) return res.status(404).json({error: 'Stadt nicht gefunden'}); if (!req.file) { return res.status(400).json({error: 'Keine Datei hochgeladen'}); } // Generate unique image ID const imageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Create image record const imageData = { id: imageId, filename: req.file.filename, originalName: req.file.originalname, mimeType: req.file.mimetype, size: req.file.size, uploadDate: new Date().toISOString(), altText: req.body.altText || `${row.name} Galerie`, entityType: 'org', entityId: cityId, imageType: 'gallery' }; db.run(`INSERT INTO images (id, filename, originalName, mimeType, size, uploadDate, altText, entityType, entityId, imageType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [imageData.id, imageData.filename, imageData.originalName, imageData.mimeType, imageData.size, imageData.uploadDate, imageData.altText, imageData.entityType, imageData.entityId, imageData.imageType], function(err) { if (err) { console.error('Error creating city gallery image record:', err); return res.status(500).json({error: 'Fehler beim Speichern des Bildes'}); } try { // Add image ID to gallery array const gallery = JSON.parse(row.gallery || '[]'); gallery.push(imageId); db.run("UPDATE orgs SET gallery = ? WHERE id = ? AND type = 'City'", [JSON.stringify(gallery), cityId], function(err) { if (err) { console.error('Error updating city gallery:', err); return res.status(500).json({error: 'Fehler beim Aktualisieren der Galerie'}); } res.json({ success: true, imageId: imageId, imageUrl: `${BACKEND_URL}/api/images/${imageId}`, gallery: gallery, message: 'Bild erfolgreich zur Galerie hinzugefügt' }); }); } catch (e) { console.error('Error parsing city gallery:', e); res.status(500).json({error: 'Fehler beim Verarbeiten der Galerie-Daten'}); } }); }); }); app.post('/api/data', (req, res) => { const { eventType, player, uuid, timestamp, char, statistics, advancements } = req.body; if (!uuid || !player) { return res.status(400).json({ error: 'UUID und Player-Name erforderlich' }); } // Prepare player data const playerData = { uuid, username: player, isOnline: eventType === 'JOIN' ? 1 : 0, isNpc: 0, isAdmin: 0, tags: JSON.stringify([]), stats: JSON.stringify({ char, statistics, advancements, lastSync: timestamp }), inventory: JSON.stringify([]), storyMarkdown: '', discordId: null }; // Check if player exists db.get("SELECT uuid FROM players WHERE uuid = ?", [uuid], (err, row) => { if (err) { console.error('Error checking player existence:', err); return res.status(500).json({ error: 'Datenbankfehler beim Überprüfen des Spielers' }); } if (row) { // Update existing player db.run(`UPDATE players SET username = ?, isOnline = ?, stats = ?, storyMarkdown = ? WHERE uuid = ?`, [playerData.username, playerData.isOnline, playerData.stats, playerData.storyMarkdown, uuid], function(err) { if (err) { console.error('Error updating player:', err); return res.status(500).json({ error: 'Fehler beim Aktualisieren des Spielers' }); } res.json({ success: true, message: 'Spieler erfolgreich aktualisiert', action: 'updated', uuid }); } ); } else { // Insert new player db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, isAdmin, tags, stats, inventory, storyMarkdown, discordId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [playerData.uuid, playerData.username, playerData.isOnline, playerData.isNpc, playerData.isAdmin, playerData.tags, playerData.stats, playerData.inventory, playerData.storyMarkdown, playerData.discordId], function(err) { if (err) { console.error('Error inserting player:', err); return res.status(500).json({ error: 'Fehler beim Erstellen des Spielers' }); } res.json({ success: true, message: 'Spieler erfolgreich erstellt', action: 'created', uuid }); } ); } }); }); // Serve uploaded files statically app.use('/uploads', express.static(UPLOAD_DIR)); app.listen(PORT, () => { console.log(`Backend running on http://localhost:${PORT}`); });