import { Router } from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import crypto from 'crypto'; import { getUserByUsername, getUserById, createUser, updateUserCredentials, deleteUser, createInviteToken, getInviteToken, useInviteToken, getInvitesByUser, getAllUsers, getInviteStats } from '../db.js'; import { authMiddleware, adminMiddleware } from '../middleware.js'; import { trackEvent } from '../analytics-db.js'; const router = Router(); const JWT_SECRET = process.env.JWT_SECRET || 'tiktok-rezepte-secret-key-change-in-production'; const JWT_EXPIRES_IN = '7d'; router.post('/register', async (req, res) => { try { const { username, password, inviteToken } = req.body; if (!username || !password || !inviteToken) { return res.status(400).json({ error: 'Benutzername, Passwort und Einladungscode erforderlich' }); } if (username.length < 3) { return res.status(400).json({ error: 'Benutzername muss mindestens 3 Zeichen lang sein' }); } if (password.length < 6) { return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' }); } const invite = getInviteToken(inviteToken); if (!invite) { return res.status(400).json({ error: 'Ungültiger Einladungscode' }); } if (invite.used_by) { return res.status(400).json({ error: 'Einladungscode wurde bereits verwendet' }); } const existing = getUserByUsername(username); if (existing) { return res.status(409).json({ error: 'Benutzername ist bereits vergeben' }); } const hashedPassword = await bcrypt.hash(password, 10); const user = createUser(username, hashedPassword); useInviteToken(inviteToken, user.id); trackEvent('user_register', user.id, { username: user.username }); const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); res.status(201).json({ user, token }); } catch (error) { res.status(500).json({ error: 'Registrierung fehlgeschlagen' }); } }); router.post('/login', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Benutzername und Passwort erforderlich' }); } const user = getUserByUsername(username); if (!user) { return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); } const valid = await bcrypt.compare(password, user.password); if (!valid) { return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); } const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); trackEvent('user_login', user.id, { username: user.username }); res.json({ user: { id: user.id, username: user.username, is_admin: !!user.is_admin, created_at: user.created_at }, token, }); } catch (error) { res.status(500).json({ error: 'Anmeldung fehlgeschlagen' }); } }); router.get('/me', (req, res) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Nicht authentifiziert' }); } try { const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); const user = getUserByUsername(decoded.username); if (!user) return res.status(401).json({ error: 'Benutzer nicht gefunden' }); res.json({ id: user.id, username: user.username, is_admin: !!user.is_admin, created_at: user.created_at }); } catch { res.status(401).json({ error: 'Ungültiges Token' }); } }); router.post('/invite', authMiddleware, adminMiddleware, (req, res) => { try { const { count = 1 } = req.body; const amount = Math.min(Math.max(parseInt(count) || 1, 1), 20); const tokens = []; for (let i = 0; i < amount; i++) { const token = crypto.randomBytes(16).toString('hex'); createInviteToken(token, req.user.id); tokens.push(token); } res.json({ tokens }); } catch (error) { res.status(500).json({ error: 'Einladung fehlgeschlagen' }); } }); router.get('/invites', authMiddleware, adminMiddleware, (req, res) => { try { const invites = getInvitesByUser(req.user.id); const stats = getInviteStats(); res.json({ invites, stats }); } catch (error) { res.status(500).json({ error: 'Fehler beim Laden der Einladungen' }); } }); router.get('/users', authMiddleware, adminMiddleware, (req, res) => { try { const users = getAllUsers(); res.json(users); } catch (error) { res.status(500).json({ error: 'Fehler beim Laden der Benutzer' }); } }); router.delete('/users/:id', authMiddleware, adminMiddleware, (req, res) => { try { const userId = Number(req.params.id); if (userId === req.user.id) { return res.status(400).json({ error: 'Du kannst dich nicht selbst löschen' }); } const user = getUserById(userId); if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden' }); deleteUser(userId); res.json({ success: true }); } catch (error) { res.status(500).json({ error: 'Fehler beim Löschen' }); } }); router.put('/credentials', authMiddleware, async (req, res) => { try { const { currentPassword, newUsername, newPassword } = req.body; const user = getUserByUsername(req.user.username); if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden' }); if (currentPassword) { const valid = await bcrypt.compare(currentPassword, user.password); if (!valid) return res.status(401).json({ error: 'Aktuelles Passwort ist falsch' }); } if (newUsername && newUsername !== user.username) { const exists = getUserByUsername(newUsername); if (exists) return res.status(409).json({ error: 'Benutzername ist bereits vergeben' }); } const updates = {}; if (newUsername) updates.username = newUsername; if (newPassword) { if (newPassword.length < 6) return res.status(400).json({ error: 'Neues Passwort muss mindestens 6 Zeichen lang sein' }); updates.password = await bcrypt.hash(newPassword, 10); } if (Object.keys(updates).length === 0) { return res.status(400).json({ error: 'Keine Änderungen angegeben' }); } const updated = updateUserCredentials(user.id, updates); const token = jwt.sign({ id: updated.id, username: updated.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); res.json({ user: updated, token }); } catch (error) { res.status(500).json({ error: 'Fehler beim Aktualisieren' }); } }); export default router;