diff --git a/src/db.ts b/src/db.ts index cfebbbe..5f4face 100644 --- a/src/db.ts +++ b/src/db.ts @@ -137,31 +137,34 @@ export default class db { // changelog entry, expects (req, this (from query helper)) or (req, collection, conditions, data) static log(req, thisOrCollection, conditions = null, data = null) { if (! (conditions || data)) { // (req, this) + console.log(11); data = thisOrCollection._update ? _.cloneDeep(thisOrCollection._update) : {}; // replace undefined with {} + // replace keys with a leading $ Object.keys(data).forEach(key => { if (key[0] === '$') { data[key.substr(1)] = data[key]; delete data[key]; } }); - new ChangelogModel({ + console.log(thisOrCollection._conditions); + new ChangelogModel(this.logEscape(_.cloneDeep({ action: req.method + ' ' + req.url, collection_name: thisOrCollection._collection.collectionName, conditions: thisOrCollection._conditions, data: data, user_id: req.authDetails.id ? req.authDetails.id : null - }).save(err => { + }))).save({validateBeforeSave: false}, err => { if (err) console.error(err); }); } else { // (req, collection, conditions, data) - new ChangelogModel({ + new ChangelogModel(this.logEscape(_.cloneDeep({ action: req.method + ' ' + req.url, collection_name: thisOrCollection, conditions: conditions, data: data, user_id: req.authDetails.id ? req.authDetails.id : null - }).save(err => { + }))).save(err => { if (err) console.error(err); }); } @@ -178,4 +181,17 @@ export default class db { }); return object; } + + private static logEscape(obj) { // replace MongoDB control characters in keys + if (Object(obj) === obj && Object.keys(obj).length > 0) { + Object.keys(obj).forEach(key => { + const safeKey = key.replace(/[$.]/g, ''); + obj[safeKey] = this.logEscape(obj[key]); + if (key !== safeKey) { + delete obj[key]; + } + }); + } + return obj; + } }; diff --git a/src/models/changelog.ts b/src/models/changelog.ts index b26bd16..57701a9 100644 --- a/src/models/changelog.ts +++ b/src/models/changelog.ts @@ -3,9 +3,9 @@ import mongoose from 'mongoose'; const ChangelogSchema = new mongoose.Schema({ action: String, collection_name: String, - conditions: Object, + conditions: mongoose.Schema.Types.Mixed, data: Object, user_id: mongoose.Schema.Types.ObjectId -}, {minimize: false}); +}, {minimize: false, strict: false}); export default mongoose.model>('changelog', ChangelogSchema); \ No newline at end of file diff --git a/src/routes/model.spec.ts b/src/routes/model.spec.ts index 5be66b7..88a963e 100644 --- a/src/routes/model.spec.ts +++ b/src/routes/model.spec.ts @@ -2,6 +2,8 @@ import should from 'should/as-function'; import ModelFileModel from '../models/model_file'; import TestHelper from "../test/helper"; import ModelModel from '../models/model'; +import UserModel from '../models/user'; +import mongoose from 'mongoose'; describe('/model', () => { @@ -232,6 +234,22 @@ describe('/model', () => { }); }); }); + it ('removes the model_id from all user.models', done => { + TestHelper.request(server, done, { + method: 'delete', + url: '/model/VN/Model%20A', + auth: {basic: 'admin'}, + httpStatus: 200 + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({status: 'OK'}); + UserModel.find({models: mongoose.Types.ObjectId("120000000000000000000001")}).lean().exec((err, res) => { + if (err) return done(err); + should(res).have.lengthOf(0); + done(); + }); + }); + }); it('returns 404 for an unknown group', done => { TestHelper.request(server, done, { method: 'delete', diff --git a/src/routes/model.ts b/src/routes/model.ts index c771ff9..70bf5ac 100644 --- a/src/routes/model.ts +++ b/src/routes/model.ts @@ -3,6 +3,7 @@ import bodyParser from 'body-parser'; import ModelFileModel from '../models/model_file'; import ModelModel from '../models/model'; +import UserModel from '../models/user'; import _ from 'lodash'; import ModelValidate from './validate/model'; import res400 from './validate/res400'; @@ -54,7 +55,7 @@ router.post('/model/:group', (req, res, next) => { {$push: {models: model as never}} ).log(req).lean().exec(err => { if (err) return next(err); - res.json({status: 'OK'}) + res.json({status: 'OK'}); }); } } @@ -62,7 +63,7 @@ router.post('/model/:group', (req, res, next) => { new ModelModel({group: req.params.group, models: [model]}).save((err, data) => { if (err) return next(err); db.log(req, 'models', {_id: data._id}, data.toObject()); - res.json({status: 'OK'}) + res.json({status: 'OK'}); }); } }); @@ -77,21 +78,26 @@ router.delete('/model/:group(((?!file)[^\\/]+?))/:name', (req, res, next) => { if (!data || !data.models.find(e => e.name === req.params.name)) { return res.status(404).json({status: 'Not found'}); } - if (data.models.length > 1) { // only remove model - ModelModel.findOneAndUpdate( - {group: req.params.group}, - {$pull: {models: data.models.find(e => e.name === req.params.name) as never}} - ).log(req).lean().exec(err => { - if (err) return next(err); - res.json({status: 'OK'}) - }); - } - else { // remove document - ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => { - if (err) return next(err); - res.json({status: 'OK'}) - }); - } + // delete all references in user.models + UserModel.updateMany({}, {$pull: {models: data.models.find(e => e.name === req.params.name)._id as never}}, + { multi: true }).log(req).lean().exec(err => { + if (err) return next(err); + if (data.models.length > 1) { // only remove model + ModelModel.findOneAndUpdate( + {group: req.params.group}, + {$pull: {models: data.models.find(e => e.name === req.params.name) as never}} + ).log(req).lean().exec(err => { + if (err) return next(err); + res.json({status: 'OK'}) + }); + } + else { // remove document + ModelModel.findOneAndDelete({group: req.params.group}).log(req).lean().exec(err => { + if (err) return next(err); + res.json({status: 'OK'}) + }); + } + }); }); });