const express = require('express'); const session = require('express-session'); const passport = require('passport'); const DiscordStrategy = require('passport-discord').Strategy; const cors = require('cors'); 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'; 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), cityStats: r.cityStats ? JSON.parse(r.cityStats) : undefined })); res.json(parsed); }); }); // 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: JSON.parse(r.gallery), 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, bannerUrl, shopCatalog, gallery) VALUES (?, ?, ?, ?, 'active', 0, ?, '[]', 0, ?, ?, '', '[]', '[]')`, [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, bannerUrl, shopCatalog, gallery) VALUES (?, ?, ?, ?, 'active', ?, ?, '[]', 0, ?, ?, '', ?, '[]')`, [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/:imageIndex', (req, res) => { if (!req.isAuthenticated()) return res.status(401).send(); const { projectId, imageIndex } = req.params; const index = parseInt(imageIndex); // 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 || '[]'); if (index < 0 || index >= gallery.length) { return res.status(404).json({error: 'Bild nicht gefunden'}); } gallery.splice(index, 1); db.run("UPDATE projects SET gallery = ? WHERE id = ?", [JSON.stringify(gallery), projectId], function(err) { if (err) { console.error('Error deleting gallery image:', err); return res.status(500).json({error: 'Fehler beim Löschen'}); } res.json({success: true, gallery}); }); } catch (e) { res.status(500).json({error: 'Fehler beim Verarbeiten der Daten'}); } }); }); app.listen(PORT, () => { console.log(`Backend running on http://localhost:${PORT}`); });