Přesun autentizace na server

This commit is contained in:
Martin Berka 2023-06-28 00:18:33 +02:00
parent 43112d7c52
commit d0ec131d30
11 changed files with 140 additions and 161 deletions

View File

@ -54,7 +54,7 @@ Aplikace sestává ze dvou (tří) modulů.
- [ ] Popsat nginx - [ ] Popsat nginx
- [x] Popsat závislosti, co je nutné provést před vývojem a postup spuštění pro vývoj - [x] Popsat závislosti, co je nutné provést před vývojem a postup spuštění pro vývoj
- [x] Popsat dostupné env - [x] Popsat dostupné env
- [ ] Přesunout autentizaci na server (JWT?) - [x] Přesunout autentizaci na server (JWT?)
- [x] Zavést .env.template a přidat .env do .gitignore - [x] Zavést .env.template a přidat .env do .gitignore
- [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně - [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně
- [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams) - [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams)

View File

@ -1,10 +1,14 @@
import { PizzaOrder } from "./Types"; import { PizzaOrder } from "./Types";
import { getBaseUrl } from "./Utils"; import { getBaseUrl, getToken } from "./Utils";
async function request<TResponse>( async function request<TResponse>(
url: string, url: string,
config: RequestInit = {} config: RequestInit = {}
): Promise<TResponse> { ): Promise<TResponse> {
if (!config.headers) {
config.headers = {};
}
config.headers["Authorization"] = `Bearer ${getToken()}`;
return fetch(getBaseUrl() + url, config).then(response => { return fetch(getBaseUrl() + url, config).then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error(response.statusText); throw new Error(response.statusText);
@ -18,8 +22,8 @@ const api = {
post: <TBody extends BodyInit, TResponse>(url: string, body: TBody) => request<TResponse>(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }), post: <TBody extends BodyInit, TResponse>(url: string, body: TBody) => request<TResponse>(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }),
} }
export const getQrUrl = (login: string) => { export const getQrUrl = () => {
return `${getBaseUrl()}/api/qr?login=${login}`; return `${getBaseUrl()}/api/qr`;
} }
export const getData = async () => { export const getData = async () => {
@ -34,44 +38,44 @@ export const getPizzy = async () => {
return await api.get<any>('/api/pizza'); return await api.get<any>('/api/pizza');
} }
export const createPizzaDay = async (creator) => { export const createPizzaDay = async () => {
return await api.post<any, any>('/api/createPizzaDay', JSON.stringify({ creator })); return await api.post<any, any>('/api/createPizzaDay', undefined);
} }
export const deletePizzaDay = async (login) => { export const deletePizzaDay = async () => {
return await api.post<any, any>('/api/deletePizzaDay', JSON.stringify({ login })); return await api.post<any, any>('/api/deletePizzaDay', undefined);
} }
export const lockPizzaDay = async (login) => { export const lockPizzaDay = async () => {
return await api.post<any, any>('/api/lockPizzaDay', JSON.stringify({ login })); return await api.post<any, any>('/api/lockPizzaDay', undefined);
} }
export const unlockPizzaDay = async (login) => { export const unlockPizzaDay = async () => {
return await api.post<any, any>('/api/unlockPizzaDay', JSON.stringify({ login })); return await api.post<any, any>('/api/unlockPizzaDay', undefined);
} }
export const finishOrder = async (login) => { export const finishOrder = async () => {
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login })); return await api.post<any, any>('/api/finishOrder', undefined);
} }
export const finishDelivery = async (login, bankAccount, bankAccountHolder) => { export const finishDelivery = async (bankAccount, bankAccountHolder) => {
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder })); return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ bankAccount, bankAccountHolder }));
} }
export const updateChoice = async (name: string, choice: number | null) => { export const updateChoice = async (choice: number | null) => {
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ name, choice })); return await api.post<any, any>('/api/updateChoice', JSON.stringify({ choice }));
} }
export const addPizza = async (login: string, pizzaIndex: number, pizzaSizeIndex: number) => { export const addPizza = async (pizzaIndex: number, pizzaSizeIndex: number) => {
return await api.post<any, any>('/api/addPizza', JSON.stringify({ login, pizzaIndex, pizzaSizeIndex })); return await api.post<any, any>('/api/addPizza', JSON.stringify({ pizzaIndex, pizzaSizeIndex }));
} }
export const removePizza = async (login: string, pizzaOrder: PizzaOrder) => { export const removePizza = async (pizzaOrder: PizzaOrder) => {
return await api.post<any, any>('/api/removePizza', JSON.stringify({ login, pizzaOrder })); return await api.post<any, any>('/api/removePizza', JSON.stringify({ pizzaOrder }));
} }
export const updateNote = async (login: string, note?: string) => { export const updateNote = async (note?: string) => {
return await api.post<any, any>('/api/updateNote', JSON.stringify({ login, note })); return await api.post<any, any>('/api/updateNote', JSON.stringify({ note }));
} }
export const login = async (login: string) => { export const login = async (login: string) => {

View File

@ -31,8 +31,11 @@ function App() {
const choiceRef = useRef<HTMLSelectElement>(null); const choiceRef = useRef<HTMLSelectElement>(null);
const poznamkaRef = useRef<HTMLInputElement>(null); const poznamkaRef = useRef<HTMLInputElement>(null);
// Prvotní načtení aktuálního stavu // Načtení dat po přihlášení
useEffect(() => { useEffect(() => {
if (!auth || !auth.login) {
return
}
getPizzy().then(pizzy => { getPizzy().then(pizzy => {
setPizzy(pizzy); setPizzy(pizzy);
}); });
@ -42,7 +45,7 @@ function App() {
getFood().then(food => { getFood().then(food => {
setFood(food); setFood(food);
}) })
}, []); }, [auth, auth?.login]);
// Registrace socket eventů // Registrace socket eventů
useEffect(() => { useEffect(() => {
@ -91,13 +94,13 @@ function App() {
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => { const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations); const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
if (auth?.login) { if (auth?.login) {
await updateChoice(auth.login, index > -1 ? index : null); await updateChoice(index > -1 ? index : null);
} }
} }
const removeChoice = async (key: string) => { const removeChoice = async (key: string) => {
if (auth?.login) { if (auth?.login) {
await updateChoice(auth.login, null); await updateChoice(null);
if (choiceRef?.current?.value) { if (choiceRef?.current?.value) {
choiceRef.current.value = ""; choiceRef.current.value = "";
} }
@ -126,24 +129,20 @@ function App() {
const s = value.split('|'); const s = value.split('|');
const pizzaIndex = Number.parseInt(s[0]); const pizzaIndex = Number.parseInt(s[0]);
const pizzaSizeIndex = Number.parseInt(s[1]); const pizzaSizeIndex = Number.parseInt(s[1]);
await addPizza(auth.login, pizzaIndex, pizzaSizeIndex); await addPizza(pizzaIndex, pizzaSizeIndex);
} }
} }
const handlePizzaDelete = async (pizzaOrder: PizzaOrder) => { const handlePizzaDelete = async (pizzaOrder: PizzaOrder) => {
if (auth?.login) { await removePizza(pizzaOrder);
await removePizza(auth?.login, pizzaOrder);
}
} }
const handlePoznamkaChange = async () => { const handlePoznamkaChange = async () => {
if (auth?.login) {
if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) { if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) {
alert("Poznámka může mít maximálně 100 znaků"); alert("Poznámka může mít maximálně 100 znaků");
return; return;
} }
updateNote(auth.login, poznamkaRef.current?.value); updateNote(poznamkaRef.current?.value);
}
} }
// const addToCart = async () => { // const addToCart = async () => {
@ -205,10 +204,7 @@ function App() {
<Alert variant={'primary'}> <Alert variant={'primary'}>
Poslední změny: Poslední změny:
<ul> <ul>
<li>Nová žárovka zatím funguje</li> <li>Zavedení JWT, přesun autentizace na server</li>
<li>Funkční generování a zobrazení QR kódů pro Pizza day</li>
<li>Možnost zadat k Pizza day objednávce poznámku</li>
<li>Zbavení se Food API, přepsání a zahrnutí parseru do serveru</li>
</ul> </ul>
</Alert> </Alert>
<h1 className='title'>Dnes je {data.date}</h1> <h1 className='title'>Dnes je {data.date}</h1>
@ -260,7 +256,7 @@ function App() {
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<p>Pro dnešní den není aktuálně založen Pizza day.</p> <p>Pro dnešní den není aktuálně založen Pizza day.</p>
<Button onClick={async () => { <Button onClick={async () => {
await createPizzaDay(auth.login); await createPizzaDay();
}}>Založit Pizza day</Button> }}>Založit Pizza day</Button>
</div> </div>
} }
@ -279,10 +275,10 @@ function App() {
data.pizzaDay.creator === auth.login && data.pizzaDay.creator === auth.login &&
<> <>
<Button className='danger mb-3' title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => { <Button className='danger mb-3' title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => {
await deletePizzaDay(auth.login); await deletePizzaDay();
}}>Smazat Pizza day</Button> }}>Smazat Pizza day</Button>
<Button className='mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => { <Button className='mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => {
await lockPizzaDay(auth.login); await lockPizzaDay();
}}>Uzamknout</Button> }}>Uzamknout</Button>
</> </>
} }
@ -295,13 +291,13 @@ function App() {
{data.pizzaDay.creator === auth.login && {data.pizzaDay.creator === auth.login &&
<> <>
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => { <Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
await unlockPizzaDay(auth.login); await unlockPizzaDay();
}}>Odemknout</Button> }}>Odemknout</Button>
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => { {/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
await addToCart(); await addToCart();
}}>Přidat vše do košíku</Button> */} }}>Přidat vše do košíku</Button> */}
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => { <Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
await finishOrder(auth.login); await finishOrder();
}}>Objednáno</Button> }}>Objednáno</Button>
</> </>
} }
@ -314,10 +310,10 @@ function App() {
{data.pizzaDay.creator === auth.login && {data.pizzaDay.creator === auth.login &&
<div> <div>
<Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => { <Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
await lockPizzaDay(auth.login); await lockPizzaDay();
}}>Vrátit do "uzamčeno"</Button> }}>Vrátit do "uzamčeno"</Button>
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => { <Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
await finishDelivery(auth.login, bank?.bankAccount, bank?.holderName); await finishDelivery(bank?.bankAccount, bank?.holderName);
}}>Doručeno</Button> }}>Doručeno</Button>
</div> </div>
} }
@ -357,7 +353,7 @@ function App() {
<div className='qr-code'> <div className='qr-code'>
<h3>QR platba</h3> <h3>QR platba</h3>
<div>Částka: {myOrder.totalPrice} </div> <div>Částka: {myOrder.totalPrice} </div>
<img src={getQrUrl(auth.login)} alt='QR kód' /> <img src={getQrUrl()} alt='QR kód' />
<p>Generování QR kódů je v experimentální fázi - doporučujeme si překontrolovat údaje před odesláním platby.</p> <p>Generování QR kódů je v experimentální fázi - doporučujeme si překontrolovat údaje před odesláním platby.</p>
</div> </div>
} }

View File

@ -1,8 +1,8 @@
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef } from 'react';
import { Button } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import { useAuth } from './context/auth'; import { useAuth } from './context/auth';
import './Login.css';
import { login } from './Api'; import { login } from './Api';
import './Login.css';
/** /**
* Formulář pro prvotní zadání přihlašovacího jména. * Formulář pro prvotní zadání přihlašovacího jména.
@ -17,7 +17,6 @@ export default function Login() {
// TODO odchytávat cokoliv mimo 200 // TODO odchytávat cokoliv mimo 200
const token = await login(loginRef.current.value); const token = await login(loginRef.current.value);
if (token) { if (token) {
console.log("Přijali jsme token", token); // TODO smazat
auth?.setToken(token); auth?.setToken(token);
} }
} }

View File

@ -10,29 +10,29 @@ export const getBaseUrl = (): string => {
return 'http://127.0.0.1:3001'; return 'http://127.0.0.1:3001';
} }
const LOGIN_KEY = "login"; const TOKEN_KEY = "token";
/** /**
* Uloží login do local storage prohlížeče. * Uloží token do local storage prohlížeče.
* *
* @param login login * @param token token
*/ */
export const storeLogin = (login: string) => { export const storeToken = (token: string) => {
localStorage.setItem(LOGIN_KEY, login); localStorage.setItem(TOKEN_KEY, token);
} }
/** /**
* Vrátí login z local storage, pokud tam je. * Vrátí token z local storage, pokud tam je.
* *
* @returns login nebo null * @returns token nebo null
*/ */
export const getLogin = (): string | null => { export const getToken = (): string | null => {
return localStorage.getItem(LOGIN_KEY); return localStorage.getItem(TOKEN_KEY);
} }
/** /**
* Odstraní login z local storage, pokud tam je. * Odstraní token z local storage, pokud tam je.
*/ */
export const deleteLogin = () => { export const deleteToken = () => {
localStorage.removeItem(LOGIN_KEY); localStorage.removeItem(TOKEN_KEY);
} }

View File

@ -83,7 +83,7 @@ export default function Header() {
<Nav className="nav"> <Nav className="nav">
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown"> <NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
<NavDropdown.Item onClick={openBankSettings}>Nastavit číslo účtu</NavDropdown.Item> <NavDropdown.Item onClick={openBankSettings}>Nastavit číslo účtu</NavDropdown.Item>
<NavDropdown.Item onClick={auth?.clearLogin}>Odhlásit se</NavDropdown.Item> <NavDropdown.Item onClick={auth?.logout}>Odhlásit se</NavDropdown.Item>
</NavDropdown> </NavDropdown>
</Nav> </Nav>
</Navbar.Collapse> </Navbar.Collapse>

View File

@ -1,8 +1,7 @@
import React, { ReactNode, useContext, useState } from "react" import React, { ReactNode, useContext, useState } from "react"
import { useEffect } from "react" import { useEffect } from "react"
import { useJwt } from "react-jwt"; import { useJwt } from "react-jwt";
import { deleteToken, getToken, storeToken } from "../Utils";
const TOKEN_KEY = 'token';
export type AuthContextProps = { export type AuthContextProps = {
login?: string, login?: string,
@ -26,50 +25,34 @@ export const useAuth = () => {
} }
function useProvideAuth(): AuthContextProps { function useProvideAuth(): AuthContextProps {
const token = localStorage.getItem(TOKEN_KEY);
const [loginName, setLoginName] = useState<string | undefined>(); const [loginName, setLoginName] = useState<string | undefined>();
let decodedToken, isExpired; const [token, setToken] = useState<string | null>(getToken());
if (token) { const { decodedToken } = useJwt(token || '');
const payload = useJwt(token);
decodedToken = payload?.decodedToken;
isExpired = payload?.isExpired
}
useEffect(() => { useEffect(() => {
if (token) { if (token && token.length > 0) {
if (decodedToken && !isExpired) { storeToken(token);
doSetToken(token);
setLoginName((decodedToken as any).login);
}
}
}, [decodedToken, isExpired])
useEffect(() => {
if (token) {
localStorage.setItem(TOKEN_KEY, token);
} else { } else {
localStorage.removeItem(TOKEN_KEY); deleteToken();
} }
}, [token]); }, [token]);
function doSetToken(token: string) { useEffect(() => {
if (decodedToken) {
if (!decodedToken || !((decodedToken as any).login)) {
throw Error("Chyba dekódování tokenu");
}
if (isExpired) {
throw Error("Platnost tokenu vypršela");
}
setLoginName((decodedToken as any).login); setLoginName((decodedToken as any).login);
} else {
setLoginName(undefined);
} }
}, [decodedToken]);
function logout() { function logout() {
setToken(null);
setLoginName(undefined); setLoginName(undefined);
} }
return { return {
login: loginName, login: loginName,
setToken: doSetToken, setToken,
logout, logout,
} }
} }

View File

@ -1,3 +1,6 @@
# Secret pro podepisování JWT tokenů. Minimální délka 32 znaků.
# JWT_SECRET='CHANGE_ME'
# Zapne režim mockování obědových menu. # Zapne režim mockování obědových menu.
# Vhodné pro vývoj o víkendech, svátcích a dalších dnech, pro které podniky nenabízejí obědové menu. # Vhodné pro vývoj o víkendech, svátcích a dalších dnech, pro které podniky nenabízejí obědové menu.
# V tomto režimu vrací server vždy falešné datum (pracovní den) a pevně nadefinovanou, smyšlenou nabídku jídel. # V tomto režimu vrací server vždy falešné datum (pracovní den) a pevně nadefinovanou, smyšlenou nabídku jídel.

View File

@ -7,13 +7,13 @@ import jwt from 'jsonwebtoken';
* @returns JWT token * @returns JWT token
*/ */
export function generateToken(login: string): string { export function generateToken(login: string): string {
if (!process.env.JWT_TOKEN) { if (!process.env.JWT_SECRET) {
throw Error("Není vyplněna proměnná prostředí JWT_TOKEN"); throw Error("Není vyplněna proměnná prostředí JWT_SECRET");
} }
if (process.env.JWT_TOKEN.length < 32) { if (process.env.JWT_SECRET.length < 32) {
throw Error("Proměnná prostředí JWT_TOKEN musí být minimálně 32 znaků"); throw Error("Proměnná prostředí JWT_SECRET musí být minimálně 32 znaků");
} }
return jwt.sign({ login }, process.env.JWT_TOKEN); return jwt.sign({ login }, process.env.JWT_SECRET);
} }
/** /**
@ -22,11 +22,11 @@ export function generateToken(login: string): string {
* @param token JWT token * @param token JWT token
*/ */
export function verify(token: string): boolean { export function verify(token: string): boolean {
if (!process.env.JWT_TOKEN) { if (!process.env.JWT_SECRET) {
throw Error("Není vyplněna proměnná prostředí JWT_TOKEN"); throw Error("Není vyplněna proměnná prostředí JWT_SECRET");
} }
try { try {
jwt.verify(token, process.env.JWT_TOKEN); jwt.verify(token, process.env.JWT_SECRET);
return true; return true;
} catch (err) { } catch (err) {
return false; return false;
@ -38,10 +38,13 @@ export function verify(token: string): boolean {
* *
* @param token JWT token * @param token JWT token
*/ */
export function getLogin(token: string): string { export function getLogin(token?: string): string {
if (!process.env.JWT_TOKEN) { if (!process.env.JWT_SECRET) {
throw Error("Není vyplněna proměnná prostředí JWT_TOKEN"); throw Error("Není vyplněna proměnná prostředí JWT_SECRET");
} }
const payload: any = jwt.verify(token, process.env.JWT_TOKEN); if (!token) {
throw Error("Nebyl předán token");
}
const payload: any = jwt.verify(token, process.env.JWT_SECRET);
return payload.login; return payload.login;
} }

View File

@ -9,7 +9,7 @@ import path from 'path';
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
import { getQr } from "./qr"; import { getQr } from "./qr";
import { Restaurants } from "./types"; import { Restaurants } from "./types";
import { generateToken, verify } from "./auth"; import { generateToken, getLogin, verify } from "./auth";
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}`) });
@ -30,6 +30,14 @@ app.use(cors({
origin: '*' origin: '*'
})); }));
const parseToken = (req: any) => {
if (req?.headers?.authorization) {
return req.headers.authorization.split(' ')[1];
}
}
// ----------- Metody nevyžadující token --------------
app.post("/api/login", (req, res) => { app.post("/api/login", (req, res) => {
if (!req.body?.login) { if (!req.body?.login) {
throw Error("Nebyl předán login"); throw Error("Nebyl předán login");
@ -39,14 +47,18 @@ app.post("/api/login", (req, res) => {
res.status(200).json(token); res.status(200).json(token);
}); });
app.post("/api/verify", (req, res) => { // ----------------------------------------------------
if (!req.body?.token) {
res.status(401).send(); /** Middleware ověřující JWT token */
} else if (verify(req.body.token)) { app.use((req, res, next) => {
res.status(200).send(); if (!req.headers.authorization) {
} else { return res.status(401).json({ error: 'Nebyl předán autentizační token' });
res.status(403).send();
} }
const token = req.headers.authorization.split(' ')[1];
if (!verify(token)) {
return res.status(403).json({ error: 'Neplatný autentizační token' });
}
next();
}); });
/** Vrátí data pro aktuální den. */ /** Vrátí data pro aktuální den. */
@ -76,27 +88,21 @@ app.get("/api/pizza", (req, res) => {
/** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */ /** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */
app.post("/api/createPizzaDay", (req, res) => { app.post("/api/createPizzaDay", (req, res) => {
if (!req.body?.creator) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán název zakládajícího"); const data = createPizzaDay(login);
}
const data = createPizzaDay(req.body.creator);
res.status(200).json(data); res.status(200).json(data);
io.emit("message", data); io.emit("message", data);
}); });
/** Smaže pizza day pro aktuální den, za předpokladu že existuje. */ /** Smaže pizza day pro aktuální den, za předpokladu že existuje. */
app.post("/api/deletePizzaDay", (req, res) => { app.post("/api/deletePizzaDay", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login uživatele"); const data = deletePizzaDay(login);
}
const data = deletePizzaDay(req.body.login);
io.emit("message", data); io.emit("message", data);
}); });
app.post("/api/addPizza", (req, res) => { app.post("/api/addPizza", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login");
}
if (isNaN(req.body?.pizzaIndex)) { if (isNaN(req.body?.pizzaIndex)) {
throw Error("Nebyl předán index pizzy"); throw Error("Nebyl předán index pizzy");
} }
@ -112,74 +118,60 @@ app.post("/api/addPizza", (req, res) => {
if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) { if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) {
throw Error("Neplatný index velikosti pizzy: " + pizzaSizeIndex); throw Error("Neplatný index velikosti pizzy: " + pizzaSizeIndex);
} }
const data = addPizzaOrder(req.body.login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]); const data = addPizzaOrder(login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}) })
}); });
app.post("/api/removePizza", (req, res) => { app.post("/api/removePizza", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login");
}
if (!req.body?.pizzaOrder) { if (!req.body?.pizzaOrder) {
throw Error("Nebyla předána objednávka"); throw Error("Nebyla předána objednávka");
} }
const data = removePizzaOrder(req.body.login, req.body?.pizzaOrder); const data = removePizzaOrder(login, req.body?.pizzaOrder);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}); });
app.post("/api/lockPizzaDay", (req, res) => { app.post("/api/lockPizzaDay", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login"); const data = lockPizzaDay(login);
}
const data = lockPizzaDay(req.body.login);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}); });
app.post("/api/unlockPizzaDay", (req, res) => { app.post("/api/unlockPizzaDay", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login"); const data = unlockPizzaDay(login);
}
const data = unlockPizzaDay(req.body.login);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}); });
app.post("/api/finishOrder", (req, res) => { app.post("/api/finishOrder", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login"); const data = finishPizzaOrder(login);
}
const data = finishPizzaOrder(req.body.login);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}); });
app.post("/api/finishDelivery", (req, res) => { app.post("/api/finishDelivery", (req, res) => {
if (!req.body?.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login"); const data = finishPizzaDelivery(login, req.body.bankAccount, req.body.bankAccountHolder);
}
const data = finishPizzaDelivery(req.body.login, req.body.bankAccount, req.body.bankAccountHolder);
io.emit("message", data); io.emit("message", data);
res.status(200).json({}); res.status(200).json({});
}); });
app.post("/api/updateChoice", (req, res) => { app.post("/api/updateChoice", (req, res) => {
if (!req.body.hasOwnProperty('name')) { const login = getLogin(parseToken(req));
res.status(400).json({}); const data = updateChoice(login, req.body.choice);
}
const data = updateChoice(req.body.name, req.body.choice);
io.emit("message", data); io.emit("message", data);
res.status(200).json(data); res.status(200).json(data);
}); });
app.get("/api/qr", (req, res) => { app.get("/api/qr", (req, res) => {
if (!req.query?.login || typeof req.query.login !== 'string') { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login"); const img = getQr(login);
}
const img = getQr(req.query.login);
res.writeHead(200, { res.writeHead(200, {
'Content-Type': 'image/png', 'Content-Type': 'image/png',
'Content-Length': img.length 'Content-Length': img.length
@ -188,13 +180,11 @@ app.get("/api/qr", (req, res) => {
}); });
app.post("/api/updateNote", (req, res) => { app.post("/api/updateNote", (req, res) => {
if (!req.body.login) { const login = getLogin(parseToken(req));
throw Error("Nebyl předán login");
}
if (req.body.note && req.body.note.length > 100) { if (req.body.note && req.body.note.length > 100) {
throw Error("Poznámka může mít maximálně 100 znaků"); throw Error("Poznámka může mít maximálně 100 znaků");
} }
const data = updateNote(req.body.login, req.body.note); const data = updateNote(login, req.body.note);
io.emit("message", data); io.emit("message", data);
res.status(200).json(data); res.status(200).json(data);
}); });

View File

@ -218,6 +218,7 @@ export function finishPizzaDelivery(login: string, bankAccount?: string, bankAcc
clientData.pizzaDay.state = PizzaDayState.DELIVERED; clientData.pizzaDay.state = PizzaDayState.DELIVERED;
// Vygenerujeme QR kód, pokud k tomu máme data // Vygenerujeme QR kód, pokud k tomu máme data
// TODO berka je potřeba počkat na resolve promises z generateQr a až poté volat save do DB
if (bankAccount?.length && bankAccountHolder?.length) { if (bankAccount?.length && bankAccountHolder?.length) {
for (const order of clientData.pizzaDay.orders) { for (const order of clientData.pizzaDay.orders) {
if (order.customer !== login) { // zatím platí creator = objednávající, a pro toho nemá QR kód smysl if (order.customer !== login) { // zatím platí creator = objednávající, a pro toho nemá QR kód smysl