mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
1633 lines
62 KiB
JavaScript
1633 lines
62 KiB
JavaScript
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';
|
|
|
|
// CORS configuration - allow multiple origins for development
|
|
const corsOrigins = process.env.NODE_ENV === 'production'
|
|
? [FRONTEND_URL]
|
|
: [FRONTEND_URL, 'http://localhost:3000', 'http://localhost:8000', 'http://127.0.0.1:3000'];
|
|
|
|
// 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: corsOrigins, 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 || '{}'),
|
|
minecraftStats: r.minecraftStats ? JSON.parse(r.minecraftStats) : undefined,
|
|
inventory: JSON.parse(r.inventory || '[]'),
|
|
isOnline: !!r.isOnline,
|
|
organizationId: r.organizationId || undefined
|
|
}));
|
|
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', 'organizationId'];
|
|
const updateFields = [];
|
|
const values = [];
|
|
|
|
// Handle 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'});
|
|
});
|
|
});
|
|
|
|
// 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
|
|
}),
|
|
minecraftStats: 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, minecraftStats, inventory, storyMarkdown, discordId)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[npcData.uuid, npcData.username, npcData.isOnline, npcData.isNpc, npcData.tags,
|
|
npcData.stats, npcData.minecraftStats, 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({}),
|
|
minecraftStats: 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 = ?, minecraftStats = ? WHERE uuid = ?`,
|
|
[playerData.username, playerData.isOnline, playerData.minecraftStats, 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, minecraftStats, inventory, storyMarkdown, discordId)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[playerData.uuid, playerData.username, playerData.isOnline, playerData.isNpc, playerData.isAdmin,
|
|
playerData.tags, playerData.stats, playerData.minecraftStats, 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}`);
|
|
});
|