import express, { Request } from "express"; import { getDateForWeekIndex, getData, getRestaurantMenu, getToday, initIfNeeded } from "../service"; import { formatDate, getDayOfWeekIndex } from "../utils"; import getStorage from "../storage"; import { getWebsocket } from "../websocket"; import { getLogin } from "../auth"; import { parseToken } from "../utils"; import webpush from 'web-push'; const router = express.Router(); const storage = getStorage(); const ENVIRONMENT = process.env.NODE_ENV ?? 'production'; // Seznam náhodných jmen pro generování mock dat const MOCK_NAMES = [ 'Alice', 'Bob', 'Charlie', 'David', 'Eva', 'Filip', 'Gita', 'Honza', 'Ivana', 'Jakub', 'Kamila', 'Lukáš', 'Markéta', 'Nikola', 'Ondřej', 'Petra', 'Quido', 'Radek', 'Simona', 'Tomáš', 'Ursula', 'Viktor', 'Wanda', 'Xaver', 'Yvona', 'Zdeněk', 'Aneta', 'Boris', 'Cecílie', 'Daniel' ]; // Volby stravování pro mock data const LUNCH_CHOICES = [ 'SLADOVNICKA', 'TECHTOWER', 'ZASTAVKAUMICHALA', 'SENKSERIKOVA', 'OBJEDNAVAM', 'NEOBEDVAM', 'ROZHODUJI', ]; // Restaurace s menu const RESTAURANTS_WITH_MENU = [ 'SLADOVNICKA', 'TECHTOWER', 'ZASTAVKAUMICHALA', 'SENKSERIKOVA', ]; /** * Middleware pro kontrolu DEV režimu */ function requireDevMode(req: any, res: any, next: any) { if (ENVIRONMENT !== 'development' && ENVIRONMENT !== 'test') { return res.status(403).json({ error: 'Tento endpoint je dostupný pouze ve vývojovém režimu' }); } next(); } router.use(requireDevMode); /** * Vygeneruje mock data pro testování. */ router.post("/generate", async (req: Request<{}, any, any>, res, next) => { try { const dayIndex = req.body?.dayIndex ?? getDayOfWeekIndex(getToday()); const count = req.body?.count ?? Math.floor(Math.random() * 16) + 5; // 5-20 if (dayIndex < 0 || dayIndex > 4) { return res.status(400).json({ error: 'Neplatný index dne (0-4)' }); } const date = getDateForWeekIndex(dayIndex); await initIfNeeded(date); const dateKey = formatDate(date); const data = await storage.getData(dateKey); // Získání menu restaurací pro vybraný den const menus: { [key: string]: any } = {}; for (const restaurant of RESTAURANTS_WITH_MENU) { const menu = await getRestaurantMenu(restaurant as any, date); if (menu?.food?.length) { menus[restaurant] = menu.food; } } // Vygenerování náhodných uživatelů const usedNames = new Set(); for (let i = 0; i < count && usedNames.size < MOCK_NAMES.length; i++) { // Vybereme náhodné jméno, které ještě nebylo použito let name: string; do { name = MOCK_NAMES[Math.floor(Math.random() * MOCK_NAMES.length)]; } while (usedNames.has(name)); usedNames.add(name); // Vybereme náhodnou volbu stravování const choice = LUNCH_CHOICES[Math.floor(Math.random() * LUNCH_CHOICES.length)]; // Inicializace struktury pro volbu data.choices[choice] ??= {}; const userChoice: any = { trusted: false, selectedFoods: [], }; // Pokud má restaurace menu, vybereme náhodné jídlo if (RESTAURANTS_WITH_MENU.includes(choice) && menus[choice]?.length) { const foods = menus[choice]; // Vybereme náhodné jídlo (ne polévku) const mainFoods = foods.filter((f: any) => !f.isSoup); if (mainFoods.length > 0) { const randomFoodIndex = foods.indexOf(mainFoods[Math.floor(Math.random() * mainFoods.length)]); userChoice.selectedFoods = [randomFoodIndex]; } } data.choices[choice][name] = userChoice; } await storage.setData(dateKey, data); // Odeslat aktualizovaná data přes WebSocket const clientData = await getData(date); getWebsocket().emit("message", clientData); res.status(200).json({ success: true, count: usedNames.size, dayIndex }); } catch (e: any) { next(e); } }); /** * Smaže všechny volby pro daný den. */ router.post("/clear", async (req: Request<{}, any, any>, res, next) => { try { const dayIndex = req.body?.dayIndex ?? getDayOfWeekIndex(getToday()); if (dayIndex < 0 || dayIndex > 4) { return res.status(400).json({ error: 'Neplatný index dne (0-4)' }); } const date = getDateForWeekIndex(dayIndex); await initIfNeeded(date); const dateKey = formatDate(date); const data = await storage.getData(dateKey); // Vymažeme všechny volby data.choices = {}; await storage.setData(dateKey, data); // Odeslat aktualizovaná data přes WebSocket const clientData = await getData(date); getWebsocket().emit("message", clientData); res.status(200).json({ success: true, dayIndex }); } catch (e: any) { next(e); } }); /** Vrátí obsah push reminder registry (pro ladění). */ router.get("/pushRegistry", async (_req, res, next) => { try { const registry = await storage.getData('push_reminder_registry') ?? {}; const sanitized = Object.fromEntries( Object.entries(registry).map(([login, entry]: [string, any]) => [ login, { time: entry.time, endpoint: entry.subscription?.endpoint?.slice(0, 60) + '…' } ]) ); res.status(200).json(sanitized); } catch (e: any) { next(e) } }); /** Okamžitě odešle test push notifikaci přihlášenému uživateli (pro ladění). */ router.post("/testPush", async (req, res, next) => { const login = getLogin(parseToken(req)); try { const registry = await storage.getData('push_reminder_registry') ?? {}; const entry = registry[login]; if (!entry) { return res.status(404).json({ error: `Uživatel ${login} nemá uloženou push subscription. Nastav připomínku v nastavení.` }); } const publicKey = process.env.VAPID_PUBLIC_KEY; const privateKey = process.env.VAPID_PRIVATE_KEY; const subject = process.env.VAPID_SUBJECT; if (!publicKey || !privateKey || !subject) { return res.status(503).json({ error: 'VAPID klíče nejsou nastaveny' }); } webpush.setVapidDetails(subject, publicKey, privateKey); await webpush.sendNotification( entry.subscription, JSON.stringify({ title: 'Luncher test', body: 'Push notifikace fungují!' }) ); res.status(200).json({ ok: true }); } catch (e: any) { next(e) } }); export default router;