import express from "express"; import { Server } from "socket.io"; import bodyParser from "body-parser"; import cors from 'cors'; import { addChoice, addPizzaOrder, createPizzaDay, deletePizzaDay, finishPizzaDelivery, finishPizzaOrder, getData, getPizzaList, lockPizzaDay, removeChoice, removeChoices, removePizzaOrder, savePizzaList, unlockPizzaDay, updateDepartureTime, updateNote } from "./service"; import dotenv from 'dotenv'; import path from 'path'; import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; import { getQr } from "./qr"; import { generateToken, getLogin, getTrusted, verify } from "./auth"; import { Locations, Restaurants } from "../../types"; import { downloadPizzy } from "./chefie"; const ENVIRONMENT = process.env.NODE_ENV || 'production'; dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) }); // Validace nastavení JWT tokenu - nemá bez něj smysl vůbec povolit server spustit if (!process.env.JWT_SECRET) { throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); } const app = express(); const server = require("http").createServer(app); const io = new Server(server, { cors: { origin: "*", }, }); // Body-parser middleware for parsing JSON app.use(bodyParser.json()); // app.use(express.json()); app.use(cors({ origin: '*' })); // app.use((req, res, next) => { // console.log("--- Request ---") // console.log(req.url); // console.log(req.baseUrl); // console.log(req.originalUrl); // console.log(req.path); // next(); // }); app.use(express.static('public')) const parseToken = (req: any) => { if (req?.headers?.authorization) { return req.headers.authorization.split(' ')[1]; } } // ----------- Metody nevyžadující token -------------- app.get("/api/whoami", (req, res) => { res.send(req.header('remote-user')); }) app.post("/api/login", (req, res) => { // Autentizace pomocí trusted headers const remoteUser = req.header('remote-user'); if (remoteUser && remoteUser.length > 0) { res.status(200).json(generateToken(remoteUser, true)); return; } // Klasická autentizace loginem if (!req.body?.login || req.body.login.trim().length === 0) { throw Error("Nebyl předán login"); } // TODO zavést podmínky pro délku loginu (min i max) res.status(200).json(generateToken(req.body.login, false)); }); // TODO dočasné řešení - QR se zobrazuje přes , nemáme sem jak dostat token app.get("/api/qr", (req, res) => { // const login = getLogin(parseToken(req)); if (!req.query?.login) { throw Error("Nebyl předán login"); } const img = getQr(req.query.login as string); res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': img.length }); res.end(img); }); // ---------------------------------------------------- /** Middleware ověřující JWT token */ app.use((req, res, next) => { if (req.header('remote-user')) { console.log("Tvuj username: %s.", req.header('remote-user')); } if (!req.headers.authorization) { return res.status(401).json({ error: 'Nebyl předán autentizační token' }); } 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. */ app.get("/api/data", async (req, res) => { res.status(200).json(await getData()); }); /** Vrátí obědové menu pro dostupné podniky. */ app.get("/api/food", async (req, res) => { const mock = !!process.env.MOCK_DATA; const date = new Date(); const data = { [Restaurants.SLADOVNICKA]: await getMenuSladovnicka(date, mock), [Restaurants.UMOTLIKU]: await getMenuUMotliku(date, mock), [Restaurants.TECHTOWER]: await getMenuTechTower(date, mock), } res.status(200).json(data); }); /** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */ app.post("/api/createPizzaDay", async (req, res) => { const login = getLogin(parseToken(req)); const data = await createPizzaDay(login); res.status(200).json(data); io.emit("message", data); }); /** Smaže pizza day pro aktuální den, za předpokladu že existuje. */ app.post("/api/deletePizzaDay", async (req, res) => { const login = getLogin(parseToken(req)); const data = await deletePizzaDay(login); io.emit("message", data); }); app.post("/api/addPizza", async (req, res) => { const login = getLogin(parseToken(req)); if (isNaN(req.body?.pizzaIndex)) { throw Error("Nebyl předán index pizzy"); } const pizzaIndex = req.body.pizzaIndex; if (isNaN(req.body?.pizzaSizeIndex)) { throw Error("Nebyl předán index velikosti pizzy"); } const pizzaSizeIndex = req.body.pizzaSizeIndex; let pizzy = await getPizzaList(); if (!pizzy) { pizzy = await downloadPizzy(); savePizzaList(pizzy); } if (!pizzy[pizzaIndex]) { throw Error("Neplatný index pizzy: " + pizzaIndex); } if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) { throw Error("Neplatný index velikosti pizzy: " + pizzaSizeIndex); } const data = await addPizzaOrder(login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]); io.emit("message", data); res.status(200).json({}); }); app.post("/api/removePizza", async (req, res) => { const login = getLogin(parseToken(req)); if (!req.body?.pizzaOrder) { throw Error("Nebyla předána objednávka"); } const data = await removePizzaOrder(login, req.body?.pizzaOrder); io.emit("message", data); res.status(200).json({}); }); app.post("/api/lockPizzaDay", async (req, res) => { const login = getLogin(parseToken(req)); const data = await lockPizzaDay(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/unlockPizzaDay", async (req, res) => { const login = getLogin(parseToken(req)); const data = await unlockPizzaDay(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/finishOrder", async (req, res) => { const login = getLogin(parseToken(req)); const data = await finishPizzaOrder(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/finishDelivery", async (req, res) => { const login = getLogin(parseToken(req)); const data = await finishPizzaDelivery(login, req.body.bankAccount, req.body.bankAccountHolder); io.emit("message", data); res.status(200).json({}); }); app.post("/api/addChoice", async (req, res) => { const login = getLogin(parseToken(req)); const trusted = getTrusted(parseToken(req)); if (req.body.locationIndex > -1) { const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex); io.emit("message", data); res.status(200).json(data); } res.status(400); // TODO přidat popis chyby }); app.post("/api/removeChoices", async (req, res) => { const login = getLogin(parseToken(req)); const data = await removeChoices(login, req.body.locationIndex); io.emit("message", data); res.status(200).json(data); }); app.post("/api/removeChoice", async (req, res) => { const login = getLogin(parseToken(req)); const data = await removeChoice(login, req.body.locationIndex, req.body.foodIndex); io.emit("message", data); res.status(200).json(data); }); app.post("/api/updateNote", async (req, res) => { const login = getLogin(parseToken(req)); if (req.body.note && req.body.note.length > 100) { throw Error("Poznámka může mít maximálně 100 znaků"); } const data = await updateNote(login, req.body.note); io.emit("message", data); res.status(200).json(data); }); app.post("/api/changeDepartureTime", async (req, res) => { const login = getLogin(parseToken(req)); const data = await updateDepartureTime(login, req.body?.time); io.emit("message", data); res.status(200).json(data); }); io.on("connection", (socket) => { console.log(`New client connected: ${socket.id}`); socket.on("message", (message) => { io.emit("message", message); }); socket.on("disconnect", () => { console.log(`Client disconnected: ${socket.id}`); }); }); const PORT = process.env.PORT || 3001; const HOST = process.env.HOST || '0.0.0.0'; server.listen(PORT, () => { console.log(`Server listening on ${HOST}, port ${PORT}`); }); // Umožníme vypnutí serveru přes SIGINT, jinak Docker čeká než ho sestřelí process.on('SIGINT', function () { console.log("\nSIGINT (Ctrl-C), vypínám server"); process.exit(0); });