import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs'; import { join } from 'path'; import { parse } from 'csv-parse/sync'; import multer from 'multer'; import crypto from 'crypto'; import axios from 'axios'; import { getSessionStatus, getClientsMap, getSessionIdForSocket } from './broadcast.js'; // Bild-Upload-Konfiguration (temporäres Verzeichnis) const uploadsDir = join(__dirname, '../tmp'); if (!existsSync(uploadsDir)) { mkdirSync(uploadsDir, { recursive: true }); } // Map: Code -> Bild-Info (für temporäre Speicherung) const imageCodes = new Map(); // Multer-Konfiguration für Bild-Upload const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadsDir); }, filename: (req, file, cb) => { // Generiere eindeutigen Code const code = crypto.randomBytes(16).toString('hex'); const ext = file.originalname.split('.').pop(); cb(null, `${code}.${ext}`); } }); const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 // 5MB Limit }, fileFilter: (req, file, cb) => { // Nur Bilder erlauben if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Nur Bilder sind erlaubt'), false); } } }); export function setupRoutes(app, __dirname) { // Health-Check-Endpoint app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Bild-Upload-Endpoint app.post('/api/upload-image', upload.single('image'), (req, res) => { try { if (!req.file) { return res.status(400).json({ error: 'Kein Bild hochgeladen' }); } // Prüfe, ob Benutzer eingeloggt ist const sessionId = req.sessionID; const clientsMap = getClientsMap(); const client = clientsMap.get(sessionId); if (!client || !client.userName) { // Lösche hochgeladenes Bild, wenn nicht eingeloggt unlinkSync(req.file.path); return res.status(401).json({ error: 'Nicht eingeloggt' }); } // Generiere eindeutigen Code für das Bild const code = req.file.filename.split('.')[0]; const imageInfo = { code: code, filename: req.file.filename, originalName: req.file.originalname, mimetype: req.file.mimetype, size: req.file.size, uploadedBy: client.userName, uploadedAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 6 * 60 * 60 * 1000) // 6 Stunden }; // Speichere Bild-Info imageCodes.set(code, imageInfo); console.log(`[Bild-Upload] Bild hochgeladen von ${client.userName}, Code: ${code}`); res.json({ success: true, code: code, url: `/api/image/${code}` }); } catch (error) { console.error('Fehler beim Bild-Upload:', error); if (req.file && existsSync(req.file.path)) { unlinkSync(req.file.path); } res.status(500).json({ error: 'Fehler beim Hochladen des Bildes' }); } }); // Bild-Download-Endpoint (mit Code) app.get('/api/image/:code', (req, res) => { try { const { code } = req.params; const imageInfo = imageCodes.get(code); if (!imageInfo) { return res.status(404).json({ error: 'Bild nicht gefunden' }); } // Prüfe Ablaufzeit if (new Date() > imageInfo.expiresAt) { // Lösche abgelaufenes Bild const filePath = join(uploadsDir, imageInfo.filename); if (existsSync(filePath)) { unlinkSync(filePath); } imageCodes.delete(code); return res.status(410).json({ error: 'Bild abgelaufen' }); } // Prüfe, ob Benutzer eingeloggt ist (optional, für zusätzliche Sicherheit) const sessionId = req.sessionID; const clientsMap = getClientsMap(); const client = clientsMap.get(sessionId); // Wenn eingeloggt, prüfe ob Benutzer berechtigt ist (optional) // Für jetzt erlauben wir allen eingeloggten Benutzern den Zugriff const filePath = join(uploadsDir, imageInfo.filename); if (!existsSync(filePath)) { imageCodes.delete(code); return res.status(404).json({ error: 'Bilddatei nicht gefunden' }); } // Setze Content-Type res.setHeader('Content-Type', imageInfo.mimetype); res.setHeader('Content-Disposition', `inline; filename="${imageInfo.originalName}"`); // Sende Bild const imageData = readFileSync(filePath); res.send(imageData); } catch (error) { console.error('Fehler beim Laden des Bildes:', error); res.status(500).json({ error: 'Fehler beim Laden des Bildes' }); } }); // Cleanup-Funktion für abgelaufene Bilder (wird regelmäßig aufgerufen) setInterval(() => { const now = new Date(); let deletedCount = 0; for (const [code, info] of imageCodes.entries()) { if (now > info.expiresAt) { const filePath = join(uploadsDir, info.filename); if (existsSync(filePath)) { unlinkSync(filePath); } imageCodes.delete(code); deletedCount++; } } if (deletedCount > 0) { console.log(`[Bild-Cleanup] ${deletedCount} abgelaufene Bild(er) gelöscht`); } }, 30 * 60 * 1000); // Alle 30 Minuten prüfen // Session-Status-Endpoint app.get('/api/session', (req, res) => { try { const sessionId = req.sessionID; console.log('Session-Check - SessionID:', sessionId); console.log('Session-Check - Alle Clients:', Array.from(getClientsMap().keys())); // Prüfe zuerst in der clients Map const clientsMap = getClientsMap(); let client = clientsMap.get(sessionId); // Wenn kein Client mit dieser Session-ID gefunden wurde, aber es gibt Clients, // die bereits eingeloggt sind, könnte es sein, dass die Session-ID beim Reload geändert wurde. // In diesem Fall sollten wir den Client mit der Express-Session-ID verknüpfen. // Aber wir können nicht sicher sein, welcher Client zu welcher Session gehört, // daher geben wir einfach die Session-ID zurück und lassen den Client beim setSessionId // den richtigen Client finden. if (client && client.userName) { console.log('Session-Check - Client gefunden:', client.userName); res.json({ loggedIn: true, sessionId: sessionId, // Wichtig: Sende Session-ID zurück user: { sessionId: client.sessionId, userName: client.userName, gender: client.gender, age: client.age, country: client.country, isoCountryCode: client.isoCountryCode } }); } else { console.log('Session-Check - Kein Client gefunden für SessionID:', sessionId); // Prüfe auch alle Clients, um zu sehen, ob es ein Mismatch gibt for (const [sid, c] of clientsMap.entries()) { if (c.userName) { console.log('Session-Check - Gefundener Client:', sid, c.userName); } } // Sende Session-ID zurück, auch wenn nicht eingeloggt (für Login) res.json({ loggedIn: false, sessionId: sessionId }); } } catch (error) { console.error('Fehler beim Prüfen der Session:', error); res.json({ loggedIn: false }); } }); // Länderliste-Endpoint app.get('/api/countries', async (req, res) => { try { // Versuche zuerst, die CSV-Datei zu laden const csvPath = join(__dirname, '../docroot/countries.csv'); let countries = {}; try { const fileContent = readFileSync(csvPath, 'utf-8'); const records = parse(fileContent, { columns: true, skip_empty_lines: true, quote: '"', trim: true, relax_quotes: true, relax_column_count: true }); records.forEach(record => { if (record.Name && record.Code) { // Entferne alle Anführungszeichen (auch am Anfang/Ende) const name = record.Name.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim(); const code = record.Code.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim(); if (name && code) { countries[name] = code.toLowerCase(); } } }); } catch (fileError) { // Wenn die Datei nicht existiert, lade von der URL console.log('CSV-Datei nicht gefunden, lade von URL...'); const response = await axios.get('https://pkgstore.datahub.io/core/country-list/data_csv/data/d7c9d7cfb42cb69f4422dec222dbbaa8/data_csv.csv'); const lines = response.data.split('\n'); for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; // Parse CSV-Zeile mit Berücksichtigung von Anführungszeichen const parseCSVLine = (line) => { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { // Ignoriere Anführungszeichen, sie werden nicht zum Wert hinzugefügt inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { result.push(current.trim()); current = ''; } else { current += char; } } result.push(current.trim()); return result; }; const [name, code] = parseCSVLine(line); if (name && code) { // Entferne alle Anführungszeichen (auch am Anfang/Ende) const cleanName = name.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim(); const cleanCode = code.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim(); if (cleanName && cleanCode) { countries[cleanName] = cleanCode.toLowerCase(); } } } } res.json(countries); } catch (error) { console.error('Fehler beim Laden der Länderliste:', error); res.status(500).json({ error: 'Fehler beim Laden der Länderliste' }); } }); // Partners-Links app.get('/api/partners', (req, res) => { try { const csvPath = join(__dirname, '../docroot/links.csv'); const fileContent = readFileSync(csvPath, 'utf-8'); const records = parse(fileContent, { columns: true, skip_empty_lines: true, quote: '"' }); res.json(records); } catch (error) { console.error('Fehler beim Laden der Partner-Links:', error); res.status(500).json({ error: 'Fehler beim Laden der Partner-Links' }); } }); }