feat: večeře (extra meal slot)
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 36s
CI / Generate TypeScript types (pull_request) Successful in 1m18s
CI / Build client (push) Successful in 36s
CI / Build server (pull_request) Successful in 27s
CI / Build client (pull_request) Successful in 41s
CI / Server unit tests (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 10m26s
CI / Build and push Docker image (pull_request) Has been skipped
CI / Notify (pull_request) Has been skipped
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 36s
CI / Generate TypeScript types (pull_request) Successful in 1m18s
CI / Build client (push) Successful in 36s
CI / Build server (pull_request) Successful in 27s
CI / Build client (pull_request) Successful in 41s
CI / Server unit tests (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 10m26s
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:
+66
-58
@@ -3,11 +3,16 @@ import getStorage from "./storage";
|
||||
import { getMenuSladovnicka, getMenuTechTower, getMenuZastavkaUmichala, getMenuSenkSerikova, StaleWeekError } from "./restaurants";
|
||||
import { getTodayMock } from "./mock";
|
||||
import { removeAllUserPizzas } from "./pizza";
|
||||
import { ClientData, DepartureTime, LunchChoice, PizzaDayState, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types/gen/types.gen";
|
||||
import { ClientData, DepartureTime, LunchChoice, MealSlot, PizzaDayState, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types/gen/types.gen";
|
||||
|
||||
const storage = getStorage();
|
||||
const MENU_PREFIX = 'menu';
|
||||
|
||||
function getDataKey(date: Date, slot?: MealSlot): string {
|
||||
const base = formatDate(date);
|
||||
return slot === MealSlot.EXTRA ? `${base}_extra` : base;
|
||||
}
|
||||
|
||||
/** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */
|
||||
export function getToday(): Date {
|
||||
if (process.env.MOCK_DATA === 'true') {
|
||||
@@ -43,15 +48,18 @@ export function getEmptyData(date?: Date): ClientData {
|
||||
/**
|
||||
* Vrátí veškerá klientská data pro předaný den, nebo aktuální den, pokud není předán.
|
||||
*/
|
||||
export async function getData(date?: Date): Promise<ClientData> {
|
||||
const clientData = await getClientData(date);
|
||||
clientData.menus = {
|
||||
SLADOVNICKA: await getRestaurantMenu('SLADOVNICKA', date),
|
||||
// UMOTLIKU: await getRestaurantMenu('UMOTLIKU', date),
|
||||
TECHTOWER: await getRestaurantMenu('TECHTOWER', date),
|
||||
ZASTAVKAUMICHALA: await getRestaurantMenu('ZASTAVKAUMICHALA', date),
|
||||
SENKSERIKOVA: await getRestaurantMenu('SENKSERIKOVA', date),
|
||||
export async function getData(date?: Date, slot?: MealSlot): Promise<ClientData> {
|
||||
const clientData = await getClientData(date, slot);
|
||||
if (slot !== MealSlot.EXTRA) {
|
||||
clientData.menus = {
|
||||
SLADOVNICKA: await getRestaurantMenu('SLADOVNICKA', date),
|
||||
// UMOTLIKU: await getRestaurantMenu('UMOTLIKU', date),
|
||||
TECHTOWER: await getRestaurantMenu('TECHTOWER', date),
|
||||
ZASTAVKAUMICHALA: await getRestaurantMenu('ZASTAVKAUMICHALA', date),
|
||||
SENKSERIKOVA: await getRestaurantMenu('SENKSERIKOVA', date),
|
||||
}
|
||||
}
|
||||
if (slot === MealSlot.EXTRA) clientData.slot = MealSlot.EXTRA;
|
||||
return clientData;
|
||||
}
|
||||
|
||||
@@ -290,8 +298,8 @@ function generateMenuWarnings(menu: RestaurantDayMenu): string[] {
|
||||
*
|
||||
* @param date datum
|
||||
*/
|
||||
export async function initIfNeeded(date?: Date) {
|
||||
const usedDate = formatDate(date ?? getToday());
|
||||
export async function initIfNeeded(date?: Date, slot?: MealSlot) {
|
||||
const usedDate = getDataKey(date ?? getToday(), slot);
|
||||
const hasData = await storage.hasData(usedDate);
|
||||
if (!hasData) {
|
||||
await storage.setData(usedDate, getEmptyData(date || getToday()));
|
||||
@@ -307,9 +315,9 @@ export async function initIfNeeded(date?: Date) {
|
||||
* @param date datum, ke kterému se volba vztahuje
|
||||
* @returns
|
||||
*/
|
||||
export async function removeChoices(login: string, trusted: boolean, locationKey: LunchChoice, date?: Date) {
|
||||
const selectedDay = formatDate(date ?? getToday());
|
||||
let data = await getClientData(date);
|
||||
export async function removeChoices(login: string, trusted: boolean, locationKey: LunchChoice, date?: Date, slot?: MealSlot) {
|
||||
const selectedDay = getDataKey(date ?? getToday(), slot);
|
||||
let data = await getClientData(date, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (locationKey in data.choices) {
|
||||
if (data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
@@ -334,9 +342,9 @@ export async function removeChoices(login: string, trusted: boolean, locationKey
|
||||
* @param date datum, ke kterému se volba vztahuje
|
||||
* @returns
|
||||
*/
|
||||
export async function removeChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex: number, date?: Date) {
|
||||
const selectedDay = formatDate(date ?? getToday());
|
||||
let data = await getClientData(date);
|
||||
export async function removeChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex: number, date?: Date, slot?: MealSlot) {
|
||||
const selectedDay = getDataKey(date ?? getToday(), slot);
|
||||
let data = await getClientData(date, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (locationKey in data.choices) {
|
||||
if (data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
@@ -357,9 +365,9 @@ export async function removeChoice(login: string, trusted: boolean, locationKey:
|
||||
* @param date datum, ke kterému se volby vztahují
|
||||
* @param ignoredLocationKey volba, která nebude odstraněna, pokud existuje
|
||||
*/
|
||||
async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocationKey?: LunchChoice) {
|
||||
async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocationKey?: LunchChoice, slot?: MealSlot) {
|
||||
const usedDate = date ?? getToday();
|
||||
let data = await getClientData(usedDate);
|
||||
let data = await getClientData(usedDate, slot);
|
||||
for (const key of Object.keys(data.choices)) {
|
||||
const locationKey = key as LunchChoice;
|
||||
if (ignoredLocationKey != null && ignoredLocationKey == locationKey) {
|
||||
@@ -370,7 +378,7 @@ async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocation
|
||||
if (Object.keys(data.choices[locationKey]).length === 0) {
|
||||
delete data.choices[locationKey];
|
||||
}
|
||||
await storage.setData(formatDate(usedDate), data);
|
||||
await storage.setData(getDataKey(usedDate, slot), data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
@@ -409,41 +417,43 @@ function validateTrusted(data: ClientData, login: string, trusted: boolean) {
|
||||
* @param date datum, ke kterému se volba vztahuje
|
||||
* @returns aktuální data
|
||||
*/
|
||||
export async function addChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex?: number, date?: Date) {
|
||||
export async function addChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex?: number, date?: Date, slot?: MealSlot) {
|
||||
const usedDate = date ?? getToday();
|
||||
await initIfNeeded(usedDate);
|
||||
let data = await getClientData(usedDate);
|
||||
await initIfNeeded(usedDate, slot);
|
||||
let data = await getClientData(usedDate, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
await validateFoodIndex(locationKey, foodIndex, date);
|
||||
|
||||
// Pokud uživatel měl vybranou PIZZA a mění na něco jiného
|
||||
const hadPizzaChoice = data.choices.PIZZA && login in data.choices.PIZZA;
|
||||
if (hadPizzaChoice && locationKey !== LunchChoice.PIZZA && foodIndex == null) {
|
||||
// Kontrola, zda existuje Pizza day a uživatel je jeho zakladatel
|
||||
if (data.pizzaDay && data.pizzaDay.creator === login) {
|
||||
// Pokud Pizza day není ve stavu CREATED, nelze změnit volbu
|
||||
if (data.pizzaDay.state !== PizzaDayState.CREATED) {
|
||||
throw new PizzaDayConflictError(
|
||||
`Nelze změnit volbu. Pizza day je ve stavu "${data.pizzaDay.state}" a musí být nejprve dokončen nebo smazán.`
|
||||
);
|
||||
if (!slot || slot === MealSlot.OBED) {
|
||||
// Pokud uživatel měl vybranou PIZZA a mění na něco jiného
|
||||
const hadPizzaChoice = data.choices.PIZZA && login in data.choices.PIZZA;
|
||||
if (hadPizzaChoice && locationKey !== LunchChoice.PIZZA && foodIndex == null) {
|
||||
// Kontrola, zda existuje Pizza day a uživatel je jeho zakladatel
|
||||
if (data.pizzaDay && data.pizzaDay.creator === login) {
|
||||
// Pokud Pizza day není ve stavu CREATED, nelze změnit volbu
|
||||
if (data.pizzaDay.state !== PizzaDayState.CREATED) {
|
||||
throw new PizzaDayConflictError(
|
||||
`Nelze změnit volbu. Pizza day je ve stavu "${data.pizzaDay.state}" a musí být nejprve dokončen nebo smazán.`
|
||||
);
|
||||
}
|
||||
// Pizza day je ve stavu CREATED - bude smazán frontendem po potvrzení uživatelem
|
||||
// (frontend volá nejprve deletePizzaDay, pak teprve addChoice)
|
||||
}
|
||||
// Pizza day je ve stavu CREATED - bude smazán frontendem po potvrzení uživatelem
|
||||
// (frontend volá nejprve deletePizzaDay, pak teprve addChoice)
|
||||
}
|
||||
|
||||
// Smažeme pizzy uživatele (pokud Pizza day nebyl založen tímto uživatelem,
|
||||
// nebo byl již smazán frontendem)
|
||||
await removeAllUserPizzas(login, usedDate);
|
||||
// Znovu načteme data, protože removeAllUserPizzas je upravila
|
||||
data = await getClientData(usedDate);
|
||||
// Smažeme pizzy uživatele (pokud Pizza day nebyl založen tímto uživatelem,
|
||||
// nebo byl již smazán frontendem)
|
||||
await removeAllUserPizzas(login, usedDate);
|
||||
// Znovu načteme data, protože removeAllUserPizzas je upravila
|
||||
data = await getClientData(usedDate, slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Pokud měníme pouze lokaci, mažeme případné předchozí
|
||||
if (foodIndex == null) {
|
||||
data = await removeChoiceIfPresent(login, usedDate);
|
||||
data = await removeChoiceIfPresent(login, usedDate, undefined, slot);
|
||||
} else {
|
||||
// Mažeme případné ostatní volby (měla by být maximálně jedna)
|
||||
data = await removeChoiceIfPresent(login, usedDate, locationKey);
|
||||
data = await removeChoiceIfPresent(login, usedDate, locationKey, slot);
|
||||
}
|
||||
// TODO vytáhnout inicializaci "prázdné struktury" do vlastní funkce
|
||||
data.choices[locationKey] ??= {};
|
||||
@@ -459,8 +469,7 @@ export async function addChoice(login: string, trusted: boolean, locationKey: Lu
|
||||
if (foodIndex != null && !data.choices[locationKey][login].selectedFoods?.includes(foodIndex)) {
|
||||
data.choices[locationKey][login].selectedFoods?.push(foodIndex);
|
||||
}
|
||||
const selectedDate = formatDate(usedDate);
|
||||
await storage.setData(selectedDate, data);
|
||||
await storage.setData(getDataKey(usedDate, slot), data);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -498,10 +507,10 @@ async function validateFoodIndex(locationKey: LunchChoice, foodIndex?: number, d
|
||||
* @param note poznámka
|
||||
* @param date datum, ke kterému se volba vztahuje
|
||||
*/
|
||||
export async function updateNote(login: string, trusted: boolean, note?: string, date?: Date) {
|
||||
export async function updateNote(login: string, trusted: boolean, note?: string, date?: Date, slot?: MealSlot) {
|
||||
const usedDate = date ?? getToday();
|
||||
await initIfNeeded(usedDate);
|
||||
let data = await getClientData(usedDate);
|
||||
await initIfNeeded(usedDate, slot);
|
||||
let data = await getClientData(usedDate, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
const userEntry = data.choices != null && Object.entries(data.choices).find(entry => entry[1][login] != null);
|
||||
if (userEntry) {
|
||||
@@ -510,8 +519,7 @@ export async function updateNote(login: string, trusted: boolean, note?: string,
|
||||
} else {
|
||||
userEntry[1][login].note = note;
|
||||
}
|
||||
const selectedDate = formatDate(usedDate);
|
||||
await storage.setData(selectedDate, data);
|
||||
await storage.setData(getDataKey(usedDate, slot), data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@@ -523,9 +531,9 @@ export async function updateNote(login: string, trusted: boolean, note?: string,
|
||||
* @param time preferovaný čas odchodu
|
||||
* @param date datum, ke kterému se čas vztahuje
|
||||
*/
|
||||
export async function updateDepartureTime(login: string, time?: string, date?: Date) {
|
||||
export async function updateDepartureTime(login: string, time?: string, date?: Date, slot?: MealSlot) {
|
||||
const usedDate = date ?? getToday();
|
||||
let clientData = await getClientData(usedDate);
|
||||
let clientData = await getClientData(usedDate, slot);
|
||||
const found = Object.values(clientData.choices).find(location => login in location);
|
||||
// TODO validace, že se jedná o restauraci
|
||||
if (found) {
|
||||
@@ -537,7 +545,7 @@ export async function updateDepartureTime(login: string, time?: string, date?: D
|
||||
}
|
||||
found[login].departureTime = time;
|
||||
}
|
||||
await storage.setData(formatDate(usedDate), clientData);
|
||||
await storage.setData(getDataKey(usedDate, slot), clientData);
|
||||
}
|
||||
return clientData;
|
||||
}
|
||||
@@ -548,15 +556,15 @@ export async function updateDepartureTime(login: string, time?: string, date?: D
|
||||
*
|
||||
* @param login přihlašovací jméno uživatele
|
||||
*/
|
||||
export async function updateBuyer(login: string) {
|
||||
export async function updateBuyer(login: string, slot?: MealSlot) {
|
||||
const usedDate = getToday();
|
||||
let clientData = await getClientData(usedDate);
|
||||
let clientData = await getClientData(usedDate, slot);
|
||||
const userEntry = clientData.choices?.['OBJEDNAVAM']?.[login];
|
||||
if (!userEntry) {
|
||||
throw new Error("Nelze nastavit objednatele pro uživatele s jinou volbou než \"Budu objednávat\"");
|
||||
}
|
||||
userEntry.isBuyer = !(userEntry.isBuyer || false);
|
||||
await storage.setData(formatDate(usedDate), clientData);
|
||||
await storage.setData(getDataKey(usedDate, slot), clientData);
|
||||
return clientData;
|
||||
}
|
||||
|
||||
@@ -566,9 +574,9 @@ export async function updateBuyer(login: string) {
|
||||
* @param date datum pro který vrátit data, pokud není vyplněno, je použit dnešní den
|
||||
* @returns data pro klienta
|
||||
*/
|
||||
export async function getClientData(date?: Date): Promise<ClientData> {
|
||||
export async function getClientData(date?: Date, slot?: MealSlot): Promise<ClientData> {
|
||||
const targetDate = date ?? getToday();
|
||||
const dateString = formatDate(targetDate);
|
||||
const dateString = getDataKey(targetDate, slot);
|
||||
const clientData = await storage.getData<ClientData>(dateString) || getEmptyData(date);
|
||||
return {
|
||||
...clientData,
|
||||
|
||||
Reference in New Issue
Block a user