Skip to content

Instantly share code, notes, and snippets.

@felipepastorelima
Created November 8, 2019 14:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felipepastorelima/584e47eae2eca325f830082d21587cd4 to your computer and use it in GitHub Desktop.
Save felipepastorelima/584e47eae2eca325f830082d21587cd4 to your computer and use it in GitHub Desktop.
Back-end Refactor Example
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;
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