forms-angular 0.12.0-beta.296 → 0.12.0-beta.297

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.
@@ -4,25 +4,31 @@ exports.FormsAngular = void 0;
4
4
  const mongoose_1 = require("mongoose");
5
5
  // This part of forms-angular borrows from https://github.com/Alexandre-Strzelewicz/angular-bridge
6
6
  // (now https://github.com/Unitech/angular-bridge
7
- const _ = require('lodash');
8
- const util = require('util');
9
- const extend = require('node.extend');
10
- const async = require('async');
7
+ const _ = require("lodash");
8
+ const util = require("util");
9
+ const extend = require("node.extend");
10
+ const async = require("async");
11
11
  let debug = false;
12
12
  function logTheAPICalls(req, res, next) {
13
- void (res);
14
- console.log('API : ' + req.method + ' ' + req.url + ' [ ' + JSON.stringify(req.body) + ' ]');
13
+ void res;
14
+ console.log("API : " +
15
+ req.method +
16
+ " " +
17
+ req.url +
18
+ " [ " +
19
+ JSON.stringify(req.body) +
20
+ " ]");
15
21
  next();
16
22
  }
17
23
  var entityMap = {
18
- '&': '&',
19
- '<': '&lt;',
20
- '>': '&gt;',
21
- '"': '&quot;',
22
- "'": '&#39;',
23
- '/': '&#x2F;',
24
- '`': '&#x60;',
25
- '=': '&#x3D;'
24
+ "&": "&amp;",
25
+ "<": "&lt;",
26
+ ">": "&gt;",
27
+ '"': "&quot;",
28
+ "'": "&#39;",
29
+ "/": "&#x2F;",
30
+ "`": "&#x60;",
31
+ "=": "&#x3D;",
26
32
  };
27
33
  function escapeHtml(string) {
28
34
  return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) {
@@ -31,7 +37,9 @@ function escapeHtml(string) {
31
37
  }
32
38
  function processArgs(options, array) {
33
39
  if (options.authentication) {
34
- let authArray = _.isArray(options.authentication) ? options.authentication : [options.authentication];
40
+ let authArray = _.isArray(options.authentication)
41
+ ? options.authentication
42
+ : [options.authentication];
35
43
  for (let i = authArray.length - 1; i >= 0; i--) {
36
44
  array.splice(1, 0, authArray[i]);
37
45
  }
@@ -48,10 +56,10 @@ class FormsAngular {
48
56
  this.app = app;
49
57
  app.locals.formsAngular = app.locals.formsAngular || [];
50
58
  app.locals.formsAngular.push(this);
51
- mongoose.set('debug', debug);
59
+ mongoose.set("debug", debug);
52
60
  mongoose.Promise = global.Promise;
53
61
  this.options = _.extend({
54
- urlPrefix: '/api/'
62
+ urlPrefix: "/api/",
55
63
  }, options || {});
56
64
  this.resources = [];
57
65
  this.searchFunc = async.forEach;
@@ -62,41 +70,73 @@ class FormsAngular {
62
70
  this.options.plugins[pluginName] = Object.assign(this.options.plugins[pluginName], pluginObj.plugin(this, processArgs, pluginObj.options));
63
71
  }
64
72
  }
65
- const search = 'search/', schema = 'schema/', report = 'report/', resourceName = ':resourceName', id = '/:id', formName = '/:formName', newClarifier = '/new';
66
- this.app.get.apply(this.app, processArgs(this.options, ['models', this.models()]));
73
+ const search = "search/", schema = "schema/", report = "report/", resourceName = ":resourceName", id = "/:id", formName = "/:formName", newClarifier = "/new";
74
+ this.app.get.apply(this.app, processArgs(this.options, ["models", this.models()]));
67
75
  this.app.get.apply(this.app, processArgs(this.options, [search + resourceName, this.search()]));
68
76
  this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName, this.schema()]));
69
- this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName + formName, this.schema()]));
77
+ this.app.get.apply(this.app, processArgs(this.options, [
78
+ schema + resourceName + formName,
79
+ this.schema(),
80
+ ]));
70
81
  this.app.get.apply(this.app, processArgs(this.options, [report + resourceName, this.report()]));
71
- this.app.get.apply(this.app, processArgs(this.options, [report + resourceName + '/:reportName', this.report()]));
82
+ this.app.get.apply(this.app, processArgs(this.options, [
83
+ report + resourceName + "/:reportName",
84
+ this.report(),
85
+ ]));
72
86
  this.app.get.apply(this.app, processArgs(this.options, [resourceName, this.collectionGet()]));
73
87
  // return the List attributes for all records - used by record-handler's setUpLookupOptions() method, for cases
74
- // where there's a lookup that doesn't use the fngajax option
75
- this.app.get.apply(this.app, processArgs(this.options, [resourceName + '/listAll', this.entityListAll()]));
88
+ // where there's a lookup that doesn't use the fngajax option
89
+ this.app.get.apply(this.app, processArgs(this.options, [
90
+ resourceName + "/listAll",
91
+ this.entityListAll(),
92
+ ]));
76
93
  // return the List attributes for a record - used by fng-ui-select
77
- this.app.get.apply(this.app, processArgs(this.options, [resourceName + id + '/list', this.entityList()]));
94
+ this.app.get.apply(this.app, processArgs(this.options, [
95
+ resourceName + id + "/list",
96
+ this.entityList(),
97
+ ]));
78
98
  // 2x get, with and without formName
79
99
  this.app.get.apply(this.app, processArgs(this.options, [resourceName + id, this.entityGet()]));
80
- this.app.get.apply(this.app, processArgs(this.options, [resourceName + formName + id, this.entityGet()])); // We don't use the form name, but it can optionally be included so it can be referenced by the permissions check
100
+ this.app.get.apply(this.app, processArgs(this.options, [
101
+ resourceName + formName + id,
102
+ this.entityGet(),
103
+ ])); // We don't use the form name, but it can optionally be included so it can be referenced by the permissions check
81
104
  // 3x post (for creating a new record), with and without formName, and in the case of without, with or without /new (which isn't needed if there's no formName)
82
105
  this.app.post.apply(this.app, processArgs(this.options, [resourceName, this.collectionPost()]));
83
- this.app.post.apply(this.app, processArgs(this.options, [resourceName + newClarifier, this.collectionPost()]));
84
- this.app.post.apply(this.app, processArgs(this.options, [resourceName + formName + newClarifier, this.collectionPost()]));
106
+ this.app.post.apply(this.app, processArgs(this.options, [
107
+ resourceName + newClarifier,
108
+ this.collectionPost(),
109
+ ]));
110
+ this.app.post.apply(this.app, processArgs(this.options, [
111
+ resourceName + formName + newClarifier,
112
+ this.collectionPost(),
113
+ ]));
85
114
  // 2x post and 2x put (for saving modifications to existing record), with and without formName
86
115
  // (You can POST or PUT to update data)
87
116
  this.app.post.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()]));
88
- this.app.post.apply(this.app, processArgs(this.options, [resourceName + formName + id, this.entityPut()]));
117
+ this.app.post.apply(this.app, processArgs(this.options, [
118
+ resourceName + formName + id,
119
+ this.entityPut(),
120
+ ]));
89
121
  this.app.put.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()]));
90
- this.app.put.apply(this.app, processArgs(this.options, [resourceName + formName + id, this.entityPut()]));
122
+ this.app.put.apply(this.app, processArgs(this.options, [
123
+ resourceName + formName + id,
124
+ this.entityPut(),
125
+ ]));
91
126
  // 2x delete, with and without formName
92
127
  this.app.delete.apply(this.app, processArgs(this.options, [resourceName + id, this.entityDelete()]));
93
- this.app.delete.apply(this.app, processArgs(this.options, [resourceName + formName + id, this.entityDelete()]));
94
- this.app.get.apply(this.app, processArgs(this.options, ['search', this.searchAll()]));
128
+ this.app.delete.apply(this.app, processArgs(this.options, [
129
+ resourceName + formName + id,
130
+ this.entityDelete(),
131
+ ]));
132
+ this.app.get.apply(this.app, processArgs(this.options, ["search", this.searchAll()]));
95
133
  }
96
134
  getFirstMatchingField(resource, doc, keyList, type) {
97
135
  for (let i = 0; i < keyList.length; i++) {
98
- let fieldDetails = resource.model.schema['tree'][keyList[i]];
99
- if (fieldDetails.type && (!type || fieldDetails.type.name === type) && keyList[i] !== '_id') {
136
+ let fieldDetails = resource.model.schema["tree"][keyList[i]];
137
+ if (fieldDetails.type &&
138
+ (!type || fieldDetails.type.name === type) &&
139
+ keyList[i] !== "_id") {
100
140
  resource.options.listFields = [{ field: keyList[i] }];
101
141
  return doc ? doc[keyList[i]] : keyList[i];
102
142
  }
@@ -104,15 +144,15 @@ class FormsAngular {
104
144
  }
105
145
  getListFields(resource, doc, cb) {
106
146
  const that = this;
107
- let display = '';
147
+ let display = "";
108
148
  let listFields = resource.options.listFields;
109
149
  if (listFields) {
110
150
  async.map(listFields, function (aField, cbm) {
111
- if (typeof doc[aField.field] !== 'undefined') {
151
+ if (typeof doc[aField.field] !== "undefined") {
112
152
  if (aField.params) {
113
153
  if (aField.params.ref) {
114
- let fieldOptions = resource.model.schema['paths'][aField.field].options;
115
- if (typeof fieldOptions.ref === 'string') {
154
+ let fieldOptions = resource.model.schema["paths"][aField.field].options;
155
+ if (typeof fieldOptions.ref === "string") {
116
156
  let lookupResource = that.getResource(fieldOptions.ref);
117
157
  if (lookupResource) {
118
158
  let hiddenFields = that.generateHiddenFields(lookupResource, false);
@@ -129,12 +169,12 @@ class FormsAngular {
129
169
  }
130
170
  }
131
171
  else {
132
- throw new Error('No support for ref type ' + aField.params.ref.type);
172
+ throw new Error("No support for ref type " + aField.params.ref.type);
133
173
  }
134
174
  }
135
- else if (aField.params.params === 'timestamp') {
175
+ else if (aField.params.params === "timestamp") {
136
176
  let date = that.extractTimestampFromMongoID(doc[aField.field]);
137
- cbm(null, date.toLocaleDateString() + ' ' + date.toLocaleTimeString());
177
+ cbm(null, date.toLocaleDateString() + " " + date.toLocaleTimeString());
138
178
  }
139
179
  else if (!aField.params.params) {
140
180
  throw new Error(`Missing idIsList params for resource ${resource.resourceName}: ${JSON.stringify(aField.params)}`);
@@ -157,7 +197,7 @@ class FormsAngular {
157
197
  }
158
198
  }
159
199
  else {
160
- cbm(null, '');
200
+ cbm(null, "");
161
201
  }
162
202
  }, function (err, results) {
163
203
  if (err) {
@@ -165,24 +205,24 @@ class FormsAngular {
165
205
  }
166
206
  else {
167
207
  if (results) {
168
- cb(err, results.join(' ').trim());
208
+ cb(err, results.join(" ").trim());
169
209
  }
170
210
  else {
171
- console.log('No results ' + listFields);
211
+ console.log("No results " + listFields);
172
212
  }
173
213
  }
174
214
  });
175
215
  }
176
216
  else {
177
- const keyList = Object.keys(resource.model.schema['tree']);
217
+ const keyList = Object.keys(resource.model.schema["tree"]);
178
218
  // No list field specified - use the first String field,
179
- display = this.getFirstMatchingField(resource, doc, keyList, 'String') ||
180
- // and if there aren't any then just take the first field
181
- this.getFirstMatchingField(resource, doc, keyList);
219
+ display =
220
+ this.getFirstMatchingField(resource, doc, keyList, "String") ||
221
+ // and if there aren't any then just take the first field
222
+ this.getFirstMatchingField(resource, doc, keyList);
182
223
  cb(null, display.trim());
183
224
  }
184
225
  }
185
- ;
186
226
  // generate a Mongo projection that can be used to restrict a query to return only those fields from the given
187
227
  // resource that are identified as "list" fields (i.e., ones that should appear whenever records of that type are
188
228
  // displayed in a list)
@@ -204,47 +244,47 @@ class FormsAngular {
204
244
  }
205
245
  }
206
246
  else {
207
- const keyList = Object.keys(resource.model.schema['tree']);
208
- const firstField = (
247
+ const keyList = Object.keys(resource.model.schema["tree"]);
248
+ const firstField =
209
249
  // No list field specified - use the first String field,
210
- this.getFirstMatchingField(resource, undefined, keyList, 'String') ||
250
+ this.getFirstMatchingField(resource, undefined, keyList, "String") ||
211
251
  // and if there aren't any then just take the first field
212
- this.getFirstMatchingField(resource, undefined, keyList));
252
+ this.getFirstMatchingField(resource, undefined, keyList);
213
253
  projection[firstField] = 1;
214
254
  }
215
255
  return projection;
216
256
  }
217
- ;
218
257
  newResource(model, options) {
219
258
  options = options || {};
220
259
  options.suppressDeprecatedMessage = true;
221
260
  let passModel = model;
222
- if (typeof model !== 'function') {
261
+ if (typeof model !== "function") {
223
262
  passModel = model.model;
224
263
  }
225
264
  this.addResource(passModel.modelName, passModel, options);
226
265
  }
227
- ;
228
266
  // Add a resource, specifying the model and any options.
229
267
  // Models may include their own options, which means they can be passed through from the model file
230
268
  addResource(resourceName, model, options) {
231
269
  let resource = {
232
270
  resourceName: resourceName,
233
271
  resourceNameLower: resourceName.toLowerCase(),
234
- options: options || {}
272
+ options: options || {},
235
273
  };
236
274
  if (!resource.options.suppressDeprecatedMessage) {
237
- console.log('addResource is deprecated - see https://github.com/forms-angular/forms-angular/issues/39');
275
+ console.log("addResource is deprecated - see https://github.com/forms-angular/forms-angular/issues/39");
238
276
  }
239
277
  // Check all the synonyms are lower case
240
- resource.options.synonyms?.forEach(s => { s.name = s.name.toLowerCase(); });
241
- if (typeof model === 'function') {
278
+ resource.options.synonyms?.forEach((s) => {
279
+ s.name = s.name.toLowerCase();
280
+ });
281
+ if (typeof model === "function") {
242
282
  resource.model = model;
243
283
  }
244
284
  else {
245
285
  resource.model = model.model;
246
286
  for (const prop in model) {
247
- if (model.hasOwnProperty(prop) && prop !== 'model') {
287
+ if (model.hasOwnProperty(prop) && prop !== "model") {
248
288
  resource.options[prop] = model[prop];
249
289
  }
250
290
  }
@@ -264,9 +304,10 @@ class FormsAngular {
264
304
  // }
265
305
  function addSearchFields(schema, pathSoFar) {
266
306
  for (let path in schema.paths) {
267
- if (path !== '_id' && schema.paths.hasOwnProperty(path)) {
307
+ if (path !== "_id" && schema.paths.hasOwnProperty(path)) {
268
308
  const qualifiedPath = pathSoFar ? pathSoFar + "." + path : path;
269
- if (schema.paths[path].options.index && !schema.paths[path].options.noSearch) {
309
+ if (schema.paths[path].options.index &&
310
+ !schema.paths[path].options.noSearch) {
270
311
  if (resource.options.searchFields.indexOf(qualifiedPath) === -1) {
271
312
  resource.options.searchFields.push(qualifiedPath);
272
313
  }
@@ -290,19 +331,16 @@ class FormsAngular {
290
331
  this.resources.push(resource);
291
332
  }
292
333
  }
293
- ;
294
334
  getResource(name) {
295
335
  return _.find(this.resources, function (resource) {
296
- return resource.resourceName === name || resource.options.resourceName === name;
336
+ return (resource.resourceName === name || resource.options.resourceName === name);
297
337
  });
298
338
  }
299
- ;
300
339
  getResourceFromCollection(name) {
301
340
  return _.find(this.resources, function (resource) {
302
341
  return resource.model.collection.collectionName === name;
303
342
  });
304
343
  }
305
- ;
306
344
  // Using the given (already-populated) AmbiguousRecordStore, generate text suitable for
307
345
  // disambiguation of each ambiguous record, and pass that to the given disambiguateItemCallback
308
346
  // so our caller can decorate the ambiguous record in whatever way it deems appropriate.
@@ -330,12 +368,17 @@ class FormsAngular {
330
368
  const resource = that.getResource(resourceName);
331
369
  const projection = that.generateListFieldProjection(resource);
332
370
  resource.model
333
- .find({ _id: { $in: ambiguousRecordStore[resourceName].map((sr) => sr[disambiguationField]) } })
371
+ .find({
372
+ _id: {
373
+ $in: ambiguousRecordStore[resourceName].map((sr) => sr[disambiguationField]),
374
+ },
375
+ })
334
376
  .select(projection)
335
377
  .lean()
336
378
  .then((disambiguationRecs) => {
337
379
  for (const ambiguousResult of ambiguousRecordStore[resourceName]) {
338
- const disambiguator = disambiguationRecs.find((d) => d._id.toString() === ambiguousResult[disambiguationField].toString());
380
+ const disambiguator = disambiguationRecs.find((d) => d._id.toString() ===
381
+ ambiguousResult[disambiguationField].toString());
339
382
  if (disambiguator) {
340
383
  let suffix = "";
341
384
  for (const listField in projection) {
@@ -364,15 +407,20 @@ class FormsAngular {
364
407
  });
365
408
  }
366
409
  internalSearch(req, resourcesToSearch, includeResourceInResults, limit, callback) {
367
- if (typeof req.query === 'undefined') {
410
+ if (typeof req.query === "undefined") {
368
411
  req.query = {};
369
412
  }
370
- const timestamps = { sentAt: req.query.sentAt, startedAt: new Date().valueOf(), completedAt: undefined };
371
- let searches = [], resourceCount = resourcesToSearch.length, searchFor = req.query.q || '', filter = req.query.f;
413
+ const timestamps = {
414
+ sentAt: req.query.sentAt,
415
+ startedAt: new Date().valueOf(),
416
+ completedAt: undefined,
417
+ };
418
+ let searches = [], resourceCount = resourcesToSearch.length, searchFor = req.query.q || "", filter = req.query.f;
372
419
  function translate(string, array, context) {
373
420
  if (array) {
374
421
  let translation = _.find(array, function (fromTo) {
375
- return fromTo.from === string && (!fromTo.context || fromTo.context === context);
422
+ return (fromTo.from === string &&
423
+ (!fromTo.context || fromTo.context === context));
376
424
  });
377
425
  if (translation) {
378
426
  string = translation.to;
@@ -382,10 +430,10 @@ class FormsAngular {
382
430
  }
383
431
  // return a string that determines the sort order of the resultObject
384
432
  function calcResultValue(obj) {
385
- function padLeft(score, reqLength, str = '0') {
386
- return new Array(1 + reqLength - String(score).length).join(str) + score;
433
+ function padLeft(score, reqLength, str = "0") {
434
+ return (new Array(1 + reqLength - String(score).length).join(str) + score);
387
435
  }
388
- let sortString = '';
436
+ let sortString = "";
389
437
  sortString += padLeft(obj.addHits || 9, 1);
390
438
  sortString += padLeft(obj.searchImportance || 99, 2);
391
439
  sortString += padLeft(obj.weighting || 9999, 4);
@@ -398,7 +446,7 @@ class FormsAngular {
398
446
  // See if we are narrowing down the resources
399
447
  let collectionName;
400
448
  let collectionNameLower;
401
- let colonPos = searchFor.indexOf(':');
449
+ let colonPos = searchFor.indexOf(":");
402
450
  switch (colonPos) {
403
451
  case -1:
404
452
  // Original behaviour = do nothing different
@@ -410,22 +458,30 @@ class FormsAngular {
410
458
  collectionName = searchFor.slice(0, colonPos);
411
459
  collectionNameLower = collectionName.toLowerCase();
412
460
  searchFor = searchFor.slice(colonPos + 1, 999).trim();
413
- if (searchFor === '') {
414
- searchFor = '?';
461
+ if (searchFor === "") {
462
+ searchFor = "?";
415
463
  }
416
464
  break;
417
465
  }
418
466
  for (let i = 0; i < resourceCount; i++) {
419
467
  let resource = resourcesToSearch[i];
420
- if (resourceCount === 1 || (resource.options.searchImportance !== false && (!collectionName || collectionNameLower === resource.resourceNameLower || resource.options?.synonyms?.find(s => s.name === collectionNameLower)))) {
468
+ if (resourceCount === 1 ||
469
+ (resource.options.searchImportance !== false &&
470
+ (!collectionName ||
471
+ collectionNameLower === resource.resourceNameLower ||
472
+ resource.options?.synonyms?.find((s) => s.name === collectionNameLower)))) {
421
473
  let searchFields = resource.options.searchFields;
422
474
  if (searchFields.length === 0) {
423
- console.log('ERROR: Searching on a collection with no indexes ' + resource.resourceName);
475
+ console.log("ERROR: Searching on a collection with no indexes " +
476
+ resource.resourceName);
424
477
  }
425
- let synonymObj = resource.options?.synonyms?.find(s => s.name.toLowerCase() === collectionNameLower);
478
+ let synonymObj = resource.options?.synonyms?.find((s) => s.name.toLowerCase() === collectionNameLower);
426
479
  const synonymFilter = synonymObj?.filter;
427
480
  for (let m = 0; m < searchFields.length; m++) {
428
- let searchObj = { resource: resource, field: searchFields[m] };
481
+ let searchObj = {
482
+ resource: resource,
483
+ field: searchFields[m],
484
+ };
429
485
  if (synonymFilter) {
430
486
  searchObj.filter = synonymFilter;
431
487
  }
@@ -439,27 +495,32 @@ class FormsAngular {
439
495
  let searchCriteria;
440
496
  let searchStrings;
441
497
  let multiMatchPossible = false;
442
- if (searchFor === '?') {
498
+ if (searchFor === "?") {
443
499
  // interpret this as a wildcard (so there is no way to search for ?
444
500
  searchCriteria = null;
445
501
  }
446
502
  else {
447
503
  // Support for searching anywhere in a field by starting with *
448
- let startAnchor = '^';
449
- if (searchFor.slice(0, 1) === '*') {
450
- startAnchor = '';
504
+ let startAnchor = "^";
505
+ if (searchFor.slice(0, 1) === "*") {
506
+ startAnchor = "";
451
507
  searchFor = searchFor.slice(1);
452
508
  }
453
509
  // THe snippet to escape the special characters comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
454
- searchFor = searchFor.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
455
- multiMatchPossible = searchFor.includes(' ');
510
+ searchFor = searchFor.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
511
+ multiMatchPossible = searchFor.includes(" ");
456
512
  if (multiMatchPossible) {
457
- searchStrings = searchFor.split(' ');
513
+ searchStrings = searchFor.split(" ");
458
514
  }
459
- let modifiedSearchStr = multiMatchPossible ? searchStrings.join('|') : searchFor;
515
+ let modifiedSearchStr = multiMatchPossible
516
+ ? searchStrings.join("|")
517
+ : searchFor;
460
518
  searchFor = searchFor.toLowerCase(); // For later case-insensitive comparison
461
519
  // Removed the logic that preserved spaces when collection was specified because Louise asked me to.
462
- searchCriteria = { $regex: `${startAnchor}(${modifiedSearchStr})`, $options: 'i' };
520
+ searchCriteria = {
521
+ $regex: `${startAnchor}(${modifiedSearchStr})`,
522
+ $options: "i",
523
+ };
463
524
  }
464
525
  let handleSearchResultsFromIndex = function (err, docs, item, cb) {
465
526
  function handleSingleSearchResult(aDoc, cbdoc) {
@@ -475,7 +536,8 @@ class FormsAngular {
475
536
  }
476
537
  }
477
538
  }
478
- resultObject.searchImportance = item.resource.options.searchImportance || 99;
539
+ resultObject.searchImportance =
540
+ item.resource.options.searchImportance || 99;
479
541
  if (item.resource.options.localisationData) {
480
542
  resultObject.resource = translate(resultObject.resource, item.resource.options.localisationData, "resource");
481
543
  resultObject.resourceText = translate(resultObject.resourceText, item.resource.options.localisationData, "resourceText");
@@ -496,7 +558,8 @@ class FormsAngular {
496
558
  if (multiMatchPossible) {
497
559
  // record the index of string that matched, so we don't count it against another field
498
560
  for (let i = 0; i < searchStrings.length; i++) {
499
- if (!resultObject.matched.includes(i) && aDoc[item.field]?.toLowerCase().indexOf(searchStrings[i]) === 0) {
561
+ if (!resultObject.matched.includes(i) &&
562
+ aDoc[item.field]?.toLowerCase().indexOf(searchStrings[i]) === 0) {
500
563
  resultObject.matched.push(i);
501
564
  resultObject.addHits = Math.max((resultObject.addHits || 9) - 1, 0);
502
565
  // remove it from current position
@@ -527,8 +590,7 @@ class FormsAngular {
527
590
  // Use special listings format if defined
528
591
  let specialListingFormat = item.resource.options.searchResultFormat;
529
592
  if (specialListingFormat) {
530
- specialListingFormat.apply(aDoc, [req])
531
- .then((resultObj) => {
593
+ specialListingFormat.apply(aDoc, [req]).then((resultObj) => {
532
594
  resultObject = resultObj;
533
595
  resultObject.addHits = addHits;
534
596
  resultObject.disambiguationResource = disambiguationResource;
@@ -548,10 +610,11 @@ class FormsAngular {
548
610
  addHits,
549
611
  disambiguationResource,
550
612
  disambiguationId,
551
- text: description
613
+ text: description,
552
614
  };
553
615
  if (resourceCount > 1 || includeResourceInResults) {
554
- resultObject.resource = resultObject.resourceText = item.resource.resourceName;
616
+ resultObject.resource = resultObject.resourceText =
617
+ item.resource.resourceName;
555
618
  }
556
619
  handleResultsInList();
557
620
  }
@@ -577,7 +640,7 @@ class FormsAngular {
577
640
  let obj1 = {}, obj2 = {};
578
641
  obj1[item.field] = searchFilter[item.field];
579
642
  obj2[item.field] = searchCriteria;
580
- searchDoc['$and'] = [obj1, obj2];
643
+ searchDoc["$and"] = [obj1, obj2];
581
644
  }
582
645
  else {
583
646
  if (searchCriteria) {
@@ -591,13 +654,13 @@ class FormsAngular {
591
654
  }
592
655
  }
593
656
  /*
594
- 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.
595
- 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
596
- 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
597
- results end up containing people who only match either c or e (but have been accessed much more recently).
598
-
599
- Increasing the number would be a short term fix at the cost of slowing down the search.
600
- */
657
+ 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.
658
+ 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
659
+ 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
660
+ results end up containing people who only match either c or e (but have been accessed much more recently).
661
+
662
+ Increasing the number would be a short term fix at the cost of slowing down the search.
663
+ */
601
664
  // TODO : Figure out a better way to deal with this
602
665
  if (item.resource.options.searchFunc) {
603
666
  item.resource.options.searchFunc(item.resource, req, null, searchDoc, item.resource.options.searchOrder, limit ? limit + 200 : 0, null, function (err, docs) {
@@ -642,7 +705,6 @@ class FormsAngular {
642
705
  }
643
706
  });
644
707
  }
645
- ;
646
708
  wrapInternalSearch(req, res, resourcesToSearch, includeResourceInResults, limit) {
647
709
  this.internalSearch(req, resourcesToSearch, includeResourceInResults, limit, function (err, resultsObject) {
648
710
  if (err) {
@@ -653,7 +715,6 @@ class FormsAngular {
653
715
  }
654
716
  });
655
717
  }
656
- ;
657
718
  search() {
658
719
  return _.bind(function (req, res, next) {
659
720
  if (!(req.resource = this.getResource(req.params.resourceName))) {
@@ -662,34 +723,31 @@ class FormsAngular {
662
723
  this.wrapInternalSearch(req, res, [req.resource], false, 0);
663
724
  }, this);
664
725
  }
665
- ;
666
726
  searchAll() {
667
727
  return _.bind(function (req, res) {
668
728
  this.wrapInternalSearch(req, res, this.resources, true, 10);
669
729
  }, this);
670
730
  }
671
- ;
672
731
  models() {
673
732
  const that = this;
674
733
  return function (req, res) {
675
734
  // Check for optional modelFilter and call it with the request and current list. Otherwise just return the list.
676
- let resources = that.options.modelFilter ? that.options.modelFilter.call(null, req, that.resources) : that.resources;
735
+ let resources = that.options.modelFilter
736
+ ? that.options.modelFilter.call(null, req, that.resources)
737
+ : that.resources;
677
738
  if (req.query?.resourceNamesOnly) {
678
739
  resources = resources.map((r) => r.resourceName);
679
740
  }
680
741
  res.send(resources);
681
742
  };
682
743
  }
683
- ;
684
744
  renderError(err, redirectUrl, req, res) {
685
745
  res.statusMessage = err?.message || err;
686
746
  res.status(400).end(err?.message || err);
687
747
  }
688
- ;
689
748
  redirect(address, req, res) {
690
749
  res.send(address);
691
750
  }
692
- ;
693
751
  applySchemaSubset(vanilla, schema) {
694
752
  let outPath;
695
753
  if (schema) {
@@ -705,18 +763,20 @@ class FormsAngular {
705
763
  else {
706
764
  if (fld.slice(0, 8) === "_bespoke") {
707
765
  outPath[fld] = {
708
- "path": fld,
709
- "instance": schema[fld]._type,
766
+ path: fld,
767
+ instance: schema[fld]._type,
710
768
  };
711
769
  }
712
770
  else {
713
- throw new Error('No such field as ' + fld + '. Is it part of a sub-doc? If so you need the bit before the period.');
771
+ throw new Error("No such field as " +
772
+ fld +
773
+ ". Is it part of a sub-doc? If so you need the bit before the period.");
714
774
  }
715
775
  }
716
776
  outPath[fld].options = outPath[fld].options || {};
717
777
  for (const override in schema[fld]) {
718
778
  if (schema[fld].hasOwnProperty(override)) {
719
- if (override.slice(0, 1) !== '_') {
779
+ if (override.slice(0, 1) !== "_") {
720
780
  if (schema[fld].hasOwnProperty(override)) {
721
781
  if (!outPath[fld].options.form) {
722
782
  outPath[fld].options.form = {};
@@ -734,12 +794,25 @@ class FormsAngular {
734
794
  }
735
795
  return outPath;
736
796
  }
737
- ;
738
797
  preprocess(resource, paths, formName, formSchema) {
739
798
  function processInternalObject(obj) {
740
799
  return Object.keys(obj).reduce((acc, cur) => {
741
800
  const curType = typeof obj[cur];
742
- if ((!['$', '_'].includes(cur.charAt(0)) || ["$in", "$nin", "$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$regex", "$or", "$exists"].includes(cur)) && curType !== 'function') {
801
+ if ((!["$", "_"].includes(cur.charAt(0)) ||
802
+ [
803
+ "$in",
804
+ "$nin",
805
+ "$eq",
806
+ "$ne",
807
+ "$gt",
808
+ "$gte",
809
+ "$lt",
810
+ "$lte",
811
+ "$regex",
812
+ "$or",
813
+ "$exists",
814
+ ].includes(cur)) &&
815
+ curType !== "function") {
743
816
  const val = obj[cur];
744
817
  if (val) {
745
818
  if (Array.isArray(val)) {
@@ -747,7 +820,9 @@ class FormsAngular {
747
820
  acc[cur] = val;
748
821
  }
749
822
  }
750
- else if (curType === 'object' && !(val instanceof Date) && !(val instanceof RegExp)) {
823
+ else if (curType === "object" &&
824
+ !(val instanceof Date) &&
825
+ !(val instanceof RegExp)) {
751
826
  acc[cur] = processInternalObject(obj[cur]);
752
827
  }
753
828
  else {
@@ -759,22 +834,27 @@ class FormsAngular {
759
834
  }, {});
760
835
  }
761
836
  let outPath = {}, hiddenFields = [], listFields = [];
762
- if (resource && !resource.options?.doNotCacheSchema && resource.preprocessed && resource.preprocessed[formName || "__default"]) {
837
+ if (resource &&
838
+ !resource.options?.doNotCacheSchema &&
839
+ resource.preprocessed &&
840
+ resource.preprocessed[formName || "__default"]) {
763
841
  return resource.preprocessed[formName || "__default"].paths;
764
842
  }
765
843
  else {
766
844
  if (resource && resource.options && resource.options.idIsList) {
767
- paths['_id'].options = paths['_id'].options || {};
768
- paths['_id'].options.list = resource.options.idIsList;
845
+ paths["_id"].options = paths["_id"].options || {};
846
+ paths["_id"].options.list = resource.options.idIsList;
769
847
  }
770
848
  for (let element in paths) {
771
- if (paths.hasOwnProperty(element) && element !== '__v') {
849
+ if (paths.hasOwnProperty(element) && element !== "__v") {
772
850
  // check for schemas
773
851
  if (paths[element].schema) {
774
852
  let subSchemaInfo = this.preprocess(null, paths[element].schema.paths);
775
853
  outPath[element] = { schema: subSchemaInfo.paths };
776
854
  if (paths[element].options.form) {
777
- outPath[element].options = { form: extend(true, {}, paths[element].options.form) };
855
+ outPath[element].options = {
856
+ form: extend(true, {}, paths[element].options.form),
857
+ };
778
858
  }
779
859
  // this provides support for entire nested schemas that wish to remain hidden
780
860
  if (paths[element].options.secure) {
@@ -785,12 +865,16 @@ class FormsAngular {
785
865
  }
786
866
  else {
787
867
  // check for arrays
788
- let realType = paths[element].caster ? paths[element].caster : paths[element];
868
+ let realType = paths[element].caster
869
+ ? paths[element].caster
870
+ : paths[element];
789
871
  if (!realType.instance) {
790
872
  if (realType.options.type) {
791
873
  let type = realType.options.type(), typeType = typeof type;
792
- if (typeType === 'string') {
793
- realType.instance = (!isNaN(Date.parse(type))) ? 'Date' : 'String';
874
+ if (typeType === "string") {
875
+ realType.instance = !isNaN(Date.parse(type))
876
+ ? "Date"
877
+ : "String";
794
878
  }
795
879
  else {
796
880
  realType.instance = typeType;
@@ -802,12 +886,15 @@ class FormsAngular {
802
886
  hiddenFields.push(element);
803
887
  }
804
888
  if (paths[element].options.match) {
805
- outPath[element].options.match = paths[element].options.match.source || paths[element].options.match;
889
+ outPath[element].options.match =
890
+ paths[element].options.match.source ||
891
+ paths[element].options.match;
806
892
  }
807
893
  let schemaListInfo = paths[element].options.list;
808
894
  if (schemaListInfo) {
809
895
  let listFieldInfo = { field: element };
810
- if (typeof schemaListInfo === 'object' && Object.keys(schemaListInfo).length > 0) {
896
+ if (typeof schemaListInfo === "object" &&
897
+ Object.keys(schemaListInfo).length > 0) {
811
898
  listFieldInfo.params = schemaListInfo;
812
899
  }
813
900
  listFields.push(listFieldInfo);
@@ -830,7 +917,6 @@ class FormsAngular {
830
917
  return returnObj;
831
918
  }
832
919
  }
833
- ;
834
920
  schema() {
835
921
  return _.bind(function (req, res) {
836
922
  if (!(req.resource = this.getResource(req.params.resourceName))) {
@@ -844,7 +930,7 @@ class FormsAngular {
844
930
  else {
845
931
  if (formName) {
846
932
  try {
847
- formSchema = req.resource.model.schema.statics['form'](escapeHtml(formName), req);
933
+ formSchema = req.resource.model.schema.statics["form"](escapeHtml(formName), req);
848
934
  }
849
935
  catch (e) {
850
936
  return res.status(404).send(e.message);
@@ -855,7 +941,6 @@ class FormsAngular {
855
941
  }
856
942
  }, this);
857
943
  }
858
- ;
859
944
  report() {
860
945
  return _.bind(async function (req, res, next) {
861
946
  if (!(req.resource = this.getResource(req.params.resourceName))) {
@@ -863,13 +948,13 @@ class FormsAngular {
863
948
  }
864
949
  const self = this;
865
950
  req._isReport = true; // Can be used to modify findFunc behaviour
866
- if (typeof req.query === 'undefined') {
951
+ if (typeof req.query === "undefined") {
867
952
  req.query = {};
868
953
  }
869
954
  let reportSchema;
870
955
  if (req.params.reportName) {
871
956
  try {
872
- reportSchema = await req.resource.model.schema.statics['report'](req.params.reportName, req);
957
+ reportSchema = await req.resource.model.schema.statics["report"](req.params.reportName, req);
873
958
  }
874
959
  catch (e) {
875
960
  res.send({ success: false, error: e.message || e });
@@ -877,10 +962,10 @@ class FormsAngular {
877
962
  }
878
963
  else if (req.query.r) {
879
964
  switch (req.query.r[0]) {
880
- case '[':
965
+ case "[":
881
966
  reportSchema = { pipeline: JSON.parse(req.query.r) };
882
967
  break;
883
- case '{':
968
+ case "{":
884
969
  reportSchema = JSON.parse(req.query.r);
885
970
  break;
886
971
  default:
@@ -891,17 +976,17 @@ class FormsAngular {
891
976
  let fields = {};
892
977
  for (let key in req.resource.model.schema.paths) {
893
978
  if (req.resource.model.schema.paths.hasOwnProperty(key)) {
894
- if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
895
- if (key.indexOf('.') === -1) {
979
+ if (key !== "__v" &&
980
+ !req.resource.model.schema.paths[key].options.secure) {
981
+ if (key.indexOf(".") === -1) {
896
982
  fields[key] = 1;
897
983
  }
898
984
  }
899
985
  }
900
986
  }
901
987
  reportSchema = {
902
- pipeline: [
903
- { $project: fields }
904
- ], drilldown: req.params.resourceName + '/|_id|/edit'
988
+ pipeline: [{ $project: fields }],
989
+ drilldown: req.params.resourceName + "/|_id|/edit",
905
990
  };
906
991
  }
907
992
  // Replace parameters in pipeline
@@ -918,15 +1003,13 @@ class FormsAngular {
918
1003
  });
919
1004
  }, this);
920
1005
  }
921
- ;
922
1006
  hackVariablesInPipeline(runPipeline) {
923
1007
  for (let pipelineSection = 0; pipelineSection < runPipeline.length; pipelineSection++) {
924
- if (runPipeline[pipelineSection]['$match']) {
925
- this.hackVariables(runPipeline[pipelineSection]['$match']);
1008
+ if (runPipeline[pipelineSection]["$match"]) {
1009
+ this.hackVariables(runPipeline[pipelineSection]["$match"]);
926
1010
  }
927
1011
  }
928
1012
  }
929
- ;
930
1013
  hackVariables(obj) {
931
1014
  // Replace variables that cannot be serialised / deserialised. Bit of a hack, but needs must...
932
1015
  // Anything formatted 1800-01-01T00:00:00.000Z or 1800-01-01T00:00:00.000+0000 is converted to a Date
@@ -934,10 +1017,10 @@ class FormsAngular {
934
1017
  // TODO: handle arrays etc
935
1018
  for (const prop in obj) {
936
1019
  if (obj.hasOwnProperty(prop)) {
937
- if (typeof obj[prop] === 'string') {
1020
+ if (typeof obj[prop] === "string") {
938
1021
  const dateTest = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{3})?)(Z|[+ -]\d{2}:?\d{2})$/.exec(obj[prop]);
939
1022
  if (dateTest) {
940
- obj[prop] = new Date(dateTest[1] + 'Z');
1023
+ obj[prop] = new Date(dateTest[1] + "Z");
941
1024
  }
942
1025
  else if (prop !== "$regex") {
943
1026
  const objectIdTest = /^([0-9a-fA-F]{24})$/.exec(obj[prop]);
@@ -952,10 +1035,11 @@ class FormsAngular {
952
1035
  }
953
1036
  }
954
1037
  }
955
- ;
956
1038
  async sanitisePipeline(aggregationParam, hiddenFields, findFuncQry, req) {
957
1039
  let that = this;
958
- let array = Array.isArray(aggregationParam) ? aggregationParam : [aggregationParam];
1040
+ let array = Array.isArray(aggregationParam)
1041
+ ? aggregationParam
1042
+ : [aggregationParam];
959
1043
  let retVal = [];
960
1044
  let doneHiddenFields = false;
961
1045
  if (findFuncQry) {
@@ -965,12 +1049,12 @@ class FormsAngular {
965
1049
  let stage = array[pipelineSection];
966
1050
  let keys = Object.keys(stage);
967
1051
  if (keys.length !== 1) {
968
- throw new Error('Invalid pipeline instruction');
1052
+ throw new Error("Invalid pipeline instruction");
969
1053
  }
970
1054
  switch (keys[0]) {
971
- case '$project':
972
- case '$addFields':
973
- case '$count':
1055
+ case "$project":
1056
+ case "$addFields":
1057
+ case "$count":
974
1058
  case "$group":
975
1059
  case "$limit":
976
1060
  case "$replaceRoot":
@@ -979,10 +1063,10 @@ class FormsAngular {
979
1063
  case "$unwind":
980
1064
  // We don't care about these - they are all (as far as we know) safe
981
1065
  break;
982
- case '$unionWith':
1066
+ case "$unionWith":
983
1067
  /*
984
- Sanitise the pipeline we are doing a union with, removing hidden fields from that collection
985
- */
1068
+ Sanitise the pipeline we are doing a union with, removing hidden fields from that collection
1069
+ */
986
1070
  if (!stage.$unionWith.coll) {
987
1071
  stage.$unionWith = { coll: stage.$unionWith, pipeline: [] };
988
1072
  }
@@ -996,10 +1080,12 @@ class FormsAngular {
996
1080
  }
997
1081
  stage.$unionWith.pipeline = await that.sanitisePipeline(stage.$unionWith.pipeline, unionHiddenLookupFields, findFuncQry, req);
998
1082
  break;
999
- case '$match':
1000
- this.hackVariables(array[pipelineSection]['$match']);
1083
+ case "$match":
1084
+ this.hackVariables(array[pipelineSection]["$match"]);
1001
1085
  retVal.push(array[pipelineSection]);
1002
- if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
1086
+ if (!doneHiddenFields &&
1087
+ Object.keys(hiddenFields) &&
1088
+ Object.keys(hiddenFields).length > 0) {
1003
1089
  // We can now project out the hidden fields (we wait for the $match to make sure we don't break
1004
1090
  // a select that uses a hidden field
1005
1091
  retVal.push({ $project: hiddenFields });
@@ -1007,24 +1093,28 @@ class FormsAngular {
1007
1093
  }
1008
1094
  stage = null;
1009
1095
  break;
1010
- case '$lookup':
1011
- case '$graphLookup':
1096
+ case "$lookup":
1097
+ case "$graphLookup":
1012
1098
  let needFindFunc = true;
1013
- if (keys[0] === '$lookup') {
1099
+ if (keys[0] === "$lookup") {
1014
1100
  // For now at least, we only support simple $lookups with a single join field equality
1015
1101
  let lookupProps = Object.keys(stage.$lookup);
1016
- if (lookupProps.length !== 4 || lookupProps.indexOf('from') === -1 || lookupProps.indexOf('localField') === -1 || lookupProps.indexOf('foreignField') === -1 || lookupProps.indexOf('as') === -1) {
1102
+ if (lookupProps.length !== 4 ||
1103
+ lookupProps.indexOf("from") === -1 ||
1104
+ lookupProps.indexOf("localField") === -1 ||
1105
+ lookupProps.indexOf("foreignField") === -1 ||
1106
+ lookupProps.indexOf("as") === -1) {
1017
1107
  throw new Error("No support for $lookup that isn't Equality Match with a Single Join Condition");
1018
1108
  }
1019
1109
  // If we are doing a lookup using an _id (so not fishing) we don't need to do the findFunc (see tkt #12399)
1020
- if (stage.$lookup.foreignField === '_id') {
1110
+ if (stage.$lookup.foreignField === "_id") {
1021
1111
  needFindFunc = false;
1022
1112
  }
1023
1113
  }
1024
1114
  // hide any hiddenfields in the lookup collection
1025
1115
  const collectionName = stage[keys[0]].from;
1026
1116
  const lookupField = stage[keys[0]].as;
1027
- if ((collectionName + lookupField).indexOf('$') !== -1) {
1117
+ if ((collectionName + lookupField).indexOf("$") !== -1) {
1028
1118
  throw new Error('No support for lookups where the "from" or "as" is anything other than a simple string');
1029
1119
  }
1030
1120
  const resource = that.getResourceFromCollection(collectionName);
@@ -1034,7 +1124,7 @@ class FormsAngular {
1034
1124
  if (resource.options?.hide?.length > 0) {
1035
1125
  const hiddenLookupFields = this.generateHiddenFields(resource, false);
1036
1126
  let hiddenFieldsObj = {};
1037
- Object.keys(hiddenLookupFields).forEach(hf => {
1127
+ Object.keys(hiddenLookupFields).forEach((hf) => {
1038
1128
  hiddenFieldsObj[`${lookupField}.${hf}`] = false;
1039
1129
  });
1040
1130
  retVal.push({ $project: hiddenFieldsObj });
@@ -1048,13 +1138,14 @@ class FormsAngular {
1048
1138
  const nextStage = array[pipelineSection + 1];
1049
1139
  let nextKeys = Object.keys(nextStage);
1050
1140
  if (nextKeys.length !== 1) {
1051
- throw new Error('Invalid pipeline instruction');
1141
+ throw new Error("Invalid pipeline instruction");
1052
1142
  }
1053
- if (nextKeys[0] === '$unwind') {
1143
+ if (nextKeys[0] === "$unwind") {
1054
1144
  if (nextStage["$unwind"] === "$" + lookupField) {
1055
1145
  nextStageIsUnwind = true;
1056
1146
  }
1057
- if (nextStage["$unwind"] && nextStage["$unwind"].path === "$" + lookupField) {
1147
+ if (nextStage["$unwind"] &&
1148
+ nextStage["$unwind"].path === "$" + lookupField) {
1058
1149
  nextStageIsUnwind = true;
1059
1150
  if (nextStage["$unwind"].preserveNullAndEmptyArrays) {
1060
1151
  allowNulls = true;
@@ -1063,7 +1154,7 @@ class FormsAngular {
1063
1154
  }
1064
1155
  }
1065
1156
  if (!nextStageIsUnwind) {
1066
- throw new Error('No support for $lookup where the next stage is not an $unwind and the resources has a findFunc');
1157
+ throw new Error("No support for $lookup where the next stage is not an $unwind and the resources has a findFunc");
1067
1158
  }
1068
1159
  // Push the $unwind, add our own findFunc, and increment the pipelineStage counter
1069
1160
  retVal.push(array[pipelineSection + 1]);
@@ -1072,12 +1163,18 @@ class FormsAngular {
1072
1163
  // Now we need to put the lookup base into the criteria
1073
1164
  for (const prop in lookedUpFindQry) {
1074
1165
  if (lookedUpFindQry.hasOwnProperty(prop)) {
1075
- lookedUpFindQry[`${lookupField}.${prop}`] = lookedUpFindQry[prop];
1166
+ lookedUpFindQry[`${lookupField}.${prop}`] =
1167
+ lookedUpFindQry[prop];
1076
1168
  delete lookedUpFindQry[prop];
1077
1169
  }
1078
1170
  }
1079
1171
  if (allowNulls) {
1080
- lookedUpFindQry = { $or: [lookedUpFindQry, { [lookupField]: { $exists: false } }] };
1172
+ lookedUpFindQry = {
1173
+ $or: [
1174
+ lookedUpFindQry,
1175
+ { [lookupField]: { $exists: false } },
1176
+ ],
1177
+ };
1081
1178
  }
1082
1179
  retVal.push({ $match: lookedUpFindQry });
1083
1180
  }
@@ -1087,13 +1184,15 @@ class FormsAngular {
1087
1184
  break;
1088
1185
  default:
1089
1186
  // anything else is either known to be dangerous, not yet needed or we don't know what it is
1090
- throw new Error('Unsupported pipeline instruction ' + keys[0]);
1187
+ throw new Error("Unsupported pipeline instruction " + keys[0]);
1091
1188
  }
1092
1189
  if (stage) {
1093
1190
  retVal.push(stage);
1094
1191
  }
1095
1192
  }
1096
- if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
1193
+ if (!doneHiddenFields &&
1194
+ Object.keys(hiddenFields) &&
1195
+ Object.keys(hiddenFields).length > 0) {
1097
1196
  // If there was no $match we still need to hide the hidden fields
1098
1197
  retVal.unshift({ $project: hiddenFields });
1099
1198
  }
@@ -1103,19 +1202,20 @@ class FormsAngular {
1103
1202
  let runPipelineStr;
1104
1203
  let runPipelineObj;
1105
1204
  let self = this;
1106
- if (typeof req.query === 'undefined') {
1205
+ if (typeof req.query === "undefined") {
1107
1206
  req.query = {};
1108
1207
  }
1109
1208
  self.doFindFunc(req, resource, function (err, queryObj) {
1110
1209
  if (err) {
1111
- return 'There was a problem with the findFunc for model';
1210
+ return "There was a problem with the findFunc for model";
1112
1211
  }
1113
1212
  else {
1114
1213
  runPipelineStr = JSON.stringify(schema.pipeline);
1115
1214
  for (let param in req.query) {
1116
- if (param !== 'noinput' && req.query.hasOwnProperty(param)) {
1215
+ if (param !== "noinput" && req.query.hasOwnProperty(param)) {
1117
1216
  if (req.query[param]) {
1118
- if (param !== 'r') { // we don't want to copy the whole report schema (again!)
1217
+ if (param !== "r") {
1218
+ // we don't want to copy the whole report schema (again!)
1119
1219
  if (schema.params[param] !== undefined) {
1120
1220
  schema.params[param].value = req.query[param];
1121
1221
  }
@@ -1131,13 +1231,13 @@ class FormsAngular {
1131
1231
  runPipelineStr = runPipelineStr.replace(/"\(.+?\)"/g, function (match) {
1132
1232
  let sparam = schema.params[match.slice(2, -2)];
1133
1233
  if (sparam !== undefined) {
1134
- if (sparam.type === 'number') {
1234
+ if (sparam.type === "number") {
1135
1235
  return sparam.value;
1136
1236
  }
1137
1237
  else if (_.isObject(sparam.value)) {
1138
1238
  return JSON.stringify(sparam.value);
1139
1239
  }
1140
- else if (sparam.value[0] === '{') {
1240
+ else if (sparam.value[0] === "{") {
1141
1241
  return sparam.value;
1142
1242
  }
1143
1243
  else {
@@ -1154,9 +1254,11 @@ class FormsAngular {
1154
1254
  let hiddenFields = self.generateHiddenFields(resource, false);
1155
1255
  let toDo = {
1156
1256
  runAggregation: function (cb) {
1157
- self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj, req)
1257
+ self
1258
+ .sanitisePipeline(runPipelineObj, hiddenFields, queryObj, req)
1158
1259
  .then((runPipelineObj) => {
1159
- resource.model.aggregate(runPipelineObj)
1260
+ resource.model
1261
+ .aggregate(runPipelineObj)
1160
1262
  .then((results) => {
1161
1263
  cb(null, results);
1162
1264
  })
@@ -1165,22 +1267,28 @@ class FormsAngular {
1165
1267
  });
1166
1268
  })
1167
1269
  .catch((err) => {
1168
- throw new Error('Error in sanitisePipeline ' + err);
1270
+ throw new Error("Error in sanitisePipeline " + err);
1169
1271
  });
1170
- }
1272
+ },
1171
1273
  };
1172
1274
  let translations = []; // array of form {ref:'lookupname',translations:[{value:xx, display:' '}]}
1173
1275
  // if we need to do any column translations add the function to the tasks list
1174
1276
  if (schema.columnTranslations) {
1175
- toDo.applyTranslations = ['runAggregation', function (results, cb) {
1277
+ toDo.applyTranslations = [
1278
+ "runAggregation",
1279
+ function (results, cb) {
1176
1280
  function doATranslate(column, theTranslation) {
1177
- results['runAggregation'].forEach(function (resultRow) {
1281
+ results["runAggregation"].forEach(function (resultRow) {
1178
1282
  let valToTranslate = resultRow[column.field];
1179
- valToTranslate = (valToTranslate ? valToTranslate.toString() : '');
1283
+ valToTranslate = valToTranslate
1284
+ ? valToTranslate.toString()
1285
+ : "";
1180
1286
  let thisTranslation = _.find(theTranslation.translations, function (option) {
1181
1287
  return valToTranslate === option.value.toString();
1182
1288
  });
1183
- resultRow[column.field] = thisTranslation ? thisTranslation.display : ' * Missing columnTranslation * ';
1289
+ resultRow[column.field] = thisTranslation
1290
+ ? thisTranslation.display
1291
+ : " * Missing columnTranslation * ";
1184
1292
  });
1185
1293
  }
1186
1294
  schema.columnTranslations.forEach(function (columnTranslation) {
@@ -1189,18 +1297,22 @@ class FormsAngular {
1189
1297
  }
1190
1298
  if (columnTranslation.ref) {
1191
1299
  let theTranslation = _.find(translations, function (translation) {
1192
- return (translation.ref === columnTranslation.ref);
1300
+ return translation.ref === columnTranslation.ref;
1193
1301
  });
1194
1302
  if (theTranslation) {
1195
1303
  doATranslate(columnTranslation, theTranslation);
1196
1304
  }
1197
1305
  else {
1198
- cb('Invalid ref property of ' + columnTranslation.ref + ' in columnTranslations ' + columnTranslation.field);
1306
+ cb("Invalid ref property of " +
1307
+ columnTranslation.ref +
1308
+ " in columnTranslations " +
1309
+ columnTranslation.field);
1199
1310
  }
1200
1311
  }
1201
1312
  });
1202
1313
  cb(null, null);
1203
- }];
1314
+ },
1315
+ ];
1204
1316
  let callFuncs = false;
1205
1317
  for (let i = 0; i < schema.columnTranslations.length; i++) {
1206
1318
  let thisColumnTranslation = schema.columnTranslations[i];
@@ -1217,9 +1329,13 @@ class FormsAngular {
1217
1329
  let getFunc = function (ref) {
1218
1330
  let lookup = ref;
1219
1331
  return function (cb) {
1220
- let translateObject = { ref: lookup.resourceName, translations: [] };
1332
+ let translateObject = {
1333
+ ref: lookup.resourceName,
1334
+ translations: [],
1335
+ };
1221
1336
  translations.push(translateObject);
1222
- lookup.model.find({}, {})
1337
+ lookup.model
1338
+ .find({}, {})
1223
1339
  .lean()
1224
1340
  .exec()
1225
1341
  .then((findResults) => {
@@ -1228,7 +1344,8 @@ class FormsAngular {
1228
1344
  cbtest(null, j < findResults.length);
1229
1345
  }, function (cbres) {
1230
1346
  let theResult = findResults[j];
1231
- translateObject.translations[j] = translateObject.translations[j] || {};
1347
+ translateObject.translations[j] =
1348
+ translateObject.translations[j] || {};
1232
1349
  let theTranslation = translateObject.translations[j];
1233
1350
  j++;
1234
1351
  self.getListFields(lookup, theResult, function (err, description) {
@@ -1253,19 +1370,28 @@ class FormsAngular {
1253
1370
  }
1254
1371
  }
1255
1372
  else {
1256
- return callback('Invalid ref property of ' + thisColumnTranslation.ref + ' in columnTranslations ' + thisColumnTranslation.field);
1373
+ return callback("Invalid ref property of " +
1374
+ thisColumnTranslation.ref +
1375
+ " in columnTranslations " +
1376
+ thisColumnTranslation.field);
1257
1377
  }
1258
1378
  }
1259
- if (!thisColumnTranslation.translations && !thisColumnTranslation.ref && !thisColumnTranslation.fn) {
1260
- return callback('A column translation needs a ref, fn or a translations property - ' + thisColumnTranslation.field + ' has neither');
1379
+ if (!thisColumnTranslation.translations &&
1380
+ !thisColumnTranslation.ref &&
1381
+ !thisColumnTranslation.fn) {
1382
+ return callback("A column translation needs a ref, fn or a translations property - " +
1383
+ thisColumnTranslation.field +
1384
+ " has neither");
1261
1385
  }
1262
1386
  }
1263
1387
  else {
1264
- return callback('A column translation needs a field property');
1388
+ return callback("A column translation needs a field property");
1265
1389
  }
1266
1390
  }
1267
1391
  if (callFuncs) {
1268
- toDo['callFunctions'] = ['runAggregation', function (results, cb) {
1392
+ toDo["callFunctions"] = [
1393
+ "runAggregation",
1394
+ function (results, cb) {
1269
1395
  async.each(results.runAggregation, function (row, cb) {
1270
1396
  for (let i = 0; i < schema.columnTranslations.length; i++) {
1271
1397
  let thisColumnTranslation = schema.columnTranslations[i];
@@ -1276,8 +1402,9 @@ class FormsAngular {
1276
1402
  }, function () {
1277
1403
  cb(null);
1278
1404
  });
1279
- }];
1280
- toDo.applyTranslations.unshift('callFunctions'); // Make sure we do function before translating its result
1405
+ },
1406
+ ];
1407
+ toDo.applyTranslations.unshift("callFunctions"); // Make sure we do function before translating its result
1281
1408
  }
1282
1409
  }
1283
1410
  async.auto(toDo, function (err, results) {
@@ -1289,22 +1416,36 @@ class FormsAngular {
1289
1416
  success: true,
1290
1417
  schema: schema,
1291
1418
  report: results.runAggregation,
1292
- paramsUsed: schema.params
1419
+ paramsUsed: schema.params,
1293
1420
  });
1294
1421
  }
1295
1422
  });
1296
1423
  }
1297
1424
  });
1298
1425
  }
1299
- ;
1300
1426
  saveAndRespond(req, res, hiddenFields) {
1301
1427
  function internalSave(doc) {
1302
- doc.save()
1428
+ function decorateError(err) {
1429
+ let err2 = { status: "err" };
1430
+ if (!err.errors) {
1431
+ err2.message = err.message;
1432
+ }
1433
+ else {
1434
+ extend(err2, err);
1435
+ }
1436
+ if (debug) {
1437
+ console.log("Error saving record: " + JSON.stringify(err2));
1438
+ }
1439
+ res.status(400).send(err2);
1440
+ }
1441
+ doc
1442
+ .save()
1303
1443
  .then((saved) => {
1304
1444
  saved = saved.toObject();
1305
1445
  for (const hiddenField in hiddenFields) {
1306
- if (hiddenFields.hasOwnProperty(hiddenField) && hiddenFields[hiddenField]) {
1307
- let parts = hiddenField.split('.');
1446
+ if (hiddenFields.hasOwnProperty(hiddenField) &&
1447
+ hiddenFields[hiddenField]) {
1448
+ let parts = hiddenField.split(".");
1308
1449
  let lastPart = parts.length - 1;
1309
1450
  let target = saved;
1310
1451
  for (let i = 0; i < lastPart; i++) {
@@ -1319,29 +1460,27 @@ class FormsAngular {
1319
1460
  }
1320
1461
  if (doc.__toClient) {
1321
1462
  /* Use this to pass anything back to the client that is not saved in the record
1322
- Possible use case - sent a message saying that since this data has changed in a
1323
- particular way the user should consider doing something else
1324
- */
1463
+ Possible use case - sent a message saying that since this data has changed in a
1464
+ particular way the user should consider doing something else
1465
+ */
1325
1466
  saved.__toClient = doc.__toClient;
1326
1467
  }
1327
1468
  res.send(saved);
1328
1469
  })
1329
1470
  .catch((err) => {
1330
- let err2 = { status: 'err' };
1331
- if (!err.errors) {
1332
- err2.message = err.message;
1471
+ if (req.resource.options.onSaveError) {
1472
+ req.resource.options.onSaveError(err, req, res)
1473
+ .then(() => {
1474
+ decorateError(err);
1475
+ });
1333
1476
  }
1334
1477
  else {
1335
- extend(err2, err);
1336
- }
1337
- if (debug) {
1338
- console.log('Error saving record: ' + JSON.stringify(err2));
1478
+ decorateError(err);
1339
1479
  }
1340
- res.status(400).send(err2);
1341
1480
  });
1342
1481
  }
1343
1482
  let doc = req.doc;
1344
- if (typeof req.resource.options.onSave === 'function') {
1483
+ if (typeof req.resource.options.onSave === "function") {
1345
1484
  req.resource.options.onSave(doc, req, function (err) {
1346
1485
  if (err) {
1347
1486
  throw err;
@@ -1353,14 +1492,12 @@ class FormsAngular {
1353
1492
  internalSave(doc);
1354
1493
  }
1355
1494
  }
1356
- ;
1357
1495
  /**
1358
1496
  * All entities REST functions have to go through this first.
1359
1497
  */
1360
1498
  processCollection(req) {
1361
1499
  req.resource = this.getResource(req.params.resourceName);
1362
1500
  }
1363
- ;
1364
1501
  /**
1365
1502
  * Renders a view with the list of docs, which may be modified by query parameters
1366
1503
  */
@@ -1370,7 +1507,7 @@ class FormsAngular {
1370
1507
  if (!req.resource) {
1371
1508
  return next();
1372
1509
  }
1373
- if (typeof req.query === 'undefined') {
1510
+ if (typeof req.query === "undefined") {
1374
1511
  req.query = {};
1375
1512
  }
1376
1513
  try {
@@ -1379,7 +1516,9 @@ class FormsAngular {
1379
1516
  const projectParam = req.query.p ? JSON.parse(req.query.p) : {};
1380
1517
  const limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
1381
1518
  const skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
1382
- const orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
1519
+ const orderParam = req.query.o
1520
+ ? JSON.parse(req.query.o)
1521
+ : req.resource.options.listOrder;
1383
1522
  // Dates in aggregation must be Dates
1384
1523
  if (aggregationParam) {
1385
1524
  this.hackVariablesInPipeline(aggregationParam);
@@ -1399,12 +1538,11 @@ class FormsAngular {
1399
1538
  }
1400
1539
  }, this);
1401
1540
  }
1402
- ;
1403
1541
  generateProjection(hiddenFields, projectParam) {
1404
1542
  let type;
1405
1543
  function setSelectType(typeChar, checkChar) {
1406
1544
  if (type === checkChar) {
1407
- throw new Error('Cannot mix include and exclude fields in select');
1545
+ throw new Error("Cannot mix include and exclude fields in select");
1408
1546
  }
1409
1547
  else {
1410
1548
  type = typeChar;
@@ -1414,18 +1552,18 @@ class FormsAngular {
1414
1552
  if (projectParam) {
1415
1553
  let projection = Object.keys(projectParam);
1416
1554
  if (projection.length > 0) {
1417
- projection.forEach(p => {
1555
+ projection.forEach((p) => {
1418
1556
  if (projectParam[p] === 0) {
1419
- setSelectType('E', 'I');
1557
+ setSelectType("E", "I");
1420
1558
  }
1421
1559
  else if (projectParam[p] === 1) {
1422
- setSelectType('I', 'E');
1560
+ setSelectType("I", "E");
1423
1561
  }
1424
1562
  else {
1425
- throw new Error('Invalid projection: ' + projectParam);
1563
+ throw new Error("Invalid projection: " + projectParam);
1426
1564
  }
1427
1565
  });
1428
- if (type && type === 'E') {
1566
+ if (type && type === "E") {
1429
1567
  // We are excluding fields - can just merge with hiddenFields
1430
1568
  Object.assign(retVal, projectParam, hiddenFields);
1431
1569
  }
@@ -1442,17 +1580,16 @@ class FormsAngular {
1442
1580
  }
1443
1581
  return retVal;
1444
1582
  }
1445
- ;
1446
1583
  doFindFunc(req, resource, cb) {
1447
1584
  // filter out records the user has no access to unless we are just asking for list attributes
1448
- if (resource.options.findFunc && req?.route?.path !== "/api/:resourceName/:id/list") {
1585
+ if (resource.options.findFunc &&
1586
+ req?.route?.path !== "/api/:resourceName/:id/list") {
1449
1587
  resource.options.findFunc(req, cb);
1450
1588
  }
1451
1589
  else {
1452
1590
  cb(null);
1453
1591
  }
1454
1592
  }
1455
- ;
1456
1593
  async doFindFuncPromise(req, resource) {
1457
1594
  return new Promise((resolve, reject) => {
1458
1595
  this.doFindFunc(req, resource, (err, queryObj) => {
@@ -1465,7 +1602,6 @@ class FormsAngular {
1465
1602
  });
1466
1603
  });
1467
1604
  }
1468
- ;
1469
1605
  async filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1470
1606
  const that = this;
1471
1607
  let hiddenFields = this.generateHiddenFields(resource, false);
@@ -1473,13 +1609,15 @@ class FormsAngular {
1473
1609
  async function doAggregation(queryObj, cb) {
1474
1610
  if (aggregationParam) {
1475
1611
  aggregationParam = await that.sanitisePipeline(aggregationParam, hiddenFields, queryObj, req);
1476
- resource.model.aggregate(aggregationParam)
1612
+ resource.model
1613
+ .aggregate(aggregationParam)
1477
1614
  .then((aggregationResults) => {
1478
1615
  stashAggregationResults = aggregationResults;
1479
1616
  cb(_.map(aggregationResults, function (obj) {
1480
1617
  return obj._id;
1481
1618
  }));
1482
- }).catch((err) => {
1619
+ })
1620
+ .catch((err) => {
1483
1621
  throw err;
1484
1622
  });
1485
1623
  }
@@ -1499,7 +1637,7 @@ class FormsAngular {
1499
1637
  else {
1500
1638
  let query = resource.model.find(queryObj);
1501
1639
  if (idArray.length > 0) {
1502
- query = query.where('_id').in(idArray);
1640
+ query = query.where("_id").in(idArray);
1503
1641
  }
1504
1642
  if (findParam) {
1505
1643
  query = query.find(findParam);
@@ -1514,13 +1652,14 @@ class FormsAngular {
1514
1652
  if (sortOrder) {
1515
1653
  query = query.sort(sortOrder);
1516
1654
  }
1517
- query.exec()
1655
+ query
1656
+ .exec()
1518
1657
  .then((docs) => {
1519
1658
  if (stashAggregationResults) {
1520
1659
  for (const obj of docs) {
1521
1660
  // Add any fields from the aggregation results whose field name starts __ to the mongoose Document
1522
- let aggObj = stashAggregationResults.find(a => a._id.toString() === obj._id.toString());
1523
- for (const k of Object.keys(aggObj).filter((k) => k.startsWith('__'))) {
1661
+ let aggObj = stashAggregationResults.find((a) => a._id.toString() === obj._id.toString());
1662
+ for (const k of Object.keys(aggObj).filter((k) => k.startsWith("__"))) {
1524
1663
  obj[k] = aggObj[k];
1525
1664
  }
1526
1665
  }
@@ -1535,7 +1674,6 @@ class FormsAngular {
1535
1674
  }
1536
1675
  });
1537
1676
  }
1538
- ;
1539
1677
  collectionPost() {
1540
1678
  return _.bind(function (req, res, next) {
1541
1679
  this.processCollection(req);
@@ -1544,34 +1682,32 @@ class FormsAngular {
1544
1682
  return;
1545
1683
  }
1546
1684
  if (!req.body) {
1547
- throw new Error('Nothing submitted.');
1685
+ throw new Error("Nothing submitted.");
1548
1686
  }
1549
1687
  let cleansedBody = this.cleanseRequest(req);
1550
1688
  req.doc = new req.resource.model(cleansedBody);
1551
1689
  this.saveAndRespond(req, res);
1552
1690
  }, this);
1553
1691
  }
1554
- ;
1555
1692
  /**
1556
1693
  * Generate an object of fields to not expose
1557
1694
  **/
1558
1695
  generateHiddenFields(resource, state) {
1559
1696
  let hiddenFields = {};
1560
- if (resource.options['hide'] !== undefined) {
1697
+ if (resource.options["hide"] !== undefined) {
1561
1698
  resource.options.hide.forEach(function (dt) {
1562
1699
  hiddenFields[dt] = state;
1563
1700
  });
1564
1701
  }
1565
1702
  return hiddenFields;
1566
1703
  }
1567
- ;
1568
1704
  /** Sec issue
1569
1705
  * Cleanse incoming data to avoid overwrite and POST request forgery
1570
1706
  * (name may seem weird but it was in French, so it is some small improvement!)
1571
1707
  */
1572
1708
  cleanseRequest(req) {
1573
1709
  let reqData = req.body, resource = req.resource;
1574
- if (typeof resource.options['hide'] === 'undefined') {
1710
+ if (typeof resource.options["hide"] === "undefined") {
1575
1711
  return reqData;
1576
1712
  }
1577
1713
  let hiddenFields = resource.options.hide;
@@ -1584,7 +1720,6 @@ class FormsAngular {
1584
1720
  });
1585
1721
  return reqData;
1586
1722
  }
1587
- ;
1588
1723
  generateQueryForEntity(req, resource, id, cb) {
1589
1724
  let that = this;
1590
1725
  let hiddenFields = this.generateHiddenFields(resource, false);
@@ -1610,11 +1745,12 @@ class FormsAngular {
1610
1745
  else {
1611
1746
  crit = idSel;
1612
1747
  }
1613
- cb(null, resource.model.findOne(crit).select(that.generateProjection(hiddenFields, req.query?.p)));
1748
+ cb(null, resource.model
1749
+ .findOne(crit)
1750
+ .select(that.generateProjection(hiddenFields, req.query?.p)));
1614
1751
  }
1615
1752
  });
1616
1753
  }
1617
- ;
1618
1754
  /*
1619
1755
  * Entity request goes here first
1620
1756
  * It retrieves the resource
@@ -1627,11 +1763,12 @@ class FormsAngular {
1627
1763
  if (err) {
1628
1764
  return res.status(500).send({
1629
1765
  success: false,
1630
- err: util.inspect(err)
1766
+ err: util.inspect(err),
1631
1767
  });
1632
1768
  }
1633
1769
  else {
1634
- query.exec()
1770
+ query
1771
+ .exec()
1635
1772
  .then((doc) => {
1636
1773
  if (doc) {
1637
1774
  req.doc = doc;
@@ -1640,20 +1777,19 @@ class FormsAngular {
1640
1777
  else {
1641
1778
  return res.status(404).send({
1642
1779
  success: false,
1643
- err: 'Record not found'
1780
+ err: "Record not found",
1644
1781
  });
1645
1782
  }
1646
1783
  })
1647
1784
  .catch((err) => {
1648
1785
  return res.status(400).send({
1649
1786
  success: false,
1650
- err: util.inspect(err)
1787
+ err: util.inspect(err),
1651
1788
  });
1652
1789
  });
1653
1790
  }
1654
1791
  });
1655
1792
  }
1656
- ;
1657
1793
  /**
1658
1794
  * Gets a single entity
1659
1795
  *
@@ -1676,7 +1812,6 @@ class FormsAngular {
1676
1812
  });
1677
1813
  }, this);
1678
1814
  }
1679
- ;
1680
1815
  replaceHiddenFields(record, data) {
1681
1816
  const self = this;
1682
1817
  if (record) {
@@ -1692,7 +1827,6 @@ class FormsAngular {
1692
1827
  delete record._replacingHiddenFields;
1693
1828
  }
1694
1829
  }
1695
- ;
1696
1830
  entityPut() {
1697
1831
  return _.bind(function (req, res, next) {
1698
1832
  const that = this;
@@ -1702,19 +1836,20 @@ class FormsAngular {
1702
1836
  return;
1703
1837
  }
1704
1838
  if (!req.body) {
1705
- throw new Error('Nothing submitted.');
1839
+ throw new Error("Nothing submitted.");
1706
1840
  }
1707
1841
  let cleansedBody = that.cleanseRequest(req);
1708
1842
  // Merge
1709
1843
  for (let prop in cleansedBody) {
1710
1844
  if (cleansedBody.hasOwnProperty(prop)) {
1711
- req.doc.set(prop, cleansedBody[prop] === '' ? undefined : cleansedBody[prop]);
1845
+ req.doc.set(prop, cleansedBody[prop] === "" ? undefined : cleansedBody[prop]);
1712
1846
  }
1713
1847
  }
1714
1848
  if (req.resource.options.hide !== undefined) {
1715
1849
  let hiddenFields = that.generateHiddenFields(req.resource, true);
1716
1850
  hiddenFields._id = false;
1717
- req.resource.model.findById(req.doc._id, hiddenFields)
1851
+ req.resource.model
1852
+ .findById(req.doc._id, hiddenFields)
1718
1853
  .lean()
1719
1854
  .exec()
1720
1855
  .then((data) => {
@@ -1731,7 +1866,6 @@ class FormsAngular {
1731
1866
  });
1732
1867
  }, this);
1733
1868
  }
1734
- ;
1735
1869
  generateDependencyList(resource) {
1736
1870
  if (resource.options.dependents === undefined) {
1737
1871
  let that = this;
@@ -1740,18 +1874,19 @@ class FormsAngular {
1740
1874
  let fldList = [];
1741
1875
  for (let fld in schema.paths) {
1742
1876
  if (schema.paths.hasOwnProperty(fld)) {
1743
- const parts = fld.split('.');
1877
+ const parts = fld.split(".");
1744
1878
  let schemaType = schema.tree;
1745
1879
  while (parts.length > 0) {
1746
1880
  schemaType = schemaType[parts.shift()];
1747
1881
  }
1748
1882
  if (schemaType.type) {
1749
- if (schemaType.type.name === 'ObjectId' && schemaType.ref === resource.resourceName) {
1883
+ if (schemaType.type.name === "ObjectId" &&
1884
+ schemaType.ref === resource.resourceName) {
1750
1885
  fldList.push(prefix + fld);
1751
1886
  }
1752
1887
  else if (_.isArray(schemaType.type)) {
1753
1888
  schemaType.type.forEach(function (t) {
1754
- searchPaths(t, prefix + fld + '.');
1889
+ searchPaths(t, prefix + fld + ".");
1755
1890
  });
1756
1891
  }
1757
1892
  }
@@ -1761,12 +1896,13 @@ class FormsAngular {
1761
1896
  acc.push({ resource: r, keys: fldList });
1762
1897
  }
1763
1898
  }
1764
- searchPaths(r.model.schema, '');
1899
+ searchPaths(r.model.schema, "");
1765
1900
  return acc;
1766
1901
  }, []);
1767
1902
  for (let pluginName in that.options.plugins) {
1768
1903
  let thisPlugin = that.options.plugins[pluginName];
1769
- if (thisPlugin.dependencyChecks && thisPlugin.dependencyChecks[resource.resourceName]) {
1904
+ if (thisPlugin.dependencyChecks &&
1905
+ thisPlugin.dependencyChecks[resource.resourceName]) {
1770
1906
  resource.options.dependents = resource.options.dependents.concat(thisPlugin.dependencyChecks[resource.resourceName]);
1771
1907
  }
1772
1908
  }
@@ -1776,20 +1912,26 @@ class FormsAngular {
1776
1912
  this.generateDependencyList(resource);
1777
1913
  let promises = [];
1778
1914
  let foreignKeyList = [];
1779
- resource.options.dependents.forEach(collection => {
1780
- collection.keys.forEach(key => {
1915
+ resource.options.dependents.forEach((collection) => {
1916
+ collection.keys.forEach((key) => {
1781
1917
  promises.push({
1782
- p: collection.resource.model.find({ [key]: id }).limit(1).exec(),
1918
+ p: collection.resource.model
1919
+ .find({ [key]: id })
1920
+ .limit(1)
1921
+ .exec(),
1783
1922
  collection,
1784
- key
1923
+ key,
1785
1924
  });
1786
1925
  });
1787
1926
  });
1788
- return Promise.all(promises.map(p => p.p))
1789
- .then((results) => {
1927
+ return Promise.all(promises.map((p) => p.p)).then((results) => {
1790
1928
  results.forEach((r, i) => {
1791
1929
  if (r.length > 0) {
1792
- foreignKeyList.push({ resourceName: promises[i].collection.resource.resourceName, key: promises[i].key, id: r[0]._id });
1930
+ foreignKeyList.push({
1931
+ resourceName: promises[i].collection.resource.resourceName,
1932
+ key: promises[i].key,
1933
+ id: r[0]._id,
1934
+ });
1793
1935
  }
1794
1936
  });
1795
1937
  return foreignKeyList;
@@ -1800,14 +1942,15 @@ class FormsAngular {
1800
1942
  return _.bind(async function (req, res, next) {
1801
1943
  async function removeDoc(doc, resource) {
1802
1944
  switch (resource.options.handleRemove) {
1803
- case 'allow':
1945
+ case "allow":
1804
1946
  // old behaviour - no attempt to maintain data integrity
1805
1947
  return doc.deleteOne();
1806
- case 'cascade':
1948
+ case "cascade":
1807
1949
  res.status(400).send('"cascade" option not yet supported');
1808
1950
  break;
1809
1951
  default:
1810
- return that.getDependencies(resource, doc._id)
1952
+ return that
1953
+ .getDependencies(resource, doc._id)
1811
1954
  .then((dependencies) => {
1812
1955
  if (dependencies.length > 0) {
1813
1956
  throw new ForeignKeyError(resource.resourceName, dependencies);
@@ -1838,7 +1981,7 @@ class FormsAngular {
1838
1981
  }
1839
1982
  let doc = req.doc;
1840
1983
  try {
1841
- void await runDeletion(doc, req.resource);
1984
+ void (await runDeletion(doc, req.resource));
1842
1985
  res.status(200).send();
1843
1986
  }
1844
1987
  catch (e) {
@@ -1852,7 +1995,6 @@ class FormsAngular {
1852
1995
  });
1853
1996
  }, this);
1854
1997
  }
1855
- ;
1856
1998
  entityList() {
1857
1999
  return _.bind(function (req, res, next) {
1858
2000
  const that = this;
@@ -1860,7 +2002,9 @@ class FormsAngular {
1860
2002
  if (!req.resource) {
1861
2003
  return next();
1862
2004
  }
1863
- const returnRawParam = req.query?.returnRaw ? !!JSON.parse(req.query.returnRaw) : false;
2005
+ const returnRawParam = req.query?.returnRaw
2006
+ ? !!JSON.parse(req.query.returnRaw)
2007
+ : false;
1864
2008
  if (returnRawParam) {
1865
2009
  const result = { _id: req.doc._id };
1866
2010
  for (const field of req.resource.options.listFields) {
@@ -1881,8 +2025,7 @@ class FormsAngular {
1881
2025
  });
1882
2026
  }, this);
1883
2027
  }
1884
- ;
1885
- // To disambiguate the contents of items - assumed to be the results of a single resource lookup or search -
2028
+ // To disambiguate the contents of items - assumed to be the results of a single resource lookup or search -
1886
2029
  // pass the result of this function as the second argument to the disambiguate() function.
1887
2030
  // equalityProps should identify the property(s) of the items that must ALL be equal for two items to
1888
2031
  // be considered ambiguous.
@@ -1916,7 +2059,8 @@ class FormsAngular {
1916
2059
  for (let i = 0; i < items.length - 1; i++) {
1917
2060
  for (let j = i + 1; j < items.length; j++) {
1918
2061
  if (items[i][disambiguationResourceNameProp] &&
1919
- items[i][disambiguationResourceNameProp] === items[j][disambiguationResourceNameProp] &&
2062
+ items[i][disambiguationResourceNameProp] ===
2063
+ items[j][disambiguationResourceNameProp] &&
1920
2064
  !equalityProps.some((p) => items[i][p] !== items[j][p])) {
1921
2065
  if (!store[items[i][disambiguationResourceNameProp]]) {
1922
2066
  store[items[i][disambiguationResourceNameProp]] = [];
@@ -1952,7 +2096,9 @@ class FormsAngular {
1952
2096
  const findParam = req.query.f ? JSON.parse(req.query.f) : {};
1953
2097
  const limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
1954
2098
  const skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
1955
- const orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
2099
+ const orderParam = req.query.o
2100
+ ? JSON.parse(req.query.o)
2101
+ : req.resource.options.listOrder;
1956
2102
  const concatenateParam = req.query.c ? JSON.parse(req.query.c) : true;
1957
2103
  const resOpts = req.resource.options;
1958
2104
  let disambiguationField;
@@ -1982,7 +2128,9 @@ class FormsAngular {
1982
2128
  text += doc[field];
1983
2129
  }
1984
2130
  }
1985
- const disambiguationId = disambiguationField ? doc[disambiguationField] : undefined;
2131
+ const disambiguationId = disambiguationField
2132
+ ? doc[disambiguationField]
2133
+ : undefined;
1986
2134
  return { id: doc._id, text, disambiguationId };
1987
2135
  });
1988
2136
  if (disambiguationResourceName) {
@@ -2003,7 +2151,7 @@ class FormsAngular {
2003
2151
  item[listFields[0]] += ` (${disambiguationText})`;
2004
2152
  }
2005
2153
  else {
2006
- // store the text against hard-coded property name "disambiguation", rather than (say) using
2154
+ // store the text against hard-coded property name "disambiguation", rather than (say) using
2007
2155
  // item[disambiguationResourceName], because if disambiguationResourceName === disambiguationField,
2008
2156
  // that value would end up being deleted again when the this.disambiguate() call (which we have
2009
2157
  // been called from) does its final tidy-up and deletes [disambiguationField] from all of the items in docs
@@ -2020,7 +2168,6 @@ class FormsAngular {
2020
2168
  }
2021
2169
  });
2022
2170
  }
2023
- ;
2024
2171
  entityListAll() {
2025
2172
  return _.bind(function (req, res, next) {
2026
2173
  if (!(req.resource = this.getResource(req.params.resourceName))) {
@@ -2036,7 +2183,6 @@ class FormsAngular {
2036
2183
  });
2037
2184
  }, this);
2038
2185
  }
2039
- ;
2040
2186
  extractTimestampFromMongoID(record) {
2041
2187
  let timestamp = record.toString().substring(0, 8);
2042
2188
  return new Date(parseInt(timestamp, 16) * 1000);
@@ -2045,8 +2191,8 @@ class FormsAngular {
2045
2191
  exports.FormsAngular = FormsAngular;
2046
2192
  class ForeignKeyError extends global.Error {
2047
2193
  constructor(resourceName, foreignKeys) {
2048
- super(`Cannot delete this ${resourceName}, as it is: ${foreignKeys.map(d => ` the ${d.key} on ${d.resourceName} ${d.id}`).join("; ")}`);
2194
+ super(`Cannot delete this ${resourceName}, as it is: ${foreignKeys.map((d) => ` the ${d.key} on ${d.resourceName} ${d.id}`).join("; ")}`);
2049
2195
  this.name = "ForeignKeyError";
2050
- this.stack = new global.Error('').stack;
2196
+ this.stack = new global.Error("").stack;
2051
2197
  }
2052
2198
  }