salix/loopback/common/models/loggable.js

428 lines
15 KiB
JavaScript
Raw Normal View History

2021-03-22 08:08:27 +00:00
const pick = require('object.pick');
2018-10-03 07:37:34 +00:00
const LoopBackContext = require('loopback-context');
module.exports = function(Self) {
Self.setup = function() {
Self.super_.setup.call(this);
};
Self.observe('before save', async function(ctx) {
2023-01-09 12:49:35 +00:00
const models = ctx.Model.app.models;
2020-05-26 13:49:20 +00:00
const definition = ctx.Model.definition;
2023-01-09 12:49:35 +00:00
const Model = models[definition.name];
let opts = ctx.options;
if (!opts) opts = ctx.options = {};
// await txBegin(ctx);
2019-02-27 09:56:31 +00:00
// try {
// await grabUserLog(ctx, 'login');
let oldInstance;
let newInstance;
if (ctx.data) {
const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
newInstance = ctx.data;
oldInstance = changes;
if (ctx.where && !ctx.currentInstance) {
const fields = Object.keys(ctx.data);
ctx.oldInstances = await Model.find({
where: ctx.where,
fields: fields
}, opts);
}
}
// Get changes from created instance
if (ctx.isNewInstance)
newInstance = ctx.instance.__data;
ctx.hookState.oldInstance = oldInstance;
ctx.hookState.newInstance = newInstance;
// } catch (e) {
// await txEnd(ctx, true);
// }
2018-10-03 07:37:34 +00:00
});
2023-01-10 14:24:22 +00:00
Self.observe('after save', async function(ctx) {
const loopBackContext = LoopBackContext.getCurrentContext();
const models = ctx.Model.app.models;
const definition = ctx.Model.definition;
const settings = ctx.Model.definition.settings;
const relations = ctx.Model.relations;
let opts = ctx.options;
if (!opts) opts = ctx.options = {};
// try {
let primaryKey;
for (let property in definition.properties) {
if (definition.properties[property].id) {
primaryKey = property;
break;
2023-01-10 14:24:22 +00:00
}
}
if (!primaryKey) throw new Error('Primary key not found');
let originId;
2023-01-10 14:24:22 +00:00
// RELATIONS LOG
let changedModelId;
2023-01-10 14:24:22 +00:00
if (ctx.instance && !settings.log.relation) {
originId = ctx.instance.id;
changedModelId = ctx.instance.id;
} else if (settings.log.relation) {
primaryKey = relations[settings.log.relation].keyFrom;
2023-01-10 14:24:22 +00:00
if (ctx.where && ctx.where[primaryKey])
originId = ctx.where[primaryKey];
else if (ctx.instance) {
originId = ctx.instance[primaryKey];
2023-01-10 14:24:22 +00:00
changedModelId = ctx.instance.id;
}
} else {
originId = ctx.currentInstance.id;
changedModelId = ctx.currentInstance.id;
2023-01-10 14:24:22 +00:00
}
// Sets the changedModelValue to save and the instances changed in case its an updateAll
let showField = settings.log.showField;
let where;
if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) {
changedModelId = [];
where = [];
let changedInstances = await models[definition.name].find({
where: ctx.where,
fields: ['id', showField, primaryKey]
}, opts);
changedInstances.forEach(element => {
where.push(element[showField]);
changedModelId.push(element.id);
originId = element[primaryKey];
});
} else if (ctx.hookState.oldInstance)
where = ctx.instance[showField];
// Set oldInstance, newInstance, userFk and action
let oldInstance = {};
if (ctx.hookState.oldInstance)
Object.assign(oldInstance, ctx.hookState.oldInstance);
let newInstance = {};
if (ctx.hookState.newInstance)
Object.assign(newInstance, ctx.hookState.newInstance);
let userFk;
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;
const action = setActionType(ctx);
removeUnloggable(definition, oldInstance);
removeUnloggable(definition, newInstance);
oldInstance = await fkToValue(oldInstance, ctx);
newInstance = await fkToValue(newInstance, ctx);
// Prevent log with no new changes
const hasNewChanges = Object.keys(newInstance).length;
if (!hasNewChanges) return;
let logRecord = {
originFk: originId,
userFk: userFk,
action: action,
changedModel: definition.name,
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
changedModelValue: where,
oldInstance: oldInstance,
newInstance: newInstance
};
const logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx);
const logModel = settings.log.model;
await models[logModel].create(logsToSave, opts);
// await grabUserLog(ctx, 'logout');
// } catch (e) {
// await txEnd(ctx, true);
// throw e;
// }
// await txEnd(ctx);
});
2023-01-10 14:24:22 +00:00
Self.observe('before delete', async function(ctx) {
2023-01-09 12:49:35 +00:00
const models = ctx.Model.app.models;
const definition = ctx.Model.definition;
const relations = ctx.Model.relations;
2023-01-09 12:49:35 +00:00
let opts = ctx.options;
if (!opts) opts = ctx.options = {};
2019-02-27 09:56:31 +00:00
// await txBegin(ctx);
// try {
// await grabUserLog(ctx, 'login');
if (ctx.where) {
2023-01-09 12:49:35 +00:00
const affectedModel = definition.name;
let deletedInstances = await models[affectedModel].find({
where: ctx.where
2023-01-09 12:49:35 +00:00
}, opts);
2023-01-09 12:49:35 +00:00
const relation = definition.settings.log.relation;
if (relation) {
2023-01-09 12:49:35 +00:00
const primaryKey = relations[relation].keyFrom;
2021-03-22 08:08:27 +00:00
let arrangedDeletedInstances = [];
for (let i = 0; i < deletedInstances.length; i++) {
if (primaryKey)
2021-03-22 08:08:27 +00:00
deletedInstances[i].originFk = deletedInstances[i][primaryKey];
let arrangedInstance = await fkToValue(deletedInstances[i], ctx);
arrangedDeletedInstances[i] = arrangedInstance;
}
2021-03-22 08:08:27 +00:00
ctx.hookState.oldInstance = arrangedDeletedInstances;
}
2018-10-03 07:37:34 +00:00
}
// } catch (e) {
// await txEnd(ctx, true);
// }
2018-10-03 07:37:34 +00:00
});
Self.observe('after delete', async function(ctx) {
const loopBackContext = LoopBackContext.getCurrentContext();
2023-01-09 12:49:35 +00:00
const models = ctx.Model.app.models;
2020-05-26 13:49:20 +00:00
const definition = ctx.Model.definition;
2023-01-09 12:49:35 +00:00
const settings = definition.settings;
let opts = ctx.options;
if (!opts) opts = ctx.options = {};
2019-02-27 09:56:31 +00:00
// try {
if (ctx.hookState.oldInstance) {
ctx.hookState.oldInstance.forEach(async instance => {
let userFk;
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;
const changedModelValue = settings.log.changedModelValue;
const logRecord = {
originFk: instance.originFk,
userFk: userFk,
action: 'delete',
changedModel: definition.name,
changedModelId: instance.id,
changedModelValue: instance[changedModelValue],
oldInstance: instance,
newInstance: {}
};
delete instance.originFk;
const logModel = settings.log.model;
await models[logModel].create(logRecord, opts);
});
}
// await grabUserLog(ctx, 'logout');
// } catch (e) {
// await txEnd(ctx, true);
// throw e;
// }
// await txEnd(ctx);
});
2018-10-03 07:37:34 +00:00
2021-03-22 08:08:27 +00:00
// Get log values from a foreign key
async function fkToValue(instance, ctx) {
2023-01-09 12:49:35 +00:00
const models = ctx.Model.app.models;
2021-03-22 08:08:27 +00:00
const relations = ctx.Model.relations;
2023-01-09 12:49:35 +00:00
let opts = ctx.options;
if (!opts) opts = ctx.options = {};
2021-03-22 08:08:27 +00:00
const instanceCopy = JSON.parse(JSON.stringify(instance));
const result = {};
for (const key in instanceCopy) {
let value = instanceCopy[key];
2023-01-09 12:49:35 +00:00
if (value instanceof Object || value === undefined)
2021-03-22 08:08:27 +00:00
continue;
if (value) {
2023-01-09 12:49:35 +00:00
for (const relationName in relations) {
2021-03-22 08:08:27 +00:00
const relation = relations[relationName];
if (relation.keyFrom == key && key != 'id') {
const model = relation.modelTo;
const modelName = relation.modelTo.modelName;
const properties = model && model.definition.properties;
const settings = model && model.definition.settings;
2023-01-09 12:49:35 +00:00
const recordSet = await models[modelName].findById(value, null, opts);
2021-03-22 08:08:27 +00:00
const hasShowField = settings.log && settings.log.showField;
let showField = hasShowField && recordSet
&& recordSet[settings.log.showField];
if (!showField) {
const showFieldNames = [
'name',
'description',
'code',
'nickname'
];
for (field of showFieldNames) {
const propField = properties && properties[field];
const recordField = recordSet && recordSet[field];
if (propField && recordField) {
showField = field;
break;
}
}
}
if (showField && recordSet && recordSet[showField]) {
value = recordSet[showField];
break;
}
value = recordSet && recordSet.id || value;
break;
}
}
}
result[key] = value;
}
return result;
}
2019-09-12 07:49:02 +00:00
/**
* Removes unwanted properties
* @param {*} definition Model definition
* @param {*} properties Modified object properties
*/
function removeUnloggable(definition, properties) {
const objectCopy = Object.assign({}, properties);
const propList = Object.keys(objectCopy);
2019-09-12 07:49:02 +00:00
const propDefs = new Map();
for (let property in definition.properties) {
const propertyDef = definition.properties[property];
propDefs.set(property, propertyDef);
}
for (let property of propList) {
const propertyDef = propDefs.get(property);
const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$';
2019-09-12 07:49:02 +00:00
if (isPrivate || !propertyDef)
delete properties[property];
if (!propertyDef) continue;
2019-09-12 07:49:02 +00:00
2023-01-09 12:49:35 +00:00
if (propertyDef.log === false)
2019-09-12 07:49:02 +00:00
delete properties[property];
else if (propertyDef.logValue === false)
properties[property] = null;
}
}
// this function retuns all the instances changed in case this is an updateAll
function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) {
let promises = [];
if (changedInstances && typeof changedInstances == 'object') {
for (let i = 0; i < changedInstances.length; i++) {
logRecord.changedModelId = changedInstancesIds[i];
logRecord.changedModelValue = changedInstances[i];
if (ctx.oldInstances)
logRecord.oldInstance = ctx.oldInstances[i];
promises.push(JSON.parse(JSON.stringify(logRecord)));
}
} else
return logRecord;
return promises;
2018-10-03 07:37:34 +00:00
}
function setActionType(ctx) {
2021-03-22 08:08:27 +00:00
let oldInstance = ctx.hookState.oldInstance;
let newInstance = ctx.hookState.newInstance;
2018-10-03 07:37:34 +00:00
if (oldInstance && newInstance)
2018-10-03 07:37:34 +00:00
return 'update';
else if (!oldInstance && newInstance)
2018-10-03 07:37:34 +00:00
return 'insert';
return 'delete';
2018-10-03 07:37:34 +00:00
}
/**
* The functions txBegin and txEnd are used to transactionate the loggable.
* When a new transaction is created, they add a txLevel because in some cases
* the transactions are performed in a recursive way.
*
* The function grabUserLog check if the option grabUser is active in the Model and
* login or logout the user in the database depending on the action.
*
* Now they are commented out because the way to handle the errors
* that occur in the database that leave opened transactions has not been found.
* (https://redmine.verdnatura.es/issues/5036)
**/
// async function txBegin(ctx) {
// const opts = ctx.options || {};
// const models = ctx.Model.app.models;
// const definition = ctx.Model.definition;
// const Model = models[definition.name];
// if (opts.txLevel)
// opts.txLevel++;
// else if (!opts.transaction) {
// opts.txLevel = 1;
// opts.transaction = await Model.beginTransaction({});
// }
// }
// async function txEnd(ctx, undoChanges) {
// const opts = ctx.options || {};
// if (opts.txLevel) {
// opts.txLevel--;
// if (opts.txLevel === 0) {
// const tx = opts.transaction;
// delete opts.txLevel;
// delete opts.transaction;
// if (undoChanges)
// await tx.rollback();
// else
// await tx.commit();
// }
// }
// }
// async function grabUserLog(ctx, logAction) {
// const opts = ctx.options || {};
// const models = ctx.Model.app.models;
// const definition = ctx.Model.definition;
// const settings = definition.settings;
// const Model = models[definition.name];
// const hasGrabUser = settings.log && settings.log.grabUser;
// if (logAction === 'login' && hasGrabUser) {
// const loopBackContext = LoopBackContext.getCurrentContext();
// if (loopBackContext) {
// const userId = loopBackContext.active.accessToken.userId;
// const user = await models.Account.findById(userId, {fields: ['name']}, opts);
// await Model.rawSql(`CALL account.myUser_loginWithName(?)`, [user.name], opts);
// }
// } else if (logAction === 'logout' && hasGrabUser)
// await Model.rawSql(`CALL account.myUser_logout()`, null, opts);
// }
2018-10-03 07:37:34 +00:00
};