forms-angular 0.12.0-beta.18 → 0.12.0-beta.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,57 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ var __generator = (this && this.__generator) || function (thisArg, body) {
27
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
28
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
29
+ function verb(n) { return function (v) { return step([n, v]); }; }
30
+ function step(op) {
31
+ if (f) throw new TypeError("Generator is already executing.");
32
+ while (_) try {
33
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
34
+ if (y = 0, t) op = [op[0] & 2, t.value];
35
+ switch (op[0]) {
36
+ case 0: case 1: t = op; break;
37
+ case 4: _.label++; return { value: op[1], done: false };
38
+ case 5: _.label++; y = op[1]; op = [0]; continue;
39
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
40
+ default:
41
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
42
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
43
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
44
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
45
+ if (t[2]) _.ops.pop();
46
+ _.trys.pop(); continue;
47
+ }
48
+ op = body.call(thisArg, _);
49
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
50
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
51
+ }
52
+ };
2
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.FormsAngular = void 0;
3
55
  // This part of forms-angular borrows _very_ heavily from https://github.com/Alexandre-Strzelewicz/angular-bridge
4
56
  // (now https://github.com/Unitech/angular-bridge
5
57
  var _ = require('lodash');
@@ -25,241 +77,304 @@ function processArgs(options, array) {
25
77
  array[0] = options.urlPrefix + array[0];
26
78
  return array;
27
79
  }
28
- var DataForm = function (mongoose, app, options) {
29
- this.app = app;
30
- app.locals.formsAngular = app.locals.formsAngular || [];
31
- app.locals.formsAngular.push(this);
32
- this.mongoose = mongoose;
33
- mongoose.set('debug', debug);
34
- mongoose.Promise = global.Promise;
35
- this.options = _.extend({
36
- urlPrefix: '/api/'
37
- }, options || {});
38
- this.resources = [];
39
- this.searchFunc = async.forEach;
40
- this.registerRoutes();
41
- this.app.get.apply(this.app, processArgs(this.options, ['search', this.searchAll()]));
42
- for (var pluginName in this.options.plugins) {
43
- var pluginObj = this.options.plugins[pluginName];
44
- this[pluginName] = pluginObj.plugin(this, processArgs, pluginObj.options);
45
- }
46
- };
47
- module.exports = exports = DataForm;
48
- DataForm.prototype.extractTimestampFromMongoID = function (record) {
49
- var timestamp = record.toString().substring(0, 8);
50
- return new Date(parseInt(timestamp, 16) * 1000);
51
- };
52
- DataForm.prototype.getListFields = function (resource, doc, cb) {
53
- function getFirstMatchingField(keyList, type) {
54
- for (var i = 0; i < keyList.length; i++) {
55
- var fieldDetails = resource.model.schema['tree'][keyList[i]];
56
- if (fieldDetails.type && (!type || fieldDetails.type.name === type) && keyList[i] !== '_id') {
57
- resource.options.listFields = [{ field: keyList[i] }];
58
- return doc[keyList[i]];
80
+ var FormsAngular = /** @class */ (function () {
81
+ function FormsAngular(mongoose, app, options) {
82
+ this.mongoose = mongoose;
83
+ this.app = app;
84
+ app.locals.formsAngular = app.locals.formsAngular || [];
85
+ app.locals.formsAngular.push(this);
86
+ mongoose.set('debug', debug);
87
+ mongoose.Promise = global.Promise;
88
+ this.options = _.extend({
89
+ urlPrefix: '/api/'
90
+ }, options || {});
91
+ this.resources = [];
92
+ this.searchFunc = async.forEach;
93
+ var search = 'search/', schema = 'schema/', report = 'report/', resourceName = ':resourceName', id = '/:id';
94
+ this.app.get.apply(this.app, processArgs(this.options, ['models', this.models()]));
95
+ this.app.get.apply(this.app, processArgs(this.options, [search + resourceName, this.search()]));
96
+ this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName, this.schema()]));
97
+ this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName + '/:formName', this.schema()]));
98
+ this.app.get.apply(this.app, processArgs(this.options, [report + resourceName, this.report()]));
99
+ this.app.get.apply(this.app, processArgs(this.options, [report + resourceName + '/:reportName', this.report()]));
100
+ this.app.get.apply(this.app, processArgs(this.options, [resourceName, this.collectionGet()]));
101
+ this.app.post.apply(this.app, processArgs(this.options, [resourceName, this.collectionPost()]));
102
+ this.app.get.apply(this.app, processArgs(this.options, [resourceName + id, this.entityGet()]));
103
+ this.app.post.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()])); // You can POST or PUT to update data
104
+ this.app.put.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()]));
105
+ this.app.delete.apply(this.app, processArgs(this.options, [resourceName + id, this.entityDelete()]));
106
+ // return the List attributes for a record - used by select2
107
+ this.app.get.apply(this.app, processArgs(this.options, [resourceName + id + '/list', this.entityList()]));
108
+ this.app.get.apply(this.app, processArgs(this.options, ['search', this.searchAll()]));
109
+ for (var pluginName in this.options.plugins) {
110
+ if (this.options.plugins.hasOwnProperty(pluginName)) {
111
+ var pluginObj = this.options.plugins[pluginName];
112
+ this[pluginName] = pluginObj.plugin(this, processArgs, pluginObj.options);
59
113
  }
60
114
  }
61
115
  }
62
- var that = this;
63
- var display = '';
64
- var listFields = resource.options.listFields;
65
- if (listFields) {
66
- async.map(listFields, function (aField, cbm) {
67
- if (typeof doc[aField.field] !== 'undefined') {
68
- if (aField.params) {
69
- if (aField.params.ref) {
70
- var lookupResource_1 = that.getResource(resource.model.schema['paths'][aField.field].options.ref);
71
- if (lookupResource_1) {
72
- var hiddenFields = that.generateHiddenFields(lookupResource_1, false);
73
- hiddenFields.__v = 0;
74
- lookupResource_1.model.findOne({ _id: doc[aField.field] }).select(hiddenFields).exec(function (err, doc2) {
75
- if (err) {
76
- cbm(err);
77
- }
78
- else {
79
- that.getListFields(lookupResource_1, doc2, cbm);
116
+ FormsAngular.prototype.getListFields = function (resource, doc, cb) {
117
+ function getFirstMatchingField(keyList, type) {
118
+ for (var i = 0; i < keyList.length; i++) {
119
+ var fieldDetails = resource.model.schema['tree'][keyList[i]];
120
+ if (fieldDetails.type && (!type || fieldDetails.type.name === type) && keyList[i] !== '_id') {
121
+ resource.options.listFields = [{ field: keyList[i] }];
122
+ return doc[keyList[i]];
123
+ }
124
+ }
125
+ }
126
+ var that = this;
127
+ var display = '';
128
+ var listFields = resource.options.listFields;
129
+ if (listFields) {
130
+ async.map(listFields, function (aField, cbm) {
131
+ if (typeof doc[aField.field] !== 'undefined') {
132
+ if (aField.params) {
133
+ if (aField.params.ref) {
134
+ var fieldOptions = resource.model.schema['paths'][aField.field].options;
135
+ if (typeof fieldOptions.ref === 'string') {
136
+ var lookupResource_1 = that.getResource(fieldOptions.ref);
137
+ if (lookupResource_1) {
138
+ var hiddenFields = that.generateHiddenFields(lookupResource_1, false);
139
+ hiddenFields.__v = false;
140
+ lookupResource_1.model.findOne({ _id: doc[aField.field] }).select(hiddenFields).exec(function (err, doc2) {
141
+ if (err) {
142
+ cbm(err);
143
+ }
144
+ else {
145
+ that.getListFields(lookupResource_1, doc2, cbm);
146
+ }
147
+ });
80
148
  }
81
- });
149
+ }
150
+ else {
151
+ throw new Error('No support for ref type ' + aField.params.ref.type);
152
+ }
153
+ }
154
+ else if (aField.params.params === 'timestamp') {
155
+ var date = that.extractTimestampFromMongoID(doc[aField.field]);
156
+ cbm(null, date.toLocaleDateString() + ' ' + date.toLocaleTimeString());
82
157
  }
83
158
  }
84
- else if (aField.params.params === 'timestamp') {
85
- var date = this.extractTimestampFromMongoID(doc[aField.field]);
86
- cbm(null, date.toLocaleDateString() + ' ' + date.toLocaleTimeString());
159
+ else {
160
+ cbm(null, doc[aField.field]);
87
161
  }
88
162
  }
89
163
  else {
90
- cbm(null, doc[aField.field]);
164
+ cbm(null, '');
91
165
  }
92
- }
93
- else {
94
- cbm(null, '');
95
- }
96
- }, function (err, results) {
97
- if (err) {
98
- cb(err);
99
- }
100
- else {
101
- if (results) {
102
- cb(err, results.join(' ').trim());
166
+ }, function (err, results) {
167
+ if (err) {
168
+ cb(err);
103
169
  }
104
170
  else {
105
- console.log('No results ' + listFields);
171
+ if (results) {
172
+ cb(err, results.join(' ').trim());
173
+ }
174
+ else {
175
+ console.log('No results ' + listFields);
176
+ }
177
+ }
178
+ });
179
+ }
180
+ else {
181
+ var keyList = Object.keys(resource.model.schema['tree']);
182
+ // No list field specified - use the first String field,
183
+ display = getFirstMatchingField(keyList, 'String') ||
184
+ // and if there aren't any then just take the first field
185
+ getFirstMatchingField(keyList);
186
+ cb(null, display.trim());
187
+ }
188
+ };
189
+ ;
190
+ FormsAngular.prototype.newResource = function (model, options) {
191
+ options = options || {};
192
+ options.suppressDeprecatedMessage = true;
193
+ var passModel = model;
194
+ if (typeof model !== 'function') {
195
+ passModel = model.model;
196
+ }
197
+ this.addResource(passModel.modelName, passModel, options);
198
+ };
199
+ ;
200
+ // Add a resource, specifying the model and any options.
201
+ // Models may include their own options, which means they can be passed through from the model file
202
+ FormsAngular.prototype.addResource = function (resourceName, model, options) {
203
+ var resource = {
204
+ resourceName: resourceName,
205
+ options: options || {}
206
+ };
207
+ if (!resource.options.suppressDeprecatedMessage) {
208
+ console.log('addResource is deprecated - see https://github.com/forms-angular/forms-angular/issues/39');
209
+ }
210
+ if (typeof model === 'function') {
211
+ resource.model = model;
212
+ }
213
+ else {
214
+ resource.model = model.model;
215
+ for (var prop in model) {
216
+ if (model.hasOwnProperty(prop) && prop !== 'model') {
217
+ resource.options[prop] = model[prop];
106
218
  }
107
219
  }
220
+ }
221
+ extend(resource.options, this.preprocess(resource, resource.model.schema['paths'], null));
222
+ if (resource.options.searchImportance) {
223
+ this.searchFunc = async.forEachSeries;
224
+ }
225
+ if (this.searchFunc === async.forEachSeries) {
226
+ this.resources.splice(_.sortedIndexBy(this.resources, resource, function (obj) {
227
+ return obj.options.searchImportance || 99;
228
+ }), 0, resource);
229
+ }
230
+ else {
231
+ this.resources.push(resource);
232
+ }
233
+ };
234
+ ;
235
+ FormsAngular.prototype.getResource = function (name) {
236
+ return _.find(this.resources, function (resource) {
237
+ return resource.resourceName === name;
108
238
  });
109
- }
110
- else {
111
- var keyList = Object.keys(resource.model.schema['tree']);
112
- // No list field specified - use the first String field,
113
- display = getFirstMatchingField(keyList, 'String') ||
114
- // and if there aren't any then just take the first field
115
- getFirstMatchingField(keyList);
116
- cb(null, display.trim());
117
- }
118
- };
119
- /**
120
- * Registers all REST routes with the provided `app` object.
121
- */
122
- DataForm.prototype.registerRoutes = function () {
123
- var search = 'search/', schema = 'schema/', report = 'report/', resourceName = ':resourceName', id = '/:id';
124
- this.app.get.apply(this.app, processArgs(this.options, ['models', this.models()]));
125
- this.app.get.apply(this.app, processArgs(this.options, [search + resourceName, this.search()]));
126
- this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName, this.schema()]));
127
- this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName + '/:formName', this.schema()]));
128
- this.app.get.apply(this.app, processArgs(this.options, [report + resourceName, this.report()]));
129
- this.app.get.apply(this.app, processArgs(this.options, [report + resourceName + '/:reportName', this.report()]));
130
- this.app.get.apply(this.app, processArgs(this.options, [resourceName, this.collectionGet()]));
131
- this.app.post.apply(this.app, processArgs(this.options, [resourceName, this.collectionPost()]));
132
- this.app.get.apply(this.app, processArgs(this.options, [resourceName + id, this.entityGet()]));
133
- this.app.post.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()])); // You can POST or PUT to update data
134
- this.app.put.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()]));
135
- this.app.delete.apply(this.app, processArgs(this.options, [resourceName + id, this.entityDelete()]));
136
- // return the List attributes for a record - used by select2
137
- this.app.get.apply(this.app, processArgs(this.options, [resourceName + id + '/list', this.entityList()]));
138
- };
139
- DataForm.prototype.newResource = function (model, options) {
140
- options = options || {};
141
- options.suppressDeprecatedMessage = true;
142
- var passModel = model;
143
- if (typeof model !== 'function') {
144
- passModel = model.model;
145
- }
146
- this.addResource(passModel.modelName, passModel, options);
147
- };
148
- // Add a resource, specifying the model and any options.
149
- // Models may include their own options, which means they can be passed through from the model file
150
- DataForm.prototype.addResource = function (resourceName, model, options) {
151
- var resource = {
152
- resourceName: resourceName,
153
- options: options || {}
154
239
  };
155
- if (!resource.options.suppressDeprecatedMessage) {
156
- console.log('addResource is deprecated - see https://github.com/forms-angular/forms-angular/issues/39');
157
- }
158
- if (typeof model === 'function') {
159
- resource.model = model;
160
- }
161
- else {
162
- resource.model = model.model;
163
- for (var prop in model) {
164
- if (model.hasOwnProperty(prop) && prop !== 'model') {
165
- resource.options[prop] = model[prop];
240
+ ;
241
+ FormsAngular.prototype.getResourceFromCollection = function (name) {
242
+ return _.find(this.resources, function (resource) {
243
+ return resource.model.collection.collectionName === name;
244
+ });
245
+ };
246
+ ;
247
+ FormsAngular.prototype.internalSearch = function (req, resourcesToSearch, includeResourceInResults, limit, callback) {
248
+ var _a, _b, _c, _d;
249
+ if (typeof req.query === 'undefined') {
250
+ req.query = {};
251
+ }
252
+ var timestamps = { sentAt: req.query.sentAt, startedAt: new Date().valueOf(), completedAt: undefined };
253
+ var searches = [], resourceCount = resourcesToSearch.length, searchFor = req.query.q || '', filter = req.query.f;
254
+ function translate(string, array, context) {
255
+ if (array) {
256
+ var translation = _.find(array, function (fromTo) {
257
+ return fromTo.from === string && (!fromTo.context || fromTo.context === context);
258
+ });
259
+ if (translation) {
260
+ string = translation.to;
261
+ }
166
262
  }
263
+ return string;
167
264
  }
168
- }
169
- extend(resource.options, this.preprocess(resource, resource.model.schema['paths'], null));
170
- if (resource.options.searchImportance) {
171
- this.searchFunc = async.forEachSeries;
172
- }
173
- if (this.searchFunc === async.forEachSeries) {
174
- this.resources.splice(_.sortedIndexBy(this.resources, resource, function (obj) {
175
- return obj.options.searchImportance || 99;
176
- }), 0, resource);
177
- }
178
- else {
179
- this.resources.push(resource);
180
- }
181
- };
182
- DataForm.prototype.getResource = function (name) {
183
- return _.find(this.resources, function (resource) {
184
- return resource.resourceName === name;
185
- });
186
- };
187
- DataForm.prototype.getResourceFromCollection = function (name) {
188
- return _.find(this.resources, function (resource) {
189
- return resource.model.collection.collectionName === name;
190
- });
191
- };
192
- DataForm.prototype.internalSearch = function (req, resourcesToSearch, includeResourceInResults, limit, callback) {
193
- if (typeof req.query === 'undefined') {
194
- req.query = {};
195
- }
196
- var searches = [], resourceCount = resourcesToSearch.length, searchFor = req.query.q || '', filter = req.query.f;
197
- function translate(string, array, context) {
198
- if (array) {
199
- var translation = _.find(array, function (fromTo) {
200
- return fromTo.from === string && (!fromTo.context || fromTo.context === context);
201
- });
202
- if (translation) {
203
- string = translation.to;
265
+ // return a string that determines the sort order of the resultObject
266
+ function calcResultValue(obj) {
267
+ function padLeft(score, reqLength, str) {
268
+ if (str === void 0) { str = '0'; }
269
+ return new Array(1 + reqLength - String(score).length).join(str) + score;
204
270
  }
271
+ var sortString = '';
272
+ sortString += padLeft(obj.addHits || 9, 1);
273
+ sortString += padLeft(obj.searchImportance || 99, 2);
274
+ sortString += padLeft(obj.weighting || 9999, 4);
275
+ sortString += obj.text;
276
+ return sortString;
205
277
  }
206
- return string;
207
- }
208
- // return a string that determines the sort order of the resultObject
209
- function calcResultValue(obj) {
210
- function padLeft(score, reqLength, str) {
211
- if (str === void 0) { str = '0'; }
212
- return new Array(1 + reqLength - String(score).length).join(str) + score;
278
+ if (filter) {
279
+ filter = JSON.parse(filter);
213
280
  }
214
- var sortString = '';
215
- sortString += padLeft(obj.addHits || 9, 1);
216
- sortString += padLeft(obj.searchImportance || 99, 2);
217
- sortString += padLeft(obj.weighting || 9999, 4);
218
- sortString += obj.text;
219
- return sortString;
220
- }
221
- if (filter) {
222
- filter = JSON.parse(filter);
223
- }
224
- for (var i = 0; i < resourceCount; i++) {
225
- var resource = resourcesToSearch[i];
226
- if (resourceCount === 1 || resource.options.searchImportance !== false) {
227
- var schema = resource.model.schema;
228
- var indexedFields = [];
229
- for (var j = 0; j < schema._indexes.length; j++) {
230
- var attributes = schema._indexes[j][0];
231
- var field = Object.keys(attributes)[0];
232
- if (indexedFields.indexOf(field) === -1) {
233
- indexedFields.push(field);
281
+ // See if we are narrowing down the resources
282
+ var collectionName;
283
+ var collectionNameLower;
284
+ var colonPos = searchFor.indexOf(':');
285
+ switch (colonPos) {
286
+ case -1:
287
+ // Original behaviour = do nothing different
288
+ break;
289
+ case 0:
290
+ // "Special search" - yet to be implemented
291
+ break;
292
+ default:
293
+ collectionName = searchFor.slice(0, colonPos);
294
+ collectionNameLower = collectionName.toLowerCase();
295
+ searchFor = searchFor.slice(colonPos + 1, 999);
296
+ if (searchFor === '') {
297
+ searchFor = '?';
234
298
  }
235
- }
236
- for (var path in schema.paths) {
237
- if (path !== '_id' && schema.paths.hasOwnProperty(path)) {
238
- if (schema.paths[path]._index && !schema.paths[path].options.noSearch) {
239
- if (indexedFields.indexOf(path) === -1) {
240
- indexedFields.push(path);
299
+ break;
300
+ }
301
+ for (var i = 0; i < resourceCount; i++) {
302
+ var resource = resourcesToSearch[i];
303
+ if (resourceCount === 1 || (resource.options.searchImportance !== false && (!collectionName || collectionName === resource.resourceName || ((_b = (_a = resource.options) === null || _a === void 0 ? void 0 : _a.synonyms) === null || _b === void 0 ? void 0 : _b.find(function (s) { var _a; return ((_a = s.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === collectionNameLower; }))))) {
304
+ var schema = resource.model.schema;
305
+ var indexedFields = [];
306
+ for (var j = 0; j < schema._indexes.length; j++) {
307
+ var attributes = schema._indexes[j][0];
308
+ var field = Object.keys(attributes)[0];
309
+ if (indexedFields.indexOf(field) === -1) {
310
+ indexedFields.push(field);
311
+ }
312
+ }
313
+ for (var path in schema.paths) {
314
+ if (path !== '_id' && schema.paths.hasOwnProperty(path)) {
315
+ if (schema.paths[path]._index && !schema.paths[path].options.noSearch) {
316
+ if (indexedFields.indexOf(path) === -1) {
317
+ indexedFields.push(path);
318
+ }
241
319
  }
242
320
  }
243
321
  }
322
+ if (indexedFields.length === 0) {
323
+ console.log('ERROR: Searching on a collection with no indexes ' + resource.resourceName);
324
+ }
325
+ var synonymObj = (_d = (_c = resource.options) === null || _c === void 0 ? void 0 : _c.synonyms) === null || _d === void 0 ? void 0 : _d.find(function (s) { return s.name.toLowerCase() === collectionNameLower; });
326
+ var synonymFilter = synonymObj === null || synonymObj === void 0 ? void 0 : synonymObj.filter;
327
+ for (var m = 0; m < indexedFields.length; m++) {
328
+ var searchObj = { resource: resource, field: indexedFields[m] };
329
+ if (synonymFilter) {
330
+ searchObj.filter = synonymFilter;
331
+ }
332
+ searches.push(searchObj);
333
+ }
244
334
  }
245
- if (indexedFields.length === 0) {
246
- console.log('ERROR: Searching on a collection with no indexes ' + resource.resourceName);
335
+ }
336
+ var that = this;
337
+ var results = [];
338
+ var moreCount = 0;
339
+ var searchCriteria;
340
+ var searchStrings;
341
+ var multiMatchPossible = false;
342
+ if (searchFor === '?') {
343
+ // interpret this as a wildcard (so there is no way to search for ?
344
+ searchCriteria = null;
345
+ }
346
+ else {
347
+ // Support for searching anywhere in a field by starting with *
348
+ var startAnchor = '^';
349
+ if (searchFor.slice(0, 1) === '*') {
350
+ startAnchor = '';
351
+ searchFor = searchFor.slice(1);
247
352
  }
248
- for (var m = 0; m < indexedFields.length; m++) {
249
- searches.push({ resource: resource, field: indexedFields[m] });
353
+ // THe snippet to escape the special characters comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
354
+ searchFor = searchFor.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
355
+ multiMatchPossible = searchFor.includes(' ');
356
+ if (multiMatchPossible) {
357
+ searchStrings = searchFor.split(' ');
250
358
  }
359
+ var modifiedSearchStr = multiMatchPossible ? searchStrings.join('|') : searchFor;
360
+ searchFor = searchFor.toLowerCase(); // For later case-insensitive comparison
361
+ // Removed the logic that preserved spaces when collection was specified because Louise asked me to.
362
+ searchCriteria = { $regex: "".concat(startAnchor, "(").concat(modifiedSearchStr, ")"), $options: 'i' };
251
363
  }
252
- }
253
- var that = this;
254
- var results = [], moreCount = 0, searchCriteria, multiMatchPossible = searchFor.includes(' '), modifiedSearchStr = multiMatchPossible ? searchFor.split(' ').join('|') : searchFor;
255
- // Removed the logic that preserved spaces when collection was specified because Louise asked me to.
256
- searchCriteria = { $regex: '^(' + modifiedSearchStr + ')', $options: 'i' };
257
- var handleSearchResultsFromIndex = function (err, docs, item, cb) {
258
- if (!err && docs && docs.length > 0) {
259
- async.map(docs, function (aDoc, cbdoc) {
260
- // Do we already have them in the list?
364
+ var handleSearchResultsFromIndex = function (err, docs, item, cb) {
365
+ function handleSingleSearchResult(aDoc, cbdoc) {
261
366
  var thisId = aDoc._id.toString(), resultObject, resultPos;
262
367
  function handleResultsInList() {
368
+ if (multiMatchPossible) {
369
+ resultObject.matched = resultObject.matched || [];
370
+ // record the index of string that matched, so we don't count it against another field
371
+ for (var i = 0; i < searchStrings.length; i++) {
372
+ if (aDoc[item.field].toLowerCase().indexOf(searchStrings[i]) === 0) {
373
+ resultObject.matched.push(i);
374
+ break;
375
+ }
376
+ }
377
+ }
263
378
  resultObject.searchImportance = item.resource.options.searchImportance || 99;
264
379
  if (item.resource.options.localisationData) {
265
380
  resultObject.resource = translate(resultObject.resource, item.resource.options.localisationData, "resource");
@@ -269,32 +384,48 @@ DataForm.prototype.internalSearch = function (req, resourcesToSearch, includeRes
269
384
  results.splice(_.sortedIndexBy(results, resultObject, calcResultValue), 0, resultObject);
270
385
  cbdoc(null);
271
386
  }
387
+ // Do we already have them in the list?
272
388
  for (resultPos = results.length - 1; resultPos >= 0; resultPos--) {
273
389
  if (results[resultPos].id.toString() === thisId) {
274
390
  break;
275
391
  }
276
392
  }
277
393
  if (resultPos >= 0) {
278
- resultObject = {};
279
- extend(resultObject, results[resultPos]);
394
+ resultObject = Object.assign({}, results[resultPos]);
280
395
  // If they have already matched then improve their weighting
281
- // TODO: if the search string is B F currently Benjamin Barker scores same as Benjamin Franklin)
282
396
  if (multiMatchPossible) {
283
- resultObject.addHits = Math.max((resultObject.addHits || 9) - 1, 1);
397
+ // record the index of string that matched, so we don't count it against another field
398
+ for (var i = 0; i < searchStrings.length; i++) {
399
+ if (!resultObject.matched.includes(i) && aDoc[item.field].toLowerCase().indexOf(searchStrings[i]) === 0) {
400
+ resultObject.matched.push(i);
401
+ resultObject.addHits = Math.max((resultObject.addHits || 9) - 1, 0);
402
+ // remove it from current position
403
+ results.splice(resultPos, 1);
404
+ // and re-insert where appropriate
405
+ results.splice(_.sortedIndexBy(results, resultObject, calcResultValue), 0, resultObject);
406
+ break;
407
+ }
408
+ }
284
409
  }
285
- // remove it from current position
286
- results.splice(resultPos, 1);
287
- // and re-insert where appropriate
288
- results.splice(_.sortedIndexBy(results, resultObject, calcResultValue), 0, resultObject);
289
410
  cbdoc(null);
290
411
  }
291
412
  else {
292
413
  // Otherwise add them new...
414
+ var addHits_1;
415
+ if (multiMatchPossible)
416
+ // If they match the whole search phrase in one index they get smaller addHits (so they sort higher)
417
+ if (aDoc[item.field].toLowerCase().indexOf(searchFor) === 0) {
418
+ addHits_1 = 7;
419
+ }
293
420
  // Use special listings format if defined
294
421
  var specialListingFormat = item.resource.options.searchResultFormat;
295
422
  if (specialListingFormat) {
296
- resultObject = specialListingFormat.apply(aDoc);
297
- handleResultsInList();
423
+ specialListingFormat.apply(aDoc, [req])
424
+ .then(function (resultObj) {
425
+ resultObject = resultObj;
426
+ resultObject.addHits = addHits_1;
427
+ handleResultsInList();
428
+ });
298
429
  }
299
430
  else {
300
431
  that.getListFields(item.resource, aDoc, function (err, description) {
@@ -305,6 +436,7 @@ DataForm.prototype.internalSearch = function (req, resourcesToSearch, includeRes
305
436
  resultObject = {
306
437
  id: aDoc._id,
307
438
  weighting: 9999,
439
+ addHits: addHits_1,
308
440
  text: description
309
441
  };
310
442
  if (resourceCount > 1 || includeResourceInResults) {
@@ -315,772 +447,833 @@ DataForm.prototype.internalSearch = function (req, resourcesToSearch, includeRes
315
447
  });
316
448
  }
317
449
  }
318
- }, function (err) {
319
- cb(err);
320
- });
321
- }
322
- else {
323
- cb(err);
324
- }
325
- };
326
- this.searchFunc(searches, function (item, cb) {
327
- var searchDoc = {};
328
- if (filter) {
329
- that.hackVariables(filter);
330
- extend(searchDoc, filter);
331
- if (filter[item.field]) {
332
- delete searchDoc[item.field];
333
- var obj1 = {}, obj2 = {};
334
- obj1[item.field] = filter[item.field];
335
- obj2[item.field] = searchCriteria;
336
- searchDoc['$and'] = [obj1, obj2];
450
+ }
451
+ if (!err && docs && docs.length > 0) {
452
+ async.map(docs, handleSingleSearchResult, cb);
337
453
  }
338
454
  else {
339
- searchDoc[item.field] = searchCriteria;
455
+ cb(err);
340
456
  }
341
- }
342
- else {
343
- searchDoc[item.field] = searchCriteria;
344
- }
345
- // The +60 in the next line is an arbitrary safety zone for situations where items that match the string
346
- // in more than one index get filtered out.
347
- // TODO : Figure out a better way to deal with this
348
- if (item.resource.options.searchFunc) {
349
- item.resource.options.searchFunc(item.resource, req, null, searchDoc, item.resource.options.searchOrder, limit + 60, null, function (err, docs) {
350
- handleSearchResultsFromIndex(err, docs, item, cb);
351
- });
352
- }
353
- else {
354
- that.filteredFind(item.resource, req, null, searchDoc, item.resource.options.searchOrder, limit + 60, null, function (err, docs) {
355
- handleSearchResultsFromIndex(err, docs, item, cb);
356
- });
357
- }
358
- }, function () {
359
- // Strip weighting from the results
360
- results = _.map(results, function (aResult) {
361
- delete aResult.weighting;
362
- return aResult;
363
- });
364
- if (results.length > limit) {
365
- moreCount += results.length - limit;
366
- results.splice(limit);
367
- }
368
- callback({ results: results, moreCount: moreCount });
369
- });
370
- };
371
- DataForm.prototype.search = function () {
372
- return _.bind(function (req, res, next) {
373
- if (!(req.resource = this.getResource(req.params.resourceName))) {
374
- return next();
375
- }
376
- this.internalSearch(req, [req.resource], false, 10, function (resultsObject) {
377
- res.send(resultsObject);
378
- });
379
- }, this);
380
- };
381
- DataForm.prototype.searchAll = function () {
382
- return _.bind(function (req, res) {
383
- this.internalSearch(req, this.resources, true, 10, function (resultsObject) {
384
- res.send(resultsObject);
385
- });
386
- }, this);
387
- };
388
- DataForm.prototype.models = function () {
389
- var that = this;
390
- return function (req, res) {
391
- // TODO: Make this less wasteful - we only need to send the resourceNames of the resources
392
- // Check for optional modelFilter and call it with the request and current list. Otherwise just return the list.
393
- res.send(that.options.modelFilter ? that.options.modelFilter.call(null, req, that.resources) : that.resources);
394
- };
395
- };
396
- DataForm.prototype.renderError = function (err, redirectUrl, req, res) {
397
- if (typeof err === 'string') {
398
- res.send(err);
399
- }
400
- else {
401
- res.send(err.message);
402
- }
403
- };
404
- DataForm.prototype.redirect = function (address, req, res) {
405
- res.send(address);
406
- };
407
- DataForm.prototype.applySchemaSubset = function (vanilla, schema) {
408
- var outPath;
409
- if (schema) {
410
- outPath = {};
411
- for (var fld in schema) {
412
- if (schema.hasOwnProperty(fld)) {
413
- if (!vanilla[fld]) {
414
- throw new Error('No such field as ' + fld + '. Is it part of a sub-doc? If so you need the bit before the period.');
415
- }
416
- outPath[fld] = vanilla[fld];
417
- if (vanilla[fld].schema) {
418
- outPath[fld].schema = this.applySchemaSubset(outPath[fld].schema, schema[fld].schema);
457
+ };
458
+ this.searchFunc(searches, function (item, cb) {
459
+ var searchDoc = {};
460
+ var searchFilter = filter || item.filter;
461
+ if (searchFilter) {
462
+ that.hackVariables(searchFilter);
463
+ extend(searchDoc, searchFilter);
464
+ if (searchFilter[item.field]) {
465
+ delete searchDoc[item.field];
466
+ var obj1 = {}, obj2 = {};
467
+ obj1[item.field] = searchFilter[item.field];
468
+ obj2[item.field] = searchCriteria;
469
+ searchDoc['$and'] = [obj1, obj2];
419
470
  }
420
- outPath[fld].options = outPath[fld].options || {};
421
- for (var override in schema[fld]) {
422
- if (schema[fld].hasOwnProperty(override)) {
423
- if (!outPath[fld].options.form) {
424
- outPath[fld].options.form = {};
425
- }
426
- outPath[fld].options.form[override] = schema[fld][override];
471
+ else {
472
+ if (searchCriteria) {
473
+ searchDoc[item.field] = searchCriteria;
427
474
  }
428
475
  }
429
476
  }
430
- }
431
- }
432
- else {
433
- outPath = vanilla;
434
- }
435
- return outPath;
436
- };
437
- DataForm.prototype.preprocess = function (resource, paths, formSchema) {
438
- var outPath = {}, hiddenFields = [], listFields = [];
439
- if (resource && resource.options && resource.options.idIsList) {
440
- paths['_id'].options = paths['_id'].options || {};
441
- paths['_id'].options.list = resource.options.idIsList;
442
- }
443
- for (var element in paths) {
444
- if (paths.hasOwnProperty(element) && element !== '__v') {
445
- // check for schemas
446
- if (paths[element].schema) {
447
- var subSchemaInfo = this.preprocess(null, paths[element].schema.paths);
448
- outPath[element] = { schema: subSchemaInfo.paths };
449
- if (paths[element].options.form) {
450
- outPath[element].options = { form: extend(true, {}, paths[element].options.form) };
477
+ else {
478
+ if (searchCriteria) {
479
+ searchDoc[item.field] = searchCriteria;
451
480
  }
452
481
  }
482
+ /*
483
+ The +200 below line is an (imperfect) arbitrary safety zone for situations where items that match the string in more than one index get filtered out.
484
+ An example where it fails is searching for "e c" which fails to get a old record Emily Carpenter in a big dataset sorted by date last accessed as they
485
+ are not returned within the first 200 in forenames so don't get the additional hit score and languish outside the visible results, though those visible
486
+ results end up containing people who only match either c or e (but have been accessed much more recently).
487
+
488
+ Increasing the number would be a short term fix at the cost of slowing down the search.
489
+ */
490
+ // TODO : Figure out a better way to deal with this
491
+ if (item.resource.options.searchFunc) {
492
+ item.resource.options.searchFunc(item.resource, req, null, searchDoc, item.resource.options.searchOrder, limit ? limit + 200 : 0, null, function (err, docs) {
493
+ handleSearchResultsFromIndex(err, docs, item, cb);
494
+ });
495
+ }
453
496
  else {
454
- // check for arrays
455
- var realType = paths[element].caster ? paths[element].caster : paths[element];
456
- if (!realType.instance) {
457
- if (realType.options.type) {
458
- var type = realType.options.type(), typeType = typeof type;
459
- if (typeType === 'string') {
460
- realType.instance = (!isNaN(Date.parse(type))) ? 'Date' : 'String';
461
- }
462
- else {
463
- realType.instance = typeType;
464
- }
465
- }
466
- }
467
- outPath[element] = extend(true, {}, paths[element]);
468
- if (paths[element].options.secure) {
469
- hiddenFields.push(element);
470
- }
471
- if (paths[element].options.match) {
472
- outPath[element].options.match = paths[element].options.match.source;
473
- }
474
- var schemaListInfo = paths[element].options.list;
475
- if (schemaListInfo) {
476
- var listFieldInfo = { field: element };
477
- if (typeof schemaListInfo === 'object' && Object.keys(schemaListInfo).length > 0) {
478
- listFieldInfo.params = schemaListInfo;
479
- }
480
- listFields.push(listFieldInfo);
481
- }
497
+ that.filteredFind(item.resource, req, null, searchDoc, null, item.resource.options.searchOrder, limit ? limit + 200 : 0, null, function (err, docs) {
498
+ handleSearchResultsFromIndex(err, docs, item, cb);
499
+ });
482
500
  }
483
- }
484
- }
485
- outPath = this.applySchemaSubset(outPath, formSchema);
486
- var returnObj = { paths: outPath };
487
- if (hiddenFields.length > 0) {
488
- returnObj.hide = hiddenFields;
489
- }
490
- if (listFields.length > 0) {
491
- returnObj.listFields = listFields;
492
- }
493
- return returnObj;
494
- };
495
- DataForm.prototype.schema = function () {
496
- return _.bind(function (req, res) {
497
- if (!(req.resource = this.getResource(req.params.resourceName))) {
498
- return res.status(404).end();
499
- }
500
- var formSchema = null;
501
- if (req.params.formName) {
502
- formSchema = req.resource.model.schema.statics['form'](req.params.formName);
503
- }
504
- var paths = this.preprocess(req.resource, req.resource.model.schema.paths, formSchema).paths;
505
- res.send(paths);
506
- }, this);
507
- };
508
- DataForm.prototype.report = function () {
509
- return _.bind(function (req, res, next) {
510
- if (!(req.resource = this.getResource(req.params.resourceName))) {
511
- return next();
512
- }
513
- var self = this;
514
- if (typeof req.query === 'undefined') {
515
- req.query = {};
516
- }
517
- var reportSchema;
518
- if (req.params.reportName) {
519
- reportSchema = req.resource.model.schema.statics['report'](req.params.reportName, req);
520
- }
521
- else if (req.query.r) {
522
- switch (req.query.r[0]) {
523
- case '[':
524
- reportSchema = { pipeline: JSON.parse(req.query.r) };
525
- break;
526
- case '{':
527
- reportSchema = JSON.parse(req.query.r);
528
- break;
529
- default:
530
- return self.renderError(new Error('Invalid "r" parameter'), null, req, res, next);
501
+ }, function (err) {
502
+ if (err) {
503
+ callback(err);
531
504
  }
532
- }
533
- else {
534
- var fields = {};
535
- for (var key in req.resource.model.schema.paths) {
536
- if (req.resource.model.schema.paths.hasOwnProperty(key)) {
537
- if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
538
- if (key.indexOf('.') === -1) {
539
- fields[key] = 1;
540
- }
541
- }
505
+ else {
506
+ // Strip weighting from the results
507
+ results = _.map(results, function (aResult) {
508
+ delete aResult.weighting;
509
+ return aResult;
510
+ });
511
+ if (limit && results.length > limit) {
512
+ moreCount += results.length - limit;
513
+ results.splice(limit);
542
514
  }
515
+ timestamps.completedAt = new Date().valueOf();
516
+ callback(null, { results: results, moreCount: moreCount, timestamps: timestamps });
543
517
  }
544
- reportSchema = {
545
- pipeline: [
546
- { $project: fields }
547
- ], drilldown: req.params.resourceName + '/|_id|/edit'
548
- };
549
- }
550
- // Replace parameters in pipeline
551
- var schemaCopy = {};
552
- extend(schemaCopy, reportSchema);
553
- schemaCopy.params = schemaCopy.params || [];
554
- self.reportInternal(req, req.resource, schemaCopy, function (err, result) {
518
+ });
519
+ };
520
+ ;
521
+ FormsAngular.prototype.wrapInternalSearch = function (req, res, resourcesToSearch, includeResourceInResults, limit) {
522
+ this.internalSearch(req, resourcesToSearch, includeResourceInResults, limit, function (err, resultsObject) {
555
523
  if (err) {
556
- self.renderError(err, null, req, res, next);
524
+ res.status(400, err);
557
525
  }
558
526
  else {
559
- res.send(result);
527
+ res.send(resultsObject);
560
528
  }
561
529
  });
562
- }, this);
563
- };
564
- DataForm.prototype.hackVariablesInPipeline = function (runPipeline) {
565
- for (var pipelineSection = 0; pipelineSection < runPipeline.length; pipelineSection++) {
566
- if (runPipeline[pipelineSection]['$match']) {
567
- this.hackVariables(runPipeline[pipelineSection]['$match']);
568
- }
569
- }
570
- };
571
- DataForm.prototype.hackVariables = function (obj) {
572
- // Replace variables that cannot be serialised / deserialised. Bit of a hack, but needs must...
573
- // Anything formatted 1800-01-01T00:00:00.000Z or 1800-01-01T00:00:00.000+0000 is converted to a Date
574
- // Only handles the cases I need for now
575
- // TODO: handle arrays etc
576
- for (var prop in obj) {
577
- if (obj.hasOwnProperty(prop)) {
578
- if (typeof obj[prop] === 'string') {
579
- var dateTest = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})(Z|[+ -]\d{4})$/.exec(obj[prop]);
580
- if (dateTest) {
581
- obj[prop] = new Date(dateTest[1] + 'Z');
582
- }
583
- else {
584
- var objectIdTest = /^([0-9a-fA-F]{24})$/.exec(obj[prop]);
585
- if (objectIdTest) {
586
- obj[prop] = new this.mongoose.Types.ObjectId(objectIdTest[1]);
530
+ };
531
+ ;
532
+ FormsAngular.prototype.search = function () {
533
+ return _.bind(function (req, res, next) {
534
+ if (!(req.resource = this.getResource(req.params.resourceName))) {
535
+ return next();
536
+ }
537
+ this.wrapInternalSearch(req, res, [req.resource], false, 0);
538
+ }, this);
539
+ };
540
+ ;
541
+ FormsAngular.prototype.searchAll = function () {
542
+ return _.bind(function (req, res) {
543
+ this.wrapInternalSearch(req, res, this.resources, true, 10);
544
+ }, this);
545
+ };
546
+ ;
547
+ FormsAngular.prototype.models = function () {
548
+ var that = this;
549
+ return function (req, res) {
550
+ // TODO: Make this less wasteful - we only need to send the resourceNames of the resources
551
+ // Check for optional modelFilter and call it with the request and current list. Otherwise just return the list.
552
+ res.send(that.options.modelFilter ? that.options.modelFilter.call(null, req, that.resources) : that.resources);
553
+ };
554
+ };
555
+ ;
556
+ FormsAngular.prototype.renderError = function (err, redirectUrl, req, res) {
557
+ res.statusMessage = (err === null || err === void 0 ? void 0 : err.message) || err;
558
+ res.status(400).end((err === null || err === void 0 ? void 0 : err.message) || err);
559
+ };
560
+ ;
561
+ FormsAngular.prototype.redirect = function (address, req, res) {
562
+ res.send(address);
563
+ };
564
+ ;
565
+ FormsAngular.prototype.applySchemaSubset = function (vanilla, schema) {
566
+ var outPath;
567
+ if (schema) {
568
+ outPath = {};
569
+ for (var fld in schema) {
570
+ if (schema.hasOwnProperty(fld)) {
571
+ if (vanilla[fld]) {
572
+ outPath[fld] = _.cloneDeep(vanilla[fld]);
573
+ if (vanilla[fld].schema) {
574
+ outPath[fld].schema = this.applySchemaSubset(outPath[fld].schema, schema[fld].schema);
575
+ }
576
+ }
577
+ else {
578
+ if (fld.slice(0, 8) === "_bespoke") {
579
+ outPath[fld] = {
580
+ "path": fld,
581
+ "instance": schema[fld]._type,
582
+ };
583
+ }
584
+ else {
585
+ throw new Error('No such field as ' + fld + '. Is it part of a sub-doc? If so you need the bit before the period.');
586
+ }
587
+ }
588
+ outPath[fld].options = outPath[fld].options || {};
589
+ for (var override in schema[fld]) {
590
+ if (schema[fld].hasOwnProperty(override)) {
591
+ if (override.slice(0, 1) !== '_') {
592
+ if (schema[fld].hasOwnProperty(override)) {
593
+ if (!outPath[fld].options.form) {
594
+ outPath[fld].options.form = {};
595
+ }
596
+ outPath[fld].options.form[override] = schema[fld][override];
597
+ }
598
+ }
599
+ }
587
600
  }
588
601
  }
589
602
  }
590
- else if (_.isObject(obj[prop])) {
591
- this.hackVariables(obj[prop]);
592
- }
593
- }
594
- }
595
- };
596
- DataForm.prototype.reportInternal = function (req, resource, schema, callback) {
597
- var runPipelineStr;
598
- var runPipelineObj;
599
- var self = this;
600
- if (typeof req.query === 'undefined') {
601
- req.query = {};
602
- }
603
- self.doFindFunc(req, resource, function (err, queryObj) {
604
- if (err) {
605
- return 'There was a problem with the findFunc for model';
606
603
  }
607
604
  else {
608
- runPipelineStr = JSON.stringify(schema.pipeline);
609
- for (var param in req.query) {
610
- if (req.query[param]) {
611
- if (param !== 'r') { // we don't want to copy the whole report schema (again!)
612
- schema.params[param].value = req.query[param];
605
+ outPath = vanilla;
606
+ }
607
+ return outPath;
608
+ };
609
+ ;
610
+ FormsAngular.prototype.preprocess = function (resource, paths, formSchema) {
611
+ var outPath = {}, hiddenFields = [], listFields = [];
612
+ if (resource && resource.options && resource.options.idIsList) {
613
+ paths['_id'].options = paths['_id'].options || {};
614
+ paths['_id'].options.list = resource.options.idIsList;
615
+ }
616
+ for (var element in paths) {
617
+ if (paths.hasOwnProperty(element) && element !== '__v') {
618
+ // check for schemas
619
+ if (paths[element].schema) {
620
+ var subSchemaInfo = this.preprocess(null, paths[element].schema.paths);
621
+ outPath[element] = { schema: subSchemaInfo.paths };
622
+ if (paths[element].options.form) {
623
+ outPath[element].options = { form: extend(true, {}, paths[element].options.form) };
613
624
  }
614
- }
615
- }
616
- // Replace parameters with the value
617
- if (runPipelineStr) {
618
- runPipelineStr = runPipelineStr.replace(/\"\(.+?\)\"/g, function (match) {
619
- var sparam = schema.params[match.slice(2, -2)];
620
- if (sparam.type === 'number') {
621
- return sparam.value;
625
+ // this provides support for entire nested schemas that wish to remain hidden
626
+ if (paths[element].options.secure) {
627
+ hiddenFields.push(element);
622
628
  }
623
- else if (_.isObject(sparam.value)) {
624
- return JSON.stringify(sparam.value);
629
+ // to support hiding individual properties of nested schema would require us
630
+ // to do something with subSchemaInfo.hide here
631
+ }
632
+ else {
633
+ // check for arrays
634
+ var realType = paths[element].caster ? paths[element].caster : paths[element];
635
+ if (!realType.instance) {
636
+ if (realType.options.type) {
637
+ var type = realType.options.type(), typeType = typeof type;
638
+ if (typeType === 'string') {
639
+ realType.instance = (!isNaN(Date.parse(type))) ? 'Date' : 'String';
640
+ }
641
+ else {
642
+ realType.instance = typeType;
643
+ }
644
+ }
625
645
  }
626
- else if (sparam.value[0] === '{') {
627
- return sparam.value;
646
+ outPath[element] = extend(true, {}, paths[element]);
647
+ if (paths[element].options.secure) {
648
+ hiddenFields.push(element);
628
649
  }
629
- else {
630
- return '"' + sparam.value + '"';
650
+ if (paths[element].options.match) {
651
+ outPath[element].options.match = paths[element].options.match.source || paths[element].options.match;
631
652
  }
632
- });
633
- }
634
- // Don't send the 'secure' fields
635
- var hiddenFields = self.generateHiddenFields(resource, false);
636
- for (var hiddenField in hiddenFields) {
637
- if (hiddenFields.hasOwnProperty(hiddenField)) {
638
- if (runPipelineStr.indexOf(hiddenField) !== -1) {
639
- return callback('You cannot access ' + hiddenField);
653
+ var schemaListInfo = paths[element].options.list;
654
+ if (schemaListInfo) {
655
+ var listFieldInfo = { field: element };
656
+ if (typeof schemaListInfo === 'object' && Object.keys(schemaListInfo).length > 0) {
657
+ listFieldInfo.params = schemaListInfo;
658
+ }
659
+ listFields.push(listFieldInfo);
640
660
  }
641
661
  }
642
662
  }
643
- runPipelineObj = JSON.parse(runPipelineStr);
644
- if (!_.isArray(runPipelineObj)) {
645
- runPipelineObj = [runPipelineObj];
663
+ }
664
+ outPath = this.applySchemaSubset(outPath, formSchema);
665
+ var returnObj = { paths: outPath };
666
+ if (hiddenFields.length > 0) {
667
+ returnObj.hide = hiddenFields;
668
+ }
669
+ if (listFields.length > 0) {
670
+ returnObj.listFields = listFields;
671
+ }
672
+ return returnObj;
673
+ };
674
+ ;
675
+ FormsAngular.prototype.schema = function () {
676
+ return _.bind(function (req, res) {
677
+ if (!(req.resource = this.getResource(req.params.resourceName))) {
678
+ return res.status(404).end();
646
679
  }
647
- self.hackVariablesInPipeline(runPipelineObj);
648
- // Add the findFunc query to the pipeline
649
- if (queryObj) {
650
- runPipelineObj.unshift({ $match: queryObj });
680
+ var formSchema = null;
681
+ if (req.params.formName) {
682
+ formSchema = req.resource.model.schema.statics['form'](req.params.formName, req);
651
683
  }
652
- var toDo = {
653
- runAggregation: function (cb) {
654
- resource.model.aggregate(runPipelineObj, cb);
655
- }
656
- };
657
- var translations_1 = []; // array of form {ref:'lookupname',translations:[{value:xx, display:' '}]}
658
- // if we need to do any column translations add the function to the tasks list
659
- if (schema.columnTranslations) {
660
- toDo.applyTranslations = ['runAggregation', function (results, cb) {
661
- function doATranslate(column, theTranslation) {
662
- results['runAggregation'].forEach(function (resultRow) {
663
- var valToTranslate = resultRow[column.field];
664
- valToTranslate = (valToTranslate ? valToTranslate.toString() : '');
665
- var thisTranslation = _.find(theTranslation.translations, function (option) {
666
- return valToTranslate === option.value.toString();
667
- });
668
- resultRow[column.field] = thisTranslation ? thisTranslation.display : ' * Missing columnTranslation * ';
669
- });
670
- }
671
- schema.columnTranslations.forEach(function (columnTranslation) {
672
- if (columnTranslation.translations) {
673
- doATranslate(columnTranslation, columnTranslation);
684
+ var paths = this.preprocess(req.resource, req.resource.model.schema.paths, formSchema).paths;
685
+ res.send(paths);
686
+ }, this);
687
+ };
688
+ ;
689
+ FormsAngular.prototype.report = function () {
690
+ return _.bind(function (req, res, next) {
691
+ return __awaiter(this, void 0, void 0, function () {
692
+ var self, reportSchema, fields, key, schemaCopy;
693
+ return __generator(this, function (_a) {
694
+ switch (_a.label) {
695
+ case 0:
696
+ if (!(req.resource = this.getResource(req.params.resourceName))) {
697
+ return [2 /*return*/, next()];
674
698
  }
675
- if (columnTranslation.ref) {
676
- var theTranslation = _.find(translations_1, function (translation) {
677
- return (translation.ref === columnTranslation.ref);
678
- });
679
- if (theTranslation) {
680
- doATranslate(columnTranslation, theTranslation);
681
- }
682
- else {
683
- cb('Invalid ref property of ' + columnTranslation.ref + ' in columnTranslations ' + columnTranslation.field);
684
- }
699
+ self = this;
700
+ if (typeof req.query === 'undefined') {
701
+ req.query = {};
685
702
  }
686
- });
687
- cb(null, null);
688
- }];
689
- var callFuncs = false;
690
- for (var i = 0; i < schema.columnTranslations.length; i++) {
691
- var thisColumnTranslation = schema.columnTranslations[i];
692
- if (thisColumnTranslation.field) {
693
- // if any of the column translations are adhoc funcs, set up the tasks to perform them
694
- if (thisColumnTranslation.fn) {
695
- callFuncs = true;
696
- }
697
- // if this column translation is a "ref", set up the tasks to look up the values and populate the translations
698
- if (thisColumnTranslation.ref) {
699
- var lookup = self.getResource(thisColumnTranslation.ref);
700
- if (lookup) {
701
- if (!toDo[thisColumnTranslation.ref]) {
702
- var getFunc = function (ref) {
703
- var lookup = ref;
704
- return function (cb) {
705
- var translateObject = { ref: lookup.resourceName, translations: [] };
706
- translations_1.push(translateObject);
707
- lookup.model.find({}, {}, { lean: true }, function (err, findResults) {
708
- if (err) {
709
- cb(err);
710
- }
711
- else {
712
- // TODO - this ref func can probably be done away with now that list fields can have ref
713
- var j_1 = 0;
714
- async.whilst(function () {
715
- return j_1 < findResults.length;
716
- }, function (cbres) {
717
- var theResult = findResults[j_1];
718
- translateObject.translations[j_1] = translateObject.translations[j_1] || {};
719
- var theTranslation = translateObject.translations[j_1];
720
- j_1++;
721
- self.getListFields(lookup, theResult, function (err, description) {
722
- if (err) {
723
- cbres(err);
724
- }
725
- else {
726
- theTranslation.value = theResult._id;
727
- theTranslation.display = description;
728
- cbres(null);
729
- }
730
- });
731
- }, cb);
732
- }
733
- });
734
- };
735
- };
736
- toDo[thisColumnTranslation.ref] = getFunc(lookup);
737
- toDo.applyTranslations.unshift(thisColumnTranslation.ref); // Make sure we populate lookup before doing translation
703
+ if (!req.params.reportName) return [3 /*break*/, 2];
704
+ return [4 /*yield*/, req.resource.model.schema.statics['report'](req.params.reportName, req)];
705
+ case 1:
706
+ reportSchema = _a.sent();
707
+ return [3 /*break*/, 3];
708
+ case 2:
709
+ if (req.query.r) {
710
+ switch (req.query.r[0]) {
711
+ case '[':
712
+ reportSchema = { pipeline: JSON.parse(req.query.r) };
713
+ break;
714
+ case '{':
715
+ reportSchema = JSON.parse(req.query.r);
716
+ break;
717
+ default:
718
+ return [2 /*return*/, self.renderError(new Error('Invalid "r" parameter'), null, req, res)];
738
719
  }
739
720
  }
740
721
  else {
741
- return callback('Invalid ref property of ' + thisColumnTranslation.ref + ' in columnTranslations ' + thisColumnTranslation.field);
742
- }
743
- }
744
- if (!thisColumnTranslation.translations && !thisColumnTranslation.ref && !thisColumnTranslation.fn) {
745
- return callback('A column translation needs a ref, fn or a translations property - ' + thisColumnTranslation.field + ' has neither');
746
- }
747
- }
748
- else {
749
- return callback('A column translation needs a field property');
750
- }
751
- }
752
- if (callFuncs) {
753
- toDo['callFunctions'] = ['runAggregation', function (results, cb) {
754
- async.each(results.runAggregation, function (row, cb) {
755
- for (var i = 0; i < schema.columnTranslations.length; i++) {
756
- var thisColumnTranslation = schema.columnTranslations[i];
757
- if (thisColumnTranslation.fn) {
758
- thisColumnTranslation.fn(row, cb);
722
+ fields = {};
723
+ for (key in req.resource.model.schema.paths) {
724
+ if (req.resource.model.schema.paths.hasOwnProperty(key)) {
725
+ if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
726
+ if (key.indexOf('.') === -1) {
727
+ fields[key] = 1;
728
+ }
729
+ }
759
730
  }
760
731
  }
761
- }, function () {
762
- cb(null);
732
+ reportSchema = {
733
+ pipeline: [
734
+ { $project: fields }
735
+ ], drilldown: req.params.resourceName + '/|_id|/edit'
736
+ };
737
+ }
738
+ _a.label = 3;
739
+ case 3:
740
+ schemaCopy = {};
741
+ extend(schemaCopy, reportSchema);
742
+ schemaCopy.params = schemaCopy.params || [];
743
+ self.reportInternal(req, req.resource, schemaCopy, function (err, result) {
744
+ if (err) {
745
+ res.send({ success: false, error: err.message || err });
746
+ }
747
+ else {
748
+ res.send(result);
749
+ }
763
750
  });
764
- }];
765
- toDo.applyTranslations.unshift('callFunctions'); // Make sure we do function before translating its result
766
- }
767
- }
768
- async.auto(toDo, function (err, results) {
769
- if (err) {
770
- callback(err);
771
- }
772
- else {
773
- // TODO: Could loop through schema.params and just send back the values
774
- callback(null, { success: true, schema: schema, report: results.runAggregation, paramsUsed: schema.params });
775
- }
751
+ return [2 /*return*/];
752
+ }
753
+ });
776
754
  });
755
+ }, this);
756
+ };
757
+ ;
758
+ FormsAngular.prototype.hackVariablesInPipeline = function (runPipeline) {
759
+ for (var pipelineSection = 0; pipelineSection < runPipeline.length; pipelineSection++) {
760
+ if (runPipeline[pipelineSection]['$match']) {
761
+ this.hackVariables(runPipeline[pipelineSection]['$match']);
762
+ }
777
763
  }
778
- });
779
- };
780
- DataForm.prototype.saveAndRespond = function (req, res, hiddenFields) {
781
- function internalSave(doc) {
782
- doc.save(function (err, doc2) {
783
- if (err) {
784
- var err2 = { status: 'err' };
785
- if (!err.errors) {
786
- err2.message = err.message;
787
- }
788
- else {
789
- extend(err2, err);
764
+ };
765
+ ;
766
+ FormsAngular.prototype.hackVariables = function (obj) {
767
+ // Replace variables that cannot be serialised / deserialised. Bit of a hack, but needs must...
768
+ // Anything formatted 1800-01-01T00:00:00.000Z or 1800-01-01T00:00:00.000+0000 is converted to a Date
769
+ // Only handles the cases I need for now
770
+ // TODO: handle arrays etc
771
+ for (var prop in obj) {
772
+ if (obj.hasOwnProperty(prop)) {
773
+ if (typeof obj[prop] === 'string') {
774
+ var dateTest = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})(Z|[+ -]\d{4})$/.exec(obj[prop]);
775
+ if (dateTest) {
776
+ obj[prop] = new Date(dateTest[1] + 'Z');
777
+ }
778
+ else {
779
+ var objectIdTest = /^([0-9a-fA-F]{24})$/.exec(obj[prop]);
780
+ if (objectIdTest) {
781
+ obj[prop] = new this.mongoose.Types.ObjectId(objectIdTest[1]);
782
+ }
783
+ }
790
784
  }
791
- if (debug) {
792
- console.log('Error saving record: ' + JSON.stringify(err2));
785
+ else if (_.isObject(obj[prop])) {
786
+ this.hackVariables(obj[prop]);
793
787
  }
794
- res.status(400).send(err2);
795
788
  }
796
- else {
797
- doc2 = doc2.toObject();
798
- for (var hiddenField in hiddenFields) {
799
- if (hiddenFields.hasOwnProperty(hiddenField) && hiddenFields[hiddenField]) {
800
- var parts = hiddenField.split('.');
801
- var lastPart = parts.length - 1;
802
- var target = doc2;
803
- for (var i = 0; i < lastPart; i++) {
804
- if (target.hasOwnProperty(parts[i])) {
805
- target = target[parts[i]];
806
- }
789
+ }
790
+ };
791
+ ;
792
+ FormsAngular.prototype.sanitisePipeline = function (aggregationParam, hiddenFields, findFuncQry) {
793
+ var _a, _b, _c, _d;
794
+ var that = this;
795
+ var array = Array.isArray(aggregationParam) ? aggregationParam : [aggregationParam];
796
+ var retVal = [];
797
+ var doneHiddenFields = false;
798
+ if (findFuncQry) {
799
+ retVal.unshift({ $match: findFuncQry });
800
+ }
801
+ var _loop_1 = function (pipelineSection) {
802
+ var stage = array[pipelineSection];
803
+ var keys = Object.keys(stage);
804
+ if (keys.length !== 1) {
805
+ throw new Error('Invalid pipeline instruction');
806
+ }
807
+ switch (keys[0]) {
808
+ case '$merge':
809
+ case '$out':
810
+ throw new Error('Cannot use potentially destructive pipeline stages');
811
+ case '$unionWith':
812
+ /*
813
+ Sanitise the pipeline we are doing a union with, removing hidden fields from that collection
814
+ */
815
+ var unionCollectionName = stage.$unionWith.coll;
816
+ var unionResource = that.getResourceFromCollection(unionCollectionName);
817
+ var unionHiddenLookupFields = {};
818
+ if (unionResource) {
819
+ if (((_b = (_a = unionResource.options) === null || _a === void 0 ? void 0 : _a.hide) === null || _b === void 0 ? void 0 : _b.length) > 0) {
820
+ unionHiddenLookupFields = this_1.generateHiddenFields(unionResource, false);
807
821
  }
808
- if (target.hasOwnProperty(parts[lastPart])) {
809
- delete target[parts[lastPart]];
822
+ }
823
+ stage.$unionWith.pipeline = that.sanitisePipeline(stage.$unionWith.pipeline, unionHiddenLookupFields, findFuncQry);
824
+ break;
825
+ case '$match':
826
+ this_1.hackVariables(array[pipelineSection]['$match']);
827
+ retVal.push(array[pipelineSection]);
828
+ if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
829
+ // We can now project out the hidden fields (we wait for the $match to make sure we don't break
830
+ // a select that uses a hidden field
831
+ retVal.push({ $project: hiddenFields });
832
+ doneHiddenFields = true;
833
+ }
834
+ stage = null;
835
+ break;
836
+ case '$lookup':
837
+ // hide any hiddenfields in the lookup collection
838
+ var collectionName = stage.$lookup.from;
839
+ var lookupField_1 = stage.$lookup.as;
840
+ if ((collectionName + lookupField_1).indexOf('$') !== -1) {
841
+ throw new Error('No support for lookups where the "from" or "as" is anything other than a simple string');
842
+ }
843
+ var resource = that.getResourceFromCollection(collectionName);
844
+ if (resource) {
845
+ if (((_d = (_c = resource.options) === null || _c === void 0 ? void 0 : _c.hide) === null || _d === void 0 ? void 0 : _d.length) > 0) {
846
+ var hiddenLookupFields = this_1.generateHiddenFields(resource, false);
847
+ var hiddenFieldsObj_1 = {};
848
+ Object.keys(hiddenLookupFields).forEach(function (hf) {
849
+ hiddenFieldsObj_1["".concat(lookupField_1, ".").concat(hf)] = false;
850
+ });
851
+ retVal.push({ $project: hiddenFieldsObj_1 });
810
852
  }
811
853
  }
812
- }
813
- res.send(doc2);
854
+ break;
855
+ default:
856
+ // nothing
857
+ break;
814
858
  }
815
- });
816
- }
817
- var doc = req.doc;
818
- if (typeof req.resource.options.onSave === 'function') {
819
- req.resource.options.onSave(doc, req, function (err) {
820
- if (err) {
821
- throw err;
859
+ if (stage) {
860
+ retVal.push(stage);
822
861
  }
823
- internalSave(doc);
824
- });
825
- }
826
- else {
827
- internalSave(doc);
828
- }
829
- };
830
- /**
831
- * All entities REST functions have to go through this first.
832
- */
833
- DataForm.prototype.processCollection = function (req) {
834
- req.resource = this.getResource(req.params.resourceName);
835
- };
836
- /**
837
- * Renders a view with the list of docs, which may be modified by query parameters
838
- */
839
- DataForm.prototype.collectionGet = function () {
840
- return _.bind(function (req, res, next) {
841
- this.processCollection(req);
842
- if (!req.resource) {
843
- return next();
862
+ };
863
+ var this_1 = this;
864
+ for (var pipelineSection = 0; pipelineSection < array.length; pipelineSection++) {
865
+ _loop_1(pipelineSection);
844
866
  }
867
+ if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
868
+ // If there was no $match we still need to hide the hidden fields
869
+ retVal.unshift({ $project: hiddenFields });
870
+ }
871
+ return retVal;
872
+ };
873
+ FormsAngular.prototype.reportInternal = function (req, resource, schema, callback) {
874
+ var runPipelineStr;
875
+ var runPipelineObj;
876
+ var self = this;
845
877
  if (typeof req.query === 'undefined') {
846
878
  req.query = {};
847
879
  }
848
- try {
849
- var aggregationParam = req.query.a ? JSON.parse(req.query.a) : null;
850
- var findParam = req.query.f ? JSON.parse(req.query.f) : {};
851
- var limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
852
- var skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
853
- var orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
854
- // Dates in aggregation must be Dates
855
- if (aggregationParam) {
856
- this.hackVariablesInPipeline(aggregationParam);
880
+ self.doFindFunc(req, resource, function (err, queryObj) {
881
+ if (err) {
882
+ return 'There was a problem with the findFunc for model';
857
883
  }
858
- var self_1 = this;
859
- this.filteredFind(req.resource, req, aggregationParam, findParam, orderParam, limitParam, skipParam, function (err, docs) {
884
+ else {
885
+ runPipelineStr = JSON.stringify(schema.pipeline);
886
+ for (var param in req.query) {
887
+ if (req.query.hasOwnProperty(param)) {
888
+ if (req.query[param]) {
889
+ if (param !== 'r') { // we don't want to copy the whole report schema (again!)
890
+ if (schema.params[param] !== undefined) {
891
+ schema.params[param].value = req.query[param];
892
+ }
893
+ else {
894
+ callback("No such parameter as ".concat(param, " - try one of ").concat(Object.keys(schema.params).join()));
895
+ }
896
+ }
897
+ }
898
+ }
899
+ }
900
+ // Replace parameters with the value
901
+ if (runPipelineStr) {
902
+ runPipelineStr = runPipelineStr.replace(/"\(.+?\)"/g, function (match) {
903
+ var sparam = schema.params[match.slice(2, -2)];
904
+ if (sparam.type === 'number') {
905
+ return sparam.value;
906
+ }
907
+ else if (_.isObject(sparam.value)) {
908
+ return JSON.stringify(sparam.value);
909
+ }
910
+ else if (sparam.value[0] === '{') {
911
+ return sparam.value;
912
+ }
913
+ else {
914
+ return '"' + sparam.value + '"';
915
+ }
916
+ });
917
+ }
918
+ runPipelineObj = JSON.parse(runPipelineStr);
919
+ var hiddenFields_1 = self.generateHiddenFields(resource, false);
920
+ var toDo = {
921
+ runAggregation: function (cb) {
922
+ runPipelineObj = self.sanitisePipeline(runPipelineObj, hiddenFields_1, queryObj);
923
+ resource.model.aggregate(runPipelineObj, cb);
924
+ }
925
+ };
926
+ var translations_1 = []; // array of form {ref:'lookupname',translations:[{value:xx, display:' '}]}
927
+ // if we need to do any column translations add the function to the tasks list
928
+ if (schema.columnTranslations) {
929
+ toDo.applyTranslations = ['runAggregation', function (results, cb) {
930
+ function doATranslate(column, theTranslation) {
931
+ results['runAggregation'].forEach(function (resultRow) {
932
+ var valToTranslate = resultRow[column.field];
933
+ valToTranslate = (valToTranslate ? valToTranslate.toString() : '');
934
+ var thisTranslation = _.find(theTranslation.translations, function (option) {
935
+ return valToTranslate === option.value.toString();
936
+ });
937
+ resultRow[column.field] = thisTranslation ? thisTranslation.display : ' * Missing columnTranslation * ';
938
+ });
939
+ }
940
+ schema.columnTranslations.forEach(function (columnTranslation) {
941
+ if (columnTranslation.translations) {
942
+ doATranslate(columnTranslation, columnTranslation);
943
+ }
944
+ if (columnTranslation.ref) {
945
+ var theTranslation = _.find(translations_1, function (translation) {
946
+ return (translation.ref === columnTranslation.ref);
947
+ });
948
+ if (theTranslation) {
949
+ doATranslate(columnTranslation, theTranslation);
950
+ }
951
+ else {
952
+ cb('Invalid ref property of ' + columnTranslation.ref + ' in columnTranslations ' + columnTranslation.field);
953
+ }
954
+ }
955
+ });
956
+ cb(null, null);
957
+ }];
958
+ var callFuncs = false;
959
+ for (var i = 0; i < schema.columnTranslations.length; i++) {
960
+ var thisColumnTranslation = schema.columnTranslations[i];
961
+ if (thisColumnTranslation.field) {
962
+ // if any of the column translations are adhoc funcs, set up the tasks to perform them
963
+ if (thisColumnTranslation.fn) {
964
+ callFuncs = true;
965
+ }
966
+ // if this column translation is a "ref", set up the tasks to look up the values and populate the translations
967
+ if (thisColumnTranslation.ref) {
968
+ var lookup = self.getResource(thisColumnTranslation.ref);
969
+ if (lookup) {
970
+ if (!toDo[thisColumnTranslation.ref]) {
971
+ var getFunc = function (ref) {
972
+ var lookup = ref;
973
+ return function (cb) {
974
+ var translateObject = { ref: lookup.resourceName, translations: [] };
975
+ translations_1.push(translateObject);
976
+ lookup.model.find({}, {}, { lean: true }, function (err, findResults) {
977
+ if (err) {
978
+ cb(err);
979
+ }
980
+ else {
981
+ // TODO - this ref func can probably be done away with now that list fields can have ref
982
+ var j_1 = 0;
983
+ async.whilst(function (cbtest) {
984
+ cbtest(null, j_1 < findResults.length);
985
+ }, function (cbres) {
986
+ var theResult = findResults[j_1];
987
+ translateObject.translations[j_1] = translateObject.translations[j_1] || {};
988
+ var theTranslation = translateObject.translations[j_1];
989
+ j_1++;
990
+ self.getListFields(lookup, theResult, function (err, description) {
991
+ if (err) {
992
+ cbres(err);
993
+ }
994
+ else {
995
+ theTranslation.value = theResult._id;
996
+ theTranslation.display = description;
997
+ cbres(null);
998
+ }
999
+ });
1000
+ }, cb);
1001
+ }
1002
+ });
1003
+ };
1004
+ };
1005
+ toDo[thisColumnTranslation.ref] = getFunc(lookup);
1006
+ toDo.applyTranslations.unshift(thisColumnTranslation.ref); // Make sure we populate lookup before doing translation
1007
+ }
1008
+ }
1009
+ else {
1010
+ return callback('Invalid ref property of ' + thisColumnTranslation.ref + ' in columnTranslations ' + thisColumnTranslation.field);
1011
+ }
1012
+ }
1013
+ if (!thisColumnTranslation.translations && !thisColumnTranslation.ref && !thisColumnTranslation.fn) {
1014
+ return callback('A column translation needs a ref, fn or a translations property - ' + thisColumnTranslation.field + ' has neither');
1015
+ }
1016
+ }
1017
+ else {
1018
+ return callback('A column translation needs a field property');
1019
+ }
1020
+ }
1021
+ if (callFuncs) {
1022
+ toDo['callFunctions'] = ['runAggregation', function (results, cb) {
1023
+ async.each(results.runAggregation, function (row, cb) {
1024
+ for (var i = 0; i < schema.columnTranslations.length; i++) {
1025
+ var thisColumnTranslation = schema.columnTranslations[i];
1026
+ if (thisColumnTranslation.fn) {
1027
+ thisColumnTranslation.fn(row, cb);
1028
+ }
1029
+ }
1030
+ }, function () {
1031
+ cb(null);
1032
+ });
1033
+ }];
1034
+ toDo.applyTranslations.unshift('callFunctions'); // Make sure we do function before translating its result
1035
+ }
1036
+ }
1037
+ async.auto(toDo, function (err, results) {
1038
+ if (err) {
1039
+ callback(err);
1040
+ }
1041
+ else {
1042
+ // TODO: Could loop through schema.params and just send back the values
1043
+ callback(null, {
1044
+ success: true,
1045
+ schema: schema,
1046
+ report: results.runAggregation,
1047
+ paramsUsed: schema.params
1048
+ });
1049
+ }
1050
+ });
1051
+ }
1052
+ });
1053
+ };
1054
+ ;
1055
+ FormsAngular.prototype.saveAndRespond = function (req, res, hiddenFields) {
1056
+ function internalSave(doc) {
1057
+ doc.save(function (err, doc2) {
860
1058
  if (err) {
861
- return self_1.renderError(err, null, req, res, next);
1059
+ var err2 = { status: 'err' };
1060
+ if (!err.errors) {
1061
+ err2.message = err.message;
1062
+ }
1063
+ else {
1064
+ extend(err2, err);
1065
+ }
1066
+ if (debug) {
1067
+ console.log('Error saving record: ' + JSON.stringify(err2));
1068
+ }
1069
+ res.status(400).send(err2);
862
1070
  }
863
1071
  else {
864
- res.send(docs);
1072
+ doc2 = doc2.toObject();
1073
+ for (var hiddenField in hiddenFields) {
1074
+ if (hiddenFields.hasOwnProperty(hiddenField) && hiddenFields[hiddenField]) {
1075
+ var parts = hiddenField.split('.');
1076
+ var lastPart = parts.length - 1;
1077
+ var target = doc2;
1078
+ for (var i = 0; i < lastPart; i++) {
1079
+ if (target.hasOwnProperty(parts[i])) {
1080
+ target = target[parts[i]];
1081
+ }
1082
+ }
1083
+ if (target.hasOwnProperty(parts[lastPart])) {
1084
+ delete target[parts[lastPart]];
1085
+ }
1086
+ }
1087
+ }
1088
+ res.send(doc2);
865
1089
  }
866
1090
  });
867
1091
  }
868
- catch (e) {
869
- res.send(e);
870
- }
871
- }, this);
872
- };
873
- DataForm.prototype.doFindFunc = function (req, resource, cb) {
874
- if (resource.options.findFunc) {
875
- resource.options.findFunc(req, cb);
876
- }
877
- else {
878
- cb(null);
879
- }
880
- };
881
- DataForm.prototype.filteredFind = function (resource, req, aggregationParam, findParam, sortOrder, limit, skip, callback) {
882
- var that = this;
883
- var hiddenFields = this.generateHiddenFields(resource, false);
884
- var stashAggregationResults;
885
- function doAggregation(cb) {
886
- if (aggregationParam) {
887
- resource.model.aggregate(aggregationParam, function (err, aggregationResults) {
1092
+ var doc = req.doc;
1093
+ if (typeof req.resource.options.onSave === 'function') {
1094
+ req.resource.options.onSave(doc, req, function (err) {
888
1095
  if (err) {
889
1096
  throw err;
890
1097
  }
891
- else {
892
- stashAggregationResults = aggregationResults;
893
- cb(_.map(aggregationResults, function (obj) {
894
- return obj._id;
895
- }));
896
- }
1098
+ internalSave(doc);
897
1099
  });
898
1100
  }
899
1101
  else {
900
- cb([]);
901
- }
902
- }
903
- doAggregation(function (idArray) {
904
- if (aggregationParam && idArray.length === 0) {
905
- callback(null, []);
1102
+ internalSave(doc);
906
1103
  }
907
- else {
908
- that.doFindFunc(req, resource, function (err, queryObj) {
909
- if (err) {
910
- callback(err);
1104
+ };
1105
+ ;
1106
+ /**
1107
+ * All entities REST functions have to go through this first.
1108
+ */
1109
+ FormsAngular.prototype.processCollection = function (req) {
1110
+ req.resource = this.getResource(req.params.resourceName);
1111
+ };
1112
+ ;
1113
+ /**
1114
+ * Renders a view with the list of docs, which may be modified by query parameters
1115
+ */
1116
+ FormsAngular.prototype.collectionGet = function () {
1117
+ return _.bind(function (req, res, next) {
1118
+ this.processCollection(req);
1119
+ if (!req.resource) {
1120
+ return next();
1121
+ }
1122
+ if (typeof req.query === 'undefined') {
1123
+ req.query = {};
1124
+ }
1125
+ try {
1126
+ var aggregationParam = req.query.a ? JSON.parse(req.query.a) : null;
1127
+ var findParam = req.query.f ? JSON.parse(req.query.f) : {};
1128
+ var projectParam = req.query.p ? JSON.parse(req.query.p) : {};
1129
+ var limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
1130
+ var skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
1131
+ var orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
1132
+ // Dates in aggregation must be Dates
1133
+ if (aggregationParam) {
1134
+ this.hackVariablesInPipeline(aggregationParam);
911
1135
  }
912
- else {
913
- var query = resource.model.find(queryObj);
914
- if (idArray.length > 0) {
915
- query = query.where('_id').in(idArray);
1136
+ var self_1 = this;
1137
+ this.filteredFind(req.resource, req, aggregationParam, findParam, projectParam, orderParam, limitParam, skipParam, function (err, docs) {
1138
+ if (err) {
1139
+ return self_1.renderError(err, null, req, res);
916
1140
  }
917
- query = query.find(findParam).select(hiddenFields);
918
- if (limit) {
919
- query = query.limit(limit);
1141
+ else {
1142
+ res.send(docs);
920
1143
  }
921
- if (skip) {
922
- query = query.skip(skip);
1144
+ });
1145
+ }
1146
+ catch (e) {
1147
+ res.status(400).send(e.message);
1148
+ }
1149
+ }, this);
1150
+ };
1151
+ ;
1152
+ FormsAngular.prototype.generateProjection = function (hiddenFields, projectParam) {
1153
+ var type;
1154
+ function setSelectType(typeChar, checkChar) {
1155
+ if (type === checkChar) {
1156
+ throw new Error('Cannot mix include and exclude fields in select');
1157
+ }
1158
+ else {
1159
+ type = typeChar;
1160
+ }
1161
+ }
1162
+ var retVal = hiddenFields;
1163
+ if (projectParam) {
1164
+ var projection = Object.keys(projectParam);
1165
+ if (projection.length > 0) {
1166
+ projection.forEach(function (p) {
1167
+ if (projectParam[p] === 0) {
1168
+ setSelectType('E', 'I');
923
1169
  }
924
- if (sortOrder) {
925
- query = query.sort(sortOrder);
1170
+ else if (projectParam[p] === 1) {
1171
+ setSelectType('I', 'E');
926
1172
  }
927
- query.exec(function (err, docs) {
928
- if (!err && stashAggregationResults) {
929
- docs.forEach(function (obj) {
930
- // Add any fields from the aggregation results whose field name starts __ to the mongoose Document
931
- var aggObj = stashAggregationResults.find(function (a) { return a._id.toString() === obj._id.toString(); });
932
- Object.keys(aggObj).forEach(function (k) {
933
- if (k.slice(0, 2) === '__') {
934
- obj[k] = aggObj[k];
935
- }
936
- });
937
- });
1173
+ else {
1174
+ throw new Error('Invalid projection: ' + projectParam);
1175
+ }
1176
+ });
1177
+ if (type && type === 'E') {
1178
+ // We are excluding fields - can just merge with hiddenFields
1179
+ Object.assign(retVal, projectParam, hiddenFields);
1180
+ }
1181
+ else {
1182
+ // We are selecting fields - make sure none are hidden
1183
+ retVal = projectParam;
1184
+ for (var h in hiddenFields) {
1185
+ if (hiddenFields.hasOwnProperty(h)) {
1186
+ delete retVal[h];
938
1187
  }
939
- callback(err, docs);
940
- });
1188
+ }
941
1189
  }
942
- });
943
- }
944
- });
945
- };
946
- DataForm.prototype.collectionPost = function () {
947
- return _.bind(function (req, res, next) {
948
- this.processCollection(req);
949
- if (!req.resource) {
950
- next();
951
- return;
952
- }
953
- if (!req.body) {
954
- throw new Error('Nothing submitted.');
955
- }
956
- var cleansedBody = this.cleanseRequest(req);
957
- req.doc = new req.resource.model(cleansedBody);
958
- this.saveAndRespond(req, res);
959
- }, this);
960
- };
961
- /**
962
- * Generate an object of fields to not expose
963
- **/
964
- DataForm.prototype.generateHiddenFields = function (resource, state) {
965
- var hiddenFields = {};
966
- if (resource.options['hide'] !== undefined) {
967
- resource.options.hide.forEach(function (dt) {
968
- hiddenFields[dt] = state;
969
- });
970
- }
971
- return hiddenFields;
972
- };
973
- /** Sec issue
974
- * Cleanse incoming data to avoid overwrite and POST request forgery
975
- * (name may seem weird but it was in French, so it is some small improvement!)
976
- */
977
- DataForm.prototype.cleanseRequest = function (req) {
978
- var reqData = req.body, resource = req.resource;
979
- delete reqData.__v; // Don't mess with Mongoose internal field (https://github.com/LearnBoost/mongoose/issues/1933)
980
- if (typeof resource.options['hide'] === 'undefined') {
981
- return reqData;
982
- }
983
- var hiddenFields = resource.options.hide;
984
- _.each(reqData, function (num, key) {
985
- _.each(hiddenFields, function (fi) {
986
- if (fi === key) {
987
- delete reqData[key];
988
1190
  }
989
- });
990
- });
991
- return reqData;
992
- };
993
- DataForm.prototype.generateQueryForEntity = function (req, resource, id, cb) {
994
- var hiddenFields = this.generateHiddenFields(resource, false);
995
- hiddenFields.__v = 0;
996
- this.doFindFunc(req, resource, function (err, queryObj) {
997
- if (err) {
998
- cb(err);
999
- }
1000
- else {
1001
- var idSel = { _id: id };
1002
- var crit = queryObj ? extend(queryObj, idSel) : idSel;
1003
- cb(null, resource.model.findOne(crit).select(hiddenFields));
1004
1191
  }
1005
- });
1006
- };
1007
- /*
1008
- * Entity request goes there first
1009
- * It retrieves the resource
1010
- */
1011
- DataForm.prototype.processEntity = function (req, res, next) {
1012
- if (!(req.resource = this.getResource(req.params.resourceName))) {
1013
- next();
1014
- return;
1015
- }
1016
- this.generateQueryForEntity(req, req.resource, req.params.id, function (err, query) {
1017
- if (err) {
1018
- return res.send({
1019
- success: false,
1020
- err: util.inspect(err)
1021
- });
1192
+ return retVal;
1193
+ };
1194
+ ;
1195
+ FormsAngular.prototype.doFindFunc = function (req, resource, cb) {
1196
+ if (resource.options.findFunc) {
1197
+ resource.options.findFunc(req, cb);
1022
1198
  }
1023
1199
  else {
1024
- query.exec(function (err, doc) {
1025
- if (err) {
1026
- return res.send({
1027
- success: false,
1028
- err: util.inspect(err)
1029
- });
1030
- }
1031
- else if (doc == null) {
1032
- return res.send({
1033
- success: false,
1034
- err: 'Record not found'
1035
- });
1036
- }
1037
- req.doc = doc;
1038
- next();
1039
- });
1200
+ cb(null);
1040
1201
  }
1041
- });
1042
- };
1043
- /**
1044
- * Gets a single entity
1045
- *
1046
- * @return {Function} The function to use as route
1047
- */
1048
- DataForm.prototype.entityGet = function () {
1049
- return _.bind(function (req, res, next) {
1050
- this.processEntity(req, res, function () {
1051
- if (!req.resource) {
1052
- return next();
1053
- }
1054
- if (req.resource.options.onAccess) {
1055
- req.resource.options.onAccess(req, function () {
1056
- return res.send(req.doc);
1202
+ };
1203
+ ;
1204
+ FormsAngular.prototype.filteredFind = function (resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1205
+ var that = this;
1206
+ var hiddenFields = this.generateHiddenFields(resource, false);
1207
+ var stashAggregationResults;
1208
+ function doAggregation(queryObj, cb) {
1209
+ if (aggregationParam) {
1210
+ aggregationParam = that.sanitisePipeline(aggregationParam, hiddenFields, queryObj);
1211
+ void resource.model.aggregate(aggregationParam, function (err, aggregationResults) {
1212
+ if (err) {
1213
+ throw err;
1214
+ }
1215
+ else {
1216
+ stashAggregationResults = aggregationResults;
1217
+ cb(_.map(aggregationResults, function (obj) {
1218
+ return obj._id;
1219
+ }));
1220
+ }
1057
1221
  });
1058
1222
  }
1059
1223
  else {
1060
- return res.send(req.doc);
1224
+ cb([]);
1061
1225
  }
1062
- });
1063
- }, this);
1064
- };
1065
- DataForm.prototype.replaceHiddenFields = function (record, data) {
1066
- var self = this;
1067
- if (record) {
1068
- record._replacingHiddenFields = true;
1069
- _.each(data, function (value, name) {
1070
- if (_.isObject(value)) {
1071
- self.replaceHiddenFields(record[name], value);
1226
+ }
1227
+ that.doFindFunc(req, resource, function (err, queryObj) {
1228
+ if (err) {
1229
+ callback(err);
1072
1230
  }
1073
1231
  else {
1074
- record[name] = value;
1232
+ doAggregation(queryObj, function (idArray) {
1233
+ if (aggregationParam && idArray.length === 0) {
1234
+ callback(null, []);
1235
+ }
1236
+ else {
1237
+ var query = resource.model.find(queryObj);
1238
+ if (idArray.length > 0) {
1239
+ query = query.where('_id').in(idArray);
1240
+ }
1241
+ if (findParam) {
1242
+ query = query.find(findParam);
1243
+ }
1244
+ query = query.select(that.generateProjection(hiddenFields, projectParam));
1245
+ if (limit) {
1246
+ query = query.limit(limit);
1247
+ }
1248
+ if (skip) {
1249
+ query = query.skip(skip);
1250
+ }
1251
+ if (sortOrder) {
1252
+ query = query.sort(sortOrder);
1253
+ }
1254
+ query.exec(function (err, docs) {
1255
+ if (!err && stashAggregationResults) {
1256
+ docs.forEach(function (obj) {
1257
+ // Add any fields from the aggregation results whose field name starts __ to the mongoose Document
1258
+ var aggObj = stashAggregationResults.find(function (a) { return a._id.toString() === obj._id.toString(); });
1259
+ Object.keys(aggObj).forEach(function (k) {
1260
+ if (k.slice(0, 2) === '__') {
1261
+ obj[k] = aggObj[k];
1262
+ }
1263
+ });
1264
+ });
1265
+ }
1266
+ callback(err, docs);
1267
+ });
1268
+ }
1269
+ });
1075
1270
  }
1076
1271
  });
1077
- delete record._replacingHiddenFields;
1078
- }
1079
- };
1080
- DataForm.prototype.entityPut = function () {
1081
- return _.bind(function (req, res, next) {
1082
- var that = this;
1083
- this.processEntity(req, res, function () {
1272
+ };
1273
+ ;
1274
+ FormsAngular.prototype.collectionPost = function () {
1275
+ return _.bind(function (req, res, next) {
1276
+ this.processCollection(req);
1084
1277
  if (!req.resource) {
1085
1278
  next();
1086
1279
  return;
@@ -1088,70 +1281,362 @@ DataForm.prototype.entityPut = function () {
1088
1281
  if (!req.body) {
1089
1282
  throw new Error('Nothing submitted.');
1090
1283
  }
1091
- var cleansedBody = that.cleanseRequest(req);
1092
- // Merge
1093
- _.each(cleansedBody, function (value, name) {
1094
- req.doc[name] = (value === '') ? undefined : value;
1284
+ var cleansedBody = this.cleanseRequest(req);
1285
+ req.doc = new req.resource.model(cleansedBody);
1286
+ this.saveAndRespond(req, res);
1287
+ }, this);
1288
+ };
1289
+ ;
1290
+ /**
1291
+ * Generate an object of fields to not expose
1292
+ **/
1293
+ FormsAngular.prototype.generateHiddenFields = function (resource, state) {
1294
+ var hiddenFields = {};
1295
+ if (resource.options['hide'] !== undefined) {
1296
+ resource.options.hide.forEach(function (dt) {
1297
+ hiddenFields[dt] = state;
1095
1298
  });
1096
- if (req.resource.options.hide !== undefined) {
1097
- var hiddenFields_1 = that.generateHiddenFields(req.resource, true);
1098
- hiddenFields_1._id = false;
1099
- req.resource.model.findById(req.doc._id, hiddenFields_1, { lean: true }, function (err, data) {
1100
- that.replaceHiddenFields(req.doc, data);
1101
- that.saveAndRespond(req, res, hiddenFields_1);
1102
- });
1299
+ }
1300
+ return hiddenFields;
1301
+ };
1302
+ ;
1303
+ /** Sec issue
1304
+ * Cleanse incoming data to avoid overwrite and POST request forgery
1305
+ * (name may seem weird but it was in French, so it is some small improvement!)
1306
+ */
1307
+ FormsAngular.prototype.cleanseRequest = function (req) {
1308
+ var reqData = req.body, resource = req.resource;
1309
+ delete reqData.__v; // Don't mess with Mongoose internal field (https://github.com/LearnBoost/mongoose/issues/1933)
1310
+ if (typeof resource.options['hide'] === 'undefined') {
1311
+ return reqData;
1312
+ }
1313
+ var hiddenFields = resource.options.hide;
1314
+ _.each(reqData, function (num, key) {
1315
+ _.each(hiddenFields, function (fi) {
1316
+ if (fi === key) {
1317
+ delete reqData[key];
1318
+ }
1319
+ });
1320
+ });
1321
+ return reqData;
1322
+ };
1323
+ ;
1324
+ FormsAngular.prototype.generateQueryForEntity = function (req, resource, id, cb) {
1325
+ var that = this;
1326
+ var hiddenFields = this.generateHiddenFields(resource, false);
1327
+ hiddenFields.__v = false;
1328
+ that.doFindFunc(req, resource, function (err, queryObj) {
1329
+ var _a;
1330
+ if (err) {
1331
+ cb(err);
1103
1332
  }
1104
1333
  else {
1105
- that.saveAndRespond(req, res);
1334
+ var idSel = { _id: id };
1335
+ var crit = void 0;
1336
+ if (queryObj) {
1337
+ if (queryObj._id) {
1338
+ crit = { $and: [idSel, { _id: queryObj._id }] };
1339
+ delete queryObj._id;
1340
+ if (Object.keys(queryObj).length > 0) {
1341
+ crit = extend(crit, queryObj);
1342
+ }
1343
+ }
1344
+ else {
1345
+ crit = extend(queryObj, idSel);
1346
+ }
1347
+ }
1348
+ else {
1349
+ crit = idSel;
1350
+ }
1351
+ cb(null, resource.model.findOne(crit).select(that.generateProjection(hiddenFields, (_a = req.query) === null || _a === void 0 ? void 0 : _a.p)));
1106
1352
  }
1107
1353
  });
1108
- }, this);
1109
- };
1110
- DataForm.prototype.entityDelete = function () {
1111
- return _.bind(function (req, res, next) {
1112
- function internalRemove(doc) {
1113
- doc.remove(function (err) {
1114
- if (err) {
1115
- return res.send({ success: false });
1116
- }
1117
- return res.send({ success: true });
1118
- });
1354
+ };
1355
+ ;
1356
+ /*
1357
+ * Entity request goes here first
1358
+ * It retrieves the resource
1359
+ */
1360
+ FormsAngular.prototype.processEntity = function (req, res, next) {
1361
+ if (!(req.resource = this.getResource(req.params.resourceName))) {
1362
+ next();
1363
+ return;
1119
1364
  }
1120
- this.processEntity(req, res, function () {
1121
- if (!req.resource) {
1122
- next();
1123
- return;
1365
+ this.generateQueryForEntity(req, req.resource, req.params.id, function (err, query) {
1366
+ if (err) {
1367
+ return res.status(500).send({
1368
+ success: false,
1369
+ err: util.inspect(err)
1370
+ });
1124
1371
  }
1125
- var doc = req.doc;
1126
- if (typeof req.resource.options.onRemove === 'function') {
1127
- req.resource.options.onRemove(doc, req, function (err) {
1372
+ else {
1373
+ query.exec(function (err, doc) {
1128
1374
  if (err) {
1129
- throw err;
1375
+ return res.status(400).send({
1376
+ success: false,
1377
+ err: util.inspect(err)
1378
+ });
1379
+ }
1380
+ else if (doc == null) {
1381
+ return res.status(404).send({
1382
+ success: false,
1383
+ err: 'Record not found'
1384
+ });
1130
1385
  }
1131
- internalRemove(doc);
1386
+ req.doc = doc;
1387
+ next();
1132
1388
  });
1133
1389
  }
1134
- else {
1135
- internalRemove(doc);
1136
- }
1137
1390
  });
1138
- }, this);
1139
- };
1140
- DataForm.prototype.entityList = function () {
1141
- return _.bind(function (req, res, next) {
1142
- var that = this;
1143
- this.processEntity(req, res, function () {
1144
- if (!req.resource) {
1145
- return next();
1146
- }
1147
- that.getListFields(req.resource, req.doc, function (err, display) {
1148
- if (err) {
1149
- return res.status(500).send(err);
1391
+ };
1392
+ ;
1393
+ /**
1394
+ * Gets a single entity
1395
+ *
1396
+ * @return {Function} The function to use as route
1397
+ */
1398
+ FormsAngular.prototype.entityGet = function () {
1399
+ return _.bind(function (req, res, next) {
1400
+ this.processEntity(req, res, function () {
1401
+ if (!req.resource) {
1402
+ return next();
1403
+ }
1404
+ if (req.resource.options.onAccess) {
1405
+ req.resource.options.onAccess(req, function () {
1406
+ return res.status(200).send(req.doc);
1407
+ });
1150
1408
  }
1151
1409
  else {
1152
- return res.send({ list: display });
1410
+ return res.status(200).send(req.doc);
1153
1411
  }
1154
1412
  });
1155
- });
1156
- }, this);
1157
- };
1413
+ }, this);
1414
+ };
1415
+ ;
1416
+ FormsAngular.prototype.replaceHiddenFields = function (record, data) {
1417
+ var self = this;
1418
+ if (record) {
1419
+ record._replacingHiddenFields = true;
1420
+ _.each(data, function (value, name) {
1421
+ if (_.isObject(value) && !Array.isArray(value)) {
1422
+ self.replaceHiddenFields(record[name], value);
1423
+ }
1424
+ else if (!record[name]) {
1425
+ record[name] = value;
1426
+ }
1427
+ });
1428
+ delete record._replacingHiddenFields;
1429
+ }
1430
+ };
1431
+ ;
1432
+ FormsAngular.prototype.entityPut = function () {
1433
+ return _.bind(function (req, res, next) {
1434
+ var that = this;
1435
+ this.processEntity(req, res, function () {
1436
+ if (!req.resource) {
1437
+ next();
1438
+ return;
1439
+ }
1440
+ if (!req.body) {
1441
+ throw new Error('Nothing submitted.');
1442
+ }
1443
+ var cleansedBody = that.cleanseRequest(req);
1444
+ // Merge
1445
+ for (var prop in cleansedBody) {
1446
+ if (cleansedBody.hasOwnProperty(prop)) {
1447
+ req.doc.set(prop, cleansedBody[prop] === '' ? undefined : cleansedBody[prop]);
1448
+ }
1449
+ }
1450
+ if (req.resource.options.hide !== undefined) {
1451
+ var hiddenFields_2 = that.generateHiddenFields(req.resource, true);
1452
+ hiddenFields_2._id = false;
1453
+ req.resource.model.findById(req.doc._id, hiddenFields_2, { lean: true }, function (err, data) {
1454
+ that.replaceHiddenFields(req.doc, data);
1455
+ that.saveAndRespond(req, res, hiddenFields_2);
1456
+ });
1457
+ }
1458
+ else {
1459
+ that.saveAndRespond(req, res);
1460
+ }
1461
+ });
1462
+ }, this);
1463
+ };
1464
+ ;
1465
+ FormsAngular.prototype.entityDelete = function () {
1466
+ var that = this;
1467
+ return _.bind(function (req, res, next) {
1468
+ return __awaiter(this, void 0, void 0, function () {
1469
+ function generateDependencyList(resource) {
1470
+ if (resource.options.dependents === undefined) {
1471
+ resource.options.dependents = that.resources.reduce(function (acc, r) {
1472
+ function searchPaths(schema, prefix) {
1473
+ var fldList = [];
1474
+ for (var fld in schema.paths) {
1475
+ if (schema.paths.hasOwnProperty(fld)) {
1476
+ var parts = fld.split('.');
1477
+ var schemaType = schema.tree;
1478
+ while (parts.length > 0) {
1479
+ schemaType = schemaType[parts.shift()];
1480
+ }
1481
+ if (schemaType.type) {
1482
+ if (schemaType.type.name === 'ObjectId' && schemaType.ref === resource.resourceName) {
1483
+ fldList.push(prefix + fld);
1484
+ }
1485
+ else if (_.isArray(schemaType.type)) {
1486
+ schemaType.type.forEach(function (t) {
1487
+ searchPaths(t, prefix + fld + '.');
1488
+ });
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ if (fldList.length > 0) {
1494
+ acc.push({ resource: r, keys: fldList });
1495
+ }
1496
+ }
1497
+ if (r !== resource) {
1498
+ searchPaths(r.model.schema, '');
1499
+ }
1500
+ return acc;
1501
+ }, []);
1502
+ }
1503
+ }
1504
+ function removeDoc(doc, resource) {
1505
+ return __awaiter(this, void 0, void 0, function () {
1506
+ var promises_1;
1507
+ return __generator(this, function (_a) {
1508
+ switch (resource.options.handleRemove) {
1509
+ case 'allow':
1510
+ // old behaviour - no attempt to maintain data integrity
1511
+ return [2 /*return*/, doc.remove()];
1512
+ case 'cascade':
1513
+ generateDependencyList(resource);
1514
+ res.status(400).send('"cascade" option not yet supported');
1515
+ break;
1516
+ default:
1517
+ generateDependencyList(resource);
1518
+ promises_1 = [];
1519
+ resource.options.dependents.forEach(function (collection) {
1520
+ collection.keys.forEach(function (key) {
1521
+ var _a;
1522
+ promises_1.push({
1523
+ p: collection.resource.model.find((_a = {}, _a[key] = doc._id, _a)).limit(1).exec(),
1524
+ collection: collection,
1525
+ key: key
1526
+ });
1527
+ });
1528
+ });
1529
+ return [2 /*return*/, Promise.all(promises_1.map(function (p) { return p.p; }))
1530
+ .then(function (results) {
1531
+ results.forEach(function (r, i) {
1532
+ if (r.length > 0) {
1533
+ throw new ForeignKeyError(resource.resourceName, promises_1[i].collection.resource.resourceName, promises_1[i].key, r[0]._id);
1534
+ }
1535
+ });
1536
+ return doc.remove();
1537
+ })];
1538
+ }
1539
+ return [2 /*return*/];
1540
+ });
1541
+ });
1542
+ }
1543
+ function runDeletion(doc, resource) {
1544
+ return __awaiter(this, void 0, void 0, function () {
1545
+ return __generator(this, function (_a) {
1546
+ return [2 /*return*/, new Promise(function (resolve) {
1547
+ if (resource.options.onRemove) {
1548
+ resource.options.onRemove(doc, req, function (err) {
1549
+ return __awaiter(this, void 0, void 0, function () {
1550
+ return __generator(this, function (_a) {
1551
+ if (err) {
1552
+ throw err;
1553
+ }
1554
+ resolve(removeDoc(doc, resource));
1555
+ return [2 /*return*/];
1556
+ });
1557
+ });
1558
+ });
1559
+ }
1560
+ else {
1561
+ resolve(removeDoc(doc, resource));
1562
+ }
1563
+ })];
1564
+ });
1565
+ });
1566
+ }
1567
+ return __generator(this, function (_a) {
1568
+ this.processEntity(req, res, function () {
1569
+ return __awaiter(this, void 0, void 0, function () {
1570
+ var doc, e_1;
1571
+ return __generator(this, function (_a) {
1572
+ switch (_a.label) {
1573
+ case 0:
1574
+ if (!req.resource) {
1575
+ next();
1576
+ return [2 /*return*/];
1577
+ }
1578
+ doc = req.doc;
1579
+ _a.label = 1;
1580
+ case 1:
1581
+ _a.trys.push([1, 3, , 4]);
1582
+ return [4 /*yield*/, runDeletion(doc, req.resource)];
1583
+ case 2:
1584
+ void (_a.sent());
1585
+ res.status(200).send();
1586
+ return [3 /*break*/, 4];
1587
+ case 3:
1588
+ e_1 = _a.sent();
1589
+ if (e_1 instanceof ForeignKeyError) {
1590
+ res.status(400).send(e_1.message);
1591
+ }
1592
+ else {
1593
+ res.status(500).send(e_1.message);
1594
+ }
1595
+ return [3 /*break*/, 4];
1596
+ case 4: return [2 /*return*/];
1597
+ }
1598
+ });
1599
+ });
1600
+ });
1601
+ return [2 /*return*/];
1602
+ });
1603
+ });
1604
+ }, this);
1605
+ };
1606
+ ;
1607
+ FormsAngular.prototype.entityList = function () {
1608
+ return _.bind(function (req, res, next) {
1609
+ var that = this;
1610
+ this.processEntity(req, res, function () {
1611
+ if (!req.resource) {
1612
+ return next();
1613
+ }
1614
+ that.getListFields(req.resource, req.doc, function (err, display) {
1615
+ if (err) {
1616
+ return res.status(500).send(err);
1617
+ }
1618
+ else {
1619
+ return res.send({ list: display });
1620
+ }
1621
+ });
1622
+ });
1623
+ }, this);
1624
+ };
1625
+ ;
1626
+ FormsAngular.prototype.extractTimestampFromMongoID = function (record) {
1627
+ var timestamp = record.toString().substring(0, 8);
1628
+ return new Date(parseInt(timestamp, 16) * 1000);
1629
+ };
1630
+ return FormsAngular;
1631
+ }());
1632
+ exports.FormsAngular = FormsAngular;
1633
+ var ForeignKeyError = /** @class */ (function (_super) {
1634
+ __extends(ForeignKeyError, _super);
1635
+ function ForeignKeyError(resourceName, foreignKeyOnResource, foreignItem, id) {
1636
+ var _this = _super.call(this, "Cannot delete this ".concat(resourceName, ", as it is the ").concat(foreignItem, " on ").concat(foreignKeyOnResource, " ").concat(id)) || this;
1637
+ _this.name = "ForeignKeyError";
1638
+ _this.stack = new global.Error('').stack;
1639
+ return _this;
1640
+ }
1641
+ return ForeignKeyError;
1642
+ }(global.Error));