From fdd42dc46a8125c7030194416b07adb7fcc06253 Mon Sep 17 00:00:00 2001 From: batmanisko Date: Wed, 4 Feb 2026 14:54:26 +0100 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20zobrazen=C3=AD=20minul=C3=A9ho=20t?= =?UTF-8?q?=C3=BDdne=20o=20v=C3=ADkendu=20m=C3=ADsto=20"U=C5=BE=C3=ADvejte?= =?UTF-8?q?=20v=C3=ADkend"=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Na víkendu se nyní zobrazuje páteční menu s možností procházet celý týden. Editační ovládací prvky jsou automaticky skryté díky existující logice canChangeChoice. --- client/src/App.tsx | 9 +++++++-- server/src/index.ts | 7 +++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index c0c289c..ae23bb5 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -461,7 +461,12 @@ function App() { {easterEgg && eggImage && }
- {data.isWeekend ?

Užívejte víkend :)

: <> + {data.todayDayIndex != null && data.todayDayIndex > 4 && + + Zobrazujete uplynulý týden + + } + <> {dayIndex != null &&
@@ -724,7 +729,7 @@ function App() {
}
- || "Jejda, něco se nám nepovedlo :("} + {/* { if (!isNaN(index)) { date = getDateForWeekIndex(parseInt(req.query.dayIndex)); } + } else if (getIsWeekend(getToday())) { + // Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend" + date = getDateForWeekIndex(4); } res.status(200).json(await getData(date)); }); -- 2.47.3 From d366ac39d423139eaea3ac42efeef5345ce4226c Mon Sep 17 00:00:00 2001 From: batmanisko Date: Wed, 4 Feb 2026 17:08:23 +0100 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20podpora=20per-user=20notifikac?= =?UTF-8?q?=C3=AD=20s=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 +++++ 4 files changed, 267 insertions(+), 12 deletions(-) create mode 100644 server/src/routes/notificationRoutes.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; -- 2.47.3 From b8629afef2ae9a22dec5723351f56d40e7e6c633 Mon Sep 17 00:00:00 2001 From: batmanisko Date: Wed, 4 Feb 2026 17:11:45 +0100 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20trval=C3=A9=20zobrazen=C3=AD=20QR?= =?UTF-8?q?=20k=C3=B3du=20do=20ru=C4=8Dn=C3=ADho=20zav=C5=99en=C3=AD=20(#3?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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". --- client/src/App.tsx | 28 +++++++++++++++++- server/src/index.ts | 18 +++++++++-- server/src/pizza.ts | 46 ++++++++++++++++++++++++++++- server/src/routes/pizzaDayRoutes.ts | 16 ++++++++-- 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index ae23bb5..5deb500 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -18,7 +18,7 @@ import Loader from './components/Loader'; import { getHumanDateTime, isInTheFuture } from './Utils'; import NoteModal from './components/modals/NoteModal'; import { useEasterEgg } from './context/eggs'; -import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime, setBuyer } from '../../types'; +import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime, setBuyer, dismissQr } from '../../types'; import { getLunchChoiceName } from './enums'; // import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves'; // import './FallingLeaves.scss'; @@ -729,6 +729,32 @@ function App() { } + {data.pendingQrs && data.pendingQrs.length > 0 && +

+

Nevyřízené platby

+

Máte neuhrazené QR kódy z předchozích Pizza day.

+ {data.pendingQrs.map(qr => ( +
+

+ {qr.date} — {qr.creator} ({qr.totalPrice} Kč) +

+ QR kód +
+ +
+
+ ))} +
+ } {/* { // Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend" date = getDateForWeekIndex(4); } - res.status(200).json(await getData(date)); + const data = await getData(date); + // Připojíme nevyřízené QR kódy pro přihlášeného uživatele + try { + const login = getLogin(parseToken(req)); + const pendingQrs = await getPendingQrs(login); + if (pendingQrs.length > 0) { + data.pendingQrs = pendingQrs; + } + } catch { + // Token nemusí být validní, ignorujeme + } + res.status(200).json(data); }); // Ostatní routes diff --git a/server/src/pizza.ts b/server/src/pizza.ts index f1c7d48..8ffdf69 100644 --- a/server/src/pizza.ts +++ b/server/src/pizza.ts @@ -4,9 +4,10 @@ import { generateQr } from "./qr"; import getStorage from "./storage"; import { downloadPizzy } from "./chefie"; import { getClientData, getToday, initIfNeeded } from "./service"; -import { Pizza, ClientData, PizzaDayState, PizzaSize, PizzaOrder, PizzaVariant, UdalostEnum } from "../../types/gen/types.gen"; +import { Pizza, ClientData, PizzaDayState, PizzaSize, PizzaOrder, PizzaVariant, UdalostEnum, PendingQr } from "../../types/gen/types.gen"; const storage = getStorage(); +const PENDING_QR_PREFIX = 'pending_qr'; /** * Vrátí seznam dostupných pizz pro dnešní den. @@ -241,6 +242,12 @@ export async function finishPizzaDelivery(login: string, bankAccount?: string, b let message = order.pizzaList!.map(pizza => `Pizza ${pizza.name} (${pizza.size})`).join(', '); await generateQr(order.customer, bankAccount, bankAccountHolder, order.totalPrice, message); order.hasQr = true; + // Uložíme nevyřízený QR kód pro persistentní zobrazení + await addPendingQr(order.customer, { + date: today, + creator: login, + totalPrice: order.totalPrice, + }); } } } @@ -307,4 +314,41 @@ export async function updatePizzaFee(login: string, targetLogin: string, text?: targetOrder.totalPrice = targetOrder.pizzaList.reduce((price, pizzaOrder) => price + pizzaOrder.price, 0) + (targetOrder.fee?.price ?? 0); await storage.setData(today, clientData); return clientData; +} + +/** + * Vrátí klíč pro uložení nevyřízených QR kódů uživatele. + */ +function getPendingQrKey(login: string): string { + return `${PENDING_QR_PREFIX}_${login}`; +} + +/** + * Přidá nevyřízený QR kód pro uživatele. + */ +async function addPendingQr(login: string, pendingQr: PendingQr): Promise { + const key = getPendingQrKey(login); + const existing = await storage.getData(key) ?? []; + // Nepřidáváme duplicity pro stejný den + if (!existing.some(qr => qr.date === pendingQr.date)) { + existing.push(pendingQr); + await storage.setData(key, existing); + } +} + +/** + * Vrátí nevyřízené QR kódy pro uživatele. + */ +export async function getPendingQrs(login: string): Promise { + return await storage.getData(getPendingQrKey(login)) ?? []; +} + +/** + * Označí QR kód pro daný den jako uhrazený (odstraní ho ze seznamu nevyřízených). + */ +export async function dismissPendingQr(login: string, date: string): Promise { + const key = getPendingQrKey(login); + const existing = await storage.getData(key) ?? []; + const filtered = existing.filter(qr => qr.date !== date); + await storage.setData(key, filtered); } \ No newline at end of file diff --git a/server/src/routes/pizzaDayRoutes.ts b/server/src/routes/pizzaDayRoutes.ts index 944518e..82a26c5 100644 --- a/server/src/routes/pizzaDayRoutes.ts +++ b/server/src/routes/pizzaDayRoutes.ts @@ -1,9 +1,9 @@ import express, { Request } from "express"; import { getLogin } from "../auth"; -import { createPizzaDay, deletePizzaDay, getPizzaList, addPizzaOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee } from "../pizza"; +import { createPizzaDay, deletePizzaDay, getPizzaList, addPizzaOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee, dismissPendingQr } from "../pizza"; import { parseToken } from "../utils"; import { getWebsocket } from "../websocket"; -import { AddPizzaData, FinishDeliveryData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData } from "../../../types"; +import { AddPizzaData, DismissQrData, FinishDeliveryData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData } from "../../../types"; const router = express.Router(); @@ -109,4 +109,16 @@ router.post("/updatePizzaFee", async (req: Request<{}, any, UpdatePizzaFeeData[" } catch (e: any) { next(e) } }); +/** Označí QR kód jako uhrazený. */ +router.post("/dismissQr", async (req: Request<{}, any, DismissQrData["body"]>, res, next) => { + const login = getLogin(parseToken(req)); + if (!req.body.date) { + return res.status(400).json({ error: "Nebyl předán datum" }); + } + try { + await dismissPendingQr(login, req.body.date); + res.status(200).json({}); + } catch (e: any) { next(e) } +}); + export default router; \ No newline at end of file -- 2.47.3 From 086646fd1c87099fe16e47ebd310b764c056b6fc Mon Sep 17 00:00:00 2001 From: batmanisko Date: Wed, 4 Feb 2026 17:28:52 +0100 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20p=C5=99id=C3=A1n=C3=AD=20nov=C3=BDch?= =?UTF-8?q?=20typ=C5=AF=20do=20OpenAPI=20spec=20pro=20p=C5=99e=C5=BEit?= =?UTF-8?q?=C3=AD=20regenerace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typy PendingQr, NotificationSettings a nové endpointy (dismissQr, notifications/settings) byly přidány přímo do YAML specifikace místo ručních úprav generovaných souborů. --- types/api.yml | 6 ++++ types/paths/notifications/settings.yml | 26 ++++++++++++++++ types/paths/pizzaDay/dismissQr.yml | 17 ++++++++++ types/schemas/_index.yml | 43 ++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 types/paths/notifications/settings.yml create mode 100644 types/paths/pizzaDay/dismissQr.yml diff --git a/types/api.yml b/types/api.yml index 3c546d6..8f9df9a 100644 --- a/types/api.yml +++ b/types/api.yml @@ -50,6 +50,12 @@ paths: $ref: "./paths/pizzaDay/updatePizzaDayNote.yml" /pizzaDay/updatePizzaFee: $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) /easterEggs: diff --git a/types/paths/notifications/settings.yml b/types/paths/notifications/settings.yml new file mode 100644 index 0000000..a2f46b8 --- /dev/null +++ b/types/paths/notifications/settings.yml @@ -0,0 +1,26 @@ +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" diff --git a/types/paths/pizzaDay/dismissQr.yml b/types/paths/pizzaDay/dismissQr.yml new file mode 100644 index 0000000..ffa95bb --- /dev/null +++ b/types/paths/pizzaDay/dismissQr.yml @@ -0,0 +1,17 @@ +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ý. diff --git a/types/schemas/_index.yml b/types/schemas/_index.yml index 742a219..7e6990f 100644 --- a/types/schemas/_index.yml +++ b/types/schemas/_index.yml @@ -53,6 +53,11 @@ ClientData: description: Datum a čas poslední aktualizace pizz type: string 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 --- UserLunchChoice: @@ -527,6 +532,24 @@ NotifikaceData: type: boolean ntfy: 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: type: object required: @@ -539,3 +562,23 @@ GotifyServer: type: array items: 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 -- 2.47.3