feat: večeře (extra meal slot) — dokončení, sync s masterem
CI / Generate TypeScript types (push) Successful in 34s
CI / Build server (push) Successful in 33s
CI / Server unit tests (push) Successful in 1m11s
CI / Build client (push) Successful in 33s
CI / Playwright E2E tests (push) Successful in 1m20s
CI / Build and push Docker image (push) Has been skipped
CI / Notify (push) Successful in 2s

- Nová stránka /vecere pro evidenci extra jídla (večeře/pozdní oběd)
- MealSlot enum (obed/extra), oddělený storage namespace YYYY-MM-DD_extra
- slot parametr na všech food endpointech a GET /api/data
- Push reminder: přechod na 60min cooldown, login v payloadu místo endpointu
- Smazány chybně přidané root package.json + package-lock.json (gitnexus)
- server: slot?: string → slot?: MealSlot, literály nahrazeny enum konstantami
- Přidány Jest testy izolace extra/obed storage namespace
This commit is contained in:
2026-05-06 20:14:47 +02:00
96 changed files with 3582 additions and 4289 deletions
+14 -7
View File
@@ -4,7 +4,7 @@ import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices,
import { getDayOfWeekIndex, parseToken, getFirstWorkDayOfWeek } from "../utils";
import { getWebsocket } from "../websocket";
import { callNotifikace } from "../notifikace";
import { AddChoiceData, ChangeDepartureTimeData, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen";
import { AddChoiceData, ChangeDepartureTimeData, MealSlot, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen";
// RateLimit na refresh endpoint
@@ -69,9 +69,9 @@ const parseValidateFutureDayIndex = (req: Request<{}, any, AddChoiceData["body"]
return dayIndex;
}
const parseSlot = (body: Record<string, any>): string | undefined => {
const parseSlot = (body: Record<string, any>): MealSlot | undefined => {
const slot = body?.slot;
if (slot != null && slot !== 'obed' && slot !== 'extra') {
if (slot != null && slot !== MealSlot.OBED && slot !== MealSlot.EXTRA) {
throw Error(`Neplatný slot: ${slot}`);
}
return slot ?? undefined;
@@ -204,13 +204,20 @@ router.post("/updateBuyer", async (req, res, next) => {
} catch (e: any) { next(e) }
});
// /api/food/refresh?type=week&heslo=docasnyheslo
// /api/food/refresh?type=week (přihlášený uživatel, nebo ?heslo=... pro bypass rate limitu)
export const refreshMetoda = async (req: Request, res: Response) => {
const { type, heslo } = req.query as { type?: string; heslo?: string };
if (heslo !== "docasnyheslo" && heslo !== "tohleheslopavelnesmizjistit123") {
return res.status(403).json({ error: "Neplatné heslo" });
const bypassPassword = process.env.REFRESH_BYPASS_PASSWORD;
const isBypass = !!bypassPassword && heslo === bypassPassword;
if (!isBypass) {
try {
getLogin(parseToken(req));
} catch {
return res.status(403).json({ error: "Přihlaste se prosím" });
}
}
if (!checkRateLimit("refresh") && heslo !== "tohleheslopavelnesmizjistit123") {
if (!checkRateLimit("refresh") && !isBypass) {
return res.status(429).json({ error: "Refresh už se zavolal, chvíli počkej :))" });
}
if (type !== "week" && type !== "day") {