Migrace na OpenAPI - TypeScript typy

This commit is contained in:
2025-03-05 21:05:21 +01:00
parent d144c55bf7
commit d69e09afee
40 changed files with 1295 additions and 550 deletions

View File

@@ -1,8 +1,8 @@
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getIsWeekend, getWeekNumber } from "./utils";
import { ClientData, Restaurants, DayMenu, DepartureTime, DayData, WeekMenu, LocationKey } from "../../types";
import getStorage from "./storage";
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku, getMenuZastavkaUmichala, getMenuSenkSerikova } from "./restaurants";
import { getTodayMock } from "./mock";
import { ClientData, DepartureTime, LunchChoice, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types";
const storage = getStorage();
const MENU_PREFIX = 'menu';
@@ -31,45 +31,31 @@ export const getDateForWeekIndex = (index: number) => {
function getEmptyData(date?: Date): ClientData {
const usedDate = date || getToday();
return {
todayDayIndex: getDayOfWeekIndex(getToday()),
date: getHumanDate(usedDate),
isWeekend: getIsWeekend(usedDate),
weekIndex: getDayOfWeekIndex(usedDate),
dayIndex: getDayOfWeekIndex(usedDate),
choices: {},
};
}
/**
* Přidá k datům "dopočítaná" data, která nejsou přímo uložena v databázi.
*
* @param data data z databáze
* @returns obohacená data
*/
export async function addVolatileData(data: ClientData): Promise<ClientData> {
data.todayWeekIndex = getDayOfWeekIndex(getToday());
return data;
}
/**
* 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 targetDate = date ?? getToday();
const dateString = formatDate(targetDate);
const data: DayData = await storage.getData(dateString) || getEmptyData(date);
let clientData: ClientData = { ...data };
const clientData = await getClientData(date);
clientData.menus = {
[Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date),
// [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date),
[Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date),
[Restaurants.ZASTAVKAUMICHALA]: await getRestaurantMenu(Restaurants.ZASTAVKAUMICHALA, date),
[Restaurants.SENKSERIKOVA]: await getRestaurantMenu(Restaurants.SENKSERIKOVA, date),
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),
}
clientData = await addVolatileData(clientData);
return clientData;
}
/**
* Vrátí klíč, pod kterým je uloženo menu pro předané datum.
* Vrátí klíč, pod kterým je uloženo menu pro týden příslušící předanému datu.
*
* @param date datum
* @returns databázový klíč
@@ -80,13 +66,13 @@ function getMenuKey(date: Date) {
}
/**
* Vrátí menu restaurací pro předané datum, pokud již existují.
* Vrátí menu všech podniků pro celý týden do kterého spadá předané datum, pokud již existují.
*
* @param date datum
* @returns menu restaurací pro předané datum
* @returns menu restaurací pro týden příslušící předanému datu
*/
async function getMenu(date: Date): Promise<WeekMenu | undefined> {
return await storage.getData(getMenuKey(date));
return await storage.getData<WeekMenu | undefined>(getMenuKey(date));
}
// TODO přesun do restaurants.ts
@@ -98,7 +84,7 @@ async function getMenu(date: Date): Promise<WeekMenu | undefined> {
* @param date datum, ke kterému získat menu
* @param mock příznak, zda chceme pouze mock data
*/
export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<DayMenu> {
export async function getRestaurantMenu(restaurant: keyof typeof Restaurant, date?: Date): Promise<RestaurantDayMenu> {
const usedDate = date ?? getToday();
const dayOfWeekIndex = getDayOfWeekIndex(usedDate);
const now = new Date().getTime();
@@ -110,41 +96,41 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
};
}
let menus = await getMenu(usedDate);
if (menus == null) {
menus = [];
let weekMenu = await getMenu(usedDate);
if (weekMenu == null) {
weekMenu = [{}, {}, {}, {}, {}];
}
for (let i = 0; i < 5; i++) {
if (menus[i] == null) {
menus[i] = {};
if (weekMenu[i] == null) {
weekMenu[i] = {};
}
if (menus[i][restaurant] == null) {
menus[i][restaurant] = {
if (weekMenu[i][restaurant] == null) {
weekMenu[i][restaurant] = {
lastUpdate: now,
closed: false,
food: [],
};
}
}
if (!menus[dayOfWeekIndex][restaurant]?.food?.length) {
if (!weekMenu[dayOfWeekIndex][restaurant]?.food?.length) {
const firstDay = getFirstWorkDayOfWeek(usedDate);
const mock = process.env.MOCK_DATA === 'true';
switch (restaurant) {
case Restaurants.SLADOVNICKA:
case 'SLADOVNICKA':
try {
const sladovnickaFood = await getMenuSladovnicka(firstDay, mock);
for (let i = 0; i < sladovnickaFood.length; i++) {
menus[i][restaurant]!.food = sladovnickaFood[i];
weekMenu[i][restaurant]!.food = sladovnickaFood[i];
// Velice chatrný a nespolehlivý způsob detekce uzavření...
if (sladovnickaFood[i].length === 1 && sladovnickaFood[i][0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') {
menus[i][restaurant]!.closed = true;
weekMenu[i][restaurant]!.closed = true;
}
}
} catch (e: any) {
console.error("Selhalo načtení jídel pro podnik Sladovnická", e);
}
break;
// case Restaurants.UMOTLIKU:
// case 'UMOTLIKU':
// try {
// const uMotlikuFood = await getMenuUMotliku(firstDay, mock);
// for (let i = 0; i < uMotlikuFood.length; i++) {
@@ -157,39 +143,39 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
// console.error("Selhalo načtení jídel pro podnik U Motlíků", e);
// }
// break;
case Restaurants.TECHTOWER:
case 'TECHTOWER':
try {
const techTowerFood = await getMenuTechTower(firstDay, mock);
for (let i = 0; i < techTowerFood.length; i++) {
menus[i][restaurant]!.food = techTowerFood[i];
weekMenu[i][restaurant]!.food = techTowerFood[i];
if (techTowerFood[i]?.length === 1 && techTowerFood[i][0].name.toLowerCase() === 'svátek') {
menus[i][restaurant]!.closed = true;
weekMenu[i][restaurant]!.closed = true;
}
}
break;
} catch (e: any) {
console.error("Selhalo načtení jídel pro podnik TechTower", e);
}
case Restaurants.ZASTAVKAUMICHALA:
case 'ZASTAVKAUMICHALA':
try {
const zastavkaUmichalaFood = await getMenuZastavkaUmichala(firstDay, mock);
for (let i = 0; i < zastavkaUmichalaFood.length; i++) {
menus[i][restaurant]!.food = zastavkaUmichalaFood[i];
weekMenu[i][restaurant]!.food = zastavkaUmichalaFood[i];
if (zastavkaUmichalaFood[i]?.length === 1 && zastavkaUmichalaFood[i][0].name === 'Pro tento den není uveřejněna nabídka jídel.') {
menus[i][restaurant]!.closed = true;
weekMenu[i][restaurant]!.closed = true;
}
}
break;
} catch (e: any) {
console.error("Selhalo načtení jídel pro podnik Zastávka u Michala", e);
}
case Restaurants.SENKSERIKOVA:
case 'SENKSERIKOVA':
try {
const senkSerikovaFood = await getMenuSenkSerikova(firstDay, mock);
for (let i = 0; i < senkSerikovaFood.length; i++) {
menus[i][restaurant]!.food = senkSerikovaFood[i];
weekMenu[i][restaurant]!.food = senkSerikovaFood[i];
if (senkSerikovaFood[i]?.length === 1 && senkSerikovaFood[i][0].name === 'Pro tento den nebylo zadáno menu.') {
menus[i][restaurant]!.closed = true;
weekMenu[i][restaurant]!.closed = true;
}
}
break;
@@ -197,9 +183,9 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
console.error("Selhalo načtení jídel pro podnik Pivovarský šenk Šeříková", e);
}
}
await storage.setData(getMenuKey(usedDate), menus);
await storage.setData(getMenuKey(usedDate), weekMenu);
}
return menus[dayOfWeekIndex][restaurant]!;
return weekMenu[dayOfWeekIndex][restaurant]!;
}
/**
@@ -224,9 +210,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: LocationKey, date?: Date) {
export async function removeChoices(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, date?: Date) {
const selectedDay = formatDate(date ?? getToday());
let data: DayData = await storage.getData(selectedDay);
let data = await getClientData(date);
validateTrusted(data, login, trusted);
if (locationKey in data.choices) {
if (data.choices[locationKey] && login in data.choices[locationKey]) {
@@ -251,15 +237,15 @@ 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: LocationKey, foodIndex: number, date?: Date) {
export async function removeChoice(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, foodIndex: number, date?: Date) {
const selectedDay = formatDate(date ?? getToday());
let data: DayData = await storage.getData(selectedDay);
let data = await getClientData(date);
validateTrusted(data, login, trusted);
if (locationKey in data.choices) {
if (data.choices[locationKey] && login in data.choices[locationKey]) {
const index = data.choices[locationKey][login].options.indexOf(foodIndex);
if (index > -1) {
data.choices[locationKey][login].options.splice(index, 1)
const index = data.choices[locationKey][login].selectedFoods?.indexOf(foodIndex);
if (index && index > -1) {
data.choices[locationKey][login].selectedFoods?.splice(index, 1);
await storage.setData(selectedDay, data);
}
}
@@ -274,10 +260,11 @@ 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: string, ignoredLocationKey?: LocationKey) {
let data: DayData = await storage.getData(date);
async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocationKey?: keyof typeof LunchChoice) {
const usedDate = date ?? getToday();
let data = await getClientData(usedDate);
for (const key of Object.keys(data.choices)) {
const locationKey = key as LocationKey;
const locationKey = key as keyof typeof LunchChoice;
if (ignoredLocationKey != null && ignoredLocationKey == locationKey) {
continue;
}
@@ -286,7 +273,7 @@ async function removeChoiceIfPresent(login: string, date: string, ignoredLocatio
if (Object.keys(data.choices[locationKey]).length === 0) {
delete data.choices[locationKey];
}
await storage.setData(date, data);
await storage.setData(formatDate(usedDate), data);
}
}
return data;
@@ -325,19 +312,18 @@ 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: LocationKey, foodIndex?: number, date?: Date) {
export async function addChoice(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, foodIndex?: number, date?: Date) {
const usedDate = date ?? getToday();
await initIfNeeded(usedDate);
const selectedDate = formatDate(usedDate);
let data: DayData = await storage.getData(selectedDate);
let data = await getClientData(usedDate);
validateTrusted(data, login, trusted);
await validateFoodIndex(locationKey, foodIndex, date);
// Pokud měníme pouze lokaci, mažeme případné předchozí
if (foodIndex == null) {
data = await removeChoiceIfPresent(login, selectedDate);
data = await removeChoiceIfPresent(login, usedDate);
} else {
// Mažeme případné ostatní volby (měla by být maximálně jedna)
removeChoiceIfPresent(login, selectedDate, locationKey);
removeChoiceIfPresent(login, usedDate, locationKey);
}
// TODO vytáhnout inicializaci "prázdné struktury" do vlastní funkce
if (!(data.choices[locationKey])) {
@@ -349,12 +335,13 @@ export async function addChoice(login: string, trusted: boolean, locationKey: Lo
}
data.choices[locationKey][login] = {
trusted,
options: []
selectedFoods: []
};
}
if (foodIndex != null && !data.choices[locationKey][login].options.includes(foodIndex)) {
data.choices[locationKey][login].options.push(foodIndex);
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);
return data;
}
@@ -366,7 +353,7 @@ export async function addChoice(login: string, trusted: boolean, locationKey: Lo
* @param foodIndex index jídla pro danou lokalitu
* @param date datum, pro které je validace prováděna
*/
async function validateFoodIndex(locationKey: LocationKey, foodIndex?: number, date?: Date) {
async function validateFoodIndex(locationKey: keyof typeof LunchChoice, foodIndex?: number, date?: Date) {
if (foodIndex != null) {
if (typeof foodIndex !== 'number') {
throw Error(`Neplatný index ${foodIndex} typu ${typeof foodIndex}`);
@@ -374,13 +361,12 @@ async function validateFoodIndex(locationKey: LocationKey, foodIndex?: number, d
if (foodIndex < 0) {
throw Error(`Neplatný index ${foodIndex}`);
}
if (!(locationKey in Restaurants)) {
if (!Object.keys(Restaurant).includes(locationKey)) {
throw Error(`Neplatný index ${foodIndex} pro lokalitu ${locationKey} nepodporující indexy`);
}
const usedDate = date ?? getToday();
const restaurantKey = Restaurants[locationKey as keyof typeof Restaurants]
const menu = await getRestaurantMenu(restaurantKey, usedDate);
if (foodIndex > (menu.food.length - 1)) {
const menu = await getRestaurantMenu(locationKey as keyof typeof Restaurant, usedDate);
if (menu.food?.length && foodIndex > (menu.food.length - 1)) {
throw new Error(`Neplatný index ${foodIndex} pro lokalitu ${locationKey}`);
}
}
@@ -397,16 +383,16 @@ async function validateFoodIndex(locationKey: LocationKey, foodIndex?: number, d
export async function updateNote(login: string, trusted: boolean, note?: string, date?: Date) {
const usedDate = date ?? getToday();
await initIfNeeded(usedDate);
const selectedDate = formatDate(usedDate);
let data: DayData = await storage.getData(selectedDate);
let data = await getClientData(usedDate);
validateTrusted(data, login, trusted);
const userEntry = data.choices != null && Object.entries(data.choices).find(entry => entry[1][login] != null);
if (userEntry) {
if (!note || !note.length) {
if (!note?.length) {
delete userEntry[1][login].note;
} else {
userEntry[1][login].note = note;
}
const selectedDate = formatDate(usedDate);
await storage.setData(selectedDate, data);
}
return data;
@@ -420,8 +406,8 @@ export async function updateNote(login: string, trusted: boolean, note?: string,
* @param date datum, ke kterému se čas vztahuje
*/
export async function updateDepartureTime(login: string, time?: string, date?: Date) {
const selectedDate = formatDate(date ?? getToday());
let clientData: DayData = await storage.getData(selectedDate);
const usedDate = date ?? getToday();
let clientData = await getClientData(usedDate);
const found = Object.values(clientData.choices).find(location => login in location);
// TODO validace, že se jedná o restauraci
if (found) {
@@ -433,7 +419,23 @@ export async function updateDepartureTime(login: string, time?: string, date?: D
}
found[login].departureTime = time;
}
await storage.setData(selectedDate, clientData);
await storage.setData(formatDate(usedDate), clientData);
}
return clientData;
}
/**
* Vrátí data pro klienta pro předaný nebo aktuální den.
*
* @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> {
const targetDate = date ?? getToday();
const dateString = formatDate(targetDate);
const clientData = await storage.getData<ClientData>(dateString) || getEmptyData(date);
return {
...clientData,
todayDayIndex: getDayOfWeekIndex(getToday()),
}
}