175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
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 || 80;
|
|
|
|
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 Port ${PORT}`);
|
|
});
|