333 lines
11 KiB
JavaScript
333 lines
11 KiB
JavaScript
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
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';
|
|
|
|
// __dirname für ES-Module
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// 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
|
|
// Extrahiere Session-ID wie in broadcast.js (entferne s: Präfix und Signatur)
|
|
let sessionId = req.sessionID;
|
|
if (sessionId && sessionId.startsWith('s:')) {
|
|
const parts = sessionId.split('.');
|
|
if (parts.length > 0) {
|
|
sessionId = parts[0].substring(2); // Entferne 's:' Präfix
|
|
}
|
|
}
|
|
|
|
console.log(`[Bild-Upload] Session-ID: ${sessionId}, Alle Clients:`, Array.from(getClientsMap().keys()));
|
|
|
|
const clientsMap = getClientsMap();
|
|
const client = clientsMap.get(sessionId);
|
|
|
|
if (!client || !client.userName) {
|
|
console.log(`[Bild-Upload] Client nicht gefunden für Session-ID: ${sessionId}`);
|
|
// 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' });
|
|
}
|
|
});
|
|
}
|
|
|