feat: spin the wheel, objednavani scrape menu, historie cen
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 39s
CI / Playwright E2E tests (push) Successful in 1m52s
CI / Build and push Docker image (push) Successful in 51s
CI / Notify (push) Successful in 2s

This commit is contained in:
2026-09-23 15:41:15 +02:00
parent 682deaf20b
commit e281615cc3
35 changed files with 3979 additions and 15 deletions
+72
View File
@@ -1,5 +1,7 @@
import express from "express";
import { getStores, addStore, updateStore, removeStore } from "../stores";
import { getStoreMenu, getStoreMenuHistory, getStoreMenuStatus, refreshStoreMenu, renameStoreMenus } from "../storeMenus";
import { StoreMenuProvider } from "../../../types/gen/types.gen";
const router = express.Router();
@@ -10,6 +12,69 @@ router.get("/", async (_req, res, next) => {
} catch (e: any) { next(e); }
});
router.get("/menu", async (req, res, next) => {
const name = req.query.name;
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Nebyl předán název obchodu' });
}
try {
res.status(200).json(await getStoreMenu(name));
} catch (e: any) {
if (e.message === 'Obchod nebyl nalezen') {
return res.status(404).json({ error: e.message });
}
next(e);
}
});
router.get("/menu/status", async (_req, res, next) => {
try {
res.status(200).json(await getStoreMenuStatus());
} catch (e: any) { next(e); }
});
router.get("/menu/history", async (req, res, next) => {
const name = req.query.name;
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Nebyl předán název obchodu' });
}
try {
res.status(200).json(await getStoreMenuHistory(name));
} catch (e: any) {
if (e.message === 'Obchod nebyl nalezen') {
return res.status(404).json({ error: e.message });
}
next(e);
}
});
router.post("/menu/refresh", async (req, res, next) => {
const { name, provider, heslo } = req.body ?? {};
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Nebyl předán název obchodu' });
}
if (!Object.values<string>(StoreMenuProvider).includes(provider)) {
return res.status(400).json({ error: 'Neplatná dovozová služba' });
}
if (!heslo || typeof heslo !== 'string') {
return res.status(400).json({ error: 'Nebylo předáno heslo' });
}
try {
res.status(200).json(await refreshStoreMenu(name, provider, heslo));
} catch (e: any) {
if (e.message === 'UNAUTHORIZED') {
return res.status(403).json({ error: 'Nesprávné heslo' });
}
if (e.message === 'Obchod nebyl nalezen') {
return res.status(404).json({ error: e.message });
}
if (e.message?.startsWith('Obchod nemá URL')) {
return res.status(400).json({ error: e.message });
}
next(e);
}
});
router.post("/add", async (req, res, next) => {
const { name, heslo, urls } = req.body ?? {};
if (!name || typeof name !== 'string') {
@@ -48,6 +113,12 @@ router.post("/update", async (req, res, next) => {
}
try {
const stores = await updateStore(name, heslo, newName ?? undefined, urls ?? undefined);
// Uložené nabídky jsou vázané na název obchodu
const renamed = newName && stores.find(s => s.name.toLowerCase() === newName.trim().toLowerCase());
const original = stores.find(s => s.name.toLowerCase() === name.trim().toLowerCase());
if (renamed && !original) {
await renameStoreMenus(name.trim(), renamed.name);
}
res.status(200).json(stores);
} catch (e: any) {
if (e.message === 'UNAUTHORIZED') {
@@ -67,6 +138,7 @@ router.post("/delete", async (req, res, next) => {
}
try {
const stores = await removeStore(name, heslo);
await renameStoreMenus(name.trim());
res.status(200).json(stores);
} catch (e: any) {
if (e.message === 'UNAUTHORIZED') {