Zpracování chyb z API
This commit is contained in:
parent
8a67325c85
commit
bc181defa8
@ -1,18 +1,39 @@
|
|||||||
|
import { toast } from "react-toastify";
|
||||||
import { PizzaOrder } from "./types";
|
import { PizzaOrder } from "./types";
|
||||||
import { getBaseUrl, getToken } from "./Utils";
|
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>(
|
async function request<TResponse>(
|
||||||
url: string,
|
url: string,
|
||||||
config: RequestInit = {}
|
config: RequestInit = {}
|
||||||
): Promise<TResponse> {
|
): Promise<TResponse> {
|
||||||
config.headers = config?.headers ? new Headers(config.headers) : new Headers();
|
config.headers = config?.headers ? new Headers(config.headers) : new Headers();
|
||||||
config.headers.set("Authorization", `Bearer ${getToken()}`);
|
config.headers.set("Authorization", `Bearer ${getToken()}`);
|
||||||
return fetch(getBaseUrl() + url, config).then(response => {
|
try {
|
||||||
|
const response = await fetch(getBaseUrl() + url, config);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(response.statusText);
|
const json = await response.json();
|
||||||
|
// Vyhodíme samotnou hlášku z odpovědi, odchytí si jí errorHandler
|
||||||
|
throw new Error(json.error);
|
||||||
}
|
}
|
||||||
return response.json() as TResponse;
|
return response.json() as TResponse;
|
||||||
});
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket';
|
import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket';
|
||||||
import { addChoice, addPizza, changeDepartureTime, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getQrUrl, lockPizzaDay, removeChoice, removeChoices, removePizza, unlockPizzaDay, updateNote } from './Api';
|
import { addChoice, addPizza, changeDepartureTime, createPizzaDay, deletePizzaDay, errorHandler, finishDelivery, finishOrder, getData, getQrUrl, lockPizzaDay, removeChoice, removeChoices, removePizza, unlockPizzaDay, updateNote } from './Api';
|
||||||
import { useAuth } from './context/auth';
|
import { useAuth } from './context/auth';
|
||||||
import Login from './Login';
|
import Login from './Login';
|
||||||
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
|
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
|
||||||
@ -165,7 +165,7 @@ function App() {
|
|||||||
const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const index = Object.keys(Locations).indexOf(event.target.value as unknown as Locations);
|
const index = Object.keys(Locations).indexOf(event.target.value as unknown as Locations);
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
await addChoice(index, undefined, dayIndex);
|
await errorHandler(() => addChoice(index, undefined, dayIndex));
|
||||||
if (foodChoiceRef.current?.value) {
|
if (foodChoiceRef.current?.value) {
|
||||||
foodChoiceRef.current.value = "";
|
foodChoiceRef.current.value = "";
|
||||||
}
|
}
|
||||||
@ -177,14 +177,14 @@ function App() {
|
|||||||
const restaurantKey = choiceRef.current.value;
|
const restaurantKey = choiceRef.current.value;
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
const locationIndex = Object.keys(Locations).indexOf(restaurantKey as unknown as Locations);
|
const locationIndex = Object.keys(Locations).indexOf(restaurantKey as unknown as Locations);
|
||||||
await addChoice(locationIndex, Number(event.target.value), dayIndex);
|
await errorHandler(() => addChoice(locationIndex, Number(event.target.value), dayIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const doRemoveChoices = async (locationKey: string) => {
|
const doRemoveChoices = async (locationKey: string) => {
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
await removeChoices(Number(locationKey), dayIndex);
|
await errorHandler(() => removeChoices(Number(locationKey), dayIndex));
|
||||||
// Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo
|
// Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo
|
||||||
if (choiceRef?.current?.value) {
|
if (choiceRef?.current?.value) {
|
||||||
choiceRef.current.value = "";
|
choiceRef.current.value = "";
|
||||||
@ -197,7 +197,7 @@ function App() {
|
|||||||
|
|
||||||
const doRemoveFoodChoice = async (locationKey: string, foodIndex: number) => {
|
const doRemoveFoodChoice = async (locationKey: string, foodIndex: number) => {
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
await removeChoice(Number(locationKey), foodIndex, dayIndex);
|
await errorHandler(() => removeChoice(Number(locationKey), foodIndex, dayIndex));
|
||||||
if (choiceRef?.current?.value) {
|
if (choiceRef?.current?.value) {
|
||||||
choiceRef.current.value = "";
|
choiceRef.current.value = "";
|
||||||
}
|
}
|
||||||
@ -358,11 +358,7 @@ function App() {
|
|||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Možnost náhledu na celý týden a výběru na následující dny v týdnu</li>
|
<li>Ochrana proti některým Stánkovinám</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>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
{dayIndex != null &&
|
{dayIndex != null &&
|
||||||
|
@ -7,7 +7,7 @@ import dotenv from 'dotenv';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getQr } from "./qr";
|
import { getQr } from "./qr";
|
||||||
import { generateToken, getLogin, getTrusted, verify } from "./auth";
|
import { generateToken, getLogin, getTrusted, verify } from "./auth";
|
||||||
import { getDayOfWeekIndex } from "./utils";
|
import { InsufficientPermissions, getDayOfWeekIndex } from "./utils";
|
||||||
|
|
||||||
const ENVIRONMENT = process.env.NODE_ENV || 'production';
|
const ENVIRONMENT = process.env.NODE_ENV || 'production';
|
||||||
dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) });
|
dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) });
|
||||||
@ -219,7 +219,7 @@ app.post("/api/finishDelivery", async (req, res) => {
|
|||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/addChoice", async (req, res) => {
|
app.post("/api/addChoice", async (req, res, next) => {
|
||||||
const login = getLogin(parseToken(req));
|
const login = getLogin(parseToken(req));
|
||||||
const trusted = getTrusted(parseToken(req));
|
const trusted = getTrusted(parseToken(req));
|
||||||
if (req.body.locationIndex > -1) {
|
if (req.body.locationIndex > -1) {
|
||||||
@ -233,15 +233,18 @@ app.post("/api/addChoice", async (req, res) => {
|
|||||||
}
|
}
|
||||||
date = getDateForWeekIndex(dayIndex);
|
date = getDateForWeekIndex(dayIndex);
|
||||||
}
|
}
|
||||||
const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
|
try {
|
||||||
io.emit("message", data);
|
const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
|
||||||
return res.status(200).json(data);
|
io.emit("message", data);
|
||||||
|
return res.status(200).json(data);
|
||||||
|
} catch (e: any) { next(e) }
|
||||||
}
|
}
|
||||||
return res.status(400); // TODO přidat popis chyby
|
return res.status(400); // TODO přidat popis chyby
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/removeChoices", async (req, res) => {
|
app.post("/api/removeChoices", async (req, res, next) => {
|
||||||
const login = getLogin(parseToken(req));
|
const login = getLogin(parseToken(req));
|
||||||
|
const trusted = getTrusted(parseToken(req));
|
||||||
let date = undefined;
|
let date = undefined;
|
||||||
if (req.body.dayIndex != null) {
|
if (req.body.dayIndex != null) {
|
||||||
let dayIndex;
|
let dayIndex;
|
||||||
@ -252,13 +255,16 @@ app.post("/api/removeChoices", async (req, res) => {
|
|||||||
}
|
}
|
||||||
date = getDateForWeekIndex(dayIndex);
|
date = getDateForWeekIndex(dayIndex);
|
||||||
}
|
}
|
||||||
const data = await removeChoices(login, req.body.locationIndex, date);
|
try {
|
||||||
io.emit("message", data);
|
const data = await removeChoices(login, trusted, req.body.locationIndex, date);
|
||||||
res.status(200).json(data);
|
io.emit("message", data);
|
||||||
|
res.status(200).json(data);
|
||||||
|
} catch (e: any) { next(e) }
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/removeChoice", async (req, res) => {
|
app.post("/api/removeChoice", async (req, res, next) => {
|
||||||
const login = getLogin(parseToken(req));
|
const login = getLogin(parseToken(req));
|
||||||
|
const trusted = getTrusted(parseToken(req));
|
||||||
let date = undefined;
|
let date = undefined;
|
||||||
if (req.body.dayIndex != null) {
|
if (req.body.dayIndex != null) {
|
||||||
let dayIndex;
|
let dayIndex;
|
||||||
@ -269,9 +275,11 @@ app.post("/api/removeChoice", async (req, res) => {
|
|||||||
}
|
}
|
||||||
date = getDateForWeekIndex(dayIndex);
|
date = getDateForWeekIndex(dayIndex);
|
||||||
}
|
}
|
||||||
const data = await removeChoice(login, req.body.locationIndex, req.body.foodIndex, date);
|
try {
|
||||||
io.emit("message", data);
|
const data = await removeChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
|
||||||
res.status(200).json(data);
|
io.emit("message", data);
|
||||||
|
res.status(200).json(data);
|
||||||
|
} catch (e: any) { next(e) }
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO přejmenovat, ať je jasné, že to patří k Pizza day
|
// TODO přejmenovat, ať je jasné, že to patří k Pizza day
|
||||||
@ -302,6 +310,16 @@ app.post("/api/changeDepartureTime", async (req, res) => {
|
|||||||
res.status(200).json(data);
|
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) => {
|
io.on("connection", (socket) => {
|
||||||
console.log(`New client connected: ${socket.id}`);
|
console.log(`New client connected: ${socket.id}`);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils";
|
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils";
|
||||||
import { callNotifikace } from "./notifikace";
|
import { callNotifikace } from "./notifikace";
|
||||||
import { generateQr } from "./qr";
|
import { generateQr } from "./qr";
|
||||||
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, Locations, Restaurants, Food, Menu } from "../../types";
|
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, Locations, Restaurants, Menu } from "../../types";
|
||||||
import getStorage from "./storage";
|
import getStorage from "./storage";
|
||||||
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
|
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
|
||||||
import { downloadPizzy } from "./chefie";
|
import { downloadPizzy } from "./chefie";
|
||||||
@ -345,14 +345,15 @@ export async function initIfNeeded(date?: Date) {
|
|||||||
* Odstraní kompletně volbu uživatele (včetně případných podřízených jídel).
|
* Odstraní kompletně volbu uživatele (včetně případných podřízených jídel).
|
||||||
*
|
*
|
||||||
* @param login login 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 location vybrané "umístění"
|
||||||
* @param date datum, ke kterému se volba vztahuje
|
* @param date datum, ke kterému se volba vztahuje
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function removeChoices(login: string, location: Locations, date?: Date) {
|
export async function removeChoices(login: string, trusted: boolean, location: Locations, date?: Date) {
|
||||||
const selectedDay = formatDate(date ?? getToday());
|
const selectedDay = formatDate(date ?? getToday());
|
||||||
let data: ClientData = await storage.getData(selectedDay);
|
let data: ClientData = await storage.getData(selectedDay);
|
||||||
// TODO zajistit, že neověřený uživatel se stejným loginem nemůže mazat volby ověřeného
|
validateTrusted(data, login, trusted);
|
||||||
if (location in data.choices) {
|
if (location in data.choices) {
|
||||||
if (login in data.choices[location]) {
|
if (login in data.choices[location]) {
|
||||||
delete data.choices[location][login]
|
delete data.choices[location][login]
|
||||||
@ -370,15 +371,16 @@ export async function removeChoices(login: string, location: Locations, date?: D
|
|||||||
* Neodstraňuje volbu samotnou, k tomu slouží {@link removeChoices}.
|
* Neodstraňuje volbu samotnou, k tomu slouží {@link removeChoices}.
|
||||||
*
|
*
|
||||||
* @param login login 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 location vybrané "umístění"
|
||||||
* @param foodIndex index jídla v jídelním lístku daného umístění, pokud existuje
|
* @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
|
* @param date datum, ke kterému se volba vztahuje
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function removeChoice(login: string, location: Locations, foodIndex: number, date?: Date) {
|
export async function removeChoice(login: string, trusted: boolean, location: Locations, foodIndex: number, date?: Date) {
|
||||||
const selectedDay = formatDate(date ?? getToday());
|
const selectedDay = formatDate(date ?? getToday());
|
||||||
let data: ClientData = await storage.getData(selectedDay);
|
let data: ClientData = await storage.getData(selectedDay);
|
||||||
// TODO řešit ověření uživatele
|
validateTrusted(data, login, trusted);
|
||||||
if (location in data.choices) {
|
if (location in data.choices) {
|
||||||
if (login in data.choices[location]) {
|
if (login in data.choices[location]) {
|
||||||
const index = data.choices[location][login].options.indexOf(foodIndex);
|
const index = data.choices[location][login].options.indexOf(foodIndex);
|
||||||
@ -411,20 +413,13 @@ async function removeChoiceIfPresent(login: string, date: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Přidá volbu uživatele.
|
* Ověří, zda se neověřený uživatel nepokouší přepsat údaje ověřeného a případně vyhodí chybu.
|
||||||
*
|
*
|
||||||
* @param login login uživatele
|
* @param data aktuální klientská data
|
||||||
* @param location vybrané "umístění"
|
* @param login přihlašovací jméno uživatele
|
||||||
* @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
|
* @param trusted příznak, zda se jedná o ověřeného uživatele
|
||||||
* @param date datum, ke kterému se volba vztahuje
|
|
||||||
* @returns aktuální data
|
|
||||||
*/
|
*/
|
||||||
export async function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number, date?: Date) {
|
function validateTrusted(data: ClientData, login: string, trusted: boolean) {
|
||||||
await initIfNeeded();
|
|
||||||
const selectedDate = formatDate(date ?? getToday());
|
|
||||||
let data: ClientData = await storage.getData(selectedDate);
|
|
||||||
// Ověření, že se neověřený užívatel nepokouší přepsat údaje ověřeného
|
|
||||||
const locations = Object.values(data?.choices);
|
const locations = Object.values(data?.choices);
|
||||||
let found = false;
|
let found = false;
|
||||||
if (!trusted) {
|
if (!trusted) {
|
||||||
@ -435,8 +430,26 @@ export async function addChoice(login: string, trusted: boolean, location: Locat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!trusted && found) {
|
if (!trusted && found) {
|
||||||
throw Error("Nelze změnit volbu ověřeného uživatele");
|
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
|
||||||
|
* @param date datum, ke kterému se volba vztahuje
|
||||||
|
* @returns aktuální data
|
||||||
|
*/
|
||||||
|
export async function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number, date?: Date) {
|
||||||
|
await initIfNeeded();
|
||||||
|
const selectedDate = formatDate(date ?? getToday());
|
||||||
|
let data: ClientData = await storage.getData(selectedDate);
|
||||||
|
validateTrusted(data, login, trusted);
|
||||||
// Pokud měníme pouze lokaci, mažeme případné předchozí
|
// Pokud měníme pouze lokaci, mažeme případné předchozí
|
||||||
if (foodIndex == null) {
|
if (foodIndex == null) {
|
||||||
data = await removeChoiceIfPresent(login, selectedDate);
|
data = await removeChoiceIfPresent(login, selectedDate);
|
||||||
|
@ -37,4 +37,7 @@ export const getDayOfWeekIndex = (date: Date) => {
|
|||||||
export function getIsWeekend(date: Date) {
|
export function getIsWeekend(date: Date) {
|
||||||
const index = getDayOfWeekIndex(date);
|
const index = getDayOfWeekIndex(date);
|
||||||
return index == 5 || index == 6;
|
return index == 5 || index == 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO umístit do samostatného souboru
|
||||||
|
export class InsufficientPermissions extends Error { }
|
Loading…
x
Reference in New Issue
Block a user