From 6b70967e080b4cb9a167539b9308e8d5213ec3d1 Mon Sep 17 00:00:00 2001 From: vincenzo Date: Fri, 28 Apr 2023 11:19:08 -0300 Subject: [PATCH 1/5] feat: create migrations and models database --- package.json | 6 ++- .../20230428132535-create-product.js | 34 ++++++++++++++++ .../20230428133057-create-warehouse.js | 37 ++++++++++++++++++ ...20230428133210-create-product-warehouse.js | 39 +++++++++++++++++++ src/infra/database/models/Product.js | 34 ++++++++++++++++ src/infra/database/models/ProductWarehouse.js | 34 ++++++++++++++++ src/infra/database/models/Warehouse.js | 38 ++++++++++++++++++ 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/infra/database/migrations/20230428132535-create-product.js create mode 100644 src/infra/database/migrations/20230428133057-create-warehouse.js create mode 100644 src/infra/database/migrations/20230428133210-create-product-warehouse.js create mode 100644 src/infra/database/models/Product.js create mode 100644 src/infra/database/models/ProductWarehouse.js create mode 100644 src/infra/database/models/Warehouse.js diff --git a/package.json b/package.json index 8763dc7..1742a1b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,11 @@ "lint": " eslint 'src/**/*.js' 'test/**/*.js' './**/*.js'", "lint-fix": "eslint --fix 'src/**/*.js' 'test/**/*.js' './**/*.js'", "format": "prettier -w .", - "commit": "git-cz" + "commit": "git-cz", + "migrate": "node_modules/.bin/sequelize db:migrate", + "migrate:undo": "node_modules/.bin/sequelize db:migrate:undo:all", + "migrate:create": "node_modules/.bin/sequelize model:generate --name", + "migrate:reset": "yarn migrate:undo && yarn migrate" }, "repository": { "type": "git", diff --git a/src/infra/database/migrations/20230428132535-create-product.js b/src/infra/database/migrations/20230428132535-create-product.js new file mode 100644 index 0000000..a897445 --- /dev/null +++ b/src/infra/database/migrations/20230428132535-create-product.js @@ -0,0 +1,34 @@ +'use strict'; +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('Products', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING, + allowNull: false + }, + sku: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + unique: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('Products'); + } +}; diff --git a/src/infra/database/migrations/20230428133057-create-warehouse.js b/src/infra/database/migrations/20230428133057-create-warehouse.js new file mode 100644 index 0000000..613b966 --- /dev/null +++ b/src/infra/database/migrations/20230428133057-create-warehouse.js @@ -0,0 +1,37 @@ +'use strict'; +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('Warehouses', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + locality: { + type: Sequelize.STRING, + allowNull: false + }, + quantity: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false + }, + type: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('Warehouses'); + } +}; diff --git a/src/infra/database/migrations/20230428133210-create-product-warehouse.js b/src/infra/database/migrations/20230428133210-create-product-warehouse.js new file mode 100644 index 0000000..545d8a5 --- /dev/null +++ b/src/infra/database/migrations/20230428133210-create-product-warehouse.js @@ -0,0 +1,39 @@ +'use strict'; +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('ProductWarehouses', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + productId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { model: 'Products', key: 'id' }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + warehouseId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { model: 'Warehouses', key: 'id' }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('ProductWarehouses'); + } +}; diff --git a/src/infra/database/models/Product.js b/src/infra/database/models/Product.js new file mode 100644 index 0000000..dc5d99d --- /dev/null +++ b/src/infra/database/models/Product.js @@ -0,0 +1,34 @@ +'use strict'; +const { Model } = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Product extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + const { Product, ProductWarehouse } = models; + Product.hasMany(ProductWarehouse, { foreinKey: 'productId' }); + } + } + Product.init( + { + name: { + type: DataTypes.STRING, + allowNull: false + }, + sku: { + type: DataTypes.NUMBER.UNSIGNED, + allowNull: false, + unique: true + } + }, + { + sequelize, + modelName: 'Product' + } + ); + return Product; +}; diff --git a/src/infra/database/models/ProductWarehouse.js b/src/infra/database/models/ProductWarehouse.js new file mode 100644 index 0000000..9ec0a1b --- /dev/null +++ b/src/infra/database/models/ProductWarehouse.js @@ -0,0 +1,34 @@ +'use strict'; +const { Model } = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class ProductWarehouse extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + const { ProductWarehouse, Product, Warehouse } = models; + ProductWarehouse.belongsTo(Product, { sourceKey: 'productId' }); + ProductWarehouse.belongsTo(Warehouse, { sourceKey: 'warehouseId' }); + } + } + ProductWarehouse.init( + { + productId: { + type: DataTypes.STRING, + allowNull: false + }, + warehouseId: { + type: DataTypes.STRING, + allowNull: false + } + }, + { + sequelize, + modelName: 'ProductWarehouse' + } + ); + return ProductWarehouse; +}; diff --git a/src/infra/database/models/Warehouse.js b/src/infra/database/models/Warehouse.js new file mode 100644 index 0000000..b1acc32 --- /dev/null +++ b/src/infra/database/models/Warehouse.js @@ -0,0 +1,38 @@ +'use strict'; +const { Model } = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Warehouse extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + const { Warehouse, WarehouseWarehouse } = models; + Warehouse.hasMany(WarehouseWarehouse, { foreinKey: 'warehouseId' }); + } + } + Warehouse.init( + { + locality: { + type: DataTypes.STRING, + allowNull: false + }, + quantity: { + type: DataTypes.NUMBER.UNSIGNED, + allowNull: false, + defaultValue: 0 + }, + type: { + type: DataTypes.STRING, + allowNull: false + } + }, + { + sequelize, + modelName: 'Warehouse' + } + ); + return Warehouse; +}; From ec814d9dec0cbaf284e793dc2d4675e2218777b2 Mon Sep 17 00:00:00 2001 From: vincenzo Date: Fri, 28 Apr 2023 11:19:39 -0300 Subject: [PATCH 2/5] feat: create domain entities --- docker-compose.yml | 4 ++-- src/domain/entities/Product.js | 15 +++++++++++++++ src/domain/entities/Warehouse.js | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/domain/entities/Product.js create mode 100644 src/domain/entities/Warehouse.js diff --git a/docker-compose.yml b/docker-compose.yml index a83f7a5..f41a89d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: app: - container_name: app + container_name: app_belezanaweb image: node:18-alpine working_dir: /app volumes: @@ -15,7 +15,7 @@ services: - database database: - container_name: db_mysql + container_name: db_mysql_belezanaweb image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 'root' diff --git a/src/domain/entities/Product.js b/src/domain/entities/Product.js new file mode 100644 index 0000000..6f5c784 --- /dev/null +++ b/src/domain/entities/Product.js @@ -0,0 +1,15 @@ +const { attributes } = require('structure'); + +const types = { + required_string: { + type: String, + required: true + } +}; + +const Product = attributes({ + sku: Number, + name: String +})(class Product {}); + +module.exports = Product; diff --git a/src/domain/entities/Warehouse.js b/src/domain/entities/Warehouse.js new file mode 100644 index 0000000..36155f3 --- /dev/null +++ b/src/domain/entities/Warehouse.js @@ -0,0 +1,20 @@ +const { attributes } = require('structure'); + +const types = { + required_string: { + type: String, + required: true + }, + required_number: { + type: String, + required: true + } +}; + +const Warehouse = attributes({ + locality: { ...types.required_string }, + quantity: Number, + type: { ...types.required_number } +})(class Warehouse {}); + +module.exports = Warehouse; From d1acead25737f0c2fc2eaaee196fc57a97a2a0a8 Mon Sep 17 00:00:00 2001 From: vincenzo Date: Fri, 28 Apr 2023 12:53:26 -0300 Subject: [PATCH 3/5] refactor: create ModelsLoaders, update migration product --- src/infra/database/ModelsLoader.js | 35 +++++++++++++ .../20230428132535-create-product.js | 52 ++++++++++--------- src/infra/database/models/Warehouse.js | 4 +- src/infra/database/models/index.js | 19 +++++++ 4 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 src/infra/database/ModelsLoader.js create mode 100644 src/infra/database/models/index.js diff --git a/src/infra/database/ModelsLoader.js b/src/infra/database/ModelsLoader.js new file mode 100644 index 0000000..c41522c --- /dev/null +++ b/src/infra/database/ModelsLoader.js @@ -0,0 +1,35 @@ +const fs = require('fs'); +const path = require('path'); +const { DataTypes } = require('sequelize'); + +/* this module is responsible for loading sequelize +models and associations into the database object */ + +module.exports = { + load({ sequelize, baseFolder, indexFile = 'index.js' }) { + sequelize.addHook('beforeCount', function (options) { + if (this._scope.include && this._scope.include.length > 0) { + options.distinct = true; + options.col = this._scope.col || options.col || `"${this.options.name.singular}".id`; + } + + if (options.include && options.include.length > 0) { + options.include = null; + } + }); + fs.readdirSync(baseFolder) + .filter((file) => { + return file.indexOf('.') !== 0 && file !== indexFile && file.slice(-3) === '.js'; + }) + .forEach((file) => { + const model = require(path.join(baseFolder, file)); + return model(sequelize, DataTypes); + }); + + for (const model in sequelize.models) { + sequelize.models[model].associate(sequelize.models); + } + + return sequelize; + } +}; diff --git a/src/infra/database/migrations/20230428132535-create-product.js b/src/infra/database/migrations/20230428132535-create-product.js index a897445..cf4fd05 100644 --- a/src/infra/database/migrations/20230428132535-create-product.js +++ b/src/infra/database/migrations/20230428132535-create-product.js @@ -2,31 +2,33 @@ /** @type {import('sequelize-cli').Migration} */ module.exports = { async up(queryInterface, Sequelize) { - await queryInterface.createTable('Products', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER - }, - name: { - type: Sequelize.STRING, - allowNull: false - }, - sku: { - type: Sequelize.INTEGER.UNSIGNED, - allowNull: false, - unique: true - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE - } - }); + await queryInterface + .createTable('Products', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING, + allowNull: false + }, + sku: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + unique: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }) + .then(() => queryInterface.addIndex('Products', ['sku'])); }, async down(queryInterface, Sequelize) { await queryInterface.dropTable('Products'); diff --git a/src/infra/database/models/Warehouse.js b/src/infra/database/models/Warehouse.js index b1acc32..5c0b209 100644 --- a/src/infra/database/models/Warehouse.js +++ b/src/infra/database/models/Warehouse.js @@ -9,8 +9,8 @@ module.exports = (sequelize, DataTypes) => { */ static associate(models) { // define association here - const { Warehouse, WarehouseWarehouse } = models; - Warehouse.hasMany(WarehouseWarehouse, { foreinKey: 'warehouseId' }); + const { Warehouse, ProductWarehouse } = models; + Warehouse.hasMany(ProductWarehouse, { foreinKey: 'warehouseId' }); } } Warehouse.init( diff --git a/src/infra/database/models/index.js b/src/infra/database/models/index.js new file mode 100644 index 0000000..1fd2d6c --- /dev/null +++ b/src/infra/database/models/index.js @@ -0,0 +1,19 @@ +const ModelsLoader = require('../ModelsLoader'); +const Sequelize = require('sequelize'); + +module.exports = ({ config }) => { + const databaseConfig = config.infra.db; + if (!databaseConfig) { + console.error('Database config file log not found, disabling database.'); + return false; + } + const sequelize = new Sequelize(databaseConfig); + sequelize + .authenticate() + .then((response) => console.log('Connection has been established successfully.')) + .catch((error) => console.error('Unable to connect to the database:', error)); + return ModelsLoader.load({ + sequelize, + baseFolder: __dirname + }); +}; From ddc3c580f720a86b6fee2eeb552a0d061729911b Mon Sep 17 00:00:00 2001 From: vincenzo Date: Fri, 28 Apr 2023 12:54:40 -0300 Subject: [PATCH 4/5] feat: add new domains and create repository layer --- src/domain/entities/Inventory.js | 11 ++++++++++ src/domain/entities/Product.js | 19 +++++++++++++--- src/infra/errors/Exceptions.js | 9 ++++++++ src/infra/errors/InternalServerError.js | 7 ++++++ src/infra/repositories/Repository.js | 22 +++++++++++++++++++ .../repositories/product/ProductMapper.js | 12 ++++++++++ .../repositories/product/ProductRepository.js | 14 ++++++++++++ 7 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/domain/entities/Inventory.js create mode 100644 src/infra/errors/Exceptions.js create mode 100644 src/infra/errors/InternalServerError.js create mode 100644 src/infra/repositories/Repository.js create mode 100644 src/infra/repositories/product/ProductMapper.js create mode 100644 src/infra/repositories/product/ProductRepository.js diff --git a/src/domain/entities/Inventory.js b/src/domain/entities/Inventory.js new file mode 100644 index 0000000..38114b7 --- /dev/null +++ b/src/domain/entities/Inventory.js @@ -0,0 +1,11 @@ +const { attributes } = require('structure'); +const Warehouse = require('./Warehouse'); + +const Inventory = attributes({ + quantity: Number, + warehouses: { + type: Array, + itemType: Warehouse + } +}); +module.exports = Inventory; diff --git a/src/domain/entities/Product.js b/src/domain/entities/Product.js index 6f5c784..dd7cf86 100644 --- a/src/domain/entities/Product.js +++ b/src/domain/entities/Product.js @@ -1,15 +1,28 @@ const { attributes } = require('structure'); +const Inventory = require('./Inventory'); const types = { required_string: { type: String, required: true + }, + required_number: { + type: Number, + required: true } }; const Product = attributes({ - sku: Number, - name: String + sku: { ...types.required_number }, + name: { ...types.required_string } })(class Product {}); -module.exports = Product; +const ProductOutput = attributes({ + id: { ...types.required_number }, + sku: { ...types.required_number }, + name: { ...types.required_string }, + inventory: Inventory, + isMarketable: Boolean +})(class ProductOutput {}); + +module.exports = { Product, ProductOutput }; diff --git a/src/infra/errors/Exceptions.js b/src/infra/errors/Exceptions.js new file mode 100644 index 0000000..f4dd186 --- /dev/null +++ b/src/infra/errors/Exceptions.js @@ -0,0 +1,9 @@ +module.exports = ({ internalServerError: InternalServerError }) => ({ + internalError: (details) => { + const error = new InternalServerError(details); + return { + status: 500, + body: error.name + }; + } +}); diff --git a/src/infra/errors/InternalServerError.js b/src/infra/errors/InternalServerError.js new file mode 100644 index 0000000..a05a52f --- /dev/null +++ b/src/infra/errors/InternalServerError.js @@ -0,0 +1,7 @@ +class InternalServerError extends Error { + constructor(details) { + super(`Internal Server Error ${details}`); + this.name = 'InternalServerError'; + } +} +module.exports = () => InternalServerError; diff --git a/src/infra/repositories/Repository.js b/src/infra/repositories/Repository.js new file mode 100644 index 0000000..484a144 --- /dev/null +++ b/src/infra/repositories/Repository.js @@ -0,0 +1,22 @@ +class Repository { + constructor({ ResourceModel, ResourceMapper, Exceptions }) { + this.ResourceModel = ResourceModel; + this.ResourceMapper = ResourceMapper; + this.Exceptions = Exceptions; + } + + async create(domainEntity) { + const model = await new this.ResourceModel(this.ResourceMapper.toInputDatabase(domainEntity)); + const dbCreatedResource = await model.save(); + console.log('dbCreatedResource', dbCreatedResource); + return this.ResourceMapper.toOutputDabase(dbCreatedResource); + } + + async findAll(query) { + const model = await this.ResourceModel.findAll(query); + if (!model) return model; + return model.map(this.ResourceMapper.toOutputDabase); + } +} + +module.exports = Repository; diff --git a/src/infra/repositories/product/ProductMapper.js b/src/infra/repositories/product/ProductMapper.js new file mode 100644 index 0000000..e07cc8b --- /dev/null +++ b/src/infra/repositories/product/ProductMapper.js @@ -0,0 +1,12 @@ +const { Product, ProductOutput } = require('../../../domain/entities/Product'); + +const ProductMapper = { + toInputDatabase(product) { + return new Product({ ...product }).toJSON(); + }, + toOutputDabase({ dataValues }) { + return new ProductOutput({ ...dataValues }).toJSON(); + } +}; + +module.exports = () => ProductMapper; diff --git a/src/infra/repositories/product/ProductRepository.js b/src/infra/repositories/product/ProductRepository.js new file mode 100644 index 0000000..d64a20a --- /dev/null +++ b/src/infra/repositories/product/ProductRepository.js @@ -0,0 +1,14 @@ +const Repository = require('../Repository'); + +class ProductRepository extends Repository { + constructor({ sequelize, productMapper, exceptions }) { + const { Product } = sequelize.models; + super({ + ResourceModel: Product, + ResourceMapper: productMapper, + Exceptions: exceptions + }); + } +} + +module.exports = ProductRepository; From fc03c391b31c506b3402e7bd4057fcf3cf6bb87a Mon Sep 17 00:00:00 2001 From: vincenzo Date: Fri, 28 Apr 2023 12:55:40 -0300 Subject: [PATCH 5/5] feat: implement router /products with operation and usecase to create single new product --- .../product/createProductOperation.js | 6 +++++ src/app/usecases/createProductUsecase.js | 6 +++++ src/container.js | 27 ++++++++++++++----- src/interfaces/http/Router.js | 4 +-- .../http/controllers/productController.js | 18 +++++++++++++ 5 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/app/operations/product/createProductOperation.js create mode 100644 src/app/usecases/createProductUsecase.js create mode 100644 src/interfaces/http/controllers/productController.js diff --git a/src/app/operations/product/createProductOperation.js b/src/app/operations/product/createProductOperation.js new file mode 100644 index 0000000..9d71da4 --- /dev/null +++ b/src/app/operations/product/createProductOperation.js @@ -0,0 +1,6 @@ +module.exports = ({ createProductUsecase }) => ({ + execute: async (data) => { + const response = await createProductUsecase.execute(data); + return response; + } +}); diff --git a/src/app/usecases/createProductUsecase.js b/src/app/usecases/createProductUsecase.js new file mode 100644 index 0000000..8cae9b3 --- /dev/null +++ b/src/app/usecases/createProductUsecase.js @@ -0,0 +1,6 @@ +module.exports = ({ productRepository }) => ({ + execute: async (data) => { + const response = await productRepository.create(data); + return response; + } +}); diff --git a/src/container.js b/src/container.js index add36dd..dafd0cf 100644 --- a/src/container.js +++ b/src/container.js @@ -5,6 +5,7 @@ const Config = require('../config'); const Server = require('../src/interfaces/http/Server'); const Application = require('./app/Application'); const Router = require('../src/interfaces/http/Router'); +const sequelize = require('./infra/database/models'); container .register({ @@ -12,14 +13,26 @@ container container: awilix.asValue(container), server: awilix.asClass(Server).singleton(), application: awilix.asClass(Application).singleton(), - router: awilix.asFunction(Router).singleton() + router: awilix.asFunction(Router).singleton(), + sequelize: awilix.asFunction(sequelize).singleton() }) - .loadModules(['src/interfaces/http/middlewares/**/*.js', 'src/interfaces/http/controllers/**/*.js'], { - formatName: 'camelCase', - resolverOptions: { - injectionMode: awilix.InjectionMode.PROXY, - lifetime: awilix.Lifetime.SINGLETON + .loadModules( + [ + 'src/interfaces/http/middlewares/**/*.js', + 'src/interfaces/http/controllers/**/*.js', + 'src/infra/repositories/**/*.js', + 'src/infra/errors/**/*.js', + 'src/app/operations/**/*.js', + 'src/app/usecases/**/*.js', + 'src/domain/**/*.js' + ], + { + formatName: 'camelCase', + resolverOptions: { + injectionMode: awilix.InjectionMode.PROXY, + lifetime: awilix.Lifetime.SINGLETON + } } - }); + ); module.exports = container; diff --git a/src/interfaces/http/Router.js b/src/interfaces/http/Router.js index 57067ad..8af4d8c 100644 --- a/src/interfaces/http/Router.js +++ b/src/interfaces/http/Router.js @@ -1,11 +1,11 @@ const express = require('express'); const apiRouter = express.Router(); -module.exports = ({ healthCheckMiddleware, sampleController }) => { +module.exports = ({ healthCheckMiddleware, productController }) => { apiRouter.use(express.json()); apiRouter.use(express.urlencoded({ extended: true })); - apiRouter.use('/samples', sampleController.router); + apiRouter.use('/products', productController.router); apiRouter.use('/health-check', healthCheckMiddleware); apiRouter.use('/healthcheck', healthCheckMiddleware); diff --git a/src/interfaces/http/controllers/productController.js b/src/interfaces/http/controllers/productController.js new file mode 100644 index 0000000..5e4798b --- /dev/null +++ b/src/interfaces/http/controllers/productController.js @@ -0,0 +1,18 @@ +const { Router } = require('express'); +const Status = require('http-status'); + +module.exports = ({ createProductOperation }) => ({ + create: async (req, res, next) => { + try { + const { body } = req; + const product = await createProductOperation.execute(body); + return res.status(Status.OK).json(product); + } catch (error) { + next(error); + } + }, + + get router() { + return Router().post('/', this.create); + } +});