Compare commits

..

No commits in common. "8ec87ec2003433b7600770c7c59d1fbe5bd5713f" and "8a67325c8528e7ac73857ad9a536442174ae565a" have entirely different histories.

5 changed files with 47 additions and 115 deletions

View File

@ -1,39 +1,18 @@
import { toast } from "react-toastify";
import { PizzaOrder } from "./types";
import { getBaseUrl, getToken } from "./Utils";
/**
* Wrapper pro volání API, u kterých chceme automaticky zobrazit toaster s chybou ze serveru.
*
* @param apiFunction volaná API funkce
*/
export function errorHandler<T>(apiFunction: () => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
apiFunction().then((result) => {
resolve(result);
}).catch(e => {
toast.error(e.message, { theme: "colored" });
});
});
}
async function request<TResponse>(
url: string,
config: RequestInit = {}
): Promise<TResponse> {
config.headers = config?.headers ? new Headers(config.headers) : new Headers();
config.headers.set("Authorization", `Bearer ${getToken()}`);
try {
const response = await fetch(getBaseUrl() + url, config);
return fetch(getBaseUrl() + url, config).then(response => {
if (!response.ok) {
const json = await response.json();
// Vyhodíme samotnou hlášku z odpovědi, odchytí si jí errorHandler
throw new Error(json.error);
throw new Error(response.statusText);
}
return response.json() as TResponse;
} catch (e) {
return Promise.reject(e);
}
});
}
const api = {

View File

@ -1,7 +1,7 @@
import React, { useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react';
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket';
import { addChoice, addPizza, changeDepartureTime, createPizzaDay, deletePizzaDay, errorHandler, finishDelivery, finishOrder, getData, getQrUrl, lockPizzaDay, removeChoice, removeChoices, removePizza, unlockPizzaDay, updateNote } from './Api';
import { addChoice, addPizza, changeDepartureTime, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getQrUrl, lockPizzaDay, removeChoice, removeChoices, removePizza, unlockPizzaDay, updateNote } from './Api';
import { useAuth } from './context/auth';
import Login from './Login';
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
@ -156,22 +156,6 @@ function App() {
}
}, [choiceRef.current?.value, food])
// Navigace mezi dny pomocí klávesových šípek
const handleKeyDown = useCallback((e: any) => {
if (e.keyCode == 37 && dayIndex != null && dayIndex > 0) {
handleDayChange(dayIndex - 1);
} else if (e.keyCode == 39 && dayIndex != null && dayIndex < 4) {
handleDayChange(dayIndex + 1);
}
}, [dayIndex]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
}
}, [handleKeyDown]);
// Index v týdnu dnešního dne (0-6)
// TODO tohle má posílat server, klient je nespolehlivý
const currentDayIndex = useMemo(() => {
@ -181,7 +165,7 @@ function App() {
const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
const index = Object.keys(Locations).indexOf(event.target.value as unknown as Locations);
if (auth?.login) {
await errorHandler(() => addChoice(index, undefined, dayIndex));
await addChoice(index, undefined, dayIndex);
if (foodChoiceRef.current?.value) {
foodChoiceRef.current.value = "";
}
@ -193,14 +177,14 @@ function App() {
const restaurantKey = choiceRef.current.value;
if (auth?.login) {
const locationIndex = Object.keys(Locations).indexOf(restaurantKey as unknown as Locations);
await errorHandler(() => addChoice(locationIndex, Number(event.target.value), dayIndex));
await addChoice(locationIndex, Number(event.target.value), dayIndex);
}
}
}
const doRemoveChoices = async (locationKey: string) => {
if (auth?.login) {
await errorHandler(() => removeChoices(Number(locationKey), dayIndex));
await removeChoices(Number(locationKey), dayIndex);
// Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo
if (choiceRef?.current?.value) {
choiceRef.current.value = "";
@ -213,7 +197,7 @@ function App() {
const doRemoveFoodChoice = async (locationKey: string, foodIndex: number) => {
if (auth?.login) {
await errorHandler(() => removeChoice(Number(locationKey), foodIndex, dayIndex));
await removeChoice(Number(locationKey), foodIndex, dayIndex);
if (choiceRef?.current?.value) {
choiceRef.current.value = "";
}
@ -374,8 +358,11 @@ function App() {
<Alert variant={'primary'}>
Poslední změny:
<ul>
<li>Ochrana proti některým Stánkovinám</li>
<li>Navigace mezi dny klávesovými šipkami</li>
<li>Možnost náhledu na celý týden a výběru na následující dny v týdnu</li>
<ul>
<li>Pizza day je možno založit pouze pro aktuální den</li>
<li>Ne, šipky na klávesnici zatím nefungují</li>
</ul>
</ul>
</Alert>
{dayIndex != null &&

View File

@ -7,7 +7,7 @@ import dotenv from 'dotenv';
import path from 'path';
import { getQr } from "./qr";
import { generateToken, getLogin, getTrusted, verify } from "./auth";
import { InsufficientPermissions, getDayOfWeekIndex } from "./utils";
import { getDayOfWeekIndex } from "./utils";
const ENVIRONMENT = process.env.NODE_ENV || 'production';
dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) });
@ -219,7 +219,7 @@ app.post("/api/finishDelivery", async (req, res) => {
res.status(200).json({});
});
app.post("/api/addChoice", async (req, res, next) => {
app.post("/api/addChoice", async (req, res) => {
const login = getLogin(parseToken(req));
const trusted = getTrusted(parseToken(req));
if (req.body.locationIndex > -1) {
@ -233,18 +233,15 @@ app.post("/api/addChoice", async (req, res, next) => {
}
date = getDateForWeekIndex(dayIndex);
}
try {
const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
io.emit("message", data);
return res.status(200).json(data);
} catch (e: any) { next(e) }
const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
io.emit("message", data);
return res.status(200).json(data);
}
return res.status(400); // TODO přidat popis chyby
});
app.post("/api/removeChoices", async (req, res, next) => {
app.post("/api/removeChoices", async (req, res) => {
const login = getLogin(parseToken(req));
const trusted = getTrusted(parseToken(req));
let date = undefined;
if (req.body.dayIndex != null) {
let dayIndex;
@ -255,16 +252,13 @@ app.post("/api/removeChoices", async (req, res, next) => {
}
date = getDateForWeekIndex(dayIndex);
}
try {
const data = await removeChoices(login, trusted, req.body.locationIndex, date);
io.emit("message", data);
res.status(200).json(data);
} catch (e: any) { next(e) }
const data = await removeChoices(login, req.body.locationIndex, date);
io.emit("message", data);
res.status(200).json(data);
});
app.post("/api/removeChoice", async (req, res, next) => {
app.post("/api/removeChoice", async (req, res) => {
const login = getLogin(parseToken(req));
const trusted = getTrusted(parseToken(req));
let date = undefined;
if (req.body.dayIndex != null) {
let dayIndex;
@ -275,11 +269,9 @@ app.post("/api/removeChoice", async (req, res, next) => {
}
date = getDateForWeekIndex(dayIndex);
}
try {
const data = await removeChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
io.emit("message", data);
res.status(200).json(data);
} catch (e: any) { next(e) }
const data = await removeChoice(login, req.body.locationIndex, req.body.foodIndex, date);
io.emit("message", data);
res.status(200).json(data);
});
// TODO přejmenovat, ať je jasné, že to patří k Pizza day
@ -310,16 +302,6 @@ app.post("/api/changeDepartureTime", async (req, res) => {
res.status(200).json(data);
});
// Middleware pro zpracování chyb
app.use((err: any, req: any, res: any, next: any) => {
if (err instanceof InsufficientPermissions) {
res.status(403).send({ error: err.message })
} else {
res.status(500).send({ error: err.message })
}
next();
});
io.on("connection", (socket) => {
console.log(`New client connected: ${socket.id}`);

View File

@ -1,7 +1,7 @@
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils";
import { formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils";
import { callNotifikace } from "./notifikace";
import { generateQr } from "./qr";
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, Locations, Restaurants, Menu } from "../../types";
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, Locations, Restaurants, Food, Menu } from "../../types";
import getStorage from "./storage";
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
import { downloadPizzy } from "./chefie";
@ -345,15 +345,14 @@ export async function initIfNeeded(date?: Date) {
* Odstraní kompletně volbu uživatele (včetně případných podřízených jídel).
*
* @param login login uživatele
* @param trusted příznak, zda se jedná o ověřeného uživatele
* @param location vybrané "umístění"
* @param date datum, ke kterému se volba vztahuje
* @returns
*/
export async function removeChoices(login: string, trusted: boolean, location: Locations, date?: Date) {
export async function removeChoices(login: string, location: Locations, date?: Date) {
const selectedDay = formatDate(date ?? getToday());
let data: ClientData = await storage.getData(selectedDay);
validateTrusted(data, login, trusted);
// TODO zajistit, že neověřený uživatel se stejným loginem nemůže mazat volby ověřeného
if (location in data.choices) {
if (login in data.choices[location]) {
delete data.choices[location][login]
@ -371,16 +370,15 @@ export async function removeChoices(login: string, trusted: boolean, location: L
* Neodstraňuje volbu samotnou, k tomu slouží {@link removeChoices}.
*
* @param login login uživatele
* @param trusted příznak, zda se jedná o ověřeného uživatele
* @param location vybrané "umístění"
* @param foodIndex index jídla v jídelním lístku daného umístění, pokud existuje
* @param date datum, ke kterému se volba vztahuje
* @returns
*/
export async function removeChoice(login: string, trusted: boolean, location: Locations, foodIndex: number, date?: Date) {
export async function removeChoice(login: string, location: Locations, foodIndex: number, date?: Date) {
const selectedDay = formatDate(date ?? getToday());
let data: ClientData = await storage.getData(selectedDay);
validateTrusted(data, login, trusted);
// TODO řešit ověření uživatele
if (location in data.choices) {
if (login in data.choices[location]) {
const index = data.choices[location][login].options.indexOf(foodIndex);
@ -412,33 +410,10 @@ async function removeChoiceIfPresent(login: string, date: string) {
return data;
}
/**
* Ověří, zda se neověřený uživatel nepokouší přepsat údaje ověřeného a případně vyhodí chybu.
*
* @param data aktuální klientská data
* @param login přihlašovací jméno uživatele
* @param trusted příznak, zda se jedná o ověřeného uživatele
*/
function validateTrusted(data: ClientData, login: string, trusted: boolean) {
const locations = Object.values(data?.choices);
let found = false;
if (!trusted) {
for (const location of locations) {
if (Object.keys(location).includes(login) && location[login].trusted) {
found = true;
}
}
}
if (!trusted && found) {
throw new InsufficientPermissions("Nelze změnit volbu ověřeného uživatele");
}
}
/**
* Přidá volbu uživatele.
*
* @param login login uživatele
* @param trusted příznak, zda se jedná o ověřeného uživatele
* @param location vybrané "umístění"
* @param foodIndex volitelný index jídla v daném umístění
* @param trusted příznak, zda se jedná o ověřeného uživatele
@ -449,7 +424,19 @@ export async function addChoice(login: string, trusted: boolean, location: Locat
await initIfNeeded();
const selectedDate = formatDate(date ?? getToday());
let data: ClientData = await storage.getData(selectedDate);
validateTrusted(data, login, trusted);
// Ověření, že se neověřený užívatel nepokouší přepsat údaje ověřeného
const locations = Object.values(data?.choices);
let found = false;
if (!trusted) {
for (const location of locations) {
if (Object.keys(location).includes(login) && location[login].trusted) {
found = true;
}
}
}
if (!trusted && found) {
throw Error("Nelze změnit volbu ověřeného uživatele");
}
// Pokud měníme pouze lokaci, mažeme případné předchozí
if (foodIndex == null) {
data = await removeChoiceIfPresent(login, selectedDate);

View File

@ -37,7 +37,4 @@ export const getDayOfWeekIndex = (date: Date) => {
export function getIsWeekend(date: Date) {
const index = getDayOfWeekIndex(date);
return index == 5 || index == 6;
}
// TODO umístit do samostatného souboru
export class InsufficientPermissions extends Error { }
}