UNPKG

generator-rem-server

Version:

A generator for creating REM Servers. REM Server stands for Robert's Express & Mongo Server.

234 lines (188 loc) 7.02 kB
const Model = require('mongoose').Model; const mongooseSlugs = (schema, opts) => { opts || (opts = {}); opts.sourceField || (opts.sourceField = 'name'); opts.maxLength || (opts.maxLength = 30); opts.maxAliases || (opts.maxAliases = 30); opts.slugGenerator || (opts.slugGenerator = (val) => val .replace(/([\w\d])'([\w\d])/g, '$1$2') .replace(/([\w\d])[^\w\d]+([\w\d])/g, '$1-$2') .replace(/[^\w\d-]+/g, '') .toLowerCase()); const setSlug = function(newSlug) { if (!this.slug || this.slug === newSlug) { return newSlug; } if (this.aliasSlugs.indexOf(this.slug) === -1) { this.aliasSlugs.push(this.slug); } if (this.aliasSlugs > opts.maxAliases) { this.aliasSlugs.length = opts.maxAliases; } const i = this.aliasSlugs.indexOf(newSlug); if (i > -1) { this.aliasSlugs.splice(i, 1); } return newSlug; }; schema.add({ slug : { type: String, unique: true, maxLength: opts.maxLength, set: setSlug }, aliasSlugs: [{ type: String, default: () => [], select: false }] }); const transformQueryConditions = (conditions, transform) => { const logicalOperators = ['$or', '$and', '$nor']; for (let i = 0; i < logicalOperators.length; i += 1) { const logicalOperator = logicalOperators[i]; if (conditions[logicalOperator] === undefined) { continue; } for (let j = 0; j < conditions[logicalOperator].length; j += 1) { transformQueryConditions(conditions[logicalOperator][j], transform); } } transform(conditions); }; const addAliasSlugsQuery = function() { const conditions = this.getQuery(); let aliasSlugs; if (conditions.$aliasSlugs !== undefined) { aliasSlugs = conditions.$aliasSlugs; delete conditions.$aliasSlugs; } if (aliasSlugs === false) { return; } transformQueryConditions(this.getQuery(), (conditions) => { if (conditions.slug !== undefined) { const slugBiasedConditions = Object.assign({}, conditions); const slugAliasBiasedConditions = Object.assign({}, conditions); slugAliasBiasedConditions.aliasSlugs = slugAliasBiasedConditions.slug; delete slugAliasBiasedConditions.slug; for (const prop in conditions) { delete conditions[prop]; } conditions.$or = [ slugBiasedConditions, slugAliasBiasedConditions ]; } }); }; schema.pre('count', addAliasSlugsQuery); schema.pre('find', addAliasSlugsQuery); schema.pre('findOne', addAliasSlugsQuery); schema.pre('findOneAndRemove', addAliasSlugsQuery); schema.pre('findOneAndUpdate', addAliasSlugsQuery); schema.pre('update', addAliasSlugsQuery); schema.pre('save', function(next) { if (this.slug === undefined) { const val = this.get(opts.sourceField); if (val === undefined || val.length < 1) { this.slug = this.constructor.modelName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } else { this.slug = opts.slugGenerator(val).slice(0, opts.maxLength); } } if (!this.isModified('slug')) { return next(null); } this._suffixSlug(this.slug, (err, slug) => { if (err) { return next(err); } this._pruneAliasSlug(slug, (err) => { if (err) { return next(err); } this.slug = slug; next(null); }); }); }); schema.static('findBySlug', function(slug, cb) { return this.findOne({ $or: [{ slug }, { aliasSlugs: slug }] }, cb); }); const originalStaticUpdate = schema.statics.update || Model.update; schema.static('update', function(query, delta, opts, cb) { const newSlug = delta.slug || delta.$set && delta.$set.slug; if (newSlug === undefined && (!delta.$unset || !delta.$unset.slug)) { return originalStaticUpdate.call(this, query, delta, opts, cb); } // NOTE: Taken from Model.prototype.update. Modified accordingly. // See => https://github.com/Automattic/mongoose/blob/4.4.3/lib/query.js#L1998 if (typeof opts === 'function') { cb = opts; opts = undefined; } else if (typeof doc === 'function') { cb = delta; delta = query; query = undefined; opts = undefined; } else if (typeof query === 'function') { cb = query; query = undefined; delta = undefined; opts = undefined; } else if (typeof query === 'object' && !delta && !opts && !cb) { delta = query; query = undefined; opts = undefined; cb = undefined; } query || (query = {}); delta || (delta = {}); opts || (opts = {}); cb || (cb = function() {}); const updateDocumentSlug = (document, cb) => { document.slug = newSlug; document.save({ safe: opts.safe }, (err, _, nModified) => { if (err) { return cb(err); } return cb(null, { n: 1, nModified }); }); }; if (opts.multi === true) { return this.find(query, (err, documents) => { if (err) { return cb(err); } let ii = 0; const results = { n: 0, nModified: 0 }; const onDocumentUpdated = (err, updateResults) => { if (err) { return cb(err); } results.n += updateResults.n; results.nModified += updateResults.nModified; ii += 1; if (ii === documents.length) { cb(null, results); } }; for (let i = 0; i < documents.length; i += 1) { updateDocumentSlug(documents[i], onDocumentUpdated); } }); } const upsertDocument = (delta) => { if (delta.$set) { delta = delta.$set; } (new this(delta)).save((err, document) => { if (err) { return cb(err); } return cb(null, { n : 0, nUpserted: 1, nModified: 0, _id : document._id }); }); }; return this.findOne(query, (err, document) => { if (err) { return cb(err); } if (!document) { if (opts.upsert) { return upsertDocument(delta); } return cb(null, { n: 0, nModified: 0 }); } updateDocumentSlug(document, cb); }); }); schema.method('_suffixSlug', function(slug, cb) { this.constructor.findOne({ _id : { $ne: this._id }, slug : { $regex: `^${slug}` }, $aliasSlugs: false }).sort('-slug').select('slug').exec((err, document) => { if (err) { return cb(err); } if (!document) { return cb(null, slug); } const lastSlugSuffix = document.slug.slice(slug.length + 1); const slugSuffix = lastSlugSuffix ? parseFloat(lastSlugSuffix) + 1 : 2; cb(null, `${slug}-${slugSuffix}`); }); }); schema.method('_pruneAliasSlug', function(aliasSlug, cb) { this.constructor.update({ aliasSlugs: aliasSlug }, { $pull: { aliasSlugs: aliasSlug } }, cb); }); }; module.exports = mongooseSlugs;