order-service/index.js (199 lines of code) (raw):

/** * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const express = require('express'); const cors = require('cors'); const app = express(); const axios = require('axios'); app.use(cors()); app.use(express.json()); const port = process.env.PORT || 8080; app.listen(port, () => { console.log(`API listening on port ${port}`); }); const {Firestore} = require('@google-cloud/firestore'); const {PubSub} = require('@google-cloud/pubsub'); const db = new Firestore(); const pubsub = new PubSub(); const admin = require('firebase-admin'); admin.initializeApp(); const TOPIC_NAME = 'order-topic'; const inventoryServer = axios.create({ baseURL: process.env.INVENTORY_SERVICE_URL, headers: { get: { "Content-Type": 'application/json' } } }) app.get('/order', async (req, res) => { try { const orderColl = await db.collection(`orders`).get(); const orders = orderColl.docs.map(d => d.data()); res.json({status: 'success', data: orders}); } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) app.get('/orders/customer', async (req, res) => { try { if (req.headers['authorization'] != null && req.headers['authorization'] != "undefined") { var userId = await getUserIdFromAuthHeader(req.headers['authorization']); const orderColl = await db.collection(`orders`).where('userId', '==', userId).get(); const orders = orderColl.docs.map(d => d.data()); res.json({status: 'success', data: orders}); } else { console.error('Unauthorized'); res.status(401).json({error: 'Unauthorized'}); } } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) app.get('/order/:orderNumber', async (req, res) => { try { const orderNumber = req.params.orderNumber; const orderDoc = await db.doc(`orders/${orderNumber}`).get(); if (orderDoc.exists) { res.json({status: 'success', data: orderDoc.data()}); } else { res.status(404).json({error: `Order "${orderNumber}" not found`}); } } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) async function getUserIdFromAuthHeader(authHeader) { if (authHeader) { const token = await admin.auth().verifyIdToken(authHeader); return token.uid; } } app.post('/order', async (req, res) => { try { var userId = null; if (req.headers['authorization'] != null && req.headers['authorization'] != "undefined") { userId = await getUserIdFromAuthHeader(req.headers['authorization']); } if (!await inventoryAvailable(req.body.orderItems)) { throw 'Incorrect Order Quantity or Item'; } if (userId) { req.body.userId = userId; } const orderNumber = await createOrderRecord(req.body); await subtractFromInventory(req.body.orderItems); res.json({orderNumber: orderNumber}); const data = req.body; if (userId) { data.userId = userId; } data.orderNumber = orderNumber; publishMessage(data); } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) app.delete('/order/:orderNumber', async (req, res) => { try { const orderDoc = db.doc(`orders/${req.params.orderNumber}`); await orderDoc.delete(); res.json({status: 'success'}); } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) app.patch('/order/:orderNumber', async (req, res) => { try { const orderDoc = db.doc(`orders/${req.params.orderNumber}`); await orderDoc.update(req.body); res.json({status: 'success'}); } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } }) async function inventoryAvailable(orderItems) { const inventory = await inventoryServer.get("/getAvailableInventory"); const inventoryDict = {}; for (item in inventory.data) { inventoryDict[parseInt( inventory.data[item].ItemID)] = inventory.data[item].Inventory; } for (oI in orderItems) { var orderItem = orderItems[oI]; if (!(orderItem.id in inventoryDict) || (inventoryDict[orderItem.id] < orderItem.quantity)) { return false; } } return true; } async function createOrderRecord(requestBody) { const orderNumber = getNewOrderNumber(); const orderDoc = db.doc(`orders/${orderNumber}`); await orderDoc.set({ orderNumber: orderNumber, userId: requestBody.userId ? requestBody.userId : '', name: requestBody.name, email: requestBody.email, address: requestBody.address, city: requestBody.city, state: requestBody.state, zip: requestBody.zip, orderItems: requestBody.orderItems, status: 'New', statusUpdatedAt: new Date(), placedAt: new Date() }); return orderNumber; } async function subtractFromInventory(orderItems) { await inventoryServer.post("/updateInventoryItem", orderItems.map(x => ({ itemID: x.id, inventoryChange: -x.quantity })) ); } function getNewOrderNumber() { return Math.round(10000 + Math.random() * 90000); } async function publishMessage(data) { try { const dataBuffer = Buffer.from(JSON.stringify(data)) const messageId = await pubsub .topic(TOPIC_NAME) .publishMessage({data: dataBuffer}); console.log(`Message ${messageId} published.`); } catch (error) { console.error(`Received error while publishing: ${error.message}`); } } app.post('/order/points', async (req, res) => { try { const orderNumber = req.body.message.attributes.orderNumber; const rewardPoints = parseInt(req.body.message.attributes.rewardPoints); const totalAmount = parseFloat( parseFloat(req.body.message.attributes.totalAmount).toFixed(2)); const updateRec = { 'rewardPoints': rewardPoints, 'totalAmount': totalAmount }; const orderDoc = db.doc(`orders/${orderNumber}`); if (orderDoc) { await orderDoc.update(updateRec); res.json({status: 'success'}); } else { console.log("Order not found. orderNumber: ", orderNumber); } } catch (ex) { console.error(ex); res.status(500).json({error: ex.toString()}); } })