Created
November 8, 2019 14:37
-
-
Save felipepastorelima/584e47eae2eca325f830082d21587cd4 to your computer and use it in GitHub Desktop.
Back-end Refactor Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const models = require('../models'); | |
const SequelizeRepository = require('./sequelizeRepository'); | |
const AuditLogRepository = require('./auditLogRepository'); | |
const FileRepository = require('./fileRepository'); | |
const lodash = require('lodash'); | |
const SequelizeFilterUtils = require('../utils/sequelizeFilterUtils'); | |
const Sequelize = models.Sequelize; | |
const Op = Sequelize.Op; | |
/** | |
* Handles database operations for the Order. | |
* See https://sequelize.org/v5/index.html to learn how to customize it. | |
*/ | |
class OrderRepository { | |
/** | |
* Creates the Order. | |
* | |
* @param {Object} data | |
* @param {Object} [options] | |
*/ | |
async create(data, options) { | |
const currentUser = SequelizeRepository.getCurrentUser( | |
options, | |
); | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
const record = await models.order.create( | |
{ | |
...lodash.pick(data, [ | |
'id', | |
'delivered', | |
'importHash', | |
'updatedAt', | |
'createdAt', | |
]), | |
createdById: currentUser.id, | |
updatedById: currentUser.id, | |
}, | |
{ | |
transaction, | |
}, | |
); | |
await record.setCustomer(data.customer || null, { | |
transaction, | |
}); | |
await record.setProducts(data.products || [], { | |
transaction, | |
}); | |
await record.setEmployee(data.employee || null, { | |
transaction, | |
}); | |
await FileRepository.replaceRelationFiles( | |
{ | |
belongsTo: models.order.getTableName(), | |
belongsToColumn: 'attachments', | |
belongsToId: record.id, | |
}, | |
data.attachments, | |
options, | |
); | |
await this._createAuditLog( | |
AuditLogRepository.CREATE, | |
record, | |
data, | |
options, | |
); | |
return this.findById(record.id, options); | |
} | |
/** | |
* Updates the Order. | |
* | |
* @param {Object} data | |
* @param {Object} [options] | |
*/ | |
async update(id, data, options) { | |
const currentUser = SequelizeRepository.getCurrentUser( | |
options, | |
); | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
let record = await models.order.findByPk(id, { | |
transaction, | |
}); | |
record = await record.update( | |
{ | |
...lodash.pick(data, [ | |
'id', | |
'delivered', | |
'importHash', | |
'updatedAt', | |
'createdAt', | |
]), | |
updatedById: currentUser.id, | |
}, | |
{ | |
transaction, | |
}, | |
); | |
await record.setCustomer(data.customer || null, { | |
transaction, | |
}); | |
await record.setProducts(data.products || [], { | |
transaction, | |
}); | |
await record.setEmployee(data.employee || null, { | |
transaction, | |
}); | |
await FileRepository.replaceRelationFiles( | |
{ | |
belongsTo: models.order.getTableName(), | |
belongsToColumn: 'attachments', | |
belongsToId: record.id, | |
}, | |
data.attachments, | |
options, | |
); | |
await this._createAuditLog( | |
AuditLogRepository.UPDATE, | |
record, | |
data, | |
options, | |
); | |
return this.findById(record.id, options); | |
} | |
/** | |
* Deletes the Order. | |
* | |
* @param {string} id | |
* @param {Object} [options] | |
*/ | |
async destroy(id, options) { | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
let record = await models.order.findByPk(id, { | |
transaction, | |
}); | |
await record.destroy({ | |
transaction, | |
}); | |
await this._createAuditLog( | |
AuditLogRepository.DELETE, | |
record, | |
null, | |
options, | |
); | |
} | |
/** | |
* Finds the Order and its relations. | |
* | |
* @param {string} id | |
* @param {Object} [options] | |
*/ | |
async findById(id, options) { | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
const include = [ | |
{ | |
model: models.customer, | |
as: 'customer', | |
}, | |
{ | |
model: models.user, | |
as: 'employee', | |
}, | |
]; | |
const record = await models.order.findByPk(id, { | |
include, | |
transaction, | |
}); | |
return this._fillWithRelationsAndFiles(record, options); | |
} | |
/** | |
* Counts the number of Orders based on the filter. | |
* | |
* @param {Object} filter | |
* @param {Object} [options] | |
*/ | |
async count(filter, options) { | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
return models.order.count( | |
{ | |
where: filter, | |
}, | |
{ | |
transaction, | |
}, | |
); | |
} | |
/** | |
* Finds the Orders based on the query. | |
* See https://sequelize.org/v5/manual/querying.html to learn how to | |
* customize the query. | |
* | |
* @param {Object} query | |
* @param {Object} query.filter | |
* @param {number} query.limit | |
* @param {number} query.offset | |
* @param {string} query.orderBy | |
* @param {Object} [options] | |
* | |
* @returns {Promise<Object>} response - Object containing the rows and the count. | |
*/ | |
async findAndCountAll( | |
{ filter, limit, offset, orderBy } = { | |
filter: null, | |
limit: 0, | |
offset: 0, | |
orderBy: null, | |
}, | |
options, | |
) { | |
let where = {}; | |
let include = [ | |
{ | |
model: models.customer, | |
as: 'customer', | |
}, | |
{ | |
model: models.user, | |
as: 'employee', | |
}, | |
]; | |
if (filter) { | |
if (filter.id) { | |
where = { | |
...where, | |
['id']: SequelizeFilterUtils.uuid(filter.id), | |
}; | |
} | |
if (filter.customer) { | |
where = { | |
...where, | |
['customerId']: SequelizeFilterUtils.uuid( | |
filter.customer, | |
), | |
}; | |
} | |
if (filter.employee) { | |
where = { | |
...where, | |
['employeeId']: SequelizeFilterUtils.uuid( | |
filter.employee, | |
), | |
}; | |
} | |
if (filter.createdAtRange) { | |
const [start, end] = filter.createdAtRange; | |
if (start) { | |
where = { | |
...where, | |
['createdAt']: { | |
...where.createdAt, | |
[Op.gte]: start, | |
}, | |
}; | |
} | |
if (end) { | |
where = { | |
...where, | |
['createdAt']: { | |
...where.createdAt, | |
[Op.lte]: end, | |
}, | |
}; | |
} | |
} | |
} | |
let { | |
rows, | |
count, | |
} = await models.order.findAndCountAll({ | |
where, | |
include, | |
limit: limit ? Number(limit) : undefined, | |
offset: offset ? Number(offset) : undefined, | |
order: orderBy | |
? [orderBy.split('_')] | |
: [['createdAt', 'DESC']], | |
transaction: SequelizeRepository.getTransaction( | |
options, | |
), | |
}); | |
rows = await this._fillWithRelationsAndFilesForRows( | |
rows, | |
options, | |
); | |
return { rows, count }; | |
} | |
/** | |
* Lists the Orders to populate the autocomplete. | |
* See https://sequelize.org/v5/manual/querying.html to learn how to | |
* customize the query. | |
* | |
* @param {string} query | |
* @param {number} limit | |
*/ | |
async findAllAutocomplete(query, limit) { | |
let where = {}; | |
if (query) { | |
where = { | |
[Op.or]: [ | |
{ ['id']: SequelizeFilterUtils.uuid(query) }, | |
], | |
}; | |
} | |
const records = await models.order.findAll({ | |
attributes: ['id'], | |
where, | |
limit: limit ? Number(limit) : undefined, | |
orderBy: [['id', 'ASC']], | |
}); | |
return records.map((record) => ({ | |
id: record.id, | |
label: record.id, | |
})); | |
} | |
/** | |
* Creates an audit log of the operation. | |
* | |
* @param {string} action - The action [create, update or delete]. | |
* @param {object} record - The sequelize record | |
* @param {object} data - The new data passed on the request | |
* @param {object} options | |
*/ | |
async _createAuditLog(action, record, data, options) { | |
let values = {}; | |
if (data) { | |
values = { | |
...record.get({ plain: true }), | |
productsIds: data.products, | |
attachments: data.attachments, | |
}; | |
} | |
await AuditLogRepository.log( | |
{ | |
entityName: 'order', | |
entityId: record.id, | |
action, | |
values, | |
}, | |
options, | |
); | |
} | |
/** | |
* Fills an array of Order with relations and files. | |
* | |
* @param {Array} rows | |
* @param {Object} [options] | |
*/ | |
async _fillWithRelationsAndFilesForRows(rows, options) { | |
if (!rows) { | |
return rows; | |
} | |
return Promise.all( | |
rows.map((record) => | |
this._fillWithRelationsAndFiles(record, options), | |
), | |
); | |
} | |
/** | |
* Fill the Order with the relations and files. | |
* | |
* @param {Object} record | |
* @param {Object} [options] | |
*/ | |
async _fillWithRelationsAndFiles(record, options) { | |
if (!record) { | |
return record; | |
} | |
const output = record.get({ plain: true }); | |
const transaction = SequelizeRepository.getTransaction( | |
options, | |
); | |
output.products = await record.getProducts({ | |
transaction, | |
}); | |
output.attachments = await record.getAttachments({ | |
transaction, | |
}); | |
return output; | |
} | |
} | |
module.exports = OrderRepository; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const models = require('../models'); | |
const SequelizeFilter = require('../utils/sequelizeFilter'); | |
const SequelizeAutocompleteFilter = require('../utils/sequelizeAutocompleteFilter'); | |
const AbstractRepository = require('./abstractRepository'); | |
const AuditLogRepository = require('./auditLogRepository'); | |
const FileRepository = require('./fileRepository'); | |
const lodash = require('lodash'); | |
class OrderRepository extends AbstractRepository { | |
constructor() { | |
super(); | |
this.inTableAttributes = [ | |
'id', | |
'delivered', | |
'importHash', | |
'updatedAt', | |
'createdAt', | |
]; | |
this.fileAttributes = [ | |
'attachments', | |
]; | |
this.relationToOneAttributes = { | |
customer: { | |
model: models.customer, | |
as: 'customer', | |
}, | |
employee: { | |
model: models.user, | |
as: 'employee', | |
}, | |
}; | |
this.relationToManyAttributes = { | |
products: { | |
model: models.product, | |
as: 'products', | |
}, | |
}; | |
} | |
async create(data, options) { | |
const record = await models.order.create( | |
{ | |
...lodash.pick(data, this.inTableAttributes), | |
createdById: AbstractRepository.getCurrentUser( | |
options, | |
).id, | |
updatedById: AbstractRepository.getCurrentUser( | |
options, | |
).id, | |
}, | |
{ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
await this._createOrUpdateRelations( | |
record, | |
data, | |
options, | |
); | |
await this._createOrUpdateFiles(record, data, options); | |
await this._auditLogs( | |
AuditLogRepository.CREATE, | |
record, | |
data, | |
options, | |
); | |
return this.findById(record.id, options); | |
} | |
async update(id, data, options) { | |
let record = await models.order.findByPk(id, { | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}); | |
record = await record.update( | |
{ | |
...lodash.pick(data, this.inTableAttributes), | |
updatedById: AbstractRepository.getCurrentUser( | |
options, | |
).id, | |
}, | |
{ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
await this._createOrUpdateRelations( | |
record, | |
data, | |
options, | |
); | |
await this._createOrUpdateFiles(record, data, options); | |
await this._auditLogs( | |
AuditLogRepository.UPDATE, | |
record, | |
data, | |
options, | |
); | |
return this.findById(record.id, options); | |
} | |
async destroy(id, options) { | |
let record = await models.order.findByPk(id, { | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}); | |
await record.destroy({ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}); | |
await this._auditLogs( | |
AuditLogRepository.DELETE, | |
record, | |
null, | |
options, | |
); | |
} | |
async findById(id, options) { | |
const record = await models.order.findByPk( | |
id, | |
{ | |
include: this._buildIncludeForQueries(), | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
return this._fillNonTableAttributesForRecord( | |
record, | |
null, | |
options, | |
); | |
} | |
async count(filter, options) { | |
return models.order.count( | |
{ | |
where: filter, | |
}, | |
{ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
} | |
async _auditLogs(action, record, data, options) { | |
let values = {}; | |
if (data) { | |
values = { | |
...record.get({ plain: true }), | |
}; | |
this.fileAttributes.forEach((field) => { | |
values[field] = data[field]; | |
}); | |
Object.keys(this.relationToManyAttributes).forEach( | |
(field) => { | |
values[`${field}Ids`] = data[field]; | |
}, | |
); | |
} | |
await AuditLogRepository.log( | |
{ | |
entityName: 'order', | |
entityId: record.id, | |
action, | |
values, | |
}, | |
options, | |
); | |
} | |
async _createOrUpdateRelations(record, data, options) { | |
for (const field of Object.keys( | |
this.relationToManyAttributes, | |
)) { | |
await record[`set${AbstractRepository.jsUcfirst(field)}`]( | |
data[field] || [], | |
{ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
} | |
for (const field of Object.keys( | |
this.relationToOneAttributes, | |
)) { | |
await record[`set${AbstractRepository.jsUcfirst(field)}`]( | |
data[field] || null, | |
{ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}, | |
); | |
} | |
} | |
async _createOrUpdateFiles(record, data, options) { | |
for (const field of this.fileAttributes) { | |
await FileRepository.replaceRelationFiles( | |
{ | |
belongsTo: models.order.getTableName(), | |
belongsToColumn: field, | |
belongsToId: record.id, | |
}, | |
data[field], | |
options, | |
); | |
} | |
} | |
_buildIncludeForQueries(attributes, includeToAppend) { | |
if (!attributes) { | |
return Object.keys(this.relationToOneAttributes).map( | |
(key) => this.relationToOneAttributes[key], | |
); | |
} | |
const attributesToInclude = lodash.intersection( | |
attributes, | |
Object.keys(this.relationToOneAttributes), | |
); | |
const nonIncludedYet = attributesToInclude.filter( | |
(attribute) => | |
!includeToAppend.some( | |
(included) => included.as === attribute, | |
), | |
); | |
return nonIncludedYet | |
.map( | |
(attribute) => | |
this.relationToOneAttributes[attribute], | |
) | |
.concat(includeToAppend); | |
} | |
async _fillNonTableAttributesForRows( | |
rows, | |
requestedAttributes, | |
options, | |
) { | |
if (!rows) { | |
return rows; | |
} | |
return Promise.all( | |
rows.map((record) => | |
this._fillNonTableAttributesForRecord( | |
record, | |
requestedAttributes, | |
options, | |
), | |
), | |
); | |
} | |
async _fillNonTableAttributesForRecord( | |
record, | |
requestedAttributes, | |
options, | |
) { | |
if (!record) { | |
return record; | |
} | |
function isRequestedAttribute(fieldName) { | |
if ( | |
!requestedAttributes || | |
requestedAttributes.length | |
) { | |
return true; | |
} | |
return requestedAttributes.includes(fieldName); | |
} | |
const output = record.get({ plain: true }); | |
const fields = Object.keys( | |
this.relationToManyAttributes, | |
) | |
.concat(this.fileAttributes) | |
.filter(isRequestedAttribute); | |
for (const field of fields) { | |
output[field] = await record[ | |
`get${AbstractRepository.jsUcfirst(field)}` | |
]({ | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}); | |
} | |
return output; | |
} | |
async findAndCountAll( | |
{ | |
requestedAttributes, | |
filter, | |
limit, | |
offset, | |
orderBy, | |
} = { | |
requestedAttributes: null, | |
filter: null, | |
limit: 0, | |
offset: 0, | |
orderBy: null, | |
}, | |
options, | |
) { | |
let sequelizeFilter = new SequelizeFilter( | |
models.Sequelize, | |
); | |
if (filter) { | |
if (filter.id) { | |
sequelizeFilter.appendId('id', filter.id); | |
} | |
if (filter.customer) { | |
sequelizeFilter.appendId('customerId', filter.customer); | |
} | |
if (filter.employee) { | |
sequelizeFilter.appendId('employeeId', filter.employee); | |
} | |
if (filter.createdAtRange) { | |
sequelizeFilter.appendRange( | |
'createdAt', | |
filter.createdAtRange, | |
); | |
} | |
} | |
const include = this._buildIncludeForQueries( | |
requestedAttributes, | |
sequelizeFilter.getInclude(), | |
); | |
const requestedAttributesInTable = | |
requestedAttributes && requestedAttributes.length | |
? [ | |
'id', | |
...lodash.intersection( | |
this.inTableAttributes, | |
requestedAttributes, | |
), | |
] | |
: undefined; | |
let { rows, count } = await models.order.findAndCountAll({ | |
where: sequelizeFilter.getWhere(), | |
include, | |
attributes: requestedAttributesInTable, | |
limit: limit ? Number(limit) : undefined, | |
offset: offset ? Number(offset) : undefined, | |
order: orderBy | |
? [orderBy.split('_')] | |
: [['createdAt', 'DESC']], | |
transaction: AbstractRepository.getTransaction( | |
options, | |
), | |
}); | |
rows = await this._fillNonTableAttributesForRows( | |
rows, | |
requestedAttributes, | |
options, | |
); | |
return { rows, count }; | |
} | |
async findAllAutocomplete(query, limit) { | |
const filter = new SequelizeAutocompleteFilter( | |
models.Sequelize, | |
); | |
if (query) { | |
filter.appendId('id', query); | |
} | |
const records = await models.order.findAll({ | |
attributes: ['id', 'id'], | |
where: filter.getWhere(), | |
limit: limit ? Number(limit) : undefined, | |
orderBy: [['id', 'ASC']], | |
}); | |
return records.map((record) => ({ | |
id: record.id, | |
label: record.id, | |
})); | |
} | |
} | |
module.exports = OrderRepository; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment