From 6447836bbe0c904fddd0f87e9113dd183b3e2928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=A0indel=C3=A1=C5=99?= Date: Sun, 10 Mar 2024 20:44:08 +0100 Subject: [PATCH] Implement card and limit keypadid to 5 chars --- CHANGELOG.md | 4 + models/user.js | 3 + routes/api/customerName.js | 86 ++++++------ routes/api/keypadOrder.js | 259 +++++++++++++++++++------------------ routes/kiosk_shop.js | 16 ++- routes/orders.js | 166 ++++++++++++------------ 6 files changed, 281 insertions(+), 253 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d32a56..2b59043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## ✨ Novinky +- 💳 Nově si může uživatel přidat kartu o minimální délce 6 znaků, která slouží jako bezpečnější způsob ověření + a rovněž slouží pro načtení fyzického čářového nebo QR kódu místo ručního zadávání +- 🔢 Číslo klávesnice nyní může mít maximálně délku 5 znaků, pro delší bezpečnější způsob ověření slouží karta + ## 🐞 Opravy chyb - 🎨 Kategorie se v nabídce zobrazují ihned po vytvoření, není je potřeba ještě dodatečně upravit přes formulář Upravit kategorii diff --git a/models/user.js b/models/user.js index 5765c19..dded60b 100755 --- a/models/user.js +++ b/models/user.js @@ -21,6 +21,7 @@ const userSchema = new Schema({ keypadId: { type: Number, required: true, + maxlength: 5, }, admin: { type: Boolean, @@ -64,6 +65,8 @@ const userSchema = new Schema({ }, }); +userSchema.index({}); + const model = mongoose.model("User", userSchema); export const schema = model.schema; diff --git a/routes/api/customerName.js b/routes/api/customerName.js index 9728289..2392ab0 100644 --- a/routes/api/customerName.js +++ b/routes/api/customerName.js @@ -1,69 +1,73 @@ -import { Router } from 'express' -import { ensureAuthenticatedAPI } from '../../functions/ensureAuthenticatedAPI.js' -import User from '../../models/user.js' -var router = Router() -let responseJson +import { Router } from "express"; +import { ensureAuthenticatedAPI } from "../../functions/ensureAuthenticatedAPI.js"; +import User from "../../models/user.js"; +var router = Router(); +let responseJson; // GET /api/customerName - accepts customer's keypadId and returns customer's display name -router.get('/', ensureAuthenticatedAPI, function (req, res, _next) { +router.get("/", ensureAuthenticatedAPI, function (req, res, _next) { // Check if request contains 'customer' parameter if (!req.query.customer) { - res.status(400) - res.set('Content-Type', 'application/problem+json') + res.status(400); + res.set("Content-Type", "application/problem+json"); responseJson = { - type: 'https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#customerName', + type: "https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#customerName", title: "Your request does not contain parameter 'customer'.", status: 400, - 'detail:': + "detail:": "This function requires parameter 'customer'. More details can be found in documentation https://git.io/JeodS", - 'invalid-params': [ + "invalid-params": [ { - name: 'customer', - reason: 'must be specified' - } - ] - } - res.json(responseJson) - return + name: "customer", + reason: "must be specified", + }, + ], + }; + res.json(responseJson); + return; } // Find user in database + const filter = + req.query.customer_id.length < 6 + ? { keypadId: req.query.customer_id } + : { card: req.query.customer_id }; User.findOne({ - keypadId: req.query.customer + ...filter, }) .then((user) => { // If database doesn't contain user with supplied keypadId, database returns empty object, which doesn't contain parameter displayName. - res.set('Content-Type', 'application/json') + res.set("Content-Type", "application/json"); if (!user) { - res.status(404) - res.json('NOT_FOUND') + res.status(404); + res.json("NOT_FOUND"); } else { - res.status(200) + res.status(200); const normalized = user.displayName - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - res.json(normalized) + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); + res.json(normalized); } }) .catch((_err) => { - res.status(400) - res.set('Content-Type', 'application/problem+json') + res.status(400); + res.set("Content-Type", "application/problem+json"); const responseJson = { - type: 'https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#customerName', + type: "https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#customerName", title: "Your parameter 'customer' is wrong type.", status: 400, - 'detail:': + "detail:": "Parameter 'customer' must be a 'Number'. More details can be found in documentation https://git.io/JeodS", - 'invalid-params': [ + "invalid-params": [ { - name: 'customer', - reason: 'must be natural number' - } - ] - } - res.json(responseJson) - return - }) -}) + name: "customer", + reason: "must be natural number", + }, + ], + }; + res.json(responseJson); + return; + }); +}); -export default router +export default router; diff --git a/routes/api/keypadOrder.js b/routes/api/keypadOrder.js index 7434c85..89c2b33 100644 --- a/routes/api/keypadOrder.js +++ b/routes/api/keypadOrder.js @@ -1,135 +1,139 @@ -import { Router } from 'express' -import moment from 'moment' -import { sendMail } from '../../functions/sendMail.js' -import User from '../../models/user.js' -import Order from '../../models/order.js' -import Product from '../../models/product.js' -import Delivery from '../../models/delivery.js' -import { ensureAuthenticatedAPI } from '../../functions/ensureAuthenticatedAPI.js' -var router = Router() -let responseJson -moment.locale('cs') +import { Router } from "express"; +import moment from "moment"; +import { sendMail } from "../../functions/sendMail.js"; +import User from "../../models/user.js"; +import Order from "../../models/order.js"; +import Product from "../../models/product.js"; +import Delivery from "../../models/delivery.js"; +import { ensureAuthenticatedAPI } from "../../functions/ensureAuthenticatedAPI.js"; +var router = Router(); +let responseJson; +moment.locale("cs"); /* API to order via keypad */ -router.post('/', ensureAuthenticatedAPI, function (req, res, _next) { +router.post("/", ensureAuthenticatedAPI, function (req, res, _next) { if (!req.body.customer) { // Check if request contains 'customer' parameter - res.status(400) - res.set('Content-Type', 'application/problem+json') + res.status(400); + res.set("Content-Type", "application/problem+json"); responseJson = { - type: 'https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder', + type: "https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder", title: "Your request does not contain parameter 'customer'.", status: 400, - 'detail:': + "detail:": "This function requires parameter 'customer'. More details can be found in documentation https://git.io/Jey70", - 'invalid-params': [ + "invalid-params": [ { - name: 'customer', - reason: 'must be specified' - } - ] - } - res.json(responseJson) - return + name: "customer", + reason: "must be specified", + }, + ], + }; + res.json(responseJson); + return; } else if (!req.body.product) { // Check if request contains 'product' parameter - res.status(400) - res.set('Content-Type', 'application/problem+json') + res.status(400); + res.set("Content-Type", "application/problem+json"); responseJson = { - type: 'https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder', + type: "https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder", title: "Your request does not contain parameter 'product'.", status: 400, - 'detail:': + "detail:": "This function requires parameter 'product'. More details can be found in documentation https://git.io/Jey70", - 'invalid-params': [ + "invalid-params": [ { - name: 'product', - reason: 'must be specified' - } - ] - } - res.json(responseJson) - return + name: "product", + reason: "must be specified", + }, + ], + }; + res.json(responseJson); + return; } - const newOrder = new Order() + const newOrder = new Order(); // Find user by keypadId + const filter = + req.body.customer_id.length < 6 + ? { keypadId: req.body.customer_id } + : { card: req.body.customer_id }; User.findOne({ - keypadId: req.body.customer + ...filter, }) .then((user) => { if (!user) { - res.status(404) - res.set('Content-Type', 'application/json') - res.json('USER_NOT_FOUND') - return + res.status(404); + res.set("Content-Type", "application/json"); + res.json("USER_NOT_FOUND"); + return; } - newOrder.buyerId = user._id - newOrder.keypadOrder = true + newOrder.buyerId = user._id; + newOrder.keypadOrder = true; // Get product Product.aggregate([ { $match: { - keypadId: Number(req.body.product) - } + keypadId: Number(req.body.product), + }, }, { $lookup: { - from: 'deliveries', - localField: '_id', - foreignField: 'productId', - as: 'stock' - } + from: "deliveries", + localField: "_id", + foreignField: "productId", + as: "stock", + }, }, { $project: { - keypadId: '$keypadId', - displayName: '$displayName', - description: '$description', - imagePath: '$imagePath', + keypadId: "$keypadId", + displayName: "$displayName", + description: "$description", + imagePath: "$imagePath", stock: { $filter: { // We filter only the stock object from array where ammount left is greater than 0 - input: '$stock', - as: 'stock', + input: "$stock", + as: "stock", cond: { - $gt: ['$$stock.amount_left', 0] - } - } - } - } - } + $gt: ["$$stock.amount_left", 0], + }, + }, + }, + }, + }, ]) .then((product) => { - if (typeof product[0] === 'undefined') { - res.status(404) - res.set('Content-Type', 'application/json') - res.json('PRODUCT_NOT_FOUND') - return - } else if (typeof product[0].stock[0] === 'undefined') { - res.status(404) - res.set('Content-Type', 'application/json') - res.json('STOCK_NOT_FOUND') - return + if (typeof product[0] === "undefined") { + res.status(404); + res.set("Content-Type", "application/json"); + res.json("PRODUCT_NOT_FOUND"); + return; + } else if (typeof product[0].stock[0] === "undefined") { + res.status(404); + res.set("Content-Type", "application/json"); + res.json("STOCK_NOT_FOUND"); + return; } - newOrder.deliveryId = product[0].stock[0]._id - const newAmount = product[0].stock[0].amount_left - 1 + newOrder.deliveryId = product[0].stock[0]._id; + const newAmount = product[0].stock[0].amount_left - 1; Delivery.findByIdAndUpdate(product[0].stock[0]._id, { - amount_left: newAmount + amount_left: newAmount, }) .then((delivery) => { newOrder .save() .then((order) => { - const subject = `Potvrzení objednávky - ${product[0].displayName}` - const mailPreview = `Zakoupili jste ${product[0].displayName} za ${delivery.price} Kč přes API.` + const subject = `Potvrzení objednávky - ${product[0].displayName}`; + const mailPreview = `Zakoupili jste ${product[0].displayName} za ${delivery.price} Kč přes API.`; sendMail( user.email, - 'productPurchased', + "productPurchased", { subject, mailPreview, @@ -137,82 +141,83 @@ router.post('/', ensureAuthenticatedAPI, function (req, res, _next) { productId: delivery.productId, productName: product[0].displayName, productPrice: delivery.price, - purchaseDate: moment(order.order_date).format('LLLL') + purchaseDate: moment(order.order_date).format("LLLL"), }, product[0].imagePath - ) + ); - res.status(200) - res.set('Content-Type', 'application/json') + res.status(200); + res.set("Content-Type", "application/json"); responseJson = { user, product: { name: product[0].displayName, - price: product[0].stock[0].price - } - } - res.json(responseJson) + price: product[0].stock[0].price, + }, + }; + res.json(responseJson); }) .catch((err) => { - res.status(err.status || 500) - res.set('Content-Type', 'application/json') - res.json('SYSTEM_ERROR') + res.status(err.status || 500); + res.set("Content-Type", "application/json"); + res.json("SYSTEM_ERROR"); - const subject = '[SYSTEM ERROR] Chyba při zápisu do databáze!' - const message = `Potenciálně se nepodařilo zapsat novou objednávku do databáze, ale již došlo k ponížení skladové zásoby v dodávce ID [${delivery._id}]. Zákazník ID [${user._id}], zobrazované jméno [${user.displayName}] se pokusil koupit produkt ID [${product[0]._id}], zobrazované jméno [${product[0].displayName}] za [${delivery.price}] Kč. Zkontrolujte konzistenci databáze.` - sendMail('system@system', 'systemMessage', { + const subject = + "[SYSTEM ERROR] Chyba při zápisu do databáze!"; + const message = `Potenciálně se nepodařilo zapsat novou objednávku do databáze, ale již došlo k ponížení skladové zásoby v dodávce ID [${delivery._id}]. Zákazník ID [${user._id}], zobrazované jméno [${user.displayName}] se pokusil koupit produkt ID [${product[0]._id}], zobrazované jméno [${product[0].displayName}] za [${delivery.price}] Kč. Zkontrolujte konzistenci databáze.`; + sendMail("system@system", "systemMessage", { subject, message, messageTime: moment().toISOString(), - errorMessage: err.message - }) + errorMessage: err.message, + }); - return - }) + return; + }); }) .catch((err) => { - res.status(err.status || 500) - res.set('Content-Type', 'application/json') - res.json('SYSTEM_ERROR') + res.status(err.status || 500); + res.set("Content-Type", "application/json"); + res.json("SYSTEM_ERROR"); - const subject = '[SYSTEM ERROR] Chyba při zápisu do databáze!' - const message = `Potenciálně se nepodařilo snížit skladovou zásobu v dodávce ID [${newOrder.deliveryId}] a následně vystavit objednávku. Zákazník ID [${user._id}], zobrazované jméno [${user.displayName}] se pokusil koupit produkt ID [${product[0]._id}], zobrazované jméno [${product[0].displayName}]. Zkontrolujte konzistenci databáze.` - sendMail('system@system', 'systemMessage', { + const subject = "[SYSTEM ERROR] Chyba při zápisu do databáze!"; + const message = `Potenciálně se nepodařilo snížit skladovou zásobu v dodávce ID [${newOrder.deliveryId}] a následně vystavit objednávku. Zákazník ID [${user._id}], zobrazované jméno [${user.displayName}] se pokusil koupit produkt ID [${product[0]._id}], zobrazované jméno [${product[0].displayName}]. Zkontrolujte konzistenci databáze.`; + sendMail("system@system", "systemMessage", { subject, message, messageTime: moment().toISOString(), - errorMessage: err.message - }) + errorMessage: err.message, + }); - return - }) + return; + }); }) .catch((err) => { - res.status(err.status || 500) - res.set('Content-Type', 'application/json') - res.json('SYSTEM_ERROR') - return - }) + res.status(err.status || 500); + res.set("Content-Type", "application/json"); + res.json("SYSTEM_ERROR"); + return; + }); }) .catch((_err) => { - res.status(400) - res.set('Content-Type', 'application/problem+json') + res.status(400); + res.set("Content-Type", "application/problem+json"); const responseJson = { - type: 'https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder', + type: "https://github.com/houby-studio/small-business-fridge/wiki/API-documentation#keypadOrder", title: "Your parameter 'customer' is wrong type.", status: 400, - 'detail:': + "detail:": "Parameter 'customer' must be a 'Number'. More details can be found in documentation https://git.io/Jey70", - 'invalid-params': [ + "invalid-params": [ { - name: 'customer', - reason: 'must be natural number' - } - ] - } - res.json(responseJson) - return - }) -}) + name: "customer", + reason: "must be natural number", + }, + ], + }; + res.json(responseJson); + return; + }); +}); -export default router +export default router; diff --git a/routes/kiosk_shop.js b/routes/kiosk_shop.js index 1082fea..cef32b8 100644 --- a/routes/kiosk_shop.js +++ b/routes/kiosk_shop.js @@ -168,9 +168,13 @@ router.get("/", ensureAuthenticated, function (req, res) { res.redirect("/logout"); return; } - // Find user by keypadId + // Find user by keypadId or card number depending on char length + const filter = + req.query.customer_id.length < 6 + ? { keypadId: req.query.customer_id } + : { card: req.query.customer_id }; User.findOne({ - keypadId: req.query.customer_id, + ...filter, keypadDisabled: { $in: [null, false] }, }) .then((customer) => { @@ -253,9 +257,13 @@ router.post("/", ensureAuthenticated, function (req, res) { deliveryId: req.body.product_id, }); - // Find user by keypadId + // Find user by keypadId or card number depending on char length + const filter = + req.body.customer_id.length < 6 + ? { keypadId: req.body.customer_id } + : { card: req.body.customer_id }; User.findOne({ - keypadId: req.body.customer_id, + ...filter, keypadDisabled: { $in: [null, false] }, }) .then((user) => { diff --git a/routes/orders.js b/routes/orders.js index 457cd1f..d8e058b 100644 --- a/routes/orders.js +++ b/routes/orders.js @@ -1,101 +1,105 @@ -import { Router } from 'express' -import moment from 'moment' -import Order from '../models/order.js' -import { ensureAuthenticated } from '../functions/ensureAuthenticated.js' -import { checkKiosk } from '../functions/checkKiosk.js' -import logger from '../functions/logger.js' -var router = Router() -moment.locale('cs') +import { Router } from "express"; +import moment from "moment"; +import Order from "../models/order.js"; +import { ensureAuthenticated } from "../functions/ensureAuthenticated.js"; +import { checkKiosk } from "../functions/checkKiosk.js"; +import logger from "../functions/logger.js"; +var router = Router(); +moment.locale("cs"); /* GET orders page. */ -router.get('/', ensureAuthenticated, checkKiosk, function (req, res) { - if (req.baseUrl === '/admin_orders') { - var filter +router.get("/", ensureAuthenticated, checkKiosk, function (req, res) { + if (req.baseUrl === "/admin_orders") { + var filter; if (!req.user.admin) { logger.warn( `server.routes.orders.get__User tried to access admin page without permission.`, { metadata: { - result: req.user - } + result: req.user, + }, } - ) - res.redirect('/') - return + ); + res.redirect("/"); + return; } - filter = {} + filter = {}; } else { filter = { - buyerId: req.user._id - } + buyerId: req.user._id, + }; } + Order.listIndexes().then((indexes) => { + console.log(indexes); + }); + Order.aggregate([ { - $match: filter + $match: filter, }, { $sort: { - _id: -1 - } + _id: -1, + }, }, { $lookup: { - from: 'deliveries', - localField: 'deliveryId', - foreignField: '_id', - as: 'deliveryInfo' - } + from: "deliveries", + localField: "deliveryId", + foreignField: "_id", + as: "deliveryInfo", + }, }, { - $unwind: '$deliveryInfo' + $unwind: "$deliveryInfo", }, { $lookup: { - from: 'users', - localField: 'deliveryInfo.supplierId', - foreignField: '_id', - as: 'supplierInfo' - } + from: "users", + localField: "deliveryInfo.supplierId", + foreignField: "_id", + as: "supplierInfo", + }, }, { - $unwind: '$supplierInfo' + $unwind: "$supplierInfo", }, { $lookup: { - from: 'users', - localField: 'buyerId', - foreignField: '_id', - as: 'buyerInfo' - } + from: "users", + localField: "buyerId", + foreignField: "_id", + as: "buyerInfo", + }, }, { - $unwind: '$buyerInfo' + $unwind: "$buyerInfo", }, { $lookup: { - from: 'products', - localField: 'deliveryInfo.productId', - foreignField: '_id', - as: 'productInfo' - } + from: "products", + localField: "deliveryInfo.productId", + foreignField: "_id", + as: "productInfo", + }, }, { - $unwind: '$productInfo' + $unwind: "$productInfo", }, { $group: { _id: null, totalOrders: { - $sum: 1 + $sum: 1, }, totalSpend: { - $sum: '$deliveryInfo.price' + $sum: "$deliveryInfo.price", }, results: { - $push: '$$ROOT' - } - } + $push: "$$ROOT", + }, + }, }, { $project: { @@ -107,21 +111,21 @@ router.get('/', ensureAuthenticated, checkKiosk, function (req, res) { vars: { field: { $filter: { - input: '$results', - as: 'calc', + input: "$results", + as: "calc", cond: { - $eq: ['$$calc.invoice', false] - } - } - } + $eq: ["$$calc.invoice", false], + }, + }, + }, }, in: { - $sum: '$$field.deliveryInfo.price' - } - } - } - } - } + $sum: "$$field.deliveryInfo.price", + }, + }, + }, + }, + }, ]) .then((docs) => { if (req.query.a) { @@ -130,39 +134,39 @@ router.get('/', ensureAuthenticated, checkKiosk, function (req, res) { component: req.query.c, message: req.query.m, success: req.query.s, - danger: req.query.d - } + danger: req.query.d, + }; } if (docs[0]) { logger.debug( `server.routes.orders.get__Successfully loaded ${docs[0].results.length} orders.`, { metadata: { - result: docs - } + result: docs, + }, } - ) + ); docs[0].results.forEach(function (element) { - element.order_date_format = moment(element.order_date).format('LLLL') - element.order_date = moment(element.order_date).format() - }) + element.order_date_format = moment(element.order_date).format("LLLL"); + element.order_date = moment(element.order_date).format(); + }); } - res.render('shop/orders', { - title: 'Objednávky | Lednice IT', + res.render("shop/orders", { + title: "Objednávky | Lednice IT", orders: docs[0], admin: filter, user: req.user, - alert: alert - }) + alert: alert, + }); }) .catch((err) => { logger.error(`server.routes.orders.get__Failed to load orders.`, { metadata: { - error: err.message - } - }) - }) -}) + error: err.message, + }, + }); + }); +}); -export default router +export default router;