diff --git a/.env.sample b/.env.sample index d9bc16d..b37f75f 100644 --- a/.env.sample +++ b/.env.sample @@ -2,4 +2,8 @@ API_PORT=9000 MONGODB_URI= JWT_SECRET= JWT_REFRESH_EXPIRY_TIME= -JWT_ACCESS_EXPIRY_TIME= \ No newline at end of file +JWT_ACCESS_EXPIRY_TIME= +AWS_S3_BUCKET= +AWS_S3_ACCESS_KEY_ID= +AWS_S3_SECRET_ACCESS_KEY= +AWS_S3_REGION= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0d2989b..c0d3c95 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ dist .tern-port -uploads/* \ No newline at end of file +uploads/* +tmp/uploads/* \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6832058..2820a4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "aws-sdk": "^2.1082.0", "bcrypt": "^5.0.1", "cors": "^2.8.5", "dotenv": "^10.0.0", @@ -277,6 +278,45 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/aws-sdk": { + "version": "2.1082.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1082.0.tgz", + "integrity": "sha512-aDrUZ63O/ocuC827ursDqFQAm3jhqsJu1DvMCCFg73y+FK9pXXNHp2mwdi3UeeHvtfxISCLCjuyO3VFd/tpVfA==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/aws-sdk/node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/aws-sdk/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1218,6 +1258,14 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", @@ -1839,6 +1887,14 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2684,6 +2740,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2870,6 +2935,11 @@ "node": ">=6" } }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -3289,6 +3359,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -3301,6 +3380,11 @@ "node": ">=4" } }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3314,6 +3398,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -3443,6 +3536,23 @@ "node": ">=8" } }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -3658,6 +3768,44 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "aws-sdk": { + "version": "2.1082.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1082.0.tgz", + "integrity": "sha512-aDrUZ63O/ocuC827ursDqFQAm3jhqsJu1DvMCCFg73y+FK9pXXNHp2mwdi3UeeHvtfxISCLCjuyO3VFd/tpVfA==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4379,6 +4527,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", @@ -4854,6 +5007,11 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5515,6 +5673,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5647,6 +5810,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -5981,6 +6149,22 @@ "punycode": "^2.1.0" } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -6000,6 +6184,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -6096,6 +6285,20 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 738acd7..f40d68d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "homepage": "https://github.com/kfnawaz/plaidware-wms-core#readme", "dependencies": { + "aws-sdk": "^2.1082.0", "bcrypt": "^5.0.1", "cors": "^2.8.5", "dotenv": "^10.0.0", diff --git a/src/config/aws.js b/src/config/aws.js new file mode 100644 index 0000000..38168e0 --- /dev/null +++ b/src/config/aws.js @@ -0,0 +1,36 @@ +const AWS = require("aws-sdk"); +const fs = require("fs"); +const { AWS_S3_ACCESS_KEY_ID, AWS_S3_SECRET_ACCESS_KEY, AWS_S3_BUCKET, AWS_S3_REGION } = require("./env"); + +AWS.config.update({ + maxRetries: 3, + accessKeyId: AWS_S3_ACCESS_KEY_ID, + secretAccessKey: AWS_S3_SECRET_ACCESS_KEY, + region: AWS_S3_REGION, +}); + +const S3 = new AWS.S3(); + +module.exports = { + S3: { + uploadFile: async (key, filepath) => { + const fileReadStream = fs.createReadStream(filepath); + const params = { + Bucket: AWS_S3_BUCKET, + Key: key, + Body: fileReadStream, + }; + + try { + const response = await S3.upload(params).promise(); + console.log("S3 Upload success", response); + fs.rmSync(filepath); + return response.Location; + } catch (error) { + console.log("S3 Upload Error", error); + fs.rmSync(filepath); + return false; + } + }, + }, +}; diff --git a/src/config/env.js b/src/config/env.js index bc14ef8..a365cc7 100644 --- a/src/config/env.js +++ b/src/config/env.js @@ -4,9 +4,12 @@ const envVariables = { API_PORT: process.env.API_PORT || "3000", MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:12017", JWT_SECRET: process.env.JWT_SECRET || "secret123", - JWT_REFRESH_EXPIRY_TIME: - parseInt(process.env.JWT_REFRESH_EXPIRY_TIME) || 3600, + JWT_REFRESH_EXPIRY_TIME: parseInt(process.env.JWT_REFRESH_EXPIRY_TIME) || 3600, JWT_ACCESS_EXPIRY_TIME: parseInt(process.env.JWT_ACCESS_EXPIRY_TIME) || 86400, + AWS_S3_BUCKET: process.env.AWS_S3_BUCKET, + AWS_S3_ACCESS_KEY_ID: process.env.AWS_S3_ACCESS_KEY_ID, + AWS_S3_SECRET_ACCESS_KEY: process.env.AWS_S3_SECRET_ACCESS_KEY, + AWS_S3_REGION: process.env.AWS_S3_REGION || "us-east-2", }; module.exports = envVariables; diff --git a/src/controller/inventory.controller.js b/src/controller/inventory.controller.js index ad94c20..5392c78 100644 --- a/src/controller/inventory.controller.js +++ b/src/controller/inventory.controller.js @@ -1,7 +1,9 @@ +const mongoose = require("mongoose"); + const Inventory = require("../models/Inventory"); const WidgetFamily = require("../models/WidgetFamily"); const { InventoryTypes } = require("../config/constants"); -const mongoose = require("mongoose"); +const { S3 } = require("./../config/aws"); module.exports = { /** @@ -34,11 +36,36 @@ module.exports = { } }, + getInventories: async (req, res, next) => { + let { page, perPage } = req.query; + page = page ? parseInt(page) : 0; + perPage = perPage ? parseInt(perPage) : 10; + + try { + const inventoryData = await Inventory.find() + .skip(parseInt(page) * parseInt(perPage)) + .limit(parseInt(perPage)); + if (!inventoryData) { + res.status(404); + return; + } + + for (const inventory of inventoryData) { + inventory["widgetFamilies"] = await WidgetFamily.find({ inventory: inventory._id }); + } + + res.send({ success: true, data: inventoryData }); + } catch (error) { + next(error); + } + }, + /** * Create a Inventory */ createInventory: async (req, res, next) => { - const { name, policies, widgetName } = req.body; + let { name, policies, widgetName } = req.body; + const image = req.file; if (!(name && widgetName)) { res.status(400).send("Missing params name"); @@ -49,6 +76,8 @@ module.exports = { for (const preferredLocation of policies.preferredLocations) { preferredLocations.push({ id: preferredLocation.id, type: preferredLocation.type }); } + } else if (!policies) { + policies = {}; } const verifiedPolicies = { @@ -66,12 +95,20 @@ module.exports = { policies: verifiedPolicies, }); + if (image) { + const url = await S3.uploadFile( + `inventory/${inventoryData._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, + image.path + ); + inventoryData.image_url = url; + } + await inventoryData.save(); if (!inventoryData) { res.status(404); return; } - // const widgetFamilyData = createWidgetFamiliesData(inventoryData, widgetFamily); + res.send({ success: true, data: { inventoryData } }); } catch (error) { next(error); @@ -120,6 +157,15 @@ module.exports = { }; inventory.policies = verifiedPolicies; + const image = req.file; + if (image) { + const url = await S3.uploadFile( + `inventory/${inventory._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, + image.path + ); + inventory.image_url = url; + } + await inventory.save(); res.send({ success: true, data: { inventory } }); } catch (error) { diff --git a/src/controller/inventory.router.js b/src/controller/inventory.router.js index 0beed4b..7d01aee 100644 --- a/src/controller/inventory.router.js +++ b/src/controller/inventory.router.js @@ -1,21 +1,27 @@ const router = require("express").Router(); const controller = require("./inventory.controller"); +const multer = require("multer"); +const upload = multer({ dest: "tmp/uploads/" }); +/** + * @route /inventory/ + */ +router.post("/", upload.single("image"), controller.createInventory); /** * @route /inventory/ */ -router.post("/", controller.createInventory); - -/** - * @route /inventory/ - */ -router.patch("/:id", controller.updateInventoryByID); +router.patch("/:id", upload.single("image"), controller.updateInventoryByID); /** * @route /inventory/types */ router.get("/types", controller.getInventoryTypes); +/** + * @route /inventory/all + */ +router.get("/all", controller.getInventories); + /** * @route /inventory/filter-by-type */ diff --git a/src/models/Inventory.js b/src/models/Inventory.js index 084afc0..10534b0 100644 --- a/src/models/Inventory.js +++ b/src/models/Inventory.js @@ -8,6 +8,10 @@ const schema = new mongoose.Schema( required: true, trim: true, }, + image_url: { + type: String, + trim: true, + }, widgetName: { type: String, required: true,