diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7a9dfa04 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index f1ebbc43..86e74fe5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Prueba de Backend NodeJS Crear un CRUD para crear productos conectado a MongoDB. +# URL PRODUCCION +https://backend-node-evaluation-2021.herokuapp.com/ ### InstalaciĆ³n ``` npm install diff --git a/e2e/categories.e2e.js b/e2e/categories.e2e.js index 3f1fc277..ed07b162 100644 --- a/e2e/categories.e2e.js +++ b/e2e/categories.e2e.js @@ -8,8 +8,8 @@ const USER = encodeURIComponent(config.dbUser); const PASSWORD = encodeURIComponent(config.dbPassword); const DB_NAME = config.dbName; -const MONGO_URI = `${config.dbConnection}://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}?retryWrites=true&w=majority`; -const collection = 'categories'; +const MONGO_URI = `${config.dbConnection}://${USER}:${PASSWORD}@${config.dbHost}?retryWrites=true&w=majority`; +const collection = "categories"; describe("Tests to categories", () => { let app; @@ -37,14 +37,16 @@ describe("Tests to categories", () => { it("should create a new category", async (done) => { const newCategory = { name: "Category 1", - image: 'https://via.placeholder.com/150', + image: "https://via.placeholder.com/150", }; return request(app) .post("/api/categories") .send(newCategory) .expect(201) .then(async ({ body }) => { - const rta = await database.collection(collection).findOne({ _id: ObjectId(body._id) }); + const rta = await database + .collection(collection) + .findOne({ _id: ObjectId(body._id) }); expect(body.name).toBe(rta.name); expect(body.image).toBe(rta.image); done(); @@ -61,7 +63,9 @@ describe("Tests to categories", () => { .then(async ({ body }) => { expect(body.length).toBe(1); const model = body[0]; - const rta = await database.collection(collection).findOne({ _id: ObjectId(model._id) }); + const rta = await database + .collection(collection) + .findOne({ _id: ObjectId(model._id) }); expect(model.name).toBe(rta.name); expect(model.image).toBe(rta.image); done(); @@ -76,7 +80,7 @@ describe("Tests to categories", () => { expect(categories.length > 0).toBe(true); const category = categories[0]; const changes = { - name: 'change', + name: "change", }; return request(app) .put(`/api/categories/${category._id}`) @@ -109,17 +113,16 @@ describe("Tests to categories", () => { }); describe("GET /api/categories/{id}/products", () => { - it("should return a list products by category", async (done) => { const categories = await database.collection(collection).find().toArray(); expect(categories.length > 0).toBe(true); const category = categories[0]; const products = [ - { name: "Red", price: 200, categoryId: `${category._id}` }, + { name: "Red", price: 200, categoryId: `${category._id}` }, { name: "Blue", price: 300, categoryId: `${category._id}` }, - { name: "Leon", price: 400 } + { name: "Leon", price: 400 }, ]; - await database.collection('products').insertMany(products); + await database.collection("products").insertMany(products); return request(app) .get(`/api/categories/${category._id}/products`) .expect(200) @@ -148,6 +151,4 @@ describe("Tests to categories", () => { .catch((err) => done(err)); }); }); - - }); diff --git a/e2e/product.e2e.js b/e2e/product.e2e.js index dc94f0fc..61e72bf0 100644 --- a/e2e/product.e2e.js +++ b/e2e/product.e2e.js @@ -8,8 +8,8 @@ const USER = encodeURIComponent(config.dbUser); const PASSWORD = encodeURIComponent(config.dbPassword); const DB_NAME = config.dbName; -const MONGO_URI = `${config.dbConnection}://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}?retryWrites=true&w=majority`; -const collection = 'products'; +const MONGO_URI = `${config.dbConnection}://${USER}:${PASSWORD}@${config.dbHost}?retryWrites=true&w=majority`; +const collection = "products"; describe("Tests to products", () => { let app; @@ -44,7 +44,9 @@ describe("Tests to products", () => { .send(newProduct) .expect(201) .then(async ({ body }) => { - const rta = await database.collection(collection).findOne({ _id: ObjectId(body._id) }); + const rta = await database + .collection(collection) + .findOne({ _id: ObjectId(body._id) }); expect(body.name).toBe(rta.name); expect(body.price).toBe(rta.price); done(); @@ -61,7 +63,9 @@ describe("Tests to products", () => { .then(async ({ body }) => { expect(body.length).toBe(1); const product = body[0]; - const rta = await database.collection(collection).findOne({ _id: ObjectId(product._id) }); + const rta = await database + .collection(collection) + .findOne({ _id: ObjectId(product._id) }); expect(product.name).toBe(rta.name); expect(product.price).toBe(rta.price); done(); @@ -123,6 +127,4 @@ describe("Tests to products", () => { .catch((err) => done(err)); }); }); - - }); diff --git a/package-lock.json b/package-lock.json index 76a7fcef..ecf1eeef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@hapi/boom": "^9.1.2", "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "joi": "^17.4.0", "mongodb": "^3.6.6" }, "devDependencies": { + "@types/mongodb": "^3.6.12", "jest": "^26.6.3", "nodemon": "^1.19.4", "supertest": "^6.1.3" @@ -503,6 +506,27 @@ "node": ">=0.1.95" } }, + "node_modules/@hapi/boom": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.2.tgz", + "integrity": "sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q==", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" + }, + "node_modules/@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1290,6 +1314,24 @@ "node": ">=8" } }, + "node_modules/@sideway/address": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", + "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -1349,6 +1391,15 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -1382,6 +1433,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/mongodb": { + "version": "3.6.12", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.12.tgz", + "integrity": "sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==", + "dev": true, + "dependencies": { + "@types/bson": "*", + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "14.14.41", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", @@ -6224,6 +6285,18 @@ "node": ">=8" } }, + "node_modules/joi": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", + "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10166,6 +10239,27 @@ "minimist": "^1.2.0" } }, + "@hapi/boom": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.2.tgz", + "integrity": "sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q==", + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -10773,6 +10867,24 @@ } } }, + "@sideway/address": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", + "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -10832,6 +10944,15 @@ "@babel/types": "^7.3.0" } }, + "@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -10865,6 +10986,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/mongodb": { + "version": "3.6.12", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.12.tgz", + "integrity": "sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==", + "dev": true, + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, "@types/node": { "version": "14.14.41", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", @@ -14605,6 +14736,18 @@ } } }, + "joi": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", + "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 279a5416..ca583d70 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,15 @@ "description": "Reto 9 Octubre 26: Curso de Backend con Node", "main": "index.js", "dependencies": { + "@hapi/boom": "^9.1.2", "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "joi": "^17.4.0", "mongodb": "^3.6.6" }, "devDependencies": { + "@types/mongodb": "^3.6.12", "jest": "^26.6.3", "nodemon": "^1.19.4", "supertest": "^6.1.3" @@ -17,7 +20,8 @@ "scripts": { "dev": "DEBUG=app:* nodemon src/index.js", "start": "NODE_ENV=production node src/index.js", - "test:e2e": "jest --forceExit --config ./e2e/jest-e2e.json" + "test:e2e": "jest --forceExit --config ./e2e/jest-e2e.json", + "test": "jest" }, "repository": { "type": "git", diff --git a/src/app.js b/src/app.js index 62a46b5e..2f0d4847 100644 --- a/src/app.js +++ b/src/app.js @@ -1,12 +1,15 @@ -const express = require('express'); -const cors = require('cors'); - -function createApp() { +const express = require("express"); +const cors = require("cors"); +const apiRouter = require("./routes"); +const notFoundHandler = require("./middlewares/notFoundHandler"); +function createApp() { const app = express(); app.use(cors()); app.use(express.json()); - // ADD YOUR ROUTES + app.use("/api", apiRouter); + //catch 404 + app.use(notFoundHandler); return app; } diff --git a/src/controllers/category.controller.js b/src/controllers/category.controller.js new file mode 100644 index 00000000..b67c9829 --- /dev/null +++ b/src/controllers/category.controller.js @@ -0,0 +1,61 @@ +const controller = {}; +const CategoryService = require("./../services/category.service"); +const categoryService = new CategoryService(); +controller.getAll = async (req, res) => { + try { + const categories = await categoryService.getAll(); + return res.status(200).json(categories); + } catch (error) { + console.log(error); + } +}; +controller.getById = async (req, res) => { + const { id } = req.params; + try { + const category = await categoryService.getById(id); + return res.status(200).json(category); + } catch (error) { + console.log(error); + } +}; +controller.createCategory = async (req, res) => { + const { body: category } = req; + try { + const createCategory = await categoryService.createCategory({ category }); + return res.status(201).json(createCategory); + } catch (error) { + console.log(error); + } +}; +controller.updateCategory = async (req, res) => { + const { body: category } = req; + const { id } = req.params; + try { + const updateCategoryId = await categoryService.updateCategory({ + id, + category, + }); + return res.status(200).json(updateCategoryId); + } catch (error) { + console.log(error); + } +}; +controller.deleteCategory = async (req, res) => { + const { id } = req.params; + try { + const deleteCategoryId = await categoryService.deleteCategory({ id }); + return res.status(200).json(true); + } catch (error) { + console.log(error); + } +}; +controller.productsByCategory = async (req, res) => { + const { id } = req.params; + try { + const productsByCategory = await categoryService.getProductsByCategory(id); + return res.status(200).json(productsByCategory); + } catch (error) { + console.log(error); + } +}; +module.exports = controller; diff --git a/src/controllers/product.controller.js b/src/controllers/product.controller.js new file mode 100644 index 00000000..65d59ce5 --- /dev/null +++ b/src/controllers/product.controller.js @@ -0,0 +1,53 @@ +const ProductService = require("./../services/product.service"); +const controller = {}; +const productsService = new ProductService(); +controller.getAll = async (req, res) => { + try { + const products = await productsService.getAll(); + return res.status(200).json(products); + } catch (error) { + console.error(error); + } +}; +controller.getById = async (req, res) => { + const { id } = req.params; + try { + const product = await productsService.getById({ id }); + return res.status(200).json(product); + } catch (error) { + console.log(error); + } +}; +controller.createProduct = async (req, res) => { + const { body: product } = req; + try { + const createProduct = await productsService.createProduct({ product }); + return res.status(201).json(createProduct); + } catch (error) { + console.log(error); + } +}; +controller.updateProduct = async (req, res) => { + const { body: product } = req; + const { id } = req.params; + try { + const updateProductId = await productsService.updateProduct({ + id, + product, + }); + return res.status(200).json(updateProductId); + } catch (error) { + console.log(error); + } +}; +controller.deleteProduct = async (req, res) => { + const { id } = req.params; + try { + const deleteProductId = await productsService.deleteProduct({ id }); + return res.status(200).json(true); + } catch (error) { + console.log(error); + } +}; + +module.exports = controller; diff --git a/src/lib/mongo.js b/src/lib/mongo.js new file mode 100644 index 00000000..2b12005c --- /dev/null +++ b/src/lib/mongo.js @@ -0,0 +1,69 @@ +const { MongoClient, ObjectId } = require("mongodb"); +const { config } = require("../config"); + +const USER = encodeURIComponent(config.dbUser); +const PASSWORD = encodeURIComponent(config.dbPassword); +const DB_NAME = config.dbName; + +const MONGO_URI = `${config.dbConnection}://${USER}:${PASSWORD}@${config.dbHost}/${config.dbName}?retryWrites=true&w=majority`; + +class MongoLib { + constructor() { + this.client = new MongoClient(MONGO_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + this.dbName = DB_NAME; + } + connect() { + if (!MongoLib.connection) { + MongoLib.connection = new Promise((resolve, reject) => { + this.client.connect((err) => { + if (err) { + console.log({ err }); + reject(err); + } + console.log("Connected Succesfully"); + resolve(this.client.db(this.dbName)); + }); + }); + } + return MongoLib.connection; + } + getAll(collection, query) { + return this.connect().then((db) => { + return db.collection(collection).find(query).toArray(); + }); + } + + get(collection, id) { + return this.connect().then((db) => { + return db.collection(collection).findOne({ _id: ObjectId(id) }); + }); + } + create(collection, data) { + return this.connect() + .then((db) => { + return db.collection(collection).insertOne(data); + }) + .then(() => data); + } + update(collection, data, id) { + return this.connect().then((db) => { + db.collection(collection).updateOne( + { _id: ObjectId(id) }, + { $set: data }, + { upsert: true } + ); + return db.collection(collection).findOne({ _id: ObjectId(id) }); + }); + } + delete(collection, id) { + return this.connect() + .then((db) => { + return db.collection(collection).deleteOne({ _id: ObjectId(id) }); + }) + .then(() => id); + } +} +module.exports = MongoLib; diff --git a/src/middlewares/notFoundHandler.js b/src/middlewares/notFoundHandler.js new file mode 100644 index 00000000..227196fa --- /dev/null +++ b/src/middlewares/notFoundHandler.js @@ -0,0 +1,8 @@ +const boom = require("@hapi/boom"); +function notFoundHandler(req, res) { + const { + output: { statusCode, payload }, + } = boom.notFound(); + res.status(statusCode).json(payload); +} +module.exports = notFoundHandler; diff --git a/src/middlewares/validationHandler.js b/src/middlewares/validationHandler.js new file mode 100644 index 00000000..ee452f65 --- /dev/null +++ b/src/middlewares/validationHandler.js @@ -0,0 +1,20 @@ +const boom = require("@hapi/boom"); +const joi = require("joi"); +/*function validate(data, schema) { + const {error} = joi.valid + return false; +}*/ +function validate(data, schema) { + // If schema is not a joi schema convert to a joi schema object otherwise return schema + schema = !joi.isSchema(schema) ? joi.object(schema) : schema; + const { error } = schema.validate(data); + return error; +} + +function validationHandler(schema, data = "body") { + return function (req, res, next) { + const error = validate(req[data], schema); + error ? next(boom.badRequest(error)) : next(); + }; +} +module.exports = validationHandler; diff --git a/src/routes/category.routes.js b/src/routes/category.routes.js new file mode 100644 index 00000000..972f4717 --- /dev/null +++ b/src/routes/category.routes.js @@ -0,0 +1,39 @@ +const controller = require("../controllers/category.controller"); +const router = require("express").Router(); +const { + categoryIdSchema, + createCategorySchema, + updateCategorySchema, +} = require("../schemas/category.schema"); + +const validationHandler = require("../middlewares/validationHandler"); +//get categories +router.get("/", controller.getAll); +//get category by id +router.get( + "/:id", + validationHandler({ id: categoryIdSchema }, "params"), + controller.getById +); +//create category +router.post( + "/", + validationHandler(createCategorySchema), + controller.createCategory +); +//update category +router.put( + "/:id", + validationHandler({ id: categoryIdSchema }, "params"), + validationHandler(updateCategorySchema), + controller.updateCategory +); +//delete category +router.delete( + "/:id", + validationHandler({ id: categoryIdSchema }, "params"), + controller.deleteCategory +); +//products byCategory +router.get("/:id/products", controller.productsByCategory); +module.exports = router; diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 00000000..8af6fdbf --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,6 @@ +const router = require("express").Router(); +const apiProductRouter = require("./product.routes"); +const apiCategoriesRouter = require("./category.routes"); +router.use("/products", apiProductRouter); +router.use("/categories", apiCategoriesRouter); +module.exports = router; diff --git a/src/routes/product.routes.js b/src/routes/product.routes.js new file mode 100644 index 00000000..5edd9665 --- /dev/null +++ b/src/routes/product.routes.js @@ -0,0 +1,36 @@ +const router = require("express").Router(); +const controller = require("../controllers/product.controller"); +const { + productIdSchema, + createProductSchema, + updateProductSchema, +} = require("../schemas/product.schema"); +const validationHandler = require("../middlewares/validationHandler"); +//get products +router.get("/", controller.getAll); +//get product by id +router.get( + "/:id", + validationHandler({ id: productIdSchema }, "params"), + controller.getById +); +//create Product +router.post( + "/", + validationHandler(createProductSchema), + controller.createProduct +); +//update Product +router.put( + "/:id", + validationHandler({ id: productIdSchema }, "params"), + validationHandler(updateProductSchema), + controller.updateProduct +); +//delete Product +router.delete( + "/:id", + validationHandler({ id: productIdSchema }, "params"), + controller.deleteProduct +); +module.exports = router; diff --git a/src/schemas/category.schema.js b/src/schemas/category.schema.js new file mode 100644 index 00000000..eb3bd960 --- /dev/null +++ b/src/schemas/category.schema.js @@ -0,0 +1,18 @@ +const joi = require("joi"); +const categoryIdSchema = joi.string().regex(/^[0-9a-fA-F]{24}$/); +const categoryNameSchema = joi.string().max(80); +const categoryImage = joi.string().max(300); + +const createCategorySchema = { + name: categoryNameSchema.required(), + image: categoryImage, +}; +const updateCategorySchema = { + name: categoryNameSchema, + image: categoryImage, +}; +module.exports = { + categoryIdSchema, + createCategorySchema, + updateCategorySchema, +}; diff --git a/src/schemas/product.schema.js b/src/schemas/product.schema.js new file mode 100644 index 00000000..2c7e0b8a --- /dev/null +++ b/src/schemas/product.schema.js @@ -0,0 +1,27 @@ +const joi = require("joi"); +const productIdSchema = joi.string().regex(/^[0-9a-fA-F]{24}$/); +const productNameSchema = joi.string().max(80); +const productPriceSchema = joi.number().min(1).max(100000); +const productDescription = joi.string().max(80); +const productCategoryId = joi.string().regex(/^[0-9a-fA-F]{24}$/); +const productImage = joi.string().max(300); + +const createProductSchema = { + name: productNameSchema.required(), + price: productPriceSchema, + description: productDescription, + categoryId: productCategoryId, + image: productImage, +}; +const updateProductSchema = { + name: productNameSchema, + price: productPriceSchema, + description: productDescription, + categoryId: productCategoryId, + image: productImage, +}; +module.exports = { + productIdSchema, + createProductSchema, + updateProductSchema, +}; diff --git a/src/services/category.service.js b/src/services/category.service.js new file mode 100644 index 00000000..930eeb13 --- /dev/null +++ b/src/services/category.service.js @@ -0,0 +1,41 @@ +const MongoLib = require("../lib/mongo"); +class CategoryService { + constructor() { + this.collection = "categories"; + this.mongoDB = new MongoLib(); + } + async getAll(query) { + query = query || {}; + const categories = await this.mongoDB.getAll(this.collection, query); + return categories || []; + } + async getById(id) { + const category = await this.mongoDB.get(this.collection, id); + return category || {}; + } + async createCategory({ category }) { + const createCategoryId = await this.mongoDB.create( + this.collection, + category + ); + return createCategoryId; + } + async updateCategory({ id, category }) { + const update = await this.mongoDB.update(this.collection, category, id); + const updatedCategoryId = await this.mongoDB.get( + this.collection, + update._id + ); + return updatedCategoryId; + } + async deleteCategory({ id }) { + const deleteCategoryId = await this.mongoDB.delete(this.collection, id); + return deleteCategoryId; + } + async getProductsByCategory(id) { + const query = { categoryId: id }; + const productsByCategory = await this.mongoDB.getAll("products", query); + return productsByCategory || []; + } +} +module.exports = CategoryService; diff --git a/src/services/product.service.js b/src/services/product.service.js new file mode 100644 index 00000000..4de51997 --- /dev/null +++ b/src/services/product.service.js @@ -0,0 +1,34 @@ +const MongoLib = require("../lib/mongo"); + +class ProductService { + constructor() { + this.collection = "products"; + this.mongoDB = new MongoLib(); + } + async getAll(query) { + query = query || {}; + const products = await this.mongoDB.getAll(this.collection, query); + return products || []; + } + async getById({ id }) { + const product = await this.mongoDB.get(this.collection, id); + return product || {}; + } + async createProduct({ product }) { + const createProductId = await this.mongoDB.create(this.collection, product); + return createProductId; + } + async updateProduct({ id, product }) { + const update = await this.mongoDB.update(this.collection, product, id); + const updatedProductId = await this.mongoDB.get( + this.collection, + update._id + ); + return updatedProductId; + } + async deleteProduct({ id }) { + const deleteProductId = await this.mongoDB.delete(this.collection, id); + return deleteProductId; + } +} +module.exports = ProductService;