feat: večeře (extra meal slot)
CI / Generate TypeScript types (pull_request) Successful in 11s
CI / Generate TypeScript types (push) Successful in 36s
CI / Server unit tests (pull_request) Successful in 25s
CI / Build client (pull_request) Successful in 37s
CI / Server unit tests (push) Successful in 22s
CI / Build server (push) Successful in 1m0s
CI / Build client (push) Successful in 37s
CI / Build server (pull_request) Successful in 3m14s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Has been skipped
CI / Notify (push) Successful in 2s
CI / Playwright E2E tests (pull_request) Successful in 10m34s
CI / Build and push Docker image (pull_request) Has been skipped
CI / Notify (pull_request) Has been skipped

- 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
- server: slot?: string → slot?: MealSlot, enum konstanty místo literálů
- Jest testy izolace extra/obed storage namespace
- Aktualizace changelogů (saláty, SINGLE_PAYMENT, večeře)
This commit is contained in:
2026-05-06 20:37:39 +02:00
parent 5f903797f1
commit 774be3df6d
22 changed files with 441 additions and 107 deletions
+9 -21
View File
@@ -14,13 +14,10 @@ interface RegistryEntry {
type Registry = Record<string, RegistryEntry>;
/** Mapa login → datum (YYYY-MM-DD), kdy byl uživatel naposledy upozorněn. */
const remindedToday = new Map<string, string>();
/** Mapa login → timestamp (ms) posledního odeslání připomínky. */
const lastReminded = new Map<string, number>();
function getTodayDateString(): string {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
}
const REMINDER_COOLDOWN_MS = 60 * 60 * 1000; // 60 minut mezi připomínkami
function getCurrentTimeHHMM(): string {
const now = new Date();
@@ -59,7 +56,7 @@ export async function unsubscribePush(login: string): Promise<void> {
const registry = await getRegistry();
delete registry[login];
await saveRegistry(registry);
remindedToday.delete(login);
lastReminded.delete(login);
console.log(`Push reminder: uživatel ${login} odhlášen z připomínek`);
}
@@ -68,16 +65,6 @@ export function getVapidPublicKey(): string | undefined {
return process.env.VAPID_PUBLIC_KEY;
}
/** Najde login uživatele podle push subscription endpointu. */
export async function findLoginByEndpoint(endpoint: string): Promise<string | undefined> {
const registry = await getRegistry();
for (const [login, entry] of Object.entries(registry)) {
if (entry.subscription.endpoint === endpoint) {
return login;
}
}
return undefined;
}
/** Zkontroluje a odešle připomínky uživatelům, kteří si nezvolili oběd. */
async function checkAndSendReminders(): Promise<void> {
@@ -93,7 +80,6 @@ async function checkAndSendReminders(): Promise<void> {
}
const currentTime = getCurrentTimeHHMM();
const todayStr = getTodayDateString();
// Získáme data pro dnešek jednou pro všechny uživatele
let clientData;
@@ -110,8 +96,9 @@ async function checkAndSendReminders(): Promise<void> {
continue;
}
// Už jsme dnes připomenuli
if (remindedToday.get(login) === todayStr) {
// Cooldown — nepřipomínat častěji než jednou za hodinu
const last = lastReminded.get(login) ?? 0;
if (Date.now() - last < REMINDER_COOLDOWN_MS) {
continue;
}
@@ -127,9 +114,10 @@ async function checkAndSendReminders(): Promise<void> {
JSON.stringify({
title: 'Luncher',
body: 'Ještě nemáte zvolený oběd!',
login,
})
);
remindedToday.set(login, todayStr);
lastReminded.set(login, Date.now());
console.log(`Push reminder: odeslána připomínka uživateli ${login}`);
} catch (error: any) {
if (error.statusCode === 410 || error.statusCode === 404) {