const express = require('express'); const cookieParser = require('cookie-parser'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const path = require('path'); const db = require('./db'); const posClient = require('./posClient'); const app = express(); const PORT = process.env.PORT || 8085; app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); // Static Files app.use(express.static(path.join(__dirname, 'public'))); app.use('/Getraenkekarte.pdf', express.static(path.join(__dirname, 'Getraenkekarte.pdf'))); // JWT Auth Middleware function getSecret() { const row = db.prepare('SELECT value FROM config WHERE key = ?').get('jwt_secret'); return row ? row.value : 'default_secret_key'; } function authMiddleware(req, res, next) { const token = req.cookies.admin_token || req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ error: 'Nicht authentifiziert' }); } try { const decoded = jwt.verify(token, getSecret()); req.user = decoded; next(); } catch (err) { res.status(401).json({ error: 'Ungültige Session' }); } } // ==================== Öffentliche API ==================== // 1. Produkte & Gruppen für die Speisekarte app.get('/api/menu', (req, res) => { const groups = db.prepare('SELECT * FROM product_groups ORDER BY sort_order ASC, name ASC').all(); const products = db.prepare('SELECT id, text1, text2, group_id, preis1, unit FROM products WHERE active = 1 ORDER BY sort_order ASC, text1 ASC').all(); // Gruppieren const groupMap = {}; for (const g of groups) { groupMap[g.id] = { id: g.id, name: g.name, items: [] }; } const ungrouped = []; for (const p of products) { if (p.group_id && groupMap[p.group_id]) { groupMap[p.group_id].items.push(p); } else { ungrouped.push(p); } } const result = Object.values(groupMap).filter(g => g.items.length > 0); if (ungrouped.length > 0) { result.push({ id: 'other', name: 'Weitere Spezialitäten', items: ungrouped }); } res.json({ categories: result, allProducts: products }); }); // ==================== Admin Auth ==================== // 2. Login app.post('/api/admin/login', (req, res) => { const { username, password } = req.body; const adminUserRow = db.prepare('SELECT value FROM config WHERE key = ?').get('admin_user'); const hashRow = db.prepare('SELECT value FROM config WHERE key = ?').get('admin_password_hash'); const adminUser = adminUserRow ? adminUserRow.value : 'admin'; const hash = hashRow ? hashRow.value : ''; if (username === adminUser && bcrypt.compareSync(password, hash)) { const token = jwt.sign({ user: username }, getSecret(), { expiresIn: '7d' }); res.cookie('admin_token', token, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'lax' }); return res.json({ success: true, message: 'Erfolgreich eingeloggt' }); } res.status(401).json({ error: 'Benutzername oder Passwort falsch' }); }); // 3. Logout app.post('/api/admin/logout', (req, res) => { res.clearCookie('admin_token'); res.json({ success: true }); }); // 4. Auth-Check app.get('/api/admin/check', authMiddleware, (req, res) => { res.json({ authenticated: true, user: req.user.user }); }); // ==================== Admin POS Settings & Sync ==================== // 5. Config abrufen app.get('/api/admin/config', authMiddleware, (req, res) => { const config = posClient.getConfig(); // Passwort nicht im Klartext zurückgeben delete config.admin_password_hash; delete config.jwt_secret; res.json(config); }); // 6. Config speichern app.post('/api/admin/config', authMiddleware, (req, res) => { const { pos_url, pos_pass_id, pos_app_id, pos_cash_register, new_admin_password } = req.body; const upsert = db.prepare('INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'); if (pos_url !== undefined) upsert.run('pos_url', pos_url); if (pos_pass_id !== undefined) upsert.run('pos_pass_id', pos_pass_id); if (pos_app_id !== undefined) upsert.run('pos_app_id', pos_app_id); if (pos_cash_register !== undefined) upsert.run('pos_cash_register', pos_cash_register); if (new_admin_password && new_admin_password.trim().length > 0) { const newHash = bcrypt.hashSync(new_admin_password.trim(), 10); upsert.run('admin_password_hash', newHash); } res.json({ success: true, message: 'Einstellungen gespeichert' }); }); // 7. Sync auslösen app.post('/api/admin/sync', authMiddleware, async (req, res) => { const { type } = req.body; // 'ERSTEINRICHTUNG' oder 'AENDERUNGEN' try { const result = await posClient.performSync(type || 'ERSTEINRICHTUNG'); res.json({ success: true, result }); } catch (err) { db.prepare('INSERT INTO sync_logs (status, message) VALUES (?, ?)').run('ERROR', err.message); res.status(500).json({ error: err.message }); } }); // 8. Logs & Produkte im Admin anzeigen app.get('/api/admin/data', authMiddleware, (req, res) => { const products = db.prepare('SELECT * FROM products ORDER BY updated_at DESC').all(); const groups = db.prepare('SELECT * FROM product_groups').all(); const logs = db.prepare('SELECT * FROM sync_logs ORDER BY timestamp DESC LIMIT 20').all(); res.json({ products, groups, logs }); }); // 9. Manueller Import (Test/Demo) app.post('/api/admin/mock-import', authMiddleware, (req, res) => { const mockData = req.body; const count = posClient.importJsonData(mockData); res.json({ success: true, imported: count }); }); // Admin Route Frontend app.get('/admin', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'admin.html')); }); // Server Start app.listen(PORT, '0.0.0.0', () => { console.log(`Server läuft auf http://localhost:${PORT}`); });