/** * Interception module for creating and updating fields automatically. * That is, fields that don't have to be explicitly set on a document. * * The folowing fields are supported: * * | Type | Default field name | Description | * |--------------------|-----------------------|---------------------------------------------------------------------------| * | Created At | _createdAt | Date and time when the item was first added to the collection. | * | Created By | _createdBy | The identifier of the user that first added the item to the collection. | * | Last Modified | _lastModified | Date and time of the last update of the item. | * | Modified By | _modifiedBy | The identifier of the user that made the last update of the item. | * | Version | _version | The number of times the item was successfully updated. | * * For more details on using interceptors please see the {@tutorial interceptors} tutorial. * * @example * var entree = require("entree"), * autofields = entree.resolveInterceptor("autofields"); * * entree.posts.use(autofields.interception({ createdAt: true, version: "_myUpdateCountField" })); * * @module autofields */ "use strict"; var object = require("../utils/object"); function tryToFind(val) { var type = typeof val; if (type === "string" || type === "number") { return val; } return val._id || val.username || val.email || val.name; } function getUserIdentity(context) { if (context) { var user = tryToFind(context); if (user) { return user; } return tryToFind(context.user); } return null; } function updateAutoFields(item, context, opts, upsert) { var identity, date; if (object.validateKeys(item) === "fields") { if (opts.createdAt) { item[opts.createdAt] = date = new Date(); } if (opts.createdBy) { item[opts.createdBy] = identity = getUserIdentity(context); } if (opts.version) { item[opts.version] = 1; } if (opts.lastModified) { item[opts.lastModified] = date || new Date(); } if (opts.modifiedBy) { item[opts.modifiedBy] = identity || getUserIdentity(context); } } else { if (upsert && (opts.createdAt || opts.createdBy)) { if (!item.$setOnInsert) { item.$setOnInsert = {}; } if (opts.createdAt) { item.$setOnInsert[opts.createdAt] = date = new Date(); } if (opts.createdBy) { item.$setOnInsert[opts.createdBy] = identity = getUserIdentity(context); } } if (opts.version) { if (!item.$inc) { item.$inc = {}; } item.$inc[opts.version] = 1; } if (opts.lastModified || opts.modifiedBy) { if (!item.$set) { item.$set = {}; } if (opts.lastModified) { item.$set[opts.lastModified] = date || new Date(); } if (opts.modifiedBy) { item.$set[opts.modifiedBy] = identity || getUserIdentity(context); } } } } function wrapCursor(cursor, context, opts) { var orgFn = cursor.update; cursor.update = function (data, callback) { updateAutoFields(data, context, opts); orgFn.call(cursor, data, callback); }; return cursor; } /** * Configures and returns an interceptor function. * * Options * * - `createdAt` {(boolean|string)=} - if true, defaults to "_createdAt". * - `createdBy` {(boolean|string)=} - if true, defaults to "_createdBy". * - `lastModified` {(boolean|string)=} - if true, defaults to "_lastModified". * - `modifiedBy` {(boolean|string)=} - if true, defaults to "_modifiedBy". * - `version` {(boolean|string)=} - if true, defaults to "_version". * * If any of the options is a string, then the value is used for field name for that option. * If options are omitted, all supported fields will be configured with their default names. * * @param {object=} opts - Autofields options, defines which auto-fileds should be handled and their names. * @return {function} */ exports.interception = function (opts) { if (!opts) { opts = { createdAt : "_createdAt", createdBy : "_createdBy", lastModified : "_lastModified", modifiedBy : "_modifiedBy", version : "_version" }; } else { if (opts.createdAt && typeof opts.createdAt !== "string") { opts.createdAt = "_createdAt"; } if (opts.createdBy && typeof opts.createdBy !== "string") { opts.createdBy = "_createdBy"; } if (opts.lastModified && typeof opts.lastModified !== "string") { opts.lastModified = "_lastModified"; } if (opts.modifiedBy && typeof opts.modifiedBy !== "string") { opts.modifiedBy = "_modifiedBy"; } if (opts.version && typeof opts.version !== "string") { opts.version = "_version"; } } return function (action, context, item, next, out) { try { switch (action) { case "_insert": case "_update": updateAutoFields(item, context, opts); break; case "_upsert": updateAutoFields(item, context, opts, true); break; case "_select": if (out) { next(item, function (err, cur) { if (cur) { wrapCursor(cur, context, opts); } out(err, cur); }); } else { return wrapCursor(next(item), context, opts); } return; } next(item, out); } catch (err) { if (out) { return out(err); } throw err; } }; };