Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3'

services:
app:
container_name: app
container_name: app_belezanaweb
image: node:18-alpine
working_dir: /app
volumes:
Expand All @@ -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'
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions src/app/operations/product/createProductOperation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = ({ createProductUsecase }) => ({
execute: async (data) => {
const response = await createProductUsecase.execute(data);
return response;
}
});
6 changes: 6 additions & 0 deletions src/app/usecases/createProductUsecase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = ({ productRepository }) => ({
execute: async (data) => {
const response = await productRepository.create(data);
return response;
}
});
27 changes: 20 additions & 7 deletions src/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,34 @@ 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({
config: awilix.asValue(Config),
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;
11 changes: 11 additions & 0 deletions src/domain/entities/Inventory.js
Original file line number Diff line number Diff line change
@@ -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;
28 changes: 28 additions & 0 deletions src/domain/entities/Product.js
Original file line number Diff line number Diff line change
@@ -0,0 +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: { ...types.required_number },
name: { ...types.required_string }
})(class 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 };
20 changes: 20 additions & 0 deletions src/domain/entities/Warehouse.js
Original file line number Diff line number Diff line change
@@ -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;
35 changes: 35 additions & 0 deletions src/infra/database/ModelsLoader.js
Original file line number Diff line number Diff line change
@@ -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;
}
};
36 changes: 36 additions & 0 deletions src/infra/database/migrations/20230428132535-create-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'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
}
})
.then(() => queryInterface.addIndex('Products', ['sku']));
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Products');
}
};
37 changes: 37 additions & 0 deletions src/infra/database/migrations/20230428133057-create-warehouse.js
Original file line number Diff line number Diff line change
@@ -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');
}
};
Original file line number Diff line number Diff line change
@@ -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');
}
};
34 changes: 34 additions & 0 deletions src/infra/database/models/Product.js
Original file line number Diff line number Diff line change
@@ -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;
};
34 changes: 34 additions & 0 deletions src/infra/database/models/ProductWarehouse.js
Original file line number Diff line number Diff line change
@@ -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;
};
Loading