diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js new file mode 100644 index 0000000..cb78bd2 --- /dev/null +++ b/backend/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + 'env': { + 'browser': true, + 'es2021': true, + 'node': true + }, + 'extends': 'eslint:recommended', + 'overrides': [ + ], + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module' + }, + 'rules': { + 'indent': [ + 'error', + 'tab' + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ] + } +}; diff --git a/backend/app/controllers/appController.js b/backend/app/controllers/appController.js index bdfb315..bebfa13 100644 --- a/backend/app/controllers/appController.js +++ b/backend/app/controllers/appController.js @@ -1,46 +1,46 @@ -const { getRestaurantFromId, getRestaurantFromName, getRestaurantFromHostname } = require('../datamodels'); +const { getRestaurantById, getRestaurantByName, getRestaurantByHostname } = require('../datamodels'); -const getRestaurantById = async (req, res) => { - const { params: { restaurantId } } = req; - console.log(req.params); +const getRestaurantByIdController = async (req, res) => { + const { params: { restaurantId } } = req; + console.log(req.params); - try { - const result = await getRestaurantFromId(restaurantId); - res.send(result); - } catch (error) { - res.sendStatus(404); - } + try { + const result = await getRestaurantById(restaurantId); + res.send(result); + } catch (error) { + res.sendStatus(404); + } }; -const getRestaurantByName = async (req, res) => { - const { params: { restaurantName } } = req; +const getRestaurantByNameController = async (req, res) => { + const { params: { restaurantName } } = req; - try { - const result = await getRestaurantFromName(restaurantName); - res.send(result); - } catch (error) { - res.sendStatus(404); - } + try { + const result = await getRestaurantByName(restaurantName); + res.send(result); + } catch (error) { + res.sendStatus(404); + } }; -const hostnameCheck = async (req, res) => { - const { body: { hostname } } = req; - if (!hostname) { - res.sendStatus(404); - } else { - try { - const result = await getRestaurantFromHostname(hostname); - if(!result) res.sendStatus(404); - res.send(result); - } catch (error) { - res.sendStatus(404); - } - } +const getRestaurantByHostnameController = async (req, res) => { + const { body: { hostname } } = req; + if (!hostname) { + res.sendStatus(404); + } else { + try { + const result = await getRestaurantByHostname(hostname); + if (!result) res.sendStatus(404); + res.send(result); + } catch (error) { + res.sendStatus(404); + } + } }; module.exports = { - getRestaurantById, - getRestaurantByName, - hostnameCheck, + getRestaurantByIdController, + getRestaurantByNameController, + getRestaurantByHostnameController, }; diff --git a/backend/app/controllers/eventsController/db/onOrderChange.js b/backend/app/controllers/eventsController/db/onOrderChange.js index 9b9188f..0cc152c 100644 --- a/backend/app/controllers/eventsController/db/onOrderChange.js +++ b/backend/app/controllers/eventsController/db/onOrderChange.js @@ -1,38 +1,43 @@ -const { Orders } = require("../../../db/models"); +const { Orders } = require('../../../db/models'); module.exports = async (mongoEvent, io) => { - if (mongoEvent.operationType === "insert") { - const ownerRoomName = `${mongoEvent.fullDocument.restaurantId}:owner`; - const tabRoomName = `${mongoEvent.fullDocument.restaurantId}:${mongoEvent.fullDocument.tabId}`; - //todo remove duplicate code from here. make a function that returns - //the order and use it here and in controllers - const order = await Orders.findById(mongoEvent.fullDocument._id) - .populate('items') - .populate({ - path: 'tabId', - populate: { - path: 'tableId' - } - }).exec(); + if (mongoEvent.operationType === 'insert') { + const ownerRoomName = `${mongoEvent.fullDocument.restaurantId}:owner`; + const tabRoomName = `${mongoEvent.fullDocument.restaurantId}:${mongoEvent.fullDocument.tabId}`; + //todo remove duplicate code from here. make a function that returns + //the order and use it here and in controllers + const order = await Orders.findById(mongoEvent.fullDocument._id) + .populate({ + path: 'items', + populate: { + path: 'recipe' + } + }) + .populate({ + path: 'tabId', + populate: { + path: 'tableId' + } + }).exec(); - io.to(ownerRoomName).emit("order:new", { order }); - console.log(`New order sent in room: "${ownerRoomName}"`); + io.to(ownerRoomName).emit('order:new', { order }); + console.log(`New order sent in room: "${ownerRoomName}"`); - io.to(tabRoomName).emit("order:new", { order }); - console.log(`New order sent in room: "${tabRoomName}"`); - } + io.to(tabRoomName).emit('order:new', { order }); + console.log(`New order sent in room: "${tabRoomName}"`); + } - if (mongoEvent.operationType === "update") { - const order = await Orders.findById(mongoEvent.documentKey._id); - const ownerRoomName = `${order.restaurantId}:owner`; - const tabRoomName = `${order.restaurantId}:${order.tabId}`; + if (mongoEvent.operationType === 'update') { + const order = await Orders.findById(mongoEvent.documentKey._id); + const ownerRoomName = `${order.restaurantId}:owner`; + const tabRoomName = `${order.restaurantId}:${order.tabId}`; - io.to(ownerRoomName).emit("order:update", { id: order._id, status: order.status }); - console.log(`New order update sent in room: "${ownerRoomName}"`); + io.to(ownerRoomName).emit('order:update', { id: order._id, status: order.status }); + console.log(`New order update sent in room: "${ownerRoomName}"`); - io.to(tabRoomName).emit("order:update", { id: order._id, status: order.status }); - console.log(`New order update sent in room: "${tabRoomName}"`); + io.to(tabRoomName).emit('order:update', { id: order._id, status: order.status }); + console.log(`New order update sent in room: "${tabRoomName}"`); - } + } }; \ No newline at end of file diff --git a/backend/app/controllers/eventsController/db/onTabChange.js b/backend/app/controllers/eventsController/db/onTabChange.js index ebdfb73..c078d25 100644 --- a/backend/app/controllers/eventsController/db/onTabChange.js +++ b/backend/app/controllers/eventsController/db/onTabChange.js @@ -1,48 +1,47 @@ -const { Tabs } = require("../../../db/models"); +const { Tabs } = require('../../../db/models'); const propagationDelay = 1000; module.exports = async (mongoEvent, io) => { - if (mongoEvent.operationType === "insert") { - // Give some time for the document to be created and propagated - setTimeout(() => { - console.log(' '); - - const tab = mongoEvent.fullDocument; - const ownerRoomName = `${tab.restaurantId}:owner`; - - io.to(ownerRoomName).emit("tab:open", { tableId: tab.tableId, tabId: tab._id }); - console.log(`[Events] [Delayed ${propagationDelay/1000}s] New tab open request sent in room: "${ownerRoomName}"`); - }, propagationDelay); - } - - if (mongoEvent.operationType === "update") { - - // Call waiter - if (mongoEvent.updateDescription.updatedFields?.callWaiter === 'called') { - console.log(' '); - const tab = await Tabs.findById(mongoEvent.documentKey._id) - .populate('tableId') - .exec(); - - const ownerRoomName = `${tab.restaurantId}:owner`; - - io.to(ownerRoomName).emit("waiter:notification", { tableNo: tab.tableId.tableNo, tabId: tab._id }); - console.log(`[Events] New waiter request sent in room: "${ownerRoomName}"`); - } - - // Close tab - if (mongoEvent.updateDescription.updatedFields?.status === 'closed') { - console.log(' '); - const tab = await Tabs.findById(mongoEvent.documentKey._id); - - const ownerRoomName = `${tab.restaurantId}:owner`; - const tabRoomName = `${tab.restaurantId}:${tab._id}`; - - io.to(ownerRoomName).emit("tab:closed", { tableId: tab.tableId, tabId: tab._id }); - console.log(`[Events] Tab closed sent in room: "${ownerRoomName}"`); - - io.to(tabRoomName).emit("tab:closed", { tabId: tab._id }); - console.log(`[Events] Tab closed sent in room: "${tabRoomName}"`); - } - } + if (mongoEvent.operationType === 'insert') { + console.log('Tab inserted'); + // Give some time for the document to be created and propagated + setTimeout(() => { + const tab = mongoEvent.fullDocument; + const ownerRoomName = `${tab.restaurantId}:owner`; + + io.to(ownerRoomName).emit('tab:open', { tableId: tab.tableId, tabId: tab._id }); + console.log(`[Events] [Delayed ${propagationDelay/1000}s] New tab open request sent in room: "${ownerRoomName}"`); + }, propagationDelay); + } + + if (mongoEvent.operationType === 'update') { + + // Call waiter + if (mongoEvent.updateDescription.updatedFields?.callWaiter === 'called') { + console.log(' '); + const tab = await Tabs.findById(mongoEvent.documentKey._id) + .populate('tableId') + .exec(); + + const ownerRoomName = `${tab.restaurantId}:owner`; + + io.to(ownerRoomName).emit('waiter:notification', { tableNo: tab.tableId.tableNo, tabId: tab._id }); + console.log(`[Events] New waiter request sent in room: "${ownerRoomName}"`); + } + + // Close tab + if (mongoEvent.updateDescription.updatedFields?.status === 'closed') { + console.log(' '); + const tab = await Tabs.findById(mongoEvent.documentKey._id); + + const ownerRoomName = `${tab.restaurantId}:owner`; + const tabRoomName = `${tab.restaurantId}:${tab._id}`; + + io.to(ownerRoomName).emit('tab:closed', { tableId: tab.tableId, tabId: tab._id }); + console.log(`[Events] Tab closed sent in room: "${ownerRoomName}"`); + + io.to(tabRoomName).emit('tab:closed', { tabId: tab._id }); + console.log(`[Events] Tab closed sent in room: "${tabRoomName}"`); + } + } }; \ No newline at end of file diff --git a/backend/app/controllers/eventsController/socket/onConnect.js b/backend/app/controllers/eventsController/socket/onConnect.js index 8fc4e62..29562cc 100644 --- a/backend/app/controllers/eventsController/socket/onConnect.js +++ b/backend/app/controllers/eventsController/socket/onConnect.js @@ -1,35 +1,42 @@ -const { Restaurants } = require("../../../db/models"); +const { Restaurants } = require('../../../db/models'); module.exports = async (socket) => { - if(socket.handshake.query.restaurantId){ - const restaurant = await Restaurants.findById(socket.handshake.query.restaurantId); - - //todo: implement proper authentication of owner - if(socket.handshake.query.owner === "true") { - console.log("Owner from restaurant " + restaurant.name + " connected!"); - socket.join(`${socket.handshake.query.restaurantId}:owner`); - console.log(`Connected owner to room: "${socket.handshake.query.restaurantId}:owner"`); + if(socket.handshake.query.restaurantId){ + let restaurant; + try { + restaurant = await Restaurants.findById(socket.handshake.query.restaurantId); + } catch (error) { + console.log(error); + } + if(restaurant){ + //todo: implement proper authentication of owner + if(socket.handshake.query.owner === 'true') { + console.log('Owner from restaurant ' + restaurant.name + ' connected!'); + socket.join(`${socket.handshake.query.restaurantId}:owner`); + console.log(`Connected owner to room: "${socket.handshake.query.restaurantId}:owner"`); - } else if (socket.handshake.query.tabId != null){ - console.log("Client from restaurant " + restaurant?.name + " connected!"); + } else if (socket.handshake.query.tabId != null){ + console.log('Client from restaurant ' + restaurant?.name + ' connected!'); - socket.join(`${socket.handshake.query.restaurantId}:globalNotifications`); - console.log(`Connected client to room (Global Notifications): "${socket.handshake.query.restaurantId}:globalNotifications"`); + socket.join(`${socket.handshake.query.restaurantId}:globalNotifications`); + console.log(`Connected client to room (Global Notifications): "${socket.handshake.query.restaurantId}:globalNotifications"`); - socket.join(`${socket.handshake.query.restaurantId}:${socket.handshake.query.tabId}`); - console.log(`Connected client to room (Tab): "${socket.handshake.query.restaurantId}:${socket.handshake.query.tabId}"`); + socket.join(`${socket.handshake.query.restaurantId}:${socket.handshake.query.tabId}`); + console.log(`Connected client to room (Tab): "${socket.handshake.query.restaurantId}:${socket.handshake.query.tabId}"`); - } else { - try { - delete socket.handshake.query.transport; - delete socket.handshake.query.EIO; - delete socket.handshake.query.t; - } catch (e) { - console.log("Error deleting query params from socket handshake: " + e); - } - console.log("Disconnected client that had the following query: " + JSON.stringify(socket.handshake.query)); - socket.disconnect(); - } - } + } else { + try { + delete socket.handshake.query.transport; + delete socket.handshake.query.EIO; + delete socket.handshake.query.t; + } catch (e) { + console.log('Error deleting query params from socket handshake: ' + e); + } + console.log('Disconnected client that had the following query: ' + JSON.stringify(socket.handshake.query)); + socket.disconnect(); + } + } + + } }; \ No newline at end of file diff --git a/backend/app/controllers/eventsController/socket/onDisconnect.js b/backend/app/controllers/eventsController/socket/onDisconnect.js index 8ff69ef..4cbf144 100644 --- a/backend/app/controllers/eventsController/socket/onDisconnect.js +++ b/backend/app/controllers/eventsController/socket/onDisconnect.js @@ -1,4 +1,4 @@ module.exports = (socket) => { - console.log("Client disconnected"); - }; + console.log('Client disconnected'); +}; \ No newline at end of file diff --git a/backend/app/controllers/eventsController/socket/onRequestPin.js b/backend/app/controllers/eventsController/socket/onRequestPin.js index fc4b80d..28bc29d 100644 --- a/backend/app/controllers/eventsController/socket/onRequestPin.js +++ b/backend/app/controllers/eventsController/socket/onRequestPin.js @@ -1,3 +1,3 @@ module.exports = (socket) => { - console.log("Request pin"); + console.log('Request pin'); }; \ No newline at end of file diff --git a/backend/app/controllers/eventsController/socket/onSendPin.js b/backend/app/controllers/eventsController/socket/onSendPin.js index 78b1cc9..32421be 100644 --- a/backend/app/controllers/eventsController/socket/onSendPin.js +++ b/backend/app/controllers/eventsController/socket/onSendPin.js @@ -1,14 +1,14 @@ -const { Tables } = require("../../../db/models"); +const { Tables } = require('../../../db/models'); module.exports = async (socket, data) => { - const { pin, tableId } = data; - if(!pin || !tableId) { - socket.emit("tableLockStatus", { error: "Invalid pin or tableId" }); - return; - } + const { pin, tableId } = data; + if(!pin || !tableId) { + socket.emit('tableLockStatus', { error: 'Invalid pin or tableId' }); + return; + } - const table = await Tables.findById(tableId); - if(table.pin === pin) { - socket.emit("tableLockStatus", { }); - } + const table = await Tables.findById(tableId); + if(table.pin === pin) { + socket.emit('tableLockStatus', { }); + } }; \ No newline at end of file diff --git a/backend/app/controllers/ownerController.js b/backend/app/controllers/ownerController.js index a165474..0974d5a 100644 --- a/backend/app/controllers/ownerController.js +++ b/backend/app/controllers/ownerController.js @@ -1,107 +1,106 @@ -const { closeTableAndTab, clearWaiter, getTableById, getTableByRestaurantId, getActiveOrders, updateOrder } = require('../datamodels'); - -const updateOrderStatus = async (req, res) => { - const { body: { status }, params: { orderId } } = req; - if (!status || !orderId) { - return res.status(404).send({ message: "Status and order id are required" }); - } else { - try { - const result = await updateOrder(orderId, status); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } - } +const { closeTableAndTab, clearWaiterRequested, getTableById, getTableByRestaurantId, getActiveOrders, updateOrder } = require('../datamodels'); + +const updateOrderController = async (req, res) => { + const { body: { status }, params: { orderId } } = req; + if (!status || !orderId) { + return res.status(404).send({ message: 'Status and order id are required' }); + } else { + try { + const result = await updateOrder(orderId, status); + res.send(result); + } catch (error) { + console.log(error); + res.status(500); + } + } }; -const returnActiveOrders = async (req, res) => { - const { params: { restaurantId } } = req; +const getActiveOrdersController = async (req, res) => { + const { params: { restaurantId } } = req; - if (!restaurantId) { - return res.status(404).send({ message: "Restaurant id is required" }); - } + if (!restaurantId) { + return res.status(404).send({ message: 'Restaurant id is required' }); + } - try { - const result = await getActiveOrders(restaurantId); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } + try { + const result = await getActiveOrders(restaurantId); + res.send(result); + } catch (error) { + console.log(error); + res.status(500); + } }; -const getTables = async (req, res) => { - const { params: { restaurantId } } = req; +const getTableByRestaurantIdController = async (req, res) => { + const { params: { restaurantId } } = req; - if (!restaurantId) { - return res.status(404).send({ message: "Restaurant id is required" }); - } + if (!restaurantId) { + return res.status(404).send({ message: 'Restaurant id is required' }); + } - try { - const result = await getTableByRestaurantId(restaurantId); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } + try { + const result = await getTableByRestaurantId(restaurantId); + res.send(result); + } catch (error) { + console.log(error); + res.status(500); + } }; -const getTable = async (req, res) => { - const { params: { tableId } } = req; +const getTableByIdController = async (req, res) => { + const { params: { tableId } } = req; - if (!tableId) { - return res.status(404).send({ message: "Table id is required" }); - } + if (!tableId) { + return res.status(404).send({ message: 'Table id is required' }); + } - try { - const result = await getTableById(tableId); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } + try { + const result = await getTableById(tableId); + res.send(result); + } catch (error) { + console.log(error); + res.status(500).send({ message: error.message }); + } }; -const closeTable = async (req, res) => { - const { params: { tableId } } = req; - - if (!tableId) { - return res.status(404).send({ message: "Table id is required" }); - } - - try { - const result = await closeTableAndTab(tableId); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } -} - -const clearCallWaiter = async (req, res) => { - const { params: { tabId } } = req; - - if (!tabId) { - return res.status(404).send({ message: "Tab id is required" }); - } - - try { - const result = await clearWaiter(tabId); - res.send(result); - } catch (error) { - console.log(error); - res.status(500).send({ message: error.message }); - } +const closeTableController = async (req, res) => { + const { params: { tableId } } = req; + + if (!tableId) { + return res.status(404).send({ message: 'Table id is required' }); + } + + try { + const result = await closeTableAndTab(tableId); + res.send(result); + } catch (error) { + console.log(error); + res.status(500); + } +}; + +const callWaiterController = async (req, res) => { + const { params: { tabId } } = req; + + if (!tabId) { + return res.status(404).send({ message: 'Tab id is required' }); + } + try { + const result = await clearWaiterRequested(tabId); + res.send(result); + } catch (error) { + console.log(error); + res.status(500); + } }; module.exports = { - updateOrder: updateOrderStatus, - getActiveOrders: returnActiveOrders, - getTable, - getTables, - clearCallWaiter, - closeTable, + updateOrderController, + getActiveOrdersController, + getTableByRestaurantIdController, + getTableByIdController, + closeTableController, + callWaiterController, }; diff --git a/backend/app/controllers/userController.js b/backend/app/controllers/userController.js index f453044..ce5391c 100644 --- a/backend/app/controllers/userController.js +++ b/backend/app/controllers/userController.js @@ -1,231 +1,216 @@ -const { Tables, Products, Orders, Tabs } = require("../db/models"); -const { createNewOrder } = require('../datamodels'); - -const getMenu = async (req, res) => { - const id = req.params.id; - - const products = await Products.find( - { - restaurantId: id, - type: { - $in: ['product', 'variation'] - } - }).populate('recipe').exec(); - res.send(products); +const { createNewOrder, getProducts, getRandomTable, getTable, getTableStatus, getTab, getOrder, waiterRequested, paymentRequested } = require('../datamodels'); + +const getMenuController = async (req, res) => { + if (!req.params.restaurantId) { + res.sendStatus(400); + } else { + const { params: { restaurantId } } = req; + + try { + const products = await getProducts(restaurantId); + res.send(products); + } + catch (e) { + console.log(e); + res.sendStatus(404); + } + } }; -const getRandomTable = async (req, res) => { - if (!req.params.restaurantId) { - res.sendStatus(400); - } else { - const { params: { restaurantId } } = req; - - const randomTable = await Tables.findOne({ restaurantId }); - const { _id = null } = randomTable || {}; - getTable( - { - ...req, - params: { tableId: _id } - }, - res); - } +const getRandomTableController = async (req, res) => { + if (!req.params.restaurantId) { + res.sendStatus(400); + } else { + const { params: { restaurantId } } = req; + + try { + const table = await getRandomTable(restaurantId); + res.send(table); + } + catch (e) { + console.log(e); + res.sendStatus(404); + } + } }; -const getTable = async (req, res) => { - const { params: { tableId, pin } } = req; - - try { - const table = await findTable(tableId); - - if (!table.tabOpen) { - const newTab = await createNewTab(table); - table.currentTab = newTab._id; - table.tabOpen = true; - await table.save(); - } - - // if (table.locked && table.pin !== pin) { - // res.sendStatus(403); - // } else { - const populatedTable = await populateTableData(table); - res.send(populatedTable); - // } - } catch (e) { - console.log(e); - if (e.message === 'Table not found') { - res.sendStatus(404); - } else { - res.sendStatus(500); - } - } +const getTableController = async (req, res) => { + const { params: { tableId, pin } } = req; + + try { + const table = await getTable(tableId, pin); + res.send(table); + } catch (e) { + console.log(e); + if (e.message === 'Table not found') { + res.sendStatus(404); + } else if (e.message === 'Wrong pin') { + res.sendStatus(401); + } + else { + res.sendStatus(500); + } + } }; -const getTab = async (req, res) => { - const { params: { tabId } } = req; - - try { - let tab = await Tabs.findById(tabId); - if (!tab) { - res.sendStatus(404); - } else { - tab = await tab.populate('orders').execPopulate(); - res.send(tab); - } - } catch (e) { - console.log(e); - res.sendStatus(500); - } +const getTableStatusController = async (req, res) => { + const { params: { tableId } } = req; + if (!tableId) { + res.sendStatus(404); + } else { + try { + await getTableStatus(tableId); + res.sendStatus(200); + } catch (e) { + console.log(e); + if (e.message === 'Table not found') { + res.sendStatus(404); + } + else if (e.message === 'Table is locked') { + res.sendStatus(401); + } + else { + res.sendStatus(500); + } + } + } }; -const newOrder = async (req, res) => { - const { body: { cartProducts }, params: { tabId } } = req; - try { - await createNewOrder(cartProducts, tabId); - res.sendStatus(200); - } catch (e) { - console.log(e); - res.sendStatus(500); - } +const getTabController = async (req, res) => { + const { params: { tabId } } = req; + + try { + const tab = await getTab(tabId); + res.send(tab); + } + catch (e) { + console.log(e); + if (e.message === 'Tab not found') { + res.sendStatus(404); + } + else { + res.sendStatus(500); + } + + } }; -const getOrder = async (req, res) => { - const { params: { orderId } } = req; - if (!orderId) { - res.sendStatus(400); - } else { - try { - const order = await Orders.findById(orderId); - res.send(order); - } catch (e) { - res.send(500); - } - } +const newOrderController = async (req, res) => { + const { body: { cartProducts }, params: { tabId } } = req; + if (!cartProducts || !tabId) { + res.sendStatus(400); + } else { + try { + await createNewOrder(cartProducts, tabId); + res.sendStatus(200); + } catch (e) { + console.log(e); + if (e.message === 'Tab not found') { + res.sendStatus(404); + } + else if (e.message === 'Tab closed') { + res.sendStatus(401); + } + else { + res.sendStatus(500); + } + } + } }; -const getTableStatus = async (req, res) => { - const { params: { tableId } } = req; - if (!tableId) { - res.sendStatus(404); - } else { - try { - const table = await Tables.findById(tableId); - if (table.locked) { - res.send({ - locked: "true", - nextStep: "sendPin" - }); - } else { - res.send(table); - } - } catch (e) { - res.send(500); - } - } +const getOrderController = async (req, res) => { + const { params: { orderId } } = req; + if (!orderId) { + res.sendStatus(400); + } else { + try { + const order = await getOrder(orderId); + res.send(order); + } + catch (e) { + console.log(e); + if (e.message === 'Order not found') { + res.sendStatus(404); + } + else { + res.sendStatus(500); + } + } + } }; -const findTable = async (tableId) => { - const table = await Tables.findById(tableId); - if (!table) { - throw new Error('Table not found'); - } - return table; +const callWaiterController = async (req, res) => { + const { params: { tabId } } = req; + if (!tabId) { + res.sendStatus(404); + } else { + try { + await waiterRequested(tabId); + res.sendStatus(200); + } catch (e) { + console.log(e); + if (e.message === 'Tab not found') { + res.sendStatus(404); + } else if (e.message === 'Tab closed') { + res.sendStatus(401); + } else if (e.message === 'Waiter already called') { + res.sendStatus(429); + } else { + res.sendStatus(500); + } + } + } }; -const createNewTab = async (table) => { - const newTab = await new Tabs({ - restaurantId: table.restaurantId, - tableId: table._id, - status: "open", - lastUpdated: Date.now(), - createdAt: Date.now() - }).save(); - return newTab; +const payCardController = async (req, res) => { + const { params: { tabId } } = req; + if (!tabId) { + res.sendStatus(404); + } else { + try { + await paymentRequested(tabId, 'card'); + } catch (e) { + console.log(e); + if (e.message === 'Tab not found') { + res.sendStatus(404); + } else if (e.message === 'Tab closed') { + res.sendStatus(401); + } else { + res.sendStatus(500); + } + } + } }; -const populateTableData = async (table) => { - return await table.populate({ - path: 'currentTab', - populate: { - path: 'orders', - options: { sort: { createdAt: -1 } }, - populate: { - path: 'items' - } - } - }).execPopulate(); +const payCashController = async (req, res) => { + const { params: { tabId } } = req; + if (!tabId) { + res.sendStatus(404); + } else { + try { + await paymentRequested(tabId, 'cash'); + } catch (e) { + console.log(e); + if (e.message === 'Tab not found') { + res.sendStatus(404); + } else if (e.message === 'Tab closed') { + res.sendStatus(401); + } else { + res.sendStatus(500); + } + } + } }; -const callWaiter = async (req, res) => { - const { params: { tabId } } = req; - if (!tabId) { - res.sendStatus(404); - } else { - try { - //find table that has OpenTab === tabId - const tab = await Tabs.findById(tabId); - if (!tab) { - res.sendStatus(404); - } - tab.callWaiter = 'called'; - await tab.save(); - res.sendStatus(200); - } catch (e) { - res.sendStatus(500); - } - } -}; - -const payCard = async (req, res) => { - const { params: { tabId } } = req; - if (!tabId) { - res.sendStatus(404); - } else { - try { - //find table that has OpenTab === tabId - const table = await Tables.findOne({ currentTab: tabId }); - if (!table) { - res.sendStatus(404); - } - table.waiterCalled = true; - await table.save(); - res.sendStatus(200); - } catch (e) { - console.log(e); - res.sendStatus(500); - } - } -}; - -const payCash = async (req, res) => { - const { params: { tabId } } = req; - if (!tabId) { - res.sendStatus(404); - } else { - try { - const table = await Tables.findById(tableId); - if (!table) { - res.sendStatus(404); - } else { - table.waiterCalled = true; - await table.save(); - res.send(table); - } - } catch (e) { - res.send(500); - } - } -}; - - - module.exports = { - getMenu, - getRandomTable, - getTable, - getTab, - newOrder, - getOrder, - callWaiter, - payCard, - payCash + getMenuController, + getRandomTableController, + getTableController, + getTableStatusController, + getTabController, + newOrderController, + getOrderController, + callWaiterController, + payCardController, + payCashController, }; diff --git a/backend/app/datamodels/index.js b/backend/app/datamodels/index.js index 16d3e18..f7af135 100644 --- a/backend/app/datamodels/index.js +++ b/backend/app/datamodels/index.js @@ -1,19 +1,35 @@ const { closeTableAndTab } = require('./tablesAndTabs'); -const { clearWaiter } = require('./tabs'); -const { getTableByRestaurantId, getTableById } = require('./tables'); -const { getActiveOrders, updateOrder, createNewOrder } = require('./orders'); -const { getRestaurantFromHostname, getRestaurantFromId, getRestaurantFromName } = require('./restaurants'); - +const { getTab, closeTab, newTab, waiterRequested, clearWaiterRequested, paymentRequested } = require('./tabs'); +const { getTable, getRandomTable, closeTable, getTableStatus, getTableByRestaurantId, getTableById } = require('./tables'); +const { getOrder, getActiveOrders, updateOrder, createNewOrder } = require('./orders'); +const { getRestaurantByHostname, getRestaurantById, getRestaurantByName } = require('./restaurants'); +const { getProducts } = require('./products'); module.exports = { - closeTableAndTab, - clearWaiter, - getTableByRestaurantId, - getTableById, - getActiveOrders, - updateOrder, - createNewOrder, - getRestaurantFromHostname, - getRestaurantFromId, - getRestaurantFromName, -} \ No newline at end of file + closeTableAndTab, + + getTab, + closeTab, + newTab, + waiterRequested, + clearWaiterRequested, + paymentRequested, + + getTable, + getRandomTable, + closeTable, + getTableStatus, + getTableByRestaurantId, + getTableById, + + getOrder, + getActiveOrders, + updateOrder, + createNewOrder, + + getRestaurantById, + getRestaurantByName, + getRestaurantByHostname, + + getProducts, +}; \ No newline at end of file diff --git a/backend/app/datamodels/orders.js b/backend/app/datamodels/orders.js index 9606fbd..6699f97 100644 --- a/backend/app/datamodels/orders.js +++ b/backend/app/datamodels/orders.js @@ -1,99 +1,100 @@ -const mongoose = require('mongoose'); const { Orders, Tabs } = require('../db/models'); +const getOrder = async (orderId) => { + const order = await Orders.findById(orderId); + if (!order) { + throw new Error('Order not found'); + } + + return order; +}; + const getActiveOrders = async (restaurantId) => { - const orders = await Orders.find({ - restaurantId, - status: { $in: ["recieved", "inProgress"] } - }) - .populate({ - path:'items', - populate: { - path: 'recipe', - } - }) - .populate({ - path: 'tabId', - populate: { - path: 'tableId' - } - }).exec(); - return orders; + const orders = await Orders.find({ + restaurantId, + status: { $in: ['recieved', 'inProgress'] } + }) + .populate({ + path:'items', + populate: { + path: 'recipe', + } + }) + .populate({ + path: 'tabId', + populate: { + path: 'tableId' + } + }).exec(); + return orders; }; const updateOrder = async (orderId, status) => { - if (!["received", "inProgress", "done", 'canceled'].some((msg) => msg === status)) { - throw new Error('Invalid status'); - } else { - try { - const order = await Orders.findById(orderId); - if (!order) { - throw new Error('Order not found'); - } else { - order.status = status; - await order.save(); - return order; - } - } catch (e) { - throw new Error('Error updating order'); - } - } + if (!['received', 'inProgress', 'done', 'canceled'].some((msg) => msg === status)) { + throw new Error('Invalid status'); + } else { + try { + const order = await Orders.findById(orderId); + if (!order) { + throw new Error('Order not found'); + } else { + order.status = status; + await order.save(); + return order; + } + } catch (e) { + throw new Error('Error updating order'); + } + } }; const createNewOrder = async (cartProducts, tabId) => { - if (!cartProducts || !tabId) { - throw new Error('Invalid request'); - } else { - try { - const tab = await Tabs.findById(tabId); - if (!tab) { - throw new Error('Tab not found'); - } else if (tab.status === 'closed') { - throw new Error('Tab closed'); - } else { - const totalAmount = cartProducts.reduce( - (total, cartProduct) => { - if(cartProduct.type === "product"){ - return total + parseInt(cartProduct.cartQty) * parseFloat(cartProduct.price.toFixed(2)); - } - else if(cartProduct.type === "variation") { - return total + parseInt(cartProduct.cartQty) * parseFloat(cartProduct.recipe[0].price.toFixed(2)); - } - else return 0; - }, 0); + const tab = await Tabs.findById(tabId); + if (!tab) { + throw new Error('Tab not found'); + } else if (tab.status === 'closed') { + throw new Error('Tab closed'); + } else { + const totalAmount = cartProducts.reduce( + (total, cartProduct) => { + if(cartProduct.type === 'product'){ + return total + parseInt(cartProduct.cartQty) * parseFloat(cartProduct.price.toFixed(2)); + } + else if(cartProduct.type === 'variation') { + return total + parseInt(cartProduct.cartQty) * parseFloat(cartProduct.recipe[0].price.toFixed(2)); + } + else return 0; + }, 0); - //grab all the product ids from the products array and put them into a new array. if a product has a cartQty of 3, it will be added 3 times - const items = cartProducts.flatMap(({ cartQty, _id }) => Array(cartQty).fill(_id)); + //grab all the product ids from the products array and put them into a new array. if a product has a cartQty of 3, it will be added 3 times + const items = cartProducts.flatMap(({ cartQty, _id }) => Array(cartQty).fill(_id)); - //grab all the cartProducts that are variations and the recipie[0] in an object with the cart product id and recipie[0]._id - const variations = cartProducts.flatMap(({_id, type, recipe, cartQty}) => { - if(type === "variation") { - return Array(recipe[0].cartQty).fill({id: _id, recipe: recipe[0]._id, qty: cartQty}); - } - else return []; - }); + //grab all the cartProducts that are variations and the recipie[0] in an object with the cart product id and recipie[0]._id + const variations = cartProducts.flatMap(({_id, type, recipe, cartQty}) => { + if(type === 'variation') { + return Array(recipe[0].cartQty).fill({id: _id, recipe: recipe[0]._id, qty: cartQty}); + } + else return []; + }); - const order = new Orders({ - tabId: tab._id, - restaurantId: tab.restaurantId, - totalAmount, - items, - variations - }); - tab.orders.push(order._id); + const order = new Orders({ + tabId: tab._id, + restaurantId: tab.restaurantId, + totalAmount, + items, + variations + }); + tab.orders.push(order._id); - await Promise.all([order.save(), tab.save()]); - return order; + await Promise.all([order.save(), tab.save()]); + return order; - } - } catch (e) { - throw new Error('Error creating order'); - } - } + } }; module.exports = { - getActiveOrders, - updateOrder, - createNewOrder, + getOrder, + getActiveOrders, + updateOrder, + createNewOrder, }; \ No newline at end of file diff --git a/backend/app/datamodels/products.js b/backend/app/datamodels/products.js new file mode 100644 index 0000000..0ec0c71 --- /dev/null +++ b/backend/app/datamodels/products.js @@ -0,0 +1,17 @@ +const { Products } = require('../db/models'); + +const getProducts = async (restaurantId) => { + const products = await Products.find({ + restaurantId, + type: { + $in: ['product', 'variation'] + } + }) + .populate('recipe').exec(); + + return products; +}; + +module.exports = { + getProducts +}; diff --git a/backend/app/datamodels/restaurants.js b/backend/app/datamodels/restaurants.js index e324621..c234f37 100644 --- a/backend/app/datamodels/restaurants.js +++ b/backend/app/datamodels/restaurants.js @@ -1,42 +1,42 @@ -const { Restaurants } = require("../db/models"); +const { Restaurants } = require('../db/models'); -const getRestaurantFromId = async (restaurantId) => { - const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ _id: restaurantId}); +const getRestaurantById = async (restaurantId) => { + const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ _id: restaurantId}); - return { - restaurantId: _id, - restaurantName, - ownerName, - maxInstances - } - }; + return { + restaurantId: _id, + restaurantName, + ownerName, + maxInstances + }; +}; - const getRestaurantFromName = async (restaurantNameFromUrl) => { - const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ name: restaurantNameFromUrl}); +const getRestaurantByName = async (restaurantNameFromUrl) => { + const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ name: restaurantNameFromUrl}); - return { - restaurantId: _id, - restaurantName, - ownerName, - maxInstances - } - }; + return { + restaurantId: _id, + restaurantName, + ownerName, + maxInstances + }; +}; - const getRestaurantFromHostname = async (hostname) => { - const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ externalHostnames: hostname }) +const getRestaurantByHostname = async (hostname) => { + const { _id, name: restaurantName, ownerName, maxInstances } = await Restaurants.findOne({ externalHostnames: hostname }); - return { - restaurantId: _id, - restaurantName, - ownerName, - maxInstances - } - }; + return { + restaurantId: _id, + restaurantName, + ownerName, + maxInstances + }; +}; - module.exports = { - getRestaurantFromHostname, - getRestaurantFromId, - getRestaurantFromName - }; +module.exports = { + getRestaurantById, + getRestaurantByName, + getRestaurantByHostname, +}; \ No newline at end of file diff --git a/backend/app/datamodels/tables.js b/backend/app/datamodels/tables.js index ada29d0..3c7633b 100644 --- a/backend/app/datamodels/tables.js +++ b/backend/app/datamodels/tables.js @@ -1,72 +1,139 @@ -const mongoose = require('mongoose'); const { Tables } = require('../db/models'); +const { newTab } = require('./tabs'); + +const getRandomTable = async (restaurantId) => { + const randomTable = await Tables.findOne({ restaurantId }); + if (!randomTable) { + throw new Error('No tables found for this restaurant'); + } else { + const table = await getTable(randomTable._id); + return table; + } +}; + +const getTable = async (tableId, pin) => { + const table = await Tables.findById(tableId); + + if (!table) { + throw new Error('Table not found'); + } + + if (table.locked && table.pin !== pin) { + throw new Error('Incorrect pin'); + } + + if (!table.tabOpen) { + const currentNewTab = await newTab(table); + table.currentTab = currentNewTab._id; + table.tabOpen = true; + await table.save(); + } + + const populatedTable = await populateTableData(table); + + return populatedTable; +}; const closeTable = async (tableId, session) => { - const table = await Tables.findById(tableId).session(session); + const table = await Tables.findById(tableId).session(session); - if (!table) { - throw new Error('Table not found'); - } + if (!table) { + throw new Error('Table not found'); + } - if (!table.currentTab) { - throw new Error('No current tab for the table'); - } - const currentTab = table.currentTab; - table.olderTabs.push(table.currentTab); - table.currentTab = null; - table.tabOpen = false; - table.lastUpdated = Date.now(); + if (!table.currentTab) { + throw new Error('No current tab for the table'); + } + const currentTab = table.currentTab; + table.olderTabs.push(table.currentTab); + table.currentTab = null; + table.tabOpen = false; + table.lastUpdated = Date.now(); - await table.save({ session }); + await table.save({ session }); - return { table, tabId: currentTab } + return { table, tabId: currentTab }; }; const getTableById = async (tableId) => { - const table = await Tables.findById(tableId).populate({ - path: 'currentTab', - populate: { - path: 'orders', - populate: { - path: 'items', - populate: { - path: 'recipe' - } - } - } - }).exec(); - - if (!table) { - throw new Error('Table not found'); - } - - return table; + const table = await Tables.findById(tableId).populate({ + path: 'currentTab', + populate: { + path: 'orders', + populate: { + path: 'items', + populate: { + path: 'recipe' + } + } + } + }).exec(); + + if (!table) { + throw new Error('Table not found'); + } + + return table; }; const getTableByRestaurantId = async (restaurantId) => { - const table = await Tables.find({ restaurantId }).populate({ - path: 'currentTab', - populate: { - path: 'orders', - populate: { - path: 'items', - populate: { - path: 'recipe' - } - } - } - }).exec(); - - if (!table) { - throw new Error('Table not found'); - } - - return table; + const table = await Tables.find({ restaurantId }).populate({ + path: 'currentTab', + populate: { + path: 'orders', + populate: { + path: 'items', + populate: { + path: 'recipe' + } + } + } + }).exec(); + + if (!table) { + throw new Error('Table not found'); + } + + return table; +}; + +const getTableStatus = async (tableId) => { + const table = await Tables.findById(tableId); + + if (!table) { + throw new Error('Table not found'); + } + + if (table.locked) { + throw new Error('Table is locked'); + } + + return true; }; +const populateTableData = async (table) => { + return await table.populate({ + path: 'currentTab', + populate: { + path: 'orders', + options: { sort: { createdAt: -1 } }, + populate: { + path: 'items', + populate: { + path: 'recipe' + } + } + } + }).execPopulate(); +}; module.exports = { - closeTable, - getTableByRestaurantId, - getTableById, + getTable, + getRandomTable, + closeTable, + getTableStatus, + getTableByRestaurantId, + getTableById, }; + +// TODO: refactor the getTableById and getTable to only one function and make necceary changes in the frontend \ No newline at end of file diff --git a/backend/app/datamodels/tablesAndTabs.js b/backend/app/datamodels/tablesAndTabs.js index fb1d8a9..8176277 100644 --- a/backend/app/datamodels/tablesAndTabs.js +++ b/backend/app/datamodels/tablesAndTabs.js @@ -3,28 +3,28 @@ const { closeTable } = require('./tables'); const { closeTab } = require('./tabs'); const closeTableAndTab = async (tableId) => { - let session = await mongoose.startSession(); + let session = await mongoose.startSession(); - try { - session.startTransaction(); + try { + session.startTransaction(); - const { table, tabId } = await closeTable(tableId, session); + const { table, tabId } = await closeTable(tableId, session); - await closeTab(tabId, session); + await closeTab(tabId, session); - await session.commitTransaction(); + await session.commitTransaction(); - return { message: "Closed table" }; - } catch (error) { - console.log('Transaction aborted'); - console.log(error); - await session.abortTransaction(); - throw error; - } finally { - session.endSession(); - } -} + return { message: 'Closed table' }; + } catch (error) { + console.log('Transaction aborted'); + console.log(error); + await session.abortTransaction(); + throw error; + } finally { + session.endSession(); + } +}; module.exports = { - closeTableAndTab, + closeTableAndTab, }; diff --git a/backend/app/datamodels/tabs.js b/backend/app/datamodels/tabs.js index c0e9224..4a3b82e 100644 --- a/backend/app/datamodels/tabs.js +++ b/backend/app/datamodels/tabs.js @@ -1,35 +1,95 @@ -const mongoose = require('mongoose'); const { Tabs } = require('../db/models'); +const getTab = async (tabId) => { + let tab = await Tabs.findById(tabId); + if (!tab) { + throw new Error('Tab not found'); + } else { + tab = await tab.populate('orders').execPopulate(); + return tab; + } +}; + const closeTab = async (tabId, session) => { - const tab = await Tabs.findById(tabId).session(session); + const tab = await Tabs.findById(tabId).session(session); + + if (!tab) { + throw new Error('Tab not found'); + } - if (!tab) { - throw new Error('Tab not found'); - } + tab.status = 'closed'; + tab.lastUpdated = Date.now(); - tab.status = 'closed'; - tab.lastUpdated = Date.now(); + await tab.save({ session }); - await tab.save({ session }); + return tab; +}; - return tab; -} +const clearWaiterRequested = async (tabId) => { + const tab = await Tabs.findById(tabId); -const clearWaiter = async (tabId) => { - const tab = await Tabs.findById(tabId); + if (!tab) { + throw new Error('Tab not found'); + } - if (!tab) { - throw new Error('Tab not found'); - } + tab.callWaiter = null; + await tab.save(); - tab.callWaiter = null; - await tab.save(); + return tab; +}; - return tab; -} +const waiterRequested = async (tabId) => { + const tab = await Tabs.findById(tabId); + if (!tab) { + throw new Error('Tab not found'); + } + if (tab.callWaiter === 'called') { + throw new Error('Waiter already called'); + } + if (tab.status === 'closed') { + throw new Error('Tab closed'); + } else { + tab.callWaiter = 'called'; + await tab.save(); + + return true; + } +}; + +const paymentRequested = async (tabId, paymentType, tipsPercent) => { + const tab = await Tabs.findById(tabId); + if (!tab) { + throw new Error('Tab not found'); + } + if (tab.status === 'closed') { + throw new Error('Tab closed'); + } + + tab.paymentRequested = paymentType; + if (tipsPercent) { + tab.tipsPercent = tipsPercent; + } + await tab.save(); + + return true; +}; + +const newTab = async (table) => { + const newTab = await new Tabs({ + restaurantId: table.restaurantId, + tableId: table._id, + status: 'open', + lastUpdated: Date.now(), + createdAt: Date.now() + }).save(); + return newTab; +}; module.exports = { - closeTab, - clearWaiter, + getTab, + closeTab, + newTab, + waiterRequested, + clearWaiterRequested, + paymentRequested }; diff --git a/backend/app/db/collectionWatcher.js b/backend/app/db/collectionWatcher.js index 9673765..60152af 100644 --- a/backend/app/db/collectionWatcher.js +++ b/backend/app/db/collectionWatcher.js @@ -2,30 +2,30 @@ const MongoClient = require('mongodb').MongoClient; const EventEmitter = require('events'); class CollectionWatcher extends EventEmitter { - constructor() { - super(); - this.collections = []; - } + constructor() { + super(); + this.collections = []; + } - async init(clientUrl) { - this.client = await MongoClient.connect(clientUrl); - } - subscribeToCollection(collection) { - const mongoCollection = this.client.db('rockandrolla').collection(collection); - const collectionStream = mongoCollection.watch(); + async init(clientUrl) { + this.client = await MongoClient.connect(clientUrl); + } + subscribeToCollection(collection) { + const mongoCollection = this.client.db('tabley').collection(collection); + const collectionStream = mongoCollection.watch(); - this.collections.push( - { - [collection]: collectionStream - }); + this.collections.push( + { + [collection]: collectionStream + }); - collectionStream.on('change', (change) => { - this.emit(`${collection}:change`, change); - }); - } + collectionStream.on('change', (change) => { + this.emit(`${collection}:change`, change); + }); + } } module.exports = { - CollectionWatcher -} \ No newline at end of file + CollectionWatcher +}; \ No newline at end of file diff --git a/backend/app/db/connection.js b/backend/app/db/connection.js index 6deb0aa..851ed24 100644 --- a/backend/app/db/connection.js +++ b/backend/app/db/connection.js @@ -1,15 +1,15 @@ -const mongoose = require("mongoose"); +const mongoose = require('mongoose'); -const dbUrl = `mongodb://${process.env.MONGODB_HOST || "localhost"}/tabley`; +const dbUrl = `mongodb://${process.env.MONGODB_HOST || 'localhost'}/tabley`; async function connectToDatabase() { - await mongoose.connect(dbUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, - }); + await mongoose.connect(dbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); } module.exports = { - connectToDatabase, - dbUrl - }; + connectToDatabase, + dbUrl +}; diff --git a/backend/app/db/models.js b/backend/app/db/models.js index 93cbc69..75d80af 100644 --- a/backend/app/db/models.js +++ b/backend/app/db/models.js @@ -3,138 +3,138 @@ const { Schema: { Types: { ObjectId } } } = mongoose; const { model, Schema } = mongoose; const restaurantSchema = Schema({ - name: String, - ownerName: String, - externalHostnames: Array, - maxInstances: Number, // max ws listeners - //open hours - todo for last order before! + name: String, + ownerName: String, + externalHostnames: Array, + maxInstances: Number, // max ws listeners + //open hours - todo for last order before! }); const tableSchema = Schema({ - restaurantId: String, - tableNo: Number, - tabOpen: Boolean, - options: Array, - currentTab: { - type: ObjectId, - ref: 'Tabs' - }, - olderTabs: [{ - type: ObjectId, - ref: 'Tabs' - }], - locked: Boolean, - pin: String, - lastUpdated: Date + restaurantId: String, + tableNo: Number, + tabOpen: Boolean, + options: Array, + currentTab: { + type: ObjectId, + ref: 'Tabs' + }, + olderTabs: [{ + type: ObjectId, + ref: 'Tabs' + }], + locked: Boolean, + pin: String, + lastUpdated: Date }); const productSchema = Schema({ - restaurantId: { - type: Schema.Types.ObjectId, - ref: 'Restaurants' - }, + restaurantId: { + type: Schema.Types.ObjectId, + ref: 'Restaurants' + }, - name: String, - imgUrl: String, + name: String, + imgUrl: String, - type: String, //product, reciepieProduct, variation + type: String, //product, reciepieProduct, variation - price: Number, - currency: String, + price: Number, + currency: String, - description: String, + description: String, - stock: Number, - active: Boolean, + stock: Number, + active: Boolean, - alcoholContent: String, + alcoholContent: String, - measureUnit: String, - qty: Number, + measureUnit: String, + qty: Number, - recipe: [{ - type: Schema.Types.ObjectId, - ref: 'Products' - }], + recipe: [{ + type: Schema.Types.ObjectId, + ref: 'Products' + }], - category: String, - subCategory: String, - - stock: Number, + category: String, + subCategory: String, }); const tabSchema = Schema({ - restaurantId: { - type: ObjectId, - ref: 'Restaurants' - }, - tableId: { - type: ObjectId, - ref: 'Tables' - }, - orders: [ - { - type: ObjectId, - ref: 'Orders' - } - ], - status: String, // request-to-close,closed,open - callWaiter: String, // called, pending-arival, not-called - lastUpdated: Date, - createdAt: Date + restaurantId: { + type: ObjectId, + ref: 'Restaurants' + }, + tableId: { + type: ObjectId, + ref: 'Tables' + }, + orders: [ + { + type: ObjectId, + ref: 'Orders' + } + ], + status: String, // request-to-close,closed,open + callWaiter: String, // called; improve this to an array containing an object with timestamps when the waiter was called, and when the waiter cleared the call + paymentRequested: String, // cash, card + tipsPercent: Number, + lastUpdated: Date, + createdAt: Date }); const orderSchema = Schema({ - status: { - type:String, - default: 'recieved' - }, // received, inProgress, done, canceled - restaurantId: { - type: ObjectId, - ref: 'Restaurants' - }, - tabId: { - type: ObjectId, - ref: 'Tabs' - }, - items: [{ - type: ObjectId, - ref: 'Products' - }], - variations: [], - totalAmount: Number, - createdAt: { - type: Date, - default: Date.now - }, - lastUpdated: { - type: Date, - default: Date.now - } + status: { + type:String, + default: 'recieved' + }, // received, inProgress, done, canceled + restaurantId: { + type: ObjectId, + ref: 'Restaurants' + }, + tabId: { + type: ObjectId, + ref: 'Tabs' + }, + items: [{ + type: ObjectId, + ref: 'Products' + }], + variations: [], + totalAmount: Number, + createdAt: { + type: Date, + default: Date.now + }, + lastUpdated: { + type: Date, + default: Date.now + } }); const waiterSchema = Schema({ - restaurantId: { - type: ObjectId, - ref: 'Restaurants' - }, - name: String, - enabled: Boolean, - createdAt: Date + restaurantId: { + type: ObjectId, + ref: 'Restaurants' + }, + name: String, + enabled: Boolean, + createdAt: Date }); -const Restaurants = model("Restaurants", restaurantSchema); -const Tables = model("Tables", tableSchema); -const Products = model("Products", productSchema); -const Tabs = model("Tabs", tabSchema); -const Orders = model("Orders", orderSchema); -const Waiters = model("Waiters", waiterSchema); +const Restaurants = model('Restaurants', restaurantSchema); +const Tables = model('Tables', tableSchema); +const Products = model('Products', productSchema); +const Tabs = model('Tabs', tabSchema); +const Orders = model('Orders', orderSchema); +const Waiters = model('Waiters', waiterSchema); module.exports = { - Restaurants, - Tables, - Products, - Orders, - Tabs, - Waiters -} \ No newline at end of file + Restaurants, + Tables, + Products, + Orders, + Tabs, + Waiters +}; \ No newline at end of file diff --git a/backend/app/handlers/events.js b/backend/app/handlers/events.js index 0ee7ea9..a8e84a8 100644 --- a/backend/app/handlers/events.js +++ b/backend/app/handlers/events.js @@ -1,39 +1,39 @@ -const { CollectionWatcher } = require("../db/collectionWatcher"); -const { dbUrl } = require("../db/connection"); +const { CollectionWatcher } = require('../db/collectionWatcher'); +const { dbUrl } = require('../db/connection'); -const onConnection = require("../controllers/eventsController/socket/onConnect"); -const onDisconnect = require("../controllers/eventsController/socket/onDisconnect"); +const onConnection = require('../controllers/eventsController/socket/onConnect'); +const onDisconnect = require('../controllers/eventsController/socket/onDisconnect'); -const onOrderChange = require("../controllers/eventsController/db/onOrderChange"); -const onTabChange = require("../controllers/eventsController/db/onTabChange"); +const onOrderChange = require('../controllers/eventsController/db/onOrderChange'); +const onTabChange = require('../controllers/eventsController/db/onTabChange'); const createCollectionWatcher = async (io) => { - const mongoEvents = new CollectionWatcher(); - await mongoEvents.init(dbUrl); - mongoEvents.subscribeToCollection("orders"); - mongoEvents.subscribeToCollection("tabs"); + const mongoEvents = new CollectionWatcher(); + await mongoEvents.init(dbUrl); + mongoEvents.subscribeToCollection('orders'); + mongoEvents.subscribeToCollection('tabs'); - mongoEvents.on("orders:change", (mongoEvent) => { - onOrderChange(mongoEvent, io); - }); + mongoEvents.on('orders:change', (mongoEvent) => { + onOrderChange(mongoEvent, io); + }); - mongoEvents.on("tabs:change", (mongoEvent) => { - onTabChange(mongoEvent, io); - }); + mongoEvents.on('tabs:change', (mongoEvent) => { + onTabChange(mongoEvent, io); + }); }; const socketIOEventsHandler = (io) => { - io.on("connection", (socket) => { - onConnection(socket); + io.on('connection', (socket) => { + onConnection(socket); - socket.on("disconnect", () => { - onDisconnect(socket); - }); - }); + socket.on('disconnect', () => { + onDisconnect(socket); + }); + }); }; module.exports = { - createCollectionWatcher, - createSocketHandler: socketIOEventsHandler, + createCollectionWatcher, + createSocketHandler: socketIOEventsHandler, }; \ No newline at end of file diff --git a/backend/app/handlers/requests.js b/backend/app/handlers/requests.js index 6dff6b8..1737e3f 100644 --- a/backend/app/handlers/requests.js +++ b/backend/app/handlers/requests.js @@ -1,55 +1,71 @@ -const path = require("path"); +const path = require('path'); const compression = require('compression'); -const express = require("express"); -const cors = require("cors"); -const helmet = require("helmet"); +const express = require('express'); +const basicAuth = require('express-basic-auth'); +const cors = require('cors'); +const helmet = require('helmet'); const frontendFilesPath = path.join(__dirname, '../../../frontend/web-build'); const createApp = (dependencies = {}) => { - const app = express(); - - let origins = require("../../cors.json").origins; - console.log(origins); - setInterval(() => { - try{ - delete require.cache[require.resolve("../../cors.json")]; - origins = require("../../cors.json").origins; - } catch(e) { - console.log("Express: Error reading cors.json file"); - } - }, 60000); - - app.use(compression({ filter: (req, res) => true, level: 6, algorithms: ['br', 'gzip', 'deflate'] })); - app.use(cors({ - origin: function (origin, callback) { - if (!origin) return callback(null, true); - if (origins.indexOf(origin) !== -1) { - callback(null, true) - } else { - console.log("Express: Blocked by CORS: " + origin); - callback('Not allowed by CORS') - } - } - })); - app.use(express.json()); - app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'", "https://unpkg.com", "'unsafe-inline'", "https://123rf.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'"], - }, - }, - })); - - app.use("/api/user", dependencies.userRoutes || require("../routes/userRoutes")); - app.use("/api/owner", dependencies.ownerRoutes || require("../routes/ownerRoutes")); - app.use("/api/status", dependencies.statusRoutes || require("../routes/statusRoutes")); - app.use("/api/app", dependencies.appRoutes || require("../routes/appRoutes")); - app.use("/", express.static(frontendFilesPath)); - return app; -} + const app = express(); + + let origins = require('../../cors.json').origins; + console.log(origins); + setInterval(() => { + try{ + delete require.cache[require.resolve('../../cors.json')]; + origins = require('../../cors.json').origins; + } catch(e) { + console.log('Express: Error reading cors.json file'); + } + }, 60000); + + app.use(compression({ filter: () => true, level: 6, algorithms: ['br', 'gzip', 'deflate'] })); + app.use(cors({ + origin: function (origin, callback) { + if (!origin) return callback(null, true); + if (origins.indexOf(origin) !== -1) { + callback(null, true); + } else { + console.log('Express: Blocked by CORS: ' + origin); + callback('Not allowed by CORS'); + } + } + })); + app.use(express.json()); + app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ['\'self\'', 'https://unpkg.com', '\'unsafe-inline\'', 'https://123rf.com'], + scriptSrc: ['\'self\'', '\'unsafe-inline\'', '\'wasm-unsafe-eval\'', 'blob:', 'https://cdn.jsdelivr.net'], + imgSrc: ['\'self\'', 'https://previews.123rf.com'] + }, + }, + })); + + app.use('/api/user', dependencies.userRoutes || require('../routes/userRoutes')); + app.use('/api/owner', dependencies.ownerRoutes || require('../routes/ownerRoutes')); + app.use('/api/status', dependencies.statusRoutes || require('../routes/statusRoutes')); + app.use('/api/app', dependencies.appRoutes || require('../routes/appRoutes')); + + app.use( + '/@:random/restaurant-view', + basicAuth({ + users: { '': '' }, + challenge: true, + realm: 'tabley', + }), + (req) => req.url = '', + express.static(frontendFilesPath), + ); + app.use('/@:random', (req) => req.url = '', express.static(frontendFilesPath)); + app.use('/join', (req) => req.url = '', express.static(frontendFilesPath)); + app.use('/', express.static(frontendFilesPath)); + + return app; +}; module.exports = { - createApp -} \ No newline at end of file + createApp +}; \ No newline at end of file diff --git a/backend/app/index.js b/backend/app/index.js index c440c72..10bdd73 100644 --- a/backend/app/index.js +++ b/backend/app/index.js @@ -1,8 +1,8 @@ -const { createApp } = require("./handlers/requests"); -const { createCollectionWatcher, createSocketHandler } = require("./handlers/events"); +const { createApp } = require('./handlers/requests'); +const { createCollectionWatcher, createSocketHandler } = require('./handlers/events'); module.exports = { - createApp, - createCollectionWatcher, - createSocketHandler + createApp, + createCollectionWatcher, + createSocketHandler }; \ No newline at end of file diff --git a/backend/app/routes/appRoutes.js b/backend/app/routes/appRoutes.js index 696cfae..9e0029a 100644 --- a/backend/app/routes/appRoutes.js +++ b/backend/app/routes/appRoutes.js @@ -1,10 +1,10 @@ -const express = require("express"); -const { getRestaurantById, getRestaurantByName, hostnameCheck } = require("../controllers/appController"); +const express = require('express'); +const { getRestaurantByIdController, getRestaurantByNameController, getRestaurantByHostnameController } = require('../controllers/appController'); const router = express.Router(); -router.get("/restaurantName/:restaurantName", getRestaurantByName); -router.get("/restaurantId/:restaurantId", getRestaurantById); -router.post("/hostnameCheck", hostnameCheck); +router.get('/restaurantId/:restaurantId', getRestaurantByIdController); +router.get('/restaurantName/:restaurantName', getRestaurantByNameController); +router.post('/hostnameCheck', getRestaurantByHostnameController); module.exports = router; diff --git a/backend/app/routes/ownerRoutes.js b/backend/app/routes/ownerRoutes.js index 9b6fb5a..520d67f 100644 --- a/backend/app/routes/ownerRoutes.js +++ b/backend/app/routes/ownerRoutes.js @@ -1,15 +1,15 @@ -const express = require("express"); -const { updateOrder, getActiveOrders, getTables, getTable, closeTable, clearCallWaiter } = require("../controllers/ownerController"); +const express = require('express'); +const { updateOrderController, getActiveOrdersController, getTableByRestaurantIdController, getTableByIdController, closeTableController, callWaiterController } = require('../controllers/ownerController'); const router = express.Router(); -router.post("/orders/update/:orderId", updateOrder); -router.get("/orders/active/:restaurantId", getActiveOrders); +router.post('/orders/update/:orderId', updateOrderController); +router.get('/orders/active/:restaurantId', getActiveOrdersController); -router.get("/tables/:restaurantId", getTables); +router.get('/tables/:restaurantId', getTableByRestaurantIdController); -router.get("/table/:tableId", getTable); -router.get("/table/close/:tableId", closeTable); +router.get('/table/:tableId', getTableByIdController); +router.get('/table/close/:tableId', closeTableController); -router.get("/actions/clearCallWaiter/:tabId", clearCallWaiter); +router.get('/actions/clearCallWaiter/:tabId', callWaiterController); module.exports = router; diff --git a/backend/app/routes/statusRoutes.js b/backend/app/routes/statusRoutes.js index 8422da6..b7b4647 100644 --- a/backend/app/routes/statusRoutes.js +++ b/backend/app/routes/statusRoutes.js @@ -1,9 +1,9 @@ -const express = require("express"); +const express = require('express'); const router = express.Router(); -router.get("/", (req, res) => { - res.sendStatus(200); +router.get('/', (req, res) => { + res.sendStatus(200); }); module.exports = router; diff --git a/backend/app/routes/userRoutes.js b/backend/app/routes/userRoutes.js index e47061a..de55b76 100644 --- a/backend/app/routes/userRoutes.js +++ b/backend/app/routes/userRoutes.js @@ -1,21 +1,25 @@ -const express = require("express"); -const { getMenu, getRandomTable, getTable, getTab, newOrder, getOrder, callWaiter, payCard, payCash } = require("../controllers/userController"); +const express = require('express'); +const { getMenuController, getRandomTableController, getTableController, getTableStatusController, getTabController, newOrderController, getOrderController, callWaiterController, payCardController, payCashController } = require('../controllers/userController'); +const PROD = process.env.NODE_ENV === 'production'; const router = express.Router(); -router.get("/menus/:id", getMenu); +router.get('/menus/:restaurantId', getMenuController); -router.get("/getRandomTable/:restaurantId", getRandomTable); +if (!PROD) { + router.get('/getRandomTable/:restaurantId', getRandomTableController); +} -router.get("/tables/:tableId", getTable); +router.get('/tables/:tableId', getTableController); +router.get('/tableStatus/:tableId', getTableStatusController); -router.get("/tabs/:tabId", getTab); +router.get('/tabs/:tabId', getTabController); -router.post("/orders/new/:tabId", newOrder); -router.get("/orders/:orderId", getOrder); +router.post('/orders/new/:tabId', newOrderController); +router.get('/orders/:orderId', getOrderController); -router.get("/actions/callWaiter/:tabId", callWaiter); -router.get("/actions/payCash/:tabId", payCash); -router.get("/actions/payCard/:tabId", payCard); +router.get('/actions/callWaiter/:tabId', callWaiterController); +router.get('/actions/payCash/:tabId', payCardController); +router.get('/actions/payCard/:tabId', payCashController); module.exports = router; diff --git a/backend/app/utils/helperFuntions.js b/backend/app/utils/helperFuntions.js deleted file mode 100644 index 0a453cf..0000000 --- a/backend/app/utils/helperFuntions.js +++ /dev/null @@ -1,28 +0,0 @@ -const { MongoClient } = require("mongodb"); -const { Tabs, Restaurants } = require("../db/models"); - -async function closeTab(tabId) { - try { - const now = Date.now(); - const tab = await Tabs.findById(tabId); - - if (tab.status !== 'closed') { - tab.status = 'closed'; - tab.lastUpdated = now; - await tab.save(); - - const table = await Restaurants.findById(tab.restaurantId); - table.olderTabs.push(tab._id); - table.tabOpen = false; - table.lastUpdated = now; - table.currentTab = null; - await table.save(); - return true; - } else return false; - } catch (e) { - console.log(e); - return false; - } -} - -module.exports = { closeTab }; diff --git a/backend/index.js b/backend/index.js index 98d8116..1a9ecde 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,62 +1,69 @@ -const fs = require("fs"); -const https = require("https"); -const http = require("http"); +const https = require('https'); +const http = require('http'); -const socketIo = require("socket.io"); -const { connectToDatabase, dbUrl } = require("./app/db/connection"); -const { createApp, createCollectionWatcher, createSocketHandler } = require("./app"); +const socketIo = require('socket.io'); +const { connectToDatabase } = require('./app/db/connection'); +const { createApp, createCollectionWatcher, createSocketHandler } = require('./app'); const app = createApp(); -const PROD = process.env.NODE_ENV === "production"; -const options = {}; +const PROD = process.env.NODE_ENV === 'production'; -if (PROD) { - if (!process.env.KEY || !process.env.CERT) { - console.error("Missing KEY or CERT environment variables"); - console.error("Exiting..."); - process.exit(1); - } else { - options.key = fs.readFileSync(process.env.KEY); - options.cert = fs.readFileSync(process.env.CERT); - } +if (PROD && (!process.env.KEY || !process.env.CERT)) { + console.error('Missing KEY or CERT environment variables'); + console.error('Exiting...'); + process.exit(1); } -let origins = require("./cors.json").origins; + +let origins = require('./cors.json').origins; setInterval(() => { - try { - delete require.cache[require.resolve("./cors.json")]; - origins = require("./cors.json").origins; - } catch (e) { - console.log("Socket.io: Error reading cors.json file"); - } + try { + delete require.cache[require.resolve('./cors.json')]; + origins = require('./cors.json').origins; + } catch (e) { + console.log('Socket.io: Error reading cors.json file'); + } }, 60000); -const server = PROD ? https.createServer(options, app) : http.createServer(app); +const server = PROD ? https.createServer(app) : http.createServer(app); + +if (PROD) { + server.addContext('tabley.app', { + key: process.env.KEY, + cert: process.env.CERT + }); + + server.addContext('order.rocknrolla.ro', { + key: process.env.ROCKKEY, + cert: process.env.ROCKCERT + }); +} const io = socketIo(server, { - cors: { - origin: function (origin, callback) { - if (!origin) return callback(null, true); - if (origins.indexOf(origin) !== -1) { - callback(null, true); - } else { - console.log("Socket.io: Blocked by CORS: " + origin); - callback('Not allowed by CORS'); - } - } - } + cors: { + origin: function (origin, callback) { + if (!origin) return callback(null, true); + if (origins.indexOf(origin) !== -1) { + callback(null, true); + } else { + console.log('Socket.io: Blocked by CORS: ' + origin); + callback('Not allowed by CORS'); + } + } + } }); createSocketHandler(io); const PORT = PROD ? 443 : process.env.PORT || 3000; server.listen(PORT, async () => { - try { - console.log("Connecting to MongoDB..."); - await connectToDatabase(); - await createCollectionWatcher(io); - console.log("Connection established to MongoDB..."); - console.log(`Listening on port ${PORT}..`); - } catch (error) { - console.log("Could not connect to MongoDB...", error); - } -}); + try { + console.log('Connecting to MongoDB...'); + await connectToDatabase(); + await createCollectionWatcher(io); + console.log('Connection established to MongoDB...'); + console.log(`Listening on port ${PORT}..`); + + } catch (error) { + console.log('Could not connect to MongoDB...', error); + } +}); \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 2d65988..07af959 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,18 +15,24 @@ "dependencies": { "compression": "^1.7.4", "cors": "^2.8.5", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", "express": "^4.17.1", + "express-basic-auth": "^1.2.1", "helmet": "^4.4.1", "https": "^1.0.0", "jest": "^29.5.0", "mongodb": "^5.3.0", "mongoose": "^5.11.14", "morgan": "^1.10.0", + "prettier": "^2.8.8", + "prometheus-remote-write": "^0.3.0", "sinon": "^15.0.3", "socket.io": "^4.6.1", "supertest": "^6.3.3" }, "devDependencies": { + "eslint": "^8.42.0", "nodemon": "^2.0.22" } } diff --git a/backend/yarn.lock b/backend/yarn.lock index 433147f..f736113 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -299,6 +299,57 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== + +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.5.2" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.42.0": + version "8.42.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" + integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== + +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -544,6 +595,80 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -674,6 +799,11 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + "@types/mongodb@^3.5.27": version "3.6.20" resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.20.tgz#b7c5c580644f6364002b649af1c06c3c0454e1d2" @@ -692,6 +822,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== +"@types/node@>=13.7.0": + version "20.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== + "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" @@ -740,6 +875,26 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.8.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -786,6 +941,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -871,7 +1031,7 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== -basic-auth@~2.0.1: +basic-auth@^2.0.1, basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== @@ -1186,7 +1346,7 @@ cors@^2.8.5, cors@~2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1216,7 +1376,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1228,6 +1388,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -1276,6 +1441,13 @@ diff@^5.1.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1349,11 +1521,119 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== + +eslint@^8.42.0: + version "8.42.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" + integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.42.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -1390,6 +1670,13 @@ expect@^29.5.0: jest-message-util "^29.5.0" jest-util "^29.5.0" +express-basic-auth@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381" + integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA== + dependencies: + basic-auth "^2.0.1" + express@^4.17.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -1427,16 +1714,38 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -fast-json-stable-stringify@^2.1.0: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -1444,6 +1753,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1472,6 +1788,27 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -1545,6 +1882,13 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1569,11 +1913,23 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + dependencies: + type-fest "^0.20.2" + graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -1644,6 +2000,19 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== +ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -1714,7 +2083,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1726,6 +2095,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -2162,6 +2536,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -2172,6 +2553,16 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json5@^2.2.2: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -2197,6 +2588,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -2209,11 +2608,28 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2302,7 +2718,7 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2529,6 +2945,18 @@ optional-require@^1.1.8: dependencies: require-at "^1.0.6" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -2536,7 +2964,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -2550,11 +2978,25 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -2624,6 +3066,23 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-format@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" @@ -2638,6 +3097,14 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prometheus-remote-write@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/prometheus-remote-write/-/prometheus-remote-write-0.3.0.tgz#35159407c4f2c9708a6fa526153781bdde484cf2" + integrity sha512-9xfJAGYRpmbZZCwUZzzRtJwOianNguDwv2hUF2aWDqhFUDdE6HDjPDw4tVDiRK+mnC+b53tJBCjxVBgSbMD05Q== + dependencies: + protobufjs "^6.11.3" + snappyjs "^0.6.1" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -2646,6 +3113,25 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +protobufjs@^6.11.3: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -2659,7 +3145,7 @@ pstree.remy@^1.1.8: resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== -punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== @@ -2683,6 +3169,11 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -2745,6 +3236,11 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -2764,6 +3260,25 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -2912,6 +3427,11 @@ smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +snappyjs@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/snappyjs/-/snappyjs-0.6.1.tgz#9bca9ff8c54b133a9cc84a71d22779e97fc51878" + integrity sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg== + socket.io-adapter@~2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" @@ -3025,7 +3545,7 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.1: +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -3089,6 +3609,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3125,11 +3650,23 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -3161,6 +3698,13 @@ update-browserslist-db@^1.0.10: escalade "^3.1.1" picocolors "^1.0.0" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -3212,6 +3756,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" diff --git a/frontend/src/App.js b/frontend/src/App.js index 7cb7fa6..809bd68 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -33,46 +33,63 @@ export default function App() { onClose: cartOnClose, } = useDisclose(); + const setRestaurantDetails = async ({ + restaurantName, + restaurantHostname, + }) => { + if (restaurantName) { + const restaurant = await state.api.getRestaurantByName(restaurantName); + if (restaurant) { + state.restaurantId = restaurant.restaurantId; + state.restaurantName = restaurant.restaurantName; + state.products = await state.api.getMenu(restaurant.restaurantId); + } else { + alert( + "2. Sorry but the url is not valid. Please scan a QR code or use the link provided by the restaurant." + ); + } + } else if (restaurantHostname) { + const restaurant = await state.api.getRestaurantByHostname( + restaurantHostname + ); + if (restaurant) { + state.restaurantId = restaurant.restaurantId; + state.restaurantName = restaurant.restaurantName; + state.products = await state.api.getMenu(restaurant.restaurantId); + } else { + alert( + "3. Sorry but the url is not valid. Please scan a QR code or use the link provided by the restaurant." + ); + } + } + }; + useEffect(() => { (async () => { state.api = new Api((msg) => toastEmitter.emit("showToast", msg)); try { const url = new URL(window.location); - if (url.hostname === "localhost") state.developerMode = true; + // if (url.hostname === "localhost") state.developerMode = true; + if (url.hostname === "tabley.app" || url.hostname === "localhost") { - if (!url.pathname.startsWith("/@")) { - alert( - "1. Sorry but you can't access this page directly. Please scan a QR code or use the link provided by the restaurant." - ); - } else { + if ( + url.pathname.startsWith("/@") && + url.pathname.endsWith("/restaurant-view") + ) { + let restaurantName = url.pathname + .replace("/restaurant-view", "") + .toLowerCase(); + restaurantName = restaurantName.replace("/@", ""); + await setRestaurantDetails({ restaurantName }); + state.user = "owner"; + } else if (url.pathname.startsWith("/@")) { const restaurantName = url.pathname.replace("/@", "").toLowerCase(); - - const restaurant = await state.api.getRestaurantByName( - restaurantName - ); - if (restaurant) { - state.restaurantId = restaurant.restaurantId; - state.restaurantName = restaurant.restaurantName; - state.products = await state.api.getMenu(restaurant.restaurantId); - } else { - alert( - "2. Sorry but the url is not valid. Please scan a QR code or use the link provided by the restaurant." - ); - } + await setRestaurantDetails({ restaurantName }); } } else { - const restaurant = await state.api.getRestaurantByHostname( - url.hostname - ); - if (restaurant) { - state.restaurantId = restaurant.restaurantId; - state.restaurantName = restaurant.restaurantName; - state.products = await state.api.getMenu(restaurant.restaurantId); - } else { - alert( - "3. Sorry but the url is not valid. Please scan a QR code or use the link provided by the restaurant." - ); - } + await setRestaurantDetails({ + restaurantHostname: url.hostname, + }); } } catch (e) { alert( @@ -110,7 +127,7 @@ export default function App() { )} {user === "owner" && } - {developerMode && user === "customer" && ( + {developerMode && user === "customer" && currentView === "scan" && ( )} diff --git a/frontend/src/components/Order.js b/frontend/src/components/Order.js index 2660dfd..82e5ba1 100644 --- a/frontend/src/components/Order.js +++ b/frontend/src/components/Order.js @@ -1,3 +1,5 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable no-restricted-syntax */ /* eslint-disable no-nested-ternary */ /* eslint-disable react/prop-types */ import { @@ -15,8 +17,56 @@ import React from "react"; import Time from "./Time"; +const composeItemsFromVariations = (stateOrder) => { + const order = JSON.parse(JSON.stringify(stateOrder)); + const { items, variations } = order; + const updatedItems = []; + + // Iterate over the variations + for (const variant of variations) { + // Iterate over the quantity + while (variant.qty > 0) { + const item = items.find((i) => i._id === variant.id); + const newItem = JSON.parse(JSON.stringify(item)); + // Move the item so we don't loop over it again + const itemIndex = items.indexOf(item); + order.items.splice(itemIndex, 1); + + // Update the recipe and quantity + newItem.recipe = newItem.recipe.filter( + (recipe) => recipe._id === variant.recipe + ); + newItem.cartQty = 1; + variant.qty -= 1; + + // Add the updated item to the result + updatedItems.push(newItem); + } + } + + // Add the new items to the order + order.items.push(...updatedItems); + + order.items = order.items.reduce((acc, item) => { + const existingItem = acc.find((i) => + i.type === "product" + ? i._id === item._id + : i._id === item._id && i.recipe[0]._id === item.recipe[0]._id + ); + if (existingItem) { + existingItem.cartQty += 1; + return acc; + } + // eslint-disable-next-line no-param-reassign + item.cartQty = 1; + return acc.concat(item); + }, []); + + return order; +}; + function Order({ order, orderNr }) { - const mutableItems = JSON.parse(JSON.stringify(order.items)); + const mutableItems = composeItemsFromVariations(order).items; return ( @@ -55,34 +105,68 @@ function Order({ order, orderNr }) { {`Order #${orderNr}`} - {mutableItems - .reduce((acc, item) => { - const existingItem = acc.find((i) => i._id === item._id); - if (existingItem) { - existingItem.qty += 1; - return acc; - } - // eslint-disable-next-line no-param-reassign - item.qty = 1; - return acc.concat(item); - }, []) - .map((orderedItem) => - orderedItem.type === "product" ? ( + {mutableItems.map((orderedItem) => + orderedItem.type === "product" ? ( + + + {orderedItem.cartQty}x{" "} + + + {orderedItem.name} + + + {orderedItem.price * orderedItem.cartQty} RON + + + ) : ( + + + {orderedItem.cartQty}x{" "} + - {orderedItem.qty}x {orderedItem.name} + {orderedItem.name} - - {orderedItem.price * orderedItem.qty} RON + + -{orderedItem.recipe[0].name} - ) : null - )} + + {orderedItem.recipe[0].price * orderedItem.cartQty} RON + + + ) + )} diff --git a/frontend/src/components/Toast.js b/frontend/src/components/Toast.js index e174dcc..2ee2635 100644 --- a/frontend/src/components/Toast.js +++ b/frontend/src/components/Toast.js @@ -48,11 +48,10 @@ export function ToastManager() { fontSize="md" fontWeight="medium" flexShrink={1} - color="darkText" > {title} - + {description} diff --git a/frontend/src/settings.js b/frontend/src/settings.js index 0f6bf2e..e9ad667 100644 --- a/frontend/src/settings.js +++ b/frontend/src/settings.js @@ -1,6 +1,6 @@ export const baseUrl = window.location.hostname === "localhost" ? "http://localhost/api" - : `http://${window.location.hostname}/api`; + : `https://${window.location.hostname}/api`; export const version = "0.0.1"; diff --git a/frontend/src/state/index.js b/frontend/src/state/index.js index fede387..01c1f59 100644 --- a/frontend/src/state/index.js +++ b/frontend/src/state/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-syntax */ /* eslint-disable array-callback-return */ /* eslint-disable no-param-reassign */ /* eslint-disable react-hooks/rules-of-hooks */ @@ -9,6 +10,8 @@ const hstate = hookstate( restaurantId: null, restaurantName: "Welcome to Tabley!", + isExternal: false, + user: "customer", viewHistory: ["scan"], @@ -38,6 +41,7 @@ const hstate = hookstate( subCategories: [], openOrders: [], + openOrdersPerTable: [], tables: [], ActionView: "tab", @@ -66,10 +70,108 @@ const extractCategoriesAndSubCategories = (products) => { }; }; +const transformOpenOrdersToOpenOrdersPerTable = (openOrders) => { + // these are raw orders, not hookstate objects + openOrders.forEach((order, oIndex) => { + // filter out the orders that are not 'recieved' aka open + if (order.status !== "recieved") { + openOrders.splice(oIndex, 1); + } else { + // Modify the items to only include the recipe that was ordered + for (const variant of order.variations) { + // Iterate over the quantity + while (variant.qty > 0) { + // Find the item in the order that matches the variant + const item = order.items.find((i) => i._id === variant.id); + // if the item was deleted, skip it + if (item) { + // Create a copy of the item + const newItem = JSON.parse(JSON.stringify(item)); + // Delete the item from the order so we don't loop over it again + const itemIndex = order.items.indexOf(item); + order.items.splice(itemIndex, 1); + // Update the recipe and quantity + newItem.recipe = newItem.recipe.filter( + (recipe) => recipe._id === variant.recipe + ); + newItem.cartQty = 1; + // Update the quantity of the variant + variant.qty -= 1; + // Add the updated item to the result + order.items.push(newItem); + } + } + } + // Reduce the items to only include the unique items and their quantity + // order.items.forEach((item, iIndex) => { + // const existingItem = order.items.find((i, index) => { + // if (i.type === "product") { + // if (i._id === item._id) { + // order.items.splice(index, 1); + // return true; + // } + // } + // if (i.type === "variation") { + // if (i._id === item._id && i.recipe[0]._id === item.recipe[0]._id) { + // order.items.splice(index, 1); + // return true; + // } + // } + // return false; + // }); + // if (existingItem) { + // console.log(existingItem); + // if (!item.cartQty) { + // item.cartQty = 1; + // } else { + // item.cartQty += 1; + // } + // } + // }); + + order.items = order.items.reduce((acc, item) => { + const existingItemIndex = acc.findIndex((i) => { + if (i.type === "product") { + return i._id === item._id; + } + if (i.type === "variation") { + return i._id === item._id && i.recipe[0]._id === item.recipe[0]._id; + } + return false; + }); + + if (existingItemIndex !== -1) { + if (!acc[existingItemIndex].cartQty) { + acc[existingItemIndex].cartQty = 1; + } else { + acc[existingItemIndex].cartQty += 1; + } + } else { + if (!item.cartQty) { + item.cartQty = 1; + } + acc.push(item); + } + + return acc; + }, []); + } + }); + console.log(openOrders); + return openOrders; +}; + export function globalState() { const ustate = useHookstate(hstate); return { + get isExternal() { + return ustate.isExternal.get(); + }, + set isExternal(bool) { + ustate.isExternal.set(bool); + }, + get developerMode() { return ustate.developerMode.get(); }, @@ -418,15 +520,26 @@ export function globalState() { ustate.viewHistory.set(viewHistory); }, + get openOrdersPerTable() { + return ustate.openOrdersPerTable.get(); + }, + get openOrders() { return ustate.openOrders.get(); }, + set openOrders(orders) { ustate.openOrders.set(orders); + ustate.openOrdersPerTable.set( + transformOpenOrdersToOpenOrdersPerTable(orders) + ); }, + pushNewOrder(order) { ustate.openOrders.set((prev) => [order, ...prev]); + // update openOrdersPerTable }, + updateOpenOrderStatus(orderId, status) { const openOrders = ustate.openOrders.get(); const openOrderIndex = openOrders.findIndex( @@ -439,6 +552,7 @@ export function globalState() { // If the status is not 'canceled', just update the status ustate.openOrders[openOrderIndex].status.set(status); } + // update openOrdersPerTable }, get restaurantId() { diff --git a/frontend/src/views/Categories.js b/frontend/src/views/Categories.js index 95c1741..8d33089 100644 --- a/frontend/src/views/Categories.js +++ b/frontend/src/views/Categories.js @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ import { View, VStack, @@ -13,6 +14,20 @@ import { globalState } from "../state"; const Categories = () => { const state = globalState(); + const { categories } = state; + + const sortedCategories = JSON.parse(JSON.stringify(categories)).sort( + (a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + + return 0; + } + ); return [
@@ -27,7 +42,7 @@ const Categories = () => { flexWrap="wrap" paddingX="1rem" > - {state.categories.map((category) => ( + {sortedCategories.map((category) => ( { state.category = category; diff --git a/frontend/src/views/Owner/OpenOrders.js b/frontend/src/views/Owner/OpenOrders.js index 3c96a8d..30934a4 100644 --- a/frontend/src/views/Owner/OpenOrders.js +++ b/frontend/src/views/Owner/OpenOrders.js @@ -1,5 +1,5 @@ import { View, HStack, Text } from "native-base"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import OpenOrder from "./components/OpenOrder"; import { toastEmitter } from "../../components/Toast"; @@ -23,35 +23,33 @@ const changeStatusHandler = async (orderId, status) => { }; function OpenOrders() { - const [groupedOrders, setGroupedOrders] = useState({}); + const [groupedOrders, setGroupedOrders] = useState([]); const state = globalState(); const { openOrders, selectedTable } = state; - // openOrders.forEach((order) => { - // const tableId = order.tabId.tableId._id; - // const localGroupedOrders = {}; - // if (groupedOrders[tableId]) { - // localGroupedOrders[tableId].push(order); - // } else { - // localGroupedOrders[tableId] = [order]; - // } - // setGroupedOrders({ ...groupedOrders, ...localGroupedOrders }); - // }); - - // const sortedOrders = Object.values(groupedOrders).flatMap((orders) => - // orders.sort((a, b) => - // a.tabId.tableId._id.localeCompare(b.tabId.tableId._id) - // ) - // ); - - useEffect(() => { + useMemo(() => { if (selectedTable) { - openOrders.filter( - (order) => order.tabId.tableId._id === selectedTable._id + setGroupedOrders( + openOrders.filter( + (order) => order.tabId.tableId._id === selectedTable._id + ) ); } - }, [selectedTable]); + }, [state.selectedTable?.value]); + + useEffect(() => { + if (openOrders.length > 0 && !selectedTable) { + setGroupedOrders(openOrders); + } + }, [openOrders]); + + useEffect( + () => () => { + state.selectedTable = null; + }, + [] + ); return ( @@ -59,7 +57,7 @@ function OpenOrders() { Open Orders {selectedTable && `for Table #${selectedTable.tableNo}`} - {openOrders.map((order, index) => ( + {groupedOrders.map((order, index) => ( import("./components/Table")); + function Tables() { const state = globalState(); const { tables } = state; + // useEffect(() => { + // if (state.openOrdersPerTable) { + // console.log(state.openOrdersPerTable); + // } + // }, [state.openOrdersPerTable]); + return ( @@ -17,14 +24,16 @@ function Tables() { {tables.map( (table) => table.currentTab && ( - + Loading...}> +
+ ) )} @@ -37,14 +46,16 @@ function Tables() { {tables.map( (table) => !table.currentTab && ( -
+ Loading...}> +
+ ) )} diff --git a/frontend/src/views/Owner/components/Table.js b/frontend/src/views/Owner/components/Table.js index 1d8108e..f12b204 100644 --- a/frontend/src/views/Owner/components/Table.js +++ b/frontend/src/views/Owner/components/Table.js @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-syntax */ /* eslint-disable no-else-return */ /* eslint-disable no-param-reassign */ /* eslint-disable react/jsx-props-no-spreading */ @@ -12,80 +13,80 @@ import { HStack, Pressable, } from "native-base"; -import React, { useEffect, useState } from "react"; +import React, { useMemo, useState, Suspense } from "react"; -import OccupiedTime from "./OccupiedTime"; import { globalState } from "../../../state"; -const activeOrders = (orders) => - orders.reduce((accumulator, order) => { - if (order.status === "recieved") accumulator.push(order); - return accumulator; - }, []); - -const flattenOrderItems = (orders) => orders.flatMap((order) => order.items); -const flattenVariations = (orders) => - orders.flatMap((order) => order.variations); - -const removeRecipesBasedOnVariations = (items, variations) => - items.map((item) => { - // loop through each variation and if item id === variation id, - // remove all the recipes except the one that matches the variation - // then substract 1 from variation.qty - if (item.recipe.length === 0) { - return item; - } - - variations.forEach((variation) => { - if (variation.qty === 0) return; - const newRecipe = item.recipe.find((currentRecipe) => { - if (currentRecipe._id === variation.recipe) { - variation.qty -= 1; - return true; - } - return false; +const OccupiedTime = React.lazy(() => import("./OccupiedTime")); + +function Table({ table, ...props }) { + const activeOrders = (orders) => + orders.reduce((accumulator, order) => { + if (order.status === "recieved") accumulator.push(order); + return accumulator; + }, []); + const flattenOrderItems = (orders) => orders.flatMap((order) => order.items); + const flattenVariations = (orders) => + orders.flatMap((order) => order.variations); + + const removeRecipesBasedOnVariations = (items, variations) => + items.map((item) => { + // loop through each variation and if item id === variation id, + // remove all the recipes except the one that matches the variation + // then substract 1 from variation.qty + if (item.recipe.length === 0) { + return item; + } + + variations.forEach((variation) => { + if (variation.qty === 0) return; + const newRecipe = item.recipe.find((currentRecipe) => { + if (currentRecipe._id === variation.recipe) { + variation.qty -= 1; + return true; + } + return false; + }); + if (newRecipe) item.recipe = [newRecipe]; }); - if (newRecipe) item.recipe = [newRecipe]; + + return item; }); - return item; - }); - -const groupItemsByIdAndRecipe = (items) => - items.reduce((acc, currentItem) => { - // Create a key based on _id and recipe[0]._id (if it exists) - const key = - currentItem._id + - (currentItem.recipe[0] ? currentItem.recipe[0]._id : ""); - - // If the key already exists, increment qty, else create a new entry - if (acc[key]) { - acc[key].qty += 1; - } else { - acc[key] = { - id: currentItem._id, - qty: 1, - name: currentItem.name, - recipe: currentItem.recipe[0] || null, - }; - } - - return acc; - }, {}); + const groupItemsByIdAndRecipe = (items) => + items.reduce((acc, currentItem) => { + // Create a key based on _id and recipe[0]._id (if it exists) + const key = + currentItem._id + + (currentItem.recipe[0] ? currentItem.recipe[0]._id : ""); + + // If the key already exists, increment qty, else create a new entry + if (acc[key]) { + acc[key].qty += 1; + } else { + acc[key] = { + id: currentItem._id, + qty: 1, + name: currentItem.name, + recipe: currentItem.recipe[0] || null, + }; + } + + return acc; + }, {}); -function Table({ table, ...props }) { const [tab, setTab] = useState(null); const [groupedItems, setGroupedItems] = useState([]); const state = globalState(); const { tableNo, currentTab } = table; - useEffect(() => { + useMemo(() => { if (!table.currentTab) return; setTab(JSON.parse(JSON.stringify(table.currentTab))); }, [table]); - useEffect(() => { + useMemo(() => { if (!tab) return; const orders = activeOrders(tab.orders); @@ -115,7 +116,9 @@ function Table({ table, ...props }) { {currentTab ? ( - + Loading...}> + + ) : ( "No current tab" )} diff --git a/frontend/src/views/QrScan.js b/frontend/src/views/QrScan.js index 10f2b5f..fed829f 100644 --- a/frontend/src/views/QrScan.js +++ b/frontend/src/views/QrScan.js @@ -39,6 +39,29 @@ function QrScan() { (async () => { const { status } = await BarCodeScanner.requestPermissionsAsync(); setHasPermission(status === "granted"); + + try { + const url = new URL(window.location); + if (url.pathname.startsWith("/join/")) { + const tableId = url.pathname.replace("/join/", "").toLowerCase(); + + const tableData = await state.api.getTableInfo(tableId); + if (tableData) { + state.tableId = tableId; + state.tableInfo = tableData; + goToTableHandler(); + } else { + alert( + "1. Hmm, spooky! It looks like the QR code is not valid. Please try again or ask the restaurant for help." + ); + } + } + } catch (e) { + console.log(e); + alert( + "2. Hmm, spooky! It looks like the QR code is not valid. Please try again or ask the restaurant for help." + ); + } })(); }, []); @@ -67,6 +90,7 @@ function QrScan() { } } } catch (e) { + console.log(e); console.log("Not a url, trying to parse as table"); const tableData = await api.getTableInfo(data); if (tableData) { @@ -80,7 +104,7 @@ function QrScan() { const goToTableHandler = async () => { const socket = new Socket( null, - restaurantId, + state.restaurantId, false, state.tableInfo.currentTab._id ); @@ -88,6 +112,7 @@ function QrScan() { socket.on("order:new", (data) => { console.log("new order on tab"); + console.log(data.order); state.addOrder(data.order); }); diff --git a/frontend/src/views/Table.js b/frontend/src/views/Table.js index 53db774..250edb4 100644 --- a/frontend/src/views/Table.js +++ b/frontend/src/views/Table.js @@ -17,7 +17,11 @@ const Table = () => { // state.totalProducts ? : null, state.orders.length ? state.orders.map((order, orderNr) => ( - + )) : null, !state.orders.length && !state.totalProducts ? (