diff --git a/.env-example b/.env-example index dfce8c4d..074680af 100644 --- a/.env-example +++ b/.env-example @@ -1,7 +1,14 @@ PORT=3000 + +//variables Para pruebas locales MONGO_USER= MONGO_PASSWORD= MONGO_HOST= MONGO_DB_NAME= MONGO_PORT= -MONGO_CONNECTION= \ No newline at end of file +MONGO_CONNECTION= + +//variables para MongoAtlas +ATLAS_USER= +ATLAS_PASSWORD= +ATLAS_DB_NAME= \ No newline at end of file diff --git a/README.md b/README.md index f1ebbc43..19cb183f 100644 --- a/README.md +++ b/README.md @@ -29,19 +29,19 @@ Una Categoría debe tener los siguientes atributos: ## Requerimientos ### CRUD de productos -- [ ] GET `/api/products/` Endpoint para retornar la lista de productos. -- [ ] GET `/api/products/{id}/` Endpoint para retornar un producto. -- [ ] POST `/api/products/` Endpoint para crear un producto. -- [ ] PUT `/api/products/{id}/` Endpoint para modificar un producto. -- [ ] DELETE `/api/products/{id}/` Endpoint para eliminar un producto. +- [X] GET `/api/products/` Endpoint para retornar la lista de productos. +- [X] GET `/api/products/{id}/` Endpoint para retornar un producto. +- [X] POST `/api/products/` Endpoint para crear un producto. +- [X] PUT `/api/products/{id}/` Endpoint para modificar un producto. +- [X] DELETE `/api/products/{id}/` Endpoint para eliminar un producto. ### CRUD de categorías -- [ ] GET `/api/categories/` Endpoint para retornar la lista de categorías. -- [ ] GET `/api/categories/{id}/` Endpoint para retornar un categoría. -- [ ] POST `/api/categories/` Endpoint para crear un categoría. -- [ ] PUT `/api/categories/{id}/` Endpoint para modificar un categoría. -- [ ] DELETE `/api/categories/{id}/` Endpoint para eliminar un categoría. -- [ ] GET `/api/categories/{id}/products` Endpoint para retornar la lista de productos que pertenecen a una categoría. +- [X] GET `/api/categories/` Endpoint para retornar la lista de categorías. +- [X] GET `/api/categories/{id}/` Endpoint para retornar un categoría. +- [X] POST `/api/categories/` Endpoint para crear un categoría. +- [X] PUT `/api/categories/{id}/` Endpoint para modificar un categoría. +- [X] DELETE `/api/categories/{id}/` Endpoint para eliminar un categoría. +- [X] GET `/api/categories/{id}/products` Endpoint para retornar la lista de productos que pertenecen a una categoría. ## Instrucciones @@ -60,6 +60,9 @@ npm run test:e2e ## Enviar solución de reto Debes de crear un "Fork" de este proyecto, revolverlo desde tu cuenta personal. - +esta propuesta de solucion fue hecha por +Jorge Luis Martinez Hernandez +correo usado en platzi s_k_ap3@hotmail.com +id platzi: SoyLuis ### Licencia La licencia [MIT](https://opensource.org/licenses/MIT). diff --git a/package-lock.json b/package-lock.json index 76a7fcef..1e394721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", - "mongodb": "^3.6.6" + "mongodb": "^3.6.6", + "nanoid": "^3.1.23" }, "devDependencies": { "jest": "^26.6.3", @@ -6723,6 +6724,17 @@ "dev": true, "optional": true }, + "node_modules/nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -14982,6 +14994,11 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", diff --git a/package.json b/package.json index 279a5416..8d9a0a01 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", - "mongodb": "^3.6.6" + "mongodb": "^3.6.6", + "nanoid": "^3.1.23" }, "devDependencies": { "jest": "^26.6.3", diff --git a/src/app.js b/src/app.js index 62a46b5e..e103f3ce 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,6 @@ const express = require('express'); const cors = require('cors'); +const router = require("./network/routes"); function createApp() { const app = express(); @@ -7,6 +8,8 @@ function createApp() { app.use(express.json()); // ADD YOUR ROUTES + //yo elegí crear un archivo separado para manejar las rutas este se encuentra en ./network/routes.js + router(app); return app; } diff --git a/src/components/categories/controller.js b/src/components/categories/controller.js new file mode 100644 index 00000000..b6a0466b --- /dev/null +++ b/src/components/categories/controller.js @@ -0,0 +1,46 @@ +const { nanoid } = require("nanoid") +//en este archivo se mandan a llamar las funciones para consultar la BD +//si es necesario cambiar la BD cambiar la base de datos en el archivo ./index.js +const TABLE = "categories" + +module.exports = function (injecterStore){ + + async function list(){ + //no se envia el query debido a que se desea listar todo + const result = await injecterStore.listAll(TABLE,null); + return result; + } + async function listCategorie(id){ + const result = await injecterStore.list(TABLE,id); + return result; + } + async function deleteCategorie(id){ + const result = await injecterStore.delete(TABLE,id); + return result; + } + //dado los requisitos dados no se implenetó la comprobacion de la existenca de la categorias + //tampoco su existencia previa + async function insertCategorie(data,id){ + let result + if(id){ + result = await injecterStore.update(TABLE, data._id, data); + }else{ + data._id = nanoid(); + result = await injecterStore.create(TABLE, data); + } + return result; + } + async function searchProduct({id}){ + const query = id && {categoryId: {cat: id}}; + //a quí sirvio el query :v + const result = await injecterStore.listAll("products",query); + return result; + } + return { + list, + listCategorie, + deleteCategorie, + insertCategorie, + searchProduct + } +} \ No newline at end of file diff --git a/src/components/categories/index.js b/src/components/categories/index.js new file mode 100644 index 00000000..7a19166d --- /dev/null +++ b/src/components/categories/index.js @@ -0,0 +1,7 @@ +//index of categories +const controller = require('./controller'); +const store = require("../../store/mongoDB"); +//a este punto elegí separar la base de datos del resto del codigo, por si llegado el caso se requiere cambiar +//la base de datos será necesario implementar las funciones y simplemente cambiar la ubicacion de store +//tambien me permite reutilizar codigo ya hecho +module.exports = controller(new store); \ No newline at end of file diff --git a/src/components/categories/network.js b/src/components/categories/network.js new file mode 100644 index 00000000..35c7c574 --- /dev/null +++ b/src/components/categories/network.js @@ -0,0 +1,57 @@ +const express = require('express'); + +const controller = require('./index') +const response = require("../../network/response"); + +const router = express.Router(); +//este archivo gestiona las rutas para las categorias hechas a /api/categories +router.get('/', list); +router.get('/:id', listCategorie); +router.post('/', insertCategorie); +router.put('/:id', alterCategorie); +router.get('/:id/products', searchProduct); +router.delete('/:id', deleteCategorie); + +function list(req,res, next){ + + controller.list() + .then((lista) => { + response.success(req,res,lista,200); + }).catch(next); + +} +function searchProduct(req, res, next){ + controller.searchProduct({id: req.params.id}) + .then((data => { + response.success(req, res, data, 200) + })).catch(next) +} +function listCategorie(req, res, next){ + controller.listCategorie(req.params.id) + .then(categorie =>{ + response.success(req, res, categorie, 200); + }).catch(next) +} + +function insertCategorie(req, res, next){ + controller.insertCategorie(req.body,null) + .then((data => { + response.success(req, res, data, 201) + })).catch(next) +} + +function alterCategorie(req, res, next){ + controller.insertCategorie(req.body,req.params.id) + .then((data => { + response.success(req, res, data, 202) + })).catch(next) +} + +function deleteCategorie(req, res, next){ + controller.deleteCategorie(req.params.id) + .then(product =>{ + response.success(req, res, product, 200); + }).catch(next) +} + +module.exports = router \ No newline at end of file diff --git a/src/components/products/controller.js b/src/components/products/controller.js new file mode 100644 index 00000000..c3ab8b23 --- /dev/null +++ b/src/components/products/controller.js @@ -0,0 +1,39 @@ +const { nanoid } = require("nanoid") +//en este archivo se mandan a llamar las funciones para consultar la BD +//si es necesario cambiar la BD cambiar la base de datos en el archivo ./index.js +const TABLE = "products" + +module.exports = function (injecterStore){ + + async function list(){ + //el parametro que se esta enviando como null es un query por si se necesita filtrar + //con respecto a una parametro en especifico + const result = await injecterStore.listAll(TABLE,null); + return result; + } + async function listProduct(id){ + const result = await injecterStore.list(TABLE,id); + return result; + } + async function deleteProduct(id){ + const result = await injecterStore.delete(TABLE,id); + return result; + } + //dado los requisitos dados no se implenetó la comprobacion de la existenca de la categorias + async function insertProduct(data,id){ + let result + if(id){ + result = await injecterStore.update(TABLE, data._id, data); + }else{ + data._id = nanoid(); + result = await injecterStore.create(TABLE, data); + } + return result; + } + return { + list, + listProduct, + deleteProduct, + insertProduct, + } +} \ No newline at end of file diff --git a/src/components/products/index.js b/src/components/products/index.js new file mode 100644 index 00000000..7448aacf --- /dev/null +++ b/src/components/products/index.js @@ -0,0 +1,6 @@ +//index of products +const controller = require('./controller'); +const store = require("../../store/mongoDB"); +//a este punto elegí separar la base de datos del resto del codigo, por si llegado el caso se requiere cambiar +//la base de datos será necesario implementar las funciones y simplemente cambiar la ubicacion de store +module.exports = controller(new store); \ No newline at end of file diff --git a/src/components/products/network.js b/src/components/products/network.js new file mode 100644 index 00000000..489fdd81 --- /dev/null +++ b/src/components/products/network.js @@ -0,0 +1,51 @@ +const express = require('express'); + +const controller = require('./index') +const response = require("../../network/response"); + +const router = express.Router(); +//este archivo gestiona las peticiones que se queden hacer a /api/products +router.get('/', list); +router.get('/:id', listProduct); +router.post('/', insertProduct); +router.put('/:id', alterProduct); +router.delete('/:id', deleteProduct); + +function list(req,res, next){ + + controller.list() + .then((lista) => { + response.success(req,res,lista,200); + }).catch(next); + +} + +function listProduct(req, res, next){ + controller.listProduct(req.params.id) + .then(product =>{ + response.success(req, res, product, 200); + }).catch(next) +} + +function insertProduct(req, res, next){ + controller.insertProduct(req.body,null) + .then((data => { + response.success(req, res, data, 201) + })).catch(next) +} + +function alterProduct(req, res, next){ + controller.insertProduct(req.body,req.params.id) + .then((data => { + response.success(req, res, data, 202) + })).catch(next) +} + +function deleteProduct(req, res, next){ + controller.deleteProduct(req.params.id) + .then(product =>{ + response.success(req, res, product, 200); + }).catch(next) +} + +module.exports = router \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js index 21883f8c..0a6edc3a 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -9,6 +9,9 @@ const config = { dbName: process.env.MONGO_DB_NAME, dbPort: process.env.MONGO_PORT, dbConnection: process.env.MONGO_CONNECTION, + AtlasUser: process.env.ATLAS_USER, + AtlasPasword: process.env.ATLAS_PASSWORD, + AtlasDBName: process.env.ATLAS_DB_NAME }; module.exports = { config }; diff --git a/src/index.js b/src/index.js index 389445c1..78e0f76f 100644 --- a/src/index.js +++ b/src/index.js @@ -8,4 +8,7 @@ app.listen(config.port, err => { console.error("Error: ", err); return; } + else{ + console.log(`Inicio Correcto, app escuchando en el puerto ${config.port}`); + } }); \ No newline at end of file diff --git a/src/network/response.js b/src/network/response.js new file mode 100644 index 00000000..46e36b91 --- /dev/null +++ b/src/network/response.js @@ -0,0 +1,16 @@ +//estas funciones estan pensadas para estandarizar las respuestas que de el servicio +exports.success = (req, res, message = 'Ok', status = 201) => { + res.status(status).send({ + error: false, + status: status, + body: message, + }) +} + +exports.error = (req, res, message = 'Internal Server Error', status = 500) => { + res.status(status).send({ + error: false, + status: status, + body: message, + }) +} \ No newline at end of file diff --git a/src/network/routes.js b/src/network/routes.js new file mode 100644 index 00000000..f7dc037f --- /dev/null +++ b/src/network/routes.js @@ -0,0 +1,8 @@ +const products = require("../components/products/network"); +const categories = require("../components/categories/network"); +//cree una carpeta llamada componestes, en esta carpeta manejaré cada servicio separado en carpetas +const routes = (server) => { + server.use('/api/products/',products); + server.use('/api/categories/',categories); +} +module.exports = routes \ No newline at end of file diff --git a/src/store/mongoDB.js b/src/store/mongoDB.js new file mode 100644 index 00000000..d9be5134 --- /dev/null +++ b/src/store/mongoDB.js @@ -0,0 +1,57 @@ +const {MongoClient, ObjectId} = require("mongodb"); + +const { config } = require("../config"); + +const user = encodeURIComponent(config.AtlasUser); +const password = encodeURIComponent(config.AtlasPasword); +const dbName = config.AtlasDBName; + +const mongoUri = `mongodb+srv://${user}:${password}@cluster0.0pgxi.mongodb.net/${dbName}?retryWrites=true&w=majority`; + +class MongoDB{ + constructor(){ + this.client = new MongoClient(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true}); + this.dbname = dbName; + } + connect(){ + if(!MongoDB.connection){ + MongoDB.connection = new Promise((resolve, reject) => { + this.client.connect(err => { + if(err){ + reject(err); + } + console.log("Connected succesfully to mongoAtlas") + resolve(this.client.db(this.dbName)); + }); + }); + } + return MongoDB.connection; + } + listAll(collection,query){ + return this.connect().then(db => { + return db.collection(collection).find(query).toArray(); + }) + } + list(collection, id){ + return this.connect().then(db => { + return db.collection(collection).findOne({ _id : id}); + }) + } + create(collection, data){ + return this.connect().then(db => { + return db.collection(collection).insertOne(data) + }).then(result => result.insertId); + } + update(collection, id, data){ + return this.connect().then(db => { + return db.collection(collection).updateOne({ _id: id },{ $set: data}, { upsert: true}) + }).then(result => result.upsertedId || id); + } + delete(collection, id){ + return this.connect().then(db => { + return db.collection(collection).deleteOne({ _id: id}); + }).then(() => id); + } + +} +module.exports = MongoDB \ No newline at end of file