2 Commits

Author SHA1 Message Date
c2a2659bc4 feat: trvalé zobrazení QR kódu do ručního zavření (#31)
QR kódy pro platbu za pizza day jsou nyní zobrazeny persistentně
i po následující dny, dokud uživatel nepotvrdí platbu tlačítkem
"Zaplatil jsem". Nevyřízené QR kódy jsou uloženy per-user v storage
a zobrazeny v sekci "Nevyřízené platby".
2026-02-04 17:11:45 +01:00
f36afe129a feat: podpora per-user notifikací s Discord, ntfy a Teams (#39)
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.
2026-02-04 17:08:23 +01:00
6 changed files with 1557 additions and 92 deletions

View File

@@ -50,12 +50,6 @@ paths:
$ref: "./paths/pizzaDay/updatePizzaDayNote.yml" $ref: "./paths/pizzaDay/updatePizzaDayNote.yml"
/pizzaDay/updatePizzaFee: /pizzaDay/updatePizzaFee:
$ref: "./paths/pizzaDay/updatePizzaFee.yml" $ref: "./paths/pizzaDay/updatePizzaFee.yml"
/pizzaDay/dismissQr:
$ref: "./paths/pizzaDay/dismissQr.yml"
# Notifikace (/api/notifications)
/notifications/settings:
$ref: "./paths/notifications/settings.yml"
# Easter eggy (/api/easterEggs) # Easter eggy (/api/easterEggs)
/easterEggs: /easterEggs:

527
types/gen/sdk.gen.ts Normal file
View File

@@ -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<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
/**
* 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<string, unknown>;
};
/**
* Přihlášení uživatele
*/
export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<LoginResponse, unknown, ThrowOnError>({
url: '/login',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Získání QR kódu pro platbu za Pizza day
*/
export const getPizzaQr = <ThrowOnError extends boolean = false>(options: Options<GetPizzaQrData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetPizzaQrResponse, unknown, ThrowOnError>({
url: '/qr',
...options
});
};
/**
* Načtení klientských dat pro aktuální nebo předaný den
*/
export const getData = <ThrowOnError extends boolean = false>(options?: Options<GetDataData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetDataResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/data',
...options
});
};
/**
* Přidání či nahrazení volby uživatele pro zvolený den/podnik
*/
export const addChoice = <ThrowOnError extends boolean = false>(options: Options<AddChoiceData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<AddChoiceResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<RemoveChoiceData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<RemoveChoiceResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<UpdateNoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<UpdateNoteResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<RemoveChoicesData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<RemoveChoicesResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<ChangeDepartureTimeData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<ChangeDepartureTimeResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<JdemeObedData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<SetBuyerData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/updateBuyer',
...options
});
};
/**
* Založení pizza day.
*/
export const createPizzaDay = <ThrowOnError extends boolean = false>(options?: Options<CreatePizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/create',
...options
});
};
/**
* Smazání pizza day.
*/
export const deletePizzaDay = <ThrowOnError extends boolean = false>(options?: Options<DeletePizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<LockPizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<UnlockPizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<FinishOrderData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<FinishDeliveryData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<AddPizzaData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<RemovePizzaData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<UpdatePizzaDayNoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<UpdatePizzaFeeData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<GetEasterEggData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetEasterEggResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/easterEggs',
...options
});
};
/**
* Vrátí obrázek konkrétního easter eggu
*/
export const getEasterEggImage = <ThrowOnError extends boolean = false>(options: Options<GetEasterEggImageData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetEasterEggImageResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/easterEggs/{url}',
...options
});
};
/**
* Vrátí statistiky způsobu stravování pro předaný rozsah dat.
*/
export const getStats = <ThrowOnError extends boolean = false>(options: Options<GetStatsData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetStatsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/stats',
...options
});
};
/**
* Vrátí statistiky hlasování o nových funkcích.
*/
export const getVotes = <ThrowOnError extends boolean = false>(options?: Options<GetVotesData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetVotesResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/voting/getVotes',
...options
});
};
/**
* Aktualizuje hlasování uživatele o dané funkcionalitě.
*/
export const updateVote = <ThrowOnError extends boolean = false>(options: Options<UpdateVoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options?: Options<GetVotingStatsData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetVotingStatsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/voting/stats',
...options
});
};
/**
* Vrátí nastavení notifikací přihlášeného uživatele.
*/
export const getNotificationSettings = <ThrowOnError extends boolean = false>(options?: Options<GetNotificationSettingsData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetNotificationSettingsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/notifications/settings',
...options
});
};
/**
* Uloží nastavení notifikací přihlášeného uživatele.
*/
export const updateNotificationSettings = <ThrowOnError extends boolean = false>(options: Options<UpdateNotificationSettingsData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<GetNotificationSettingsResponse, unknown, ThrowOnError>({
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 = <ThrowOnError extends boolean = false>(options: Options<DismissQrData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/dismissQr',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};

1030
types/gen/types.gen.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
get:
operationId: getNotificationSettings
summary: Vrátí nastavení notifikací pro přihlášeného uživatele.
responses:
"200":
description: Nastavení notifikací
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"
post:
operationId: updateNotificationSettings
summary: Uloží nastavení notifikací pro přihlášeného uživatele.
requestBody:
required: true
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"
responses:
"200":
description: Nastavení notifikací bylo uloženo.
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"

View File

@@ -1,17 +0,0 @@
post:
operationId: dismissQr
summary: Označí QR kód pro daný den jako uhrazený (odstraní ho ze seznamu nevyřízených).
requestBody:
required: true
content:
application/json:
schema:
properties:
date:
description: Datum Pizza day, ke kterému se QR kód vztahuje
type: string
required:
- date
responses:
"200":
description: QR kód byl označen jako uhrazený.

View File

@@ -53,11 +53,6 @@ ClientData:
description: Datum a čas poslední aktualizace pizz description: Datum a čas poslední aktualizace pizz
type: string type: string
format: date-time format: date-time
pendingQrs:
description: Nevyřízené QR kódy pro platbu z předchozích pizza day
type: array
items:
$ref: "#/PendingQr"
# --- OBĚDY --- # --- OBĚDY ---
UserLunchChoice: UserLunchChoice:
@@ -532,24 +527,6 @@ NotifikaceData:
type: boolean type: boolean
ntfy: ntfy:
type: boolean type: boolean
NotificationSettings:
description: Nastavení notifikací pro konkrétního uživatele
type: object
properties:
ntfyTopic:
description: Téma pro ntfy push notifikace
type: string
discordWebhookUrl:
description: URL webhooku Discord kanálu
type: string
teamsWebhookUrl:
description: URL webhooku MS Teams kanálu
type: string
enabledEvents:
description: Seznam událostí, o kterých chce být uživatel notifikován
type: array
items:
$ref: "#/UdalostEnum"
GotifyServer: GotifyServer:
type: object type: object
required: required:
@@ -562,23 +539,3 @@ GotifyServer:
type: array type: array
items: items:
type: string type: string
# --- NEVYŘÍZENÉ QR KÓDY ---
PendingQr:
description: Nevyřízený QR kód pro platbu z předchozího Pizza day
type: object
additionalProperties: false
required:
- date
- creator
- totalPrice
properties:
date:
description: Datum Pizza day, ke kterému se QR kód vztahuje
type: string
creator:
description: Jméno zakladatele Pizza day (objednávajícího)
type: string
totalPrice:
description: Celková cena objednávky v Kč
type: number