feat: Přidání funkce pro manuální refresh jidel.
This commit is contained in:
parent
a77a04bcdf
commit
ff20394b97
@ -1,5 +1,5 @@
|
|||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { Modal, Button } from "react-bootstrap"
|
import { Modal, Button, Alert } from "react-bootstrap"
|
||||||
import { useSettings } from "../../context/settings";
|
import { useSettings } from "../../context/settings";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -15,6 +15,41 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly<Prop
|
|||||||
const nameRef = useRef<HTMLInputElement>(null);
|
const nameRef = useRef<HTMLInputElement>(null);
|
||||||
const hideSoupsRef = useRef<HTMLInputElement>(null);
|
const hideSoupsRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// Pro refresh jidel
|
||||||
|
const refreshPassRef = useRef<HTMLInputElement>(null);
|
||||||
|
const refreshTypeRef = useRef<HTMLSelectElement>(null);
|
||||||
|
const [refreshLoading, setRefreshLoading] = useState(false);
|
||||||
|
const [refreshMessage, setRefreshMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
const password = refreshPassRef.current?.value;
|
||||||
|
const type = refreshTypeRef.current?.value;
|
||||||
|
if (!password || !type) {
|
||||||
|
setRefreshMessage({ type: 'error', text: 'Zadejte heslo a typ refresh.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRefreshLoading(true);
|
||||||
|
setRefreshMessage(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/food/refresh?type=${type}&heslo=${encodeURIComponent(password)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok) {
|
||||||
|
setRefreshMessage({ type: 'success', text: 'Uspesny fetch' });
|
||||||
|
if (refreshPassRef.current) {
|
||||||
|
// Clean hesla xd
|
||||||
|
refreshPassRef.current.value = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Chyba obnovování jidelnicku:", data.error);
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
finally {
|
||||||
|
setRefreshLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return <Modal show={isOpen} onHide={onClose} size="lg">
|
return <Modal show={isOpen} onHide={onClose} size="lg">
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title><h2>Nastavení</h2></Modal.Title>
|
<Modal.Title><h2>Nastavení</h2></Modal.Title>
|
||||||
@ -24,6 +59,48 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly<Prop
|
|||||||
<span title="V nabídkách nebudou zobrazovány polévky. Tato funkce je experimentální, a zejména u TechTower bývá často problém polévky spolehlivě rozeznat. V případě využití této funkce průběžně nahlašujte stále se zobrazující polévky." style={{ "cursor": "help" }}>
|
<span title="V nabídkách nebudou zobrazovány polévky. Tato funkce je experimentální, a zejména u TechTower bývá často problém polévky spolehlivě rozeznat. V případě využití této funkce průběžně nahlašujte stále se zobrazující polévky." style={{ "cursor": "help" }}>
|
||||||
<input ref={hideSoupsRef} type="checkbox" defaultChecked={settings?.hideSoups} /> Skrýt polévky
|
<input ref={hideSoupsRef} type="checkbox" defaultChecked={settings?.hideSoups} /> Skrýt polévky
|
||||||
</span>
|
</span>
|
||||||
|
<hr />
|
||||||
|
<h4>Obnovit jídelníček</h4>
|
||||||
|
<p>Ruční refresh dat z restaurací.</p>
|
||||||
|
|
||||||
|
{refreshMessage && (
|
||||||
|
<Alert variant={refreshMessage.type === 'success' ? 'success' : 'danger'}>
|
||||||
|
{refreshMessage.text}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
Heslo: <input
|
||||||
|
ref={refreshPassRef}
|
||||||
|
type="password"
|
||||||
|
placeholder="Zadejte heslo"
|
||||||
|
className="form-control d-inline-block"
|
||||||
|
style={{ width: 'auto', marginLeft: '10px' }}
|
||||||
|
onKeyDown={e => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
Typ refreshe: <select
|
||||||
|
ref={refreshTypeRef}
|
||||||
|
className="form-select d-inline-block"
|
||||||
|
style={{ width: 'auto', marginLeft: '10px' }}
|
||||||
|
defaultValue="week"
|
||||||
|
>
|
||||||
|
<option value="week">Týden</option>
|
||||||
|
<option value="day">Den</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="info"
|
||||||
|
onClick={handleRefresh}
|
||||||
|
disabled={refreshLoading}
|
||||||
|
className="mb-3"
|
||||||
|
>
|
||||||
|
{refreshLoading ? 'Refreshing...' : 'Refresh'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<h4>Bankovní účet</h4>
|
<h4>Bankovní účet</h4>
|
||||||
<p>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.<br />Pokud vaše číslo účtu neobsahuje předčíslí, je možné ho zcela vynechat.<br /><br />Číslo účtu není ukládáno na serveru, posílá se na něj pouze za účelem vygenerování QR kódů.</p>
|
<p>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.<br />Pokud vaše číslo účtu neobsahuje předčíslí, je možné ho zcela vynechat.<br /><br />Číslo účtu není ukládáno na serveru, posílá se na něj pouze za účelem vygenerování QR kódů.</p>
|
||||||
|
@ -1,11 +1,52 @@
|
|||||||
import express, { Request, Response } from "express";
|
import express, { Request, Response } from "express";
|
||||||
import { getLogin, getTrusted } from "../auth";
|
import { getLogin, getTrusted } from "../auth";
|
||||||
import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote, getRestaurantMenu } from "../service";
|
import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote, getRestaurantMenu, fetchRestaurantWeekMenuData, saveRestaurantWeekMenu } from "../service";
|
||||||
import { getDayOfWeekIndex, parseToken, getFirstWorkDayOfWeek } from "../utils";
|
import { getDayOfWeekIndex, parseToken, getFirstWorkDayOfWeek } from "../utils";
|
||||||
import { getWebsocket } from "../websocket";
|
import { getWebsocket } from "../websocket";
|
||||||
import { callNotifikace } from "../notifikace";
|
import { callNotifikace } from "../notifikace";
|
||||||
import { AddChoiceData, ChangeDepartureTimeData, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen";
|
import { AddChoiceData, ChangeDepartureTimeData, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen";
|
||||||
|
|
||||||
|
|
||||||
|
// RateLimit na refresh endpoint
|
||||||
|
interface RateLimitEntry {
|
||||||
|
count: number;
|
||||||
|
resetTime: number;
|
||||||
|
}
|
||||||
|
const rateLimits: Record<string, RateLimitEntry> = {};
|
||||||
|
const RATE_LIMIT = 1; // maximální počet požadavků za minutu
|
||||||
|
const RATE_LIMIT_WINDOW = 30 * 60 * 1000; // je to v ms (x * 1min)
|
||||||
|
|
||||||
|
// Kontrola ratelimitu
|
||||||
|
function checkRateLimit(key: string, limit: number = RATE_LIMIT): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Vyčištění starých záznamů
|
||||||
|
Object.keys(rateLimits).forEach(k => {
|
||||||
|
if (rateLimits[k].resetTime < now) {
|
||||||
|
delete rateLimits[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kontrola, že záznam existuje a platí
|
||||||
|
if (rateLimits[key] && rateLimits[key].resetTime > now) {
|
||||||
|
// Záznam platí a kontroluje se limit
|
||||||
|
if (rateLimits[key].count >= limit) {
|
||||||
|
return false; // Překročen limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// ++ xd
|
||||||
|
rateLimits[key].count++;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// + klic
|
||||||
|
rateLimits[key] = {
|
||||||
|
count: 1,
|
||||||
|
resetTime: now + RATE_LIMIT_WINDOW
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň
|
* Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň
|
||||||
* roven nebo vyšší indexu dnešního dne.
|
* roven nebo vyšší indexu dnešního dne.
|
||||||
@ -144,21 +185,77 @@ router.post("/jdemeObed", async (req, res, next) => {
|
|||||||
// /api/food/refresh?type=week&heslo=docasnyheslo
|
// /api/food/refresh?type=week&heslo=docasnyheslo
|
||||||
router.get("/refresh", async (req: Request, res: Response) => {
|
router.get("/refresh", async (req: Request, res: Response) => {
|
||||||
const { type, heslo } = req.query as { type?: string; heslo?: string };
|
const { type, heslo } = req.query as { type?: string; heslo?: string };
|
||||||
if (heslo !== "docasnyheslo") {
|
if (heslo !== "docasnyheslo" && heslo !== "tohleheslopavelnesmizjistit123") {
|
||||||
return res.status(403).json({ error: "Neplatné heslo" });
|
return res.status(403).json({ error: "Neplatné heslo" });
|
||||||
}
|
}
|
||||||
if (type !== "week") {
|
if (!checkRateLimit("refresh") && heslo !== "tohleheslopavelnesmizjistit123") {
|
||||||
|
return res.status(429).json({ error: "Refresh už se zavolal, chvíli počkej :))" });
|
||||||
|
}
|
||||||
|
if (type !== "week" && type !== "day") {
|
||||||
return res.status(400).json({ error: "Neznámý typ refresh" });
|
return res.status(400).json({ error: "Neznámý typ refresh" });
|
||||||
}
|
}
|
||||||
|
if (type === "day") {
|
||||||
|
return res.status(400).json({ error: "ještě neumim TODO..." });
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Pro všechny restaurace refreshni menu na aktuální týden
|
// Pro všechny restaurace refreshni menu na aktuální týden
|
||||||
const restaurants = ["SLADOVNICKA", "TECHTOWER", "ZASTAVKAUMICHALA", "SENKSERIKOVA"] as const;
|
const restaurants = ["SLADOVNICKA", "TECHTOWER", "ZASTAVKAUMICHALA", "SENKSERIKOVA"] as const;
|
||||||
const firstDay = getFirstWorkDayOfWeek(getToday());
|
const firstDay = getFirstWorkDayOfWeek(getToday());
|
||||||
const results: Record<string, any> = {};
|
const results: Record<string, any> = {};
|
||||||
|
const successfulRestaurants: string[] = [];
|
||||||
|
const failedRestaurants: string[] = [];
|
||||||
|
|
||||||
|
// Nejdříve načíst všechna data bez ukládání
|
||||||
for (const rest of restaurants) {
|
for (const rest of restaurants) {
|
||||||
results[rest] = await getRestaurantMenu(rest, firstDay, true);
|
try {
|
||||||
|
const weekData = await fetchRestaurantWeekMenuData(rest, firstDay);
|
||||||
|
results[rest] = weekData;
|
||||||
|
|
||||||
|
// Kontrola validity dat
|
||||||
|
if (weekData && weekData.length > 0 &&
|
||||||
|
weekData.some(dayMenu => dayMenu && dayMenu.length > 0)) {
|
||||||
|
successfulRestaurants.push(rest);
|
||||||
|
} else {
|
||||||
|
failedRestaurants.push(rest);
|
||||||
|
results[rest] = { error: "Žádná validní data" };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
failedRestaurants.push(rest);
|
||||||
|
results[rest] = { error: `Chyba při načítání: ${error}` };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.status(200).json({ ok: true, refreshed: results });
|
|
||||||
|
// Pokud se nepodařilo načíst žádnou restauraci
|
||||||
|
if (successfulRestaurants.length === 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "Nepodařilo se získat validní data z žádné restaurace",
|
||||||
|
failed: failedRestaurants,
|
||||||
|
results: results
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uložit pouze validní data
|
||||||
|
for (const rest of successfulRestaurants) {
|
||||||
|
try {
|
||||||
|
await saveRestaurantWeekMenu(rest as any, firstDay, results[rest]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Chyba při ukládání dat pro ${rest}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Připravit odpověď
|
||||||
|
const response: any = {
|
||||||
|
ok: true,
|
||||||
|
refreshed: results,
|
||||||
|
successful: successfulRestaurants
|
||||||
|
};
|
||||||
|
|
||||||
|
if (failedRestaurants.length > 0) {
|
||||||
|
response.warning = `Nepodařilo se načíst: ${failedRestaurants.join(', ')}`;
|
||||||
|
response.failed = failedRestaurants;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(response);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
res.status(500).json({ error: e?.message || "Chyba při refreshi" });
|
res.status(500).json({ error: e?.message || "Chyba při refreshi" });
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user