Files
singlechat/server/routes.js

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' });
}
});
}