UNPKG

loopback-softdelete-mixin

Version:

A mixin to automatically generate created and updated Date attributes for loopback Models

97 lines (75 loc) 3.58 kB
import _debug from './debug'; const debug = _debug(); export default (Model, { deletedAt = 'deletedAt', _isDeleted = '_isDeleted', scrub = false }) => { debug('SoftDelete mixin for Model %s', Model.modelName); debug('options', { deletedAt, _isDeleted, scrub }); const properties = Model.definition.properties; let scrubbed = {}; if (scrub !== false) { let propertiesToScrub = scrub; if (!Array.isArray(propertiesToScrub)) { propertiesToScrub = Object.keys(properties) .filter(prop => !properties[prop].id && prop !== _isDeleted); } scrubbed = propertiesToScrub.reduce((obj, prop) => ({ ...obj, [prop]: null }), {}); } Model.defineProperty(deletedAt, {type: Date, required: false}); Model.defineProperty(_isDeleted, {required: true, default: false}); Model.destroyAll = function softDestroyAll(where, cb) { return Model.updateAll(where, { ...scrubbed, [deletedAt]: new Date(), [_isDeleted]: true }) .then(result => (typeof cb === 'function') ? cb(null, result) : result) .catch(error => (typeof cb === 'function') ? cb(error) : Promise.reject(error)); }; Model.remove = Model.destroyAll; Model.deleteAll = Model.destroyAll; Model.destroyById = function softDestroyById(id, cb) { return Model.updateAll({ id: id }, { ...scrubbed, [deletedAt]: new Date(), [_isDeleted]: true }) .then(result => (typeof cb === 'function') ? cb(null, result) : result) .catch(error => (typeof cb === 'function') ? cb(error) : Promise.reject(error)); }; Model.removeById = Model.destroyById; Model.deleteById = Model.destroyById; Model.prototype.destroy = function softDestroy(options, cb) { const callback = (cb === undefined && typeof options === 'function') ? options : cb; return this.updateAttributes({ ...scrubbed, [deletedAt]: new Date(), [_isDeleted]: true }) .then(result => (typeof cb === 'function') ? callback(null, result) : result) .catch(error => (typeof cb === 'function') ? callback(error) : Promise.reject(error)); }; Model.prototype.remove = Model.prototype.destroy; Model.prototype.delete = Model.prototype.destroy; // Emulate default scope but with more flexibility. const queryNonDeleted = { or: [ { [_isDeleted]: { exists: false } }, { [_isDeleted]: false }, ], }; const _findOrCreate = Model.findOrCreate; Model.findOrCreate = function findOrCreateDeleted(query = {}, ...rest) { if (!query.where) query.where = {}; if (!query.deleted) { query.where = { and: [ query.where, queryNonDeleted ] }; } return _findOrCreate.call(Model, query, ...rest); }; const _find = Model.find; Model.find = function findDeleted(query = {}, ...rest) { if (!query.where) query.where = {}; if (!query.deleted) { query.where = { and: [ query.where, queryNonDeleted ] }; } return _find.call(Model, query, ...rest); }; const _count = Model.count; Model.count = function countDeleted(where = {}, ...rest) { // Because count only receives a 'where', there's nowhere to ask for the deleted entities. const whereNotDeleted = { and: [ where, queryNonDeleted ] }; return _count.call(Model, whereNotDeleted, ...rest); }; const _update = Model.update; Model.update = Model.updateAll = function updateDeleted(where = {}, ...rest) { // Because update/updateAll only receives a 'where', there's nowhere to ask for the deleted entities. const whereNotDeleted = { and: [ where, queryNonDeleted ] }; return _update.call(Model, whereNotDeleted, ...rest); }; };