From f36afe129af7a413e17ce96cc86f5f8877f62da2 Mon Sep 17 00:00:00 2001 From: batmanisko Date: Wed, 4 Feb 2026 17:08:23 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20podpora=20per-user=20notifikac=C3=AD=20?= =?UTF-8?q?s=20Discord,=20ntfy=20a=20Teams=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uživatelé mohou v nastavení konfigurovat vlastní webhook URL/topic pro Discord, MS Teams a ntfy, a zvolit události k odběru. Notifikace jsou odesílány pouze uživatelům se stejnou zvolenou lokalitou. --- .../src/components/modals/SettingsModal.tsx | 119 +- server/src/index.ts | 2 + server/src/notifikace.ts | 126 +- server/src/routes/notificationRoutes.ts | 32 + types/gen/sdk.gen.ts | 527 +++++++++ types/gen/types.gen.ts | 1030 +++++++++++++++++ 6 files changed, 1824 insertions(+), 12 deletions(-) create mode 100644 server/src/routes/notificationRoutes.ts create mode 100644 types/gen/sdk.gen.ts create mode 100644 types/gen/types.gen.ts diff --git a/client/src/components/modals/SettingsModal.tsx b/client/src/components/modals/SettingsModal.tsx index f0bdc4b..a7efc92 100644 --- a/client/src/components/modals/SettingsModal.tsx +++ b/client/src/components/modals/SettingsModal.tsx @@ -1,6 +1,8 @@ -import { useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { Modal, Button, Form } from "react-bootstrap" import { useSettings, ThemePreference } from "../../context/settings"; +import { NotificationSettings, UdalostEnum, getNotificationSettings, updateNotificationSettings } from "../../../../types"; +import { useAuth } from "../../context/auth"; type Props = { isOpen: boolean, @@ -10,12 +12,56 @@ type Props = { /** Modální dialog pro uživatelská nastavení. */ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly) { + const auth = useAuth(); const settings = useSettings(); const bankAccountRef = useRef(null); const nameRef = useRef(null); const hideSoupsRef = useRef(null); const themeRef = useRef(null); + const ntfyTopicRef = useRef(null); + const discordWebhookRef = useRef(null); + const teamsWebhookRef = useRef(null); + const [notifSettings, setNotifSettings] = useState({}); + const [enabledEvents, setEnabledEvents] = useState([]); + + useEffect(() => { + if (isOpen && auth?.login) { + getNotificationSettings().then(response => { + if (response.data) { + setNotifSettings(response.data); + setEnabledEvents(response.data.enabledEvents ?? []); + } + }).catch(() => {}); + } + }, [isOpen, auth?.login]); + + const toggleEvent = (event: UdalostEnum) => { + setEnabledEvents(prev => + prev.includes(event) ? prev.filter(e => e !== event) : [...prev, event] + ); + }; + + const handleSave = async () => { + // Uložení notifikačních nastavení na server + await updateNotificationSettings({ + body: { + ntfyTopic: ntfyTopicRef.current?.value || undefined, + discordWebhookUrl: discordWebhookRef.current?.value || undefined, + teamsWebhookUrl: teamsWebhookRef.current?.value || undefined, + enabledEvents, + } + }).catch(() => {}); + + // Uložení ostatních nastavení (localStorage) + onSave( + bankAccountRef.current?.value, + nameRef.current?.value, + hideSoupsRef.current?.checked, + themeRef.current?.value as ThemePreference, + ); + }; + return ( @@ -51,6 +97,75 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly +

Notifikace

+

+ Nastavením notifikací budete dostávat upozornění o událostech (např. "Jdeme na oběd") přímo do vámi zvoleného komunikačního kanálu. +

+ + + ntfy téma (topic) + e.stopPropagation()} + /> + + Téma pro ntfy push notifikace. Nechte prázdné pro vypnutí. + + + + + Discord webhook URL + e.stopPropagation()} + /> + + URL webhooku Discord kanálu. Nechte prázdné pro vypnutí. + + + + + MS Teams webhook URL + e.stopPropagation()} + /> + + URL webhooku MS Teams kanálu. Nechte prázdné pro vypnutí. + + + + + Události k odběru + {Object.values(UdalostEnum).map(event => ( + toggleEvent(event)} + /> + ))} + + Zvolte události, o kterých chcete být notifikováni. Notifikace jsou odesílány pouze uživatelům se stejnou zvolenou lokalitou. + + + +
+

Bankovní účet

Nastavením čísla účtu umožníte automatické generování QR kódů pro úhradu za vámi provedené objednávky v rámci Pizza day. @@ -88,7 +203,7 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly Storno - diff --git a/server/src/index.ts b/server/src/index.ts index 6b75441..4936a6b 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -13,6 +13,7 @@ import foodRoutes, { refreshMetoda } from "./routes/foodRoutes"; import votingRoutes from "./routes/votingRoutes"; import easterEggRoutes from "./routes/easterEggRoutes"; import statsRoutes from "./routes/statsRoutes"; +import notificationRoutes from "./routes/notificationRoutes"; const ENVIRONMENT = process.env.NODE_ENV ?? 'production'; dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) }); @@ -146,6 +147,7 @@ app.use("/api/food", foodRoutes); app.use("/api/voting", votingRoutes); app.use("/api/easterEggs", easterEggRoutes); app.use("/api/stats", statsRoutes); +app.use("/api/notifications", notificationRoutes); app.use('/stats', express.static('public')); app.use(express.static('public')); diff --git a/server/src/notifikace.ts b/server/src/notifikace.ts index 9663181..232c3eb 100644 --- a/server/src/notifikace.ts +++ b/server/src/notifikace.ts @@ -3,11 +3,56 @@ import dotenv from 'dotenv'; import path from 'path'; import { getClientData, getToday } from "./service"; import { getUsersByLocation, getHumanTime } from "./utils"; -import { NotifikaceData, NotifikaceInput } from '../../types'; +import { NotifikaceData, NotifikaceInput, NotificationSettings } from '../../types'; +import getStorage from "./storage"; const ENVIRONMENT = process.env.NODE_ENV ?? 'production'; dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) }); +const storage = getStorage(); +const NOTIFICATION_SETTINGS_PREFIX = 'notif'; + +/** Vrátí klíč pro uložení notifikačních nastavení uživatele. */ +function getNotificationSettingsKey(login: string): string { + return `${NOTIFICATION_SETTINGS_PREFIX}_${login}`; +} + +/** Vrátí nastavení notifikací pro daného uživatele. */ +export async function getNotificationSettings(login: string): Promise { + return await storage.getData(getNotificationSettingsKey(login)) ?? {}; +} + +/** Uloží nastavení notifikací pro daného uživatele. */ +export async function saveNotificationSettings(login: string, settings: NotificationSettings): Promise { + await storage.setData(getNotificationSettingsKey(login), settings); + return settings; +} + +/** Odešle ntfy notifikaci na dané téma. */ +async function ntfyCallToTopic(topic: string, message: string) { + const url = process.env.NTFY_HOST; + const username = process.env.NTFY_USERNAME; + const password = process.env.NTFY_PASSWD; + if (!url || !username || !password) { + return; + } + const token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64'); + try { + const response = await axios({ + url: `${url}/${topic}`, + method: 'POST', + data: message, + headers: { + 'Authorization': `Basic ${token}`, + 'Tag': 'meat_on_bone' + } + }); + console.log(response.data); + } catch (error) { + console.error(`Chyba při odesílání ntfy notifikace na topic ${topic}:`, error); + } +} + export const ntfyCall = async (data: NotifikaceInput) => { const url = process.env.NTFY_HOST const username = process.env.NTFY_USERNAME; @@ -87,10 +132,58 @@ export const teamsCall = async (data: NotifikaceInput) => { } } +/** Odešle Teams notifikaci na daný webhook URL. */ +async function teamsCallToUrl(webhookUrl: string, data: NotifikaceInput) { + const title = data.udalost; + let time = new Date(); + time.setTime(time.getTime() + 1000 * 60); + const message = 'Odcházíme v ' + getHumanTime(time) + ', ' + data.user; + const card = { + '@type': 'MessageCard', + '@context': 'http://schema.org/extensions', + 'themeColor': "0072C6", + summary: 'Summary description', + sections: [ + { + activityTitle: title, + text: message, + }, + ], + }; + try { + await axios.post(webhookUrl, card, { + headers: { + 'content-type': 'application/vnd.microsoft.teams.card.o365connector' + }, + }); + } catch (error) { + console.error(`Chyba při odesílání Teams notifikace:`, error); + } +} + +/** Odešle Discord notifikaci na daný webhook URL. */ +async function discordCall(webhookUrl: string, data: NotifikaceInput) { + let time = new Date(); + time.setTime(time.getTime() + 1000 * 60); + const message = `🍖 **${data.udalost}** — ${data.user} (odchod v ${getHumanTime(time)})`; + try { + await axios.post(webhookUrl, { + content: message, + }, { + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.error(`Chyba při odesílání Discord notifikace:`, error); + } +} + /** Zavolá notifikace na všechny konfigurované způsoby notifikace, přetížení proměných na false pro jednotlivé způsoby je vypne*/ export const callNotifikace = async ({ input, teams = true, gotify = false, ntfy = true }: NotifikaceData) => { - const notifications = []; + const notifications: Promise[] = []; + // Globální notifikace (zpětně kompatibilní) if (ntfy) { const ntfyPromises = await ntfyCall(input); if (ntfyPromises) { @@ -100,20 +193,33 @@ export const callNotifikace = async ({ input, teams = true, gotify = false, ntfy if (teams) { const teamsPromises = await teamsCall(input); if (teamsPromises) { - notifications.push(teamsPromises); + notifications.push(Promise.resolve(teamsPromises)); + } + } + + // Per-user notifikace: najdeme uživatele se stejnou lokací a odešleme dle jejich nastavení + const clientData = await getClientData(getToday()); + const usersToNotify = getUsersByLocation(clientData.choices, input.user); + for (const user of usersToNotify) { + if (user === input.user) continue; // Neposíláme notifikaci spouštějícímu uživateli + const userSettings = await getNotificationSettings(user); + if (!userSettings.enabledEvents?.includes(input.udalost)) continue; + + if (userSettings.ntfyTopic) { + notifications.push(ntfyCallToTopic(userSettings.ntfyTopic, `${input.udalost} - spustil: ${input.user}`)); + } + if (userSettings.discordWebhookUrl) { + notifications.push(discordCall(userSettings.discordWebhookUrl, input)); + } + if (userSettings.teamsWebhookUrl) { + notifications.push(teamsCallToUrl(userSettings.teamsWebhookUrl, input)); } } - // gotify bych řekl, že už je deprecated - // if (gotify) { - // const gotifyPromises = await gotifyCall(input, gotifyData); - // notifications.push(...gotifyPromises); - // } try { const results = await Promise.all(notifications); return results; } catch (error) { console.error("Error in callNotifikace: ", error); - // Handle the error as needed } -}; \ No newline at end of file +}; diff --git a/server/src/routes/notificationRoutes.ts b/server/src/routes/notificationRoutes.ts new file mode 100644 index 0000000..1772fb9 --- /dev/null +++ b/server/src/routes/notificationRoutes.ts @@ -0,0 +1,32 @@ +import express, { Request } from "express"; +import { getLogin } from "../auth"; +import { parseToken } from "../utils"; +import { getNotificationSettings, saveNotificationSettings } from "../notifikace"; +import { UpdateNotificationSettingsData } from "../../../types"; + +const router = express.Router(); + +/** Vrátí nastavení notifikací pro přihlášeného uživatele. */ +router.get("/settings", async (req, res, next) => { + const login = getLogin(parseToken(req)); + try { + const settings = await getNotificationSettings(login); + res.status(200).json(settings); + } catch (e: any) { next(e) } +}); + +/** Uloží nastavení notifikací pro přihlášeného uživatele. */ +router.post("/settings", async (req: Request<{}, any, UpdateNotificationSettingsData["body"]>, res, next) => { + const login = getLogin(parseToken(req)); + try { + const settings = await saveNotificationSettings(login, { + ntfyTopic: req.body.ntfyTopic, + discordWebhookUrl: req.body.discordWebhookUrl, + teamsWebhookUrl: req.body.teamsWebhookUrl, + enabledEvents: req.body.enabledEvents, + }); + res.status(200).json(settings); + } catch (e: any) { next(e) } +}); + +export default router; diff --git a/types/gen/sdk.gen.ts b/types/gen/sdk.gen.ts new file mode 100644 index 0000000..16488ec --- /dev/null +++ b/types/gen/sdk.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { LoginData, LoginResponse, GetPizzaQrData, GetPizzaQrResponse, GetDataData, GetDataResponse, AddChoiceData, AddChoiceResponse, RemoveChoiceData, RemoveChoiceResponse, UpdateNoteData, UpdateNoteResponse, RemoveChoicesData, RemoveChoicesResponse, ChangeDepartureTimeData, ChangeDepartureTimeResponse, JdemeObedData, SetBuyerData, CreatePizzaDayData, DeletePizzaDayData, LockPizzaDayData, UnlockPizzaDayData, FinishOrderData, FinishDeliveryData, AddPizzaData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData, GetEasterEggData, GetEasterEggResponse, GetEasterEggImageData, GetEasterEggImageResponse, GetStatsData, GetStatsResponse, GetVotesData, GetVotesResponse, UpdateVoteData, GetVotingStatsData, GetVotingStatsResponse, GetNotificationSettingsData, GetNotificationSettingsResponse, UpdateNotificationSettingsData, DismissQrData } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Přihlášení uživatele + */ +export const login = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + url: '/login', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Získání QR kódu pro platbu za Pizza day + */ +export const getPizzaQr = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + url: '/qr', + ...options + }); +}; + +/** + * Načtení klientských dat pro aktuální nebo předaný den + */ +export const getData = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/data', + ...options + }); +}; + +/** + * Přidání či nahrazení volby uživatele pro zvolený den/podnik + */ +export const addChoice = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/addChoice', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Odstranění jednoho zvoleného jídla uživatele pro zvolený den/podnik + */ +export const removeChoice = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/removeChoice', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Nastavení poznámky k volbě uživatele + */ +export const updateNote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/updateNote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Odstranění volby uživatele pro zvolený den/podnik, včetně případných jídel + */ +export const removeChoices = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/removeChoices', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Úprava preferovaného času odchodu do aktuálně zvoleného podniku. + */ +export const changeDepartureTime = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/changeDepartureTime', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Odeslání notifikací "jdeme na oběd" dle konfigurace. + */ +export const jdemeObed = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/jdemeObed', + ...options + }); +}; + +/** + * Nastavení/odnastavení aktuálně přihlášeného uživatele jako objednatele pro stav "Budu objednávat" pro aktuální den. + */ +export const setBuyer = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/food/updateBuyer', + ...options + }); +}; + +/** + * Založení pizza day. + */ +export const createPizzaDay = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/create', + ...options + }); +}; + +/** + * Smazání pizza day. + */ +export const deletePizzaDay = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/delete', + ...options + }); +}; + +/** + * Uzamkne pizza day. Nebude možné přidávat či odebírat pizzy. + */ +export const lockPizzaDay = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/lock', + ...options + }); +}; + +/** + * Odemkne pizza day. Bude opět možné přidávat či odebírat pizzy. + */ +export const unlockPizzaDay = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/unlock', + ...options + }); +}; + +/** + * Přepnutí pizza day do stavu "Pizzy objednány". Není možné měnit objednávky, příslušným uživatelům je odeslána notifikace o provedené objednávce. + */ +export const finishOrder = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/finishOrder', + ...options + }); +}; + +/** + * Převod pizza day do stavu "Pizzy byly doručeny". Pokud má objednávající nastaveno číslo účtu, je ostatním uživatelům vygenerován a následně zobrazen QR kód pro úhradu jejich objednávky. + */ +export const finishDelivery = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/finishDelivery', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Přidání pizzy do objednávky. + */ +export const addPizza = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/add', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Odstranění pizzy z objednávky. + */ +export const removePizza = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/remove', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Nastavení poznámky k objednávkám pizz přihlášeného uživatele. + */ +export const updatePizzaDayNote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/updatePizzaDayNote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Nastavení přirážky/slevy k objednávce pizz uživatele. + */ +export const updatePizzaFee = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/updatePizzaFee', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Vrátí náhodně metadata jednoho z definovaných easter egg obrázků pro přihlášeného uživatele, nebo nic, pokud žádné definované nemá. + */ +export const getEasterEgg = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/easterEggs', + ...options + }); +}; + +/** + * Vrátí obrázek konkrétního easter eggu + */ +export const getEasterEggImage = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/easterEggs/{url}', + ...options + }); +}; + +/** + * Vrátí statistiky způsobu stravování pro předaný rozsah dat. + */ +export const getStats = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/stats', + ...options + }); +}; + +/** + * Vrátí statistiky hlasování o nových funkcích. + */ +export const getVotes = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/voting/getVotes', + ...options + }); +}; + +/** + * Aktualizuje hlasování uživatele o dané funkcionalitě. + */ +export const updateVote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/voting/updateVote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Vrátí agregované statistiky hlasování o nových funkcích. + */ +export const getVotingStats = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/voting/stats', + ...options + }); +}; + +/** + * Vrátí nastavení notifikací přihlášeného uživatele. + */ +export const getNotificationSettings = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/notifications/settings', + ...options + }); +}; + +/** + * Uloží nastavení notifikací přihlášeného uživatele. + */ +export const updateNotificationSettings = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/notifications/settings', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Označí QR kód jako uhrazený (zaplacený). + */ +export const dismissQr = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/pizzaDay/dismissQr', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/types/gen/types.gen.ts b/types/gen/types.gen.ts new file mode 100644 index 0000000..fbaf660 --- /dev/null +++ b/types/gen/types.gen.ts @@ -0,0 +1,1030 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Klientský JWT token pro autentizaci a autorizaci + */ +export type JwtToken = { + /** + * Přihlašovací jméno uživatele + */ + login: string; + /** + * Příznak, zda se jedná o uživatele ověřeného doménovým přihlášením + */ + trusted: boolean; + /** + * Časové razítko vydání tokenu + */ + iat: number; +}; + +/** + * Klientská data pro jeden konkrétní den. Obsahuje menu všech načtených podniků a volby jednotlivých uživatelů. + */ +export type ClientData = { + todayDayIndex: DayIndex; + /** + * Human-readable datum dne + */ + date: string; + /** + * Příznak, zda je tento den víkend + */ + isWeekend: boolean; + dayIndex?: DayIndex; + choices: LunchChoices; + menus?: RestaurantDayMenuMap; + pizzaDay?: PizzaDay; + /** + * Seznam dostupných pizz pro předaný den + */ + pizzaList?: Array; + /** + * Datum a čas poslední aktualizace pizz + */ + pizzaListLastUpdate?: string; + /** + * Nevyřízené QR kódy pro platbu z předchozích pizza day + */ + pendingQrs?: Array; +}; + +/** + * Konkrétní volba stravování jednoho uživatele v konkrétní den. Může se jednat jak o stravovací podnik, tak možnosti "budu objednávat", "neobědvám" apod. + */ +export type UserLunchChoice = { + /** + * Příznak, zda byla tato volba provedena uživatelem ověřeným doménovým přihlášením + */ + trusted?: boolean; + /** + * Pole indexů vybraných jídel v rámci dané restaurace. Index představuje pořadí jídla v menu dané restaurace. + */ + selectedFoods?: Array; + /** + * Čas preferovaného odchodu do dané restaurace v human-readable formátu (např. 12:00) + */ + departureTime?: string; + /** + * Volitelná, veřejně viditelná uživatelská poznámka k vybrané volbě + */ + note?: string; + /** + * Příznak, zda je tento uživatel objednatelem pro stav "Budu objednávat" + */ + isBuyer?: boolean; +}; + +/** + * Objekt, kde klíčem je možnost stravování ((#LunchChoice)) a hodnotou množina uživatelů s touto volbou ((#LunchChoices)). + */ +export type LocationLunchChoicesMap = { + [key: string]: UserLunchChoice; +}; + +/** + * Objekt, představující volby všech uživatelů pro konkrétní den. Klíčem je (#LunchChoice). + */ +export type LunchChoices = { + SLADOVNICKA?: LocationLunchChoicesMap; + TECHTOWER?: LocationLunchChoicesMap; + ZASTAVKAUMICHALA?: LocationLunchChoicesMap; + SENKSERIKOVA?: LocationLunchChoicesMap; + SPSE?: LocationLunchChoicesMap; + PIZZA?: LocationLunchChoicesMap; + OBJEDNAVAM?: LocationLunchChoicesMap; + NEOBEDVAM?: LocationLunchChoicesMap; + ROZHODUJI?: LocationLunchChoicesMap; +}; + +/** + * Stravovací zařízení (restaurace, jídelna, hospoda, ...) + */ +export type Restaurant = 'SLADOVNICKA' | 'TECHTOWER' | 'ZASTAVKAUMICHALA' | 'SENKSERIKOVA'; + +/** + * Stravovací zařízení (restaurace, jídelna, hospoda, ...) + */ +export const Restaurant = { + SLADOVNICKA: 'SLADOVNICKA', + TECHTOWER: 'TECHTOWER', + ZASTAVKAUMICHALA: 'ZASTAVKAUMICHALA', + SENKSERIKOVA: 'SENKSERIKOVA' +} as const; + +/** + * Konkrétní možnost stravování (konkrétní restaurace, pizza day, objednání, neobědvání, rozhodování se, ...) + */ +export type LunchChoice = 'SLADOVNICKA' | 'TECHTOWER' | 'ZASTAVKAUMICHALA' | 'SENKSERIKOVA' | 'SPSE' | 'PIZZA' | 'OBJEDNAVAM' | 'NEOBEDVAM' | 'ROZHODUJI'; + +/** + * Konkrétní možnost stravování (konkrétní restaurace, pizza day, objednání, neobědvání, rozhodování se, ...) + */ +export const LunchChoice = { + SLADOVNICKA: 'SLADOVNICKA', + TECHTOWER: 'TECHTOWER', + ZASTAVKAUMICHALA: 'ZASTAVKAUMICHALA', + SENKSERIKOVA: 'SENKSERIKOVA', + SPSE: 'SPSE', + PIZZA: 'PIZZA', + OBJEDNAVAM: 'OBJEDNAVAM', + NEOBEDVAM: 'NEOBEDVAM', + ROZHODUJI: 'ROZHODUJI' +} as const; + +/** + * Index dne v týdnu (0 = pondělí, 4 = pátek) + */ +export type DayIndex = number; + +/** + * Pořadový index jídla v menu konkrétní restaurace + */ +export type FoodIndex = number; + +/** + * Konkrétní jídlo z menu restaurace + */ +export type Food = { + /** + * Množství standardní porce, např. 0,33l nebo 150g + */ + amount?: string; + /** + * Název/popis jídla + */ + name: string; + /** + * Cena ve formátu '135 Kč' + */ + price?: string; + /** + * Příznak, zda se jedná o polévku + */ + isSoup: boolean; + /** + * Seznam čísel alergenů obsažených v jídle + */ + allergens?: Array; +}; + +/** + * Menu restaurace na konkrétní den + */ +export type RestaurantDayMenu = { + /** + * UNIX timestamp poslední aktualizace menu + */ + lastUpdate?: number; + /** + * Příznak, zda je daný podnik v daný den zavřený + */ + closed?: boolean; + /** + * Seznam jídel pro daný den + */ + food?: Array; + /** + * Seznam varování o kvalitě/úplnosti dat menu + */ + warnings?: Array; +}; + +/** + * Objekt, kde klíčem je podnik ((#Restaurant)) a hodnotou denní menu daného podniku ((#RestaurantDayMenu)) + */ +export type RestaurantDayMenuMap = { + SLADOVNICKA?: RestaurantDayMenu; + TECHTOWER?: RestaurantDayMenu; + ZASTAVKAUMICHALA?: RestaurantDayMenu; + SENKSERIKOVA?: RestaurantDayMenu; +}; + +/** + * Pole týdenních menu jednotlivých podniků. Indexem je den v týdnu (0 = pondělí, 4 = pátek), hodnotou denní menu daného podniku. + */ +export type WeekMenu = [ + RestaurantDayMenuMap, + RestaurantDayMenuMap, + RestaurantDayMenuMap, + RestaurantDayMenuMap, + RestaurantDayMenuMap +]; + +/** + * Preferovaný čas odchodu na oběd + */ +export type DepartureTime = '10:00' | '10:15' | '10:30' | '10:45' | '11:00' | '11:15' | '11:30' | '11:45' | '12:00' | '12:15' | '12:30' | '12:45' | '13:00'; + +/** + * Preferovaný čas odchodu na oběd + */ +export const DepartureTime = { + T10_00: '10:00', + T10_15: '10:15', + T10_30: '10:30', + T10_45: '10:45', + T11_00: '11:00', + T11_15: '11:15', + T11_30: '11:30', + T11_45: '11:45', + T12_00: '12:00', + T12_15: '12:15', + T12_30: '12:30', + T12_45: '12:45', + T13_00: '13:00' +} as const; + +export type FeatureRequest = 'Ruční generování QR kódů mimo Pizza day (např. při objednávání)' | 'Možnost označovat si jídla jako oblíbená (taková jídla by se uživateli následně zvýrazňovala)' | 'Možnost úhrady v podniku za všechny jednou osobou a následné generování QR ostatním' | 'Zrušení \\"užívejte víkend\\", místo toho umožnit zpětně náhled na uplynulý týden' | 'Umožnění zobrazení vygenerovaného QR kódu i po následující dny (dokud ho uživatel ručně \\"nezavře\\", např. tlačítkem \\"Zaplatil jsem\\")' | 'Zobrazování náhledů (fotografií) pizz v rámci Pizza day' | 'Statistiky (nejoblíbenější podnik, nejpopulárnější jídla, nejobjednávanější pizzy, nejčastější uživatelé, ...)' | 'Vylepšení responzivního designu' | 'Zvýšení zabezpečení aplikace' | 'Zvýšená ochrana proti chybám uživatele (potvrzovací dialogy, překliky, ...)' | 'Celkové vylepšení UI/UX' | 'Zlepšení dokumentace/postupů pro ostatní vývojáře'; + +export const FeatureRequest = { + CUSTOM_QR: 'Ruční generování QR kódů mimo Pizza day (např. při objednávání)', + FAVORITES: 'Možnost označovat si jídla jako oblíbená (taková jídla by se uživateli následně zvýrazňovala)', + SINGLE_PAYMENT: 'Možnost úhrady v podniku za všechny jednou osobou a následné generování QR ostatním', + NO_WEEKENDS: 'Zrušení \\"užívejte víkend\\", místo toho umožnit zpětně náhled na uplynulý týden', + QR_FOREVER: 'Umožnění zobrazení vygenerovaného QR kódu i po následující dny (dokud ho uživatel ručně \\"nezavře\\", např. tlačítkem \\"Zaplatil jsem\\")', + PIZZA_PICTURES: 'Zobrazování náhledů (fotografií) pizz v rámci Pizza day', + STATISTICS: 'Statistiky (nejoblíbenější podnik, nejpopulárnější jídla, nejobjednávanější pizzy, nejčastější uživatelé, ...)', + RESPONSIVITY: 'Vylepšení responzivního designu', + SECURITY: 'Zvýšení zabezpečení aplikace', + SAFETY: 'Zvýšená ochrana proti chybám uživatele (potvrzovací dialogy, překliky, ...)', + UI: 'Celkové vylepšení UI/UX', + DEVELOPMENT: 'Zlepšení dokumentace/postupů pro ostatní vývojáře' +} as const; + +/** + * Statistiky hlasování - klíčem je název funkce, hodnotou počet hlasů + */ +export type VotingStats = { + [key: string]: number; +}; + +/** + * Data pro zobrazení easter eggů ssss + */ +export type EasterEgg = { + path: string; + url: string; + startOffset: number; + endOffset: number; + duration: number; + width?: string; + zIndex?: number; + position?: 'absolute'; + animationName?: string; + animationDuration?: string; + animationTimingFunction?: string; +}; + +/** + * Objekt, kde klíčem je zvolená možnost a hodnotou počet uživatelů, kteří tuto možnosti zvolili + */ +export type LocationStats = { + SLADOVNICKA?: number; + TECHTOWER?: number; + ZASTAVKAUMICHALA?: number; + SENKSERIKOVA?: number; + SPSE?: number; + PIZZA?: number; + OBJEDNAVAM?: number; + NEOBEDVAM?: number; + ROZHODUJI?: number; +}; + +/** + * Statistika vybraných možností pro jeden konkrétní den + */ +export type DailyStats = { + /** + * Datum v human-readable formátu + */ + date: string; + locations: LocationStats; +}; + +/** + * Pole statistik vybraných možností pro jeden konkrétní týden. Index představuje den v týdnu (0 = pondělí, 4 = pátek) + */ +export type WeeklyStats = [ + DailyStats, + DailyStats, + DailyStats, + DailyStats, + DailyStats +]; + +/** + * Stav pizza day + */ +export type PizzaDayState = 'Pizza day nebyl založen' | 'Pizza day je založen' | 'Objednávky uzamčeny' | 'Pizzy objednány' | 'Pizzy doručeny'; + +/** + * Stav pizza day + */ +export const PizzaDayState = { + NOT_CREATED: 'Pizza day nebyl založen', + CREATED: 'Pizza day je založen', + LOCKED: 'Objednávky uzamčeny', + ORDERED: 'Pizzy objednány', + DELIVERED: 'Pizzy doručeny' +} as const; + +/** + * Údaje o konkrétní variantě pizzy + */ +export type PizzaSize = { + /** + * Unikátní identifikátor varianty pizzy + */ + varId: number; + /** + * Velikost pizzy, např. "30cm" + */ + size: string; + /** + * Cena samotné pizzy v Kč + */ + pizzaPrice: number; + /** + * Cena krabice pizzy v Kč + */ + boxPrice: number; + /** + * Celková cena (pizza + krabice) + */ + price: number; +}; + +/** + * Údaje o konkrétní pizze. + */ +export type Pizza = { + /** + * Název pizzy + */ + name: string; + /** + * Seznam obsažených ingrediencí + */ + ingredients: Array; + /** + * Dostupné velikosti pizzy + */ + sizes: Array; +}; + +/** + * Konkrétní varianta (velikost) jedné pizzy. + */ +export type PizzaVariant = { + /** + * Unikátní identifikátor varianty pizzy + */ + varId: number; + /** + * Název pizzy + */ + name: string; + /** + * Velikost pizzy (např. "30cm") + */ + size: string; + /** + * Cena pizzy v Kč, včetně krabice + */ + price: number; +}; + +/** + * Údaje o objednávce pizzy jednoho uživatele. + */ +export type PizzaOrder = { + /** + * Jméno objednávajícího uživatele + */ + customer: string; + /** + * Seznam variant pizz k objednání (typicky bývá jen jedna) + */ + pizzaList?: Array; + /** + * Příplatek (např. za extra ingredience) + */ + fee?: { + /** + * Popis příplatku (např. "kuřecí maso navíc") + */ + text?: string; + /** + * Cena příplatku v Kč + */ + price?: number; + }; + /** + * Celková cena všech objednaných pizz daného uživatele, včetně krabic a příplatků + */ + totalPrice: number; + /** + * Příznak, pokud je k této objednávce vygenerován QR kód pro platbu. To je typicky pravda, pokud: + * - objednávající má v nastavení vyplněno číslo účtu + * - pizza day je ve stavu DELIVERED (Pizzy byly doručeny) + * + */ + hasQr: unknown; + /** + * Volitelná uživatelská poznámka pro objednávajícího (např. "bez oliv") + */ + note?: string; +}; + +/** + * Data o Pizza day pro konkrétní den + */ +export type PizzaDay = { + state?: PizzaDayState; + /** + * Jméno zakladatele pizza day + */ + creator?: string; + /** + * Pole objednávek jednotlivých uživatelů + */ + orders?: Array; +}; + +export type UdalostEnum = 'Zahájen pizza day' | 'Objednána pizza' | 'Jdeme na oběd'; + +export const UdalostEnum = { + ZAHAJENA_PIZZA: 'Zahájen pizza day', + OBJEDNANA_PIZZA: 'Objednána pizza', + JDEME_NA_OBED: 'Jdeme na oběd' +} as const; + +export type NotifikaceInput = { + udalost: UdalostEnum; + user: string; +}; + +export type NotifikaceData = { + input: NotifikaceInput; + gotify?: boolean; + teams?: boolean; + ntfy?: boolean; +}; + +export type GotifyServer = { + server: string; + api_keys: Array; +}; + +/** + * Nastavení notifikací pro konkrétního uživatele + */ +export type NotificationSettings = { + /** + * Téma (topic) pro ntfy notifikace. Pokud je prázdné, ntfy notifikace jsou vypnuty. + */ + ntfyTopic?: string; + /** + * URL Discord webhooku. Pokud je prázdné, Discord notifikace jsou vypnuty. + */ + discordWebhookUrl?: string; + /** + * URL MS Teams webhooku. Pokud je prázdné, Teams notifikace jsou vypnuty. + */ + teamsWebhookUrl?: string; + /** + * Události, pro které chce uživatel dostávat notifikace + */ + enabledEvents?: Array; +}; + +export type GetNotificationSettingsData = { + body?: never; + path?: never; + query?: never; + url: '/notifications/settings'; +}; + +export type GetNotificationSettingsResponses = { + 200: NotificationSettings; +}; + +export type GetNotificationSettingsResponse = GetNotificationSettingsResponses[keyof GetNotificationSettingsResponses]; + +export type UpdateNotificationSettingsData = { + body: NotificationSettings; + path?: never; + query?: never; + url: '/notifications/settings'; +}; + +export type UpdateNotificationSettingsResponses = { + 200: NotificationSettings; +}; + +export type LoginData = { + body?: { + /** + * Přihlašovací jméno uživatele. Vyžadováno pouze pokud není předáno pomocí hlaviček. + */ + login?: string; + }; + path?: never; + query?: never; + url: '/login'; +}; + +export type LoginResponses = { + /** + * Přihlášení bylo úspěšné + */ + 200: JwtToken; +}; + +export type LoginResponse = LoginResponses[keyof LoginResponses]; + +export type GetPizzaQrData = { + body?: never; + path?: never; + query: { + /** + * Přihlašovací jméno uživatele, pro kterého bude vrácen QR kód + */ + login: string; + }; + url: '/qr'; +}; + +export type GetPizzaQrResponses = { + /** + * Vygenerovaný QR kód pro platbu + */ + 200: Blob | File; +}; + +export type GetPizzaQrResponse = GetPizzaQrResponses[keyof GetPizzaQrResponses]; + +export type GetDataData = { + body?: never; + path?: never; + query?: { + /** + * Index dne v týdnu. Pokud není předán, je použit aktuální den. + */ + dayIndex?: number; + }; + url: '/data'; +}; + +export type GetDataResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type GetDataResponse = GetDataResponses[keyof GetDataResponses]; + +export type AddChoiceData = { + body: { + locationKey: LunchChoice; + dayIndex?: DayIndex; + foodIndex?: FoodIndex; + }; + path?: never; + query?: never; + url: '/food/addChoice'; +}; + +export type AddChoiceResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type AddChoiceResponse = AddChoiceResponses[keyof AddChoiceResponses]; + +export type RemoveChoiceData = { + body: { + foodIndex: FoodIndex; + locationKey: LunchChoice; + dayIndex?: DayIndex; + }; + path?: never; + query?: never; + url: '/food/removeChoice'; +}; + +export type RemoveChoiceResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type RemoveChoiceResponse = RemoveChoiceResponses[keyof RemoveChoiceResponses]; + +export type UpdateNoteData = { + body: { + dayIndex?: DayIndex; + note?: string; + }; + path?: never; + query?: never; + url: '/food/updateNote'; +}; + +export type UpdateNoteResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type UpdateNoteResponse = UpdateNoteResponses[keyof UpdateNoteResponses]; + +export type RemoveChoicesData = { + body: { + locationKey: LunchChoice; + dayIndex?: DayIndex; + }; + path?: never; + query?: never; + url: '/food/removeChoices'; +}; + +export type RemoveChoicesResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type RemoveChoicesResponse = RemoveChoicesResponses[keyof RemoveChoicesResponses]; + +export type ChangeDepartureTimeData = { + body: { + dayIndex?: DayIndex; + time?: DepartureTime; + }; + path?: never; + query?: never; + url: '/food/changeDepartureTime'; +}; + +export type ChangeDepartureTimeResponses = { + /** + * Aktuální data pro klienta + */ + 200: ClientData; +}; + +export type ChangeDepartureTimeResponse = ChangeDepartureTimeResponses[keyof ChangeDepartureTimeResponses]; + +export type JdemeObedData = { + body?: never; + path?: never; + query?: never; + url: '/food/jdemeObed'; +}; + +export type JdemeObedResponses = { + /** + * Notifikace byly odeslány. + */ + 200: unknown; +}; + +export type SetBuyerData = { + body?: never; + path?: never; + query?: never; + url: '/food/updateBuyer'; +}; + +export type SetBuyerResponses = { + /** + * Stav byl úspěšně změněn. + */ + 200: unknown; +}; + +export type CreatePizzaDayData = { + body?: never; + path?: never; + query?: never; + url: '/pizzaDay/create'; +}; + +export type CreatePizzaDayResponses = { + /** + * Pizza day byl založen. + */ + 200: unknown; +}; + +export type DeletePizzaDayData = { + body?: never; + path?: never; + query?: never; + url: '/pizzaDay/delete'; +}; + +export type DeletePizzaDayResponses = { + /** + * Pizza day byl smazán. + */ + 200: unknown; +}; + +export type LockPizzaDayData = { + body?: never; + path?: never; + query?: never; + url: '/pizzaDay/lock'; +}; + +export type LockPizzaDayResponses = { + /** + * Pizza day byl uzamčen. + */ + 200: unknown; +}; + +export type UnlockPizzaDayData = { + body?: never; + path?: never; + query?: never; + url: '/pizzaDay/unlock'; +}; + +export type UnlockPizzaDayResponses = { + /** + * Pizza day byl odemčen. + */ + 200: unknown; +}; + +export type FinishOrderData = { + body?: never; + path?: never; + query?: never; + url: '/pizzaDay/finishOrder'; +}; + +export type FinishOrderResponses = { + /** + * Pizza day byl přepnut do stavu "Pizzy objednány". + */ + 200: unknown; +}; + +export type FinishDeliveryData = { + body: { + /** + * Číslo bankovního účtu objednávajícího + */ + bankAccount?: string; + /** + * Jméno majitele bankovního účtu + */ + bankAccountHolder?: string; + }; + path?: never; + query?: never; + url: '/pizzaDay/finishDelivery'; +}; + +export type FinishDeliveryResponses = { + /** + * Pizza day byl přepnut do stavu "Pizzy doručeny". + */ + 200: unknown; +}; + +export type AddPizzaData = { + body: { + /** + * Index pizzy v nabídce + */ + pizzaIndex: number; + /** + * Index velikosti pizzy v nabídce variant + */ + pizzaSizeIndex: number; + }; + path?: never; + query?: never; + url: '/pizzaDay/add'; +}; + +export type AddPizzaResponses = { + /** + * Přidání pizzy do objednávky proběhlo úspěšně. + */ + 200: unknown; +}; + +export type RemovePizzaData = { + body: { + pizzaOrder: PizzaVariant; + }; + path?: never; + query?: never; + url: '/pizzaDay/remove'; +}; + +export type RemovePizzaResponses = { + /** + * Odstranění pizzy z objednávky proběhlo úspěšně. + */ + 200: unknown; +}; + +export type UpdatePizzaDayNoteData = { + body: { + /** + * Poznámka k objednávkám pizz, např "bez oliv". + */ + note?: string; + }; + path?: never; + query?: never; + url: '/pizzaDay/updatePizzaDayNote'; +}; + +export type UpdatePizzaDayNoteResponses = { + /** + * Nastavení poznámky k objednávkám pizz proběhlo úspěšně. + */ + 200: unknown; +}; + +export type UpdatePizzaFeeData = { + body: { + /** + * Login cíleného uživatele + */ + login: string; + /** + * Textový popis přirážky/slevy + */ + text?: string; + /** + * Částka přirážky/slevy v Kč + */ + price?: number; + }; + path?: never; + query?: never; + url: '/pizzaDay/updatePizzaFee'; +}; + +export type UpdatePizzaFeeResponses = { + /** + * Nastavení přirážky/slevy proběhlo úspěšně. + */ + 200: unknown; +}; + +export type GetEasterEggData = { + body?: never; + path?: never; + query?: never; + url: '/easterEggs'; +}; + +export type GetEasterEggResponses = { + 200: EasterEgg; +}; + +export type GetEasterEggResponse = GetEasterEggResponses[keyof GetEasterEggResponses]; + +export type GetEasterEggImageData = { + body?: never; + path: { + /** + * URL easter eggu + */ + url: string; + }; + query?: never; + url: '/easterEggs/{url}'; +}; + +export type GetEasterEggImageResponses = { + 200: Blob | File; +}; + +export type GetEasterEggImageResponse = GetEasterEggImageResponses[keyof GetEasterEggImageResponses]; + +export type GetStatsData = { + body?: never; + path?: never; + query: { + /** + * Počáteční datum pro načtení statistik + */ + startDate: string; + /** + * Koncové datum pro načtení statistik + */ + endDate: string; + }; + url: '/stats'; +}; + +export type GetStatsResponses = { + /** + * Statistiky způsobu stravování. Každý prvek v poli představuje statistiky pro jeden den z předaného rozsahu dat. + */ + 200: WeeklyStats; +}; + +export type GetStatsResponse = GetStatsResponses[keyof GetStatsResponses]; + +export type GetVotesData = { + body?: never; + path?: never; + query?: never; + url: '/voting/getVotes'; +}; + +export type GetVotesResponses = { + 200: Array; +}; + +export type GetVotesResponse = GetVotesResponses[keyof GetVotesResponses]; + +export type UpdateVoteData = { + body: { + option: FeatureRequest; + /** + * True, pokud uživatel hlasoval pro, jinak false. + */ + active: boolean; + }; + path?: never; + query?: never; + url: '/voting/updateVote'; +}; + +export type UpdateVoteResponses = { + /** + * Hlasování bylo úspěšně aktualizováno. + */ + 200: unknown; +}; + +export type GetVotingStatsData = { + body?: never; + path?: never; + query?: never; + url: '/voting/stats'; +}; + +export type GetVotingStatsResponses = { + 200: VotingStats; +}; + +export type GetVotingStatsResponse = GetVotingStatsResponses[keyof GetVotingStatsResponses]; + +/** + * Informace o nevyřízeném QR kódu pro platbu + */ +export type PendingQr = { + /** + * Datum pizza day, ke kterému se QR kód vztahuje + */ + date: string; + /** + * Jméno zakladatele pizza day (příjemce platby) + */ + creator: string; + /** + * Celková částka k úhradě + */ + totalPrice: number; +}; + +export type DismissQrData = { + body: { + /** + * Datum pizza day, jehož QR kód se má označit jako uhrazený + */ + date: string; + }; + path?: never; + query?: never; + url: '/pizzaDay/dismissQr'; +}; + +export type DismissQrResponses = { + 200: unknown; +}; + +export type ClientOptions = { + baseUrl: `${string}://${string}/api` | (string & {}); +}; \ No newline at end of file