forms-angular 0.12.0-beta.295 → 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.
- package/dist/server/data_form.js +434 -288
- package/dist/server/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/server/data_form.js
CHANGED
|
@@ -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(
|
|
8
|
-
const util = require(
|
|
9
|
-
const extend = require(
|
|
10
|
-
const async = require(
|
|
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
|
|
14
|
-
console.log(
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
'"':
|
|
22
|
-
"'":
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
"&": "&",
|
|
25
|
+
"<": "<",
|
|
26
|
+
">": ">",
|
|
27
|
+
'"': """,
|
|
28
|
+
"'": "'",
|
|
29
|
+
"/": "/",
|
|
30
|
+
"`": "`",
|
|
31
|
+
"=": "=",
|
|
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)
|
|
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(
|
|
59
|
+
mongoose.set("debug", debug);
|
|
52
60
|
mongoose.Promise = global.Promise;
|
|
53
61
|
this.options = _.extend({
|
|
54
|
-
urlPrefix:
|
|
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 =
|
|
66
|
-
this.app.get.apply(this.app, processArgs(this.options, [
|
|
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, [
|
|
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, [
|
|
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, [
|
|
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, [
|
|
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, [
|
|
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, [
|
|
84
|
-
|
|
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, [
|
|
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, [
|
|
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, [
|
|
94
|
-
|
|
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[
|
|
99
|
-
if (fieldDetails.type &&
|
|
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] !==
|
|
151
|
+
if (typeof doc[aField.field] !== "undefined") {
|
|
112
152
|
if (aField.params) {
|
|
113
153
|
if (aField.params.ref) {
|
|
114
|
-
let fieldOptions = resource.model.schema[
|
|
115
|
-
if (typeof fieldOptions.ref ===
|
|
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(
|
|
172
|
+
throw new Error("No support for ref type " + aField.params.ref.type);
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
|
-
else if (aField.params.params ===
|
|
175
|
+
else if (aField.params.params === "timestamp") {
|
|
136
176
|
let date = that.extractTimestampFromMongoID(doc[aField.field]);
|
|
137
|
-
cbm(null, date.toLocaleDateString() +
|
|
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(
|
|
208
|
+
cb(err, results.join(" ").trim());
|
|
169
209
|
}
|
|
170
210
|
else {
|
|
171
|
-
console.log(
|
|
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[
|
|
217
|
+
const keyList = Object.keys(resource.model.schema["tree"]);
|
|
178
218
|
// No list field specified - use the first String field,
|
|
179
|
-
display =
|
|
180
|
-
|
|
181
|
-
|
|
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[
|
|
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,
|
|
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 !==
|
|
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(
|
|
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 => {
|
|
241
|
-
|
|
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 !==
|
|
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 !==
|
|
307
|
+
if (path !== "_id" && schema.paths.hasOwnProperty(path)) {
|
|
268
308
|
const qualifiedPath = pathSoFar ? pathSoFar + "." + path : path;
|
|
269
|
-
if (schema.paths[path].options.index &&
|
|
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({
|
|
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() ===
|
|
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 ===
|
|
410
|
+
if (typeof req.query === "undefined") {
|
|
368
411
|
req.query = {};
|
|
369
412
|
}
|
|
370
|
-
const timestamps = {
|
|
371
|
-
|
|
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 &&
|
|
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 =
|
|
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 ||
|
|
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(
|
|
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 = {
|
|
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
|
|
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 = {
|
|
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 =
|
|
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) &&
|
|
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 =
|
|
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[
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
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
|
-
|
|
709
|
-
|
|
766
|
+
path: fld,
|
|
767
|
+
instance: schema[fld]._type,
|
|
710
768
|
};
|
|
711
769
|
}
|
|
712
770
|
else {
|
|
713
|
-
throw new Error(
|
|
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 ((![
|
|
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 ===
|
|
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 &&
|
|
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[
|
|
768
|
-
paths[
|
|
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 !==
|
|
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 = {
|
|
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
|
|
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 ===
|
|
793
|
-
realType.instance =
|
|
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 =
|
|
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 ===
|
|
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[
|
|
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 ===
|
|
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[
|
|
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 !==
|
|
895
|
-
|
|
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
|
-
|
|
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][
|
|
925
|
-
this.hackVariables(runPipeline[pipelineSection][
|
|
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] ===
|
|
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] +
|
|
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)
|
|
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(
|
|
1052
|
+
throw new Error("Invalid pipeline instruction");
|
|
969
1053
|
}
|
|
970
1054
|
switch (keys[0]) {
|
|
971
|
-
case
|
|
972
|
-
case
|
|
973
|
-
case
|
|
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
|
|
1066
|
+
case "$unionWith":
|
|
983
1067
|
/*
|
|
984
|
-
|
|
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
|
|
1000
|
-
this.hackVariables(array[pipelineSection][
|
|
1083
|
+
case "$match":
|
|
1084
|
+
this.hackVariables(array[pipelineSection]["$match"]);
|
|
1001
1085
|
retVal.push(array[pipelineSection]);
|
|
1002
|
-
if (!doneHiddenFields &&
|
|
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
|
|
1011
|
-
case
|
|
1096
|
+
case "$lookup":
|
|
1097
|
+
case "$graphLookup":
|
|
1012
1098
|
let needFindFunc = true;
|
|
1013
|
-
if (keys[0] ===
|
|
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 ||
|
|
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 ===
|
|
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(
|
|
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(
|
|
1141
|
+
throw new Error("Invalid pipeline instruction");
|
|
1052
1142
|
}
|
|
1053
|
-
if (nextKeys[0] ===
|
|
1143
|
+
if (nextKeys[0] === "$unwind") {
|
|
1054
1144
|
if (nextStage["$unwind"] === "$" + lookupField) {
|
|
1055
1145
|
nextStageIsUnwind = true;
|
|
1056
1146
|
}
|
|
1057
|
-
if (nextStage["$unwind"] &&
|
|
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(
|
|
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}`] =
|
|
1166
|
+
lookedUpFindQry[`${lookupField}.${prop}`] =
|
|
1167
|
+
lookedUpFindQry[prop];
|
|
1076
1168
|
delete lookedUpFindQry[prop];
|
|
1077
1169
|
}
|
|
1078
1170
|
}
|
|
1079
1171
|
if (allowNulls) {
|
|
1080
|
-
lookedUpFindQry = {
|
|
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(
|
|
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 &&
|
|
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 ===
|
|
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
|
|
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 !==
|
|
1215
|
+
if (param !== "noinput" && req.query.hasOwnProperty(param)) {
|
|
1117
1216
|
if (req.query[param]) {
|
|
1118
|
-
if (param !==
|
|
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 ===
|
|
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
|
|
1257
|
+
self
|
|
1258
|
+
.sanitisePipeline(runPipelineObj, hiddenFields, queryObj, req)
|
|
1158
1259
|
.then((runPipelineObj) => {
|
|
1159
|
-
resource.model
|
|
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(
|
|
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 = [
|
|
1277
|
+
toDo.applyTranslations = [
|
|
1278
|
+
"runAggregation",
|
|
1279
|
+
function (results, cb) {
|
|
1176
1280
|
function doATranslate(column, theTranslation) {
|
|
1177
|
-
results[
|
|
1281
|
+
results["runAggregation"].forEach(function (resultRow) {
|
|
1178
1282
|
let valToTranslate = resultRow[column.field];
|
|
1179
|
-
valToTranslate =
|
|
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
|
|
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
|
|
1300
|
+
return translation.ref === columnTranslation.ref;
|
|
1193
1301
|
});
|
|
1194
1302
|
if (theTranslation) {
|
|
1195
1303
|
doATranslate(columnTranslation, theTranslation);
|
|
1196
1304
|
}
|
|
1197
1305
|
else {
|
|
1198
|
-
cb(
|
|
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 = {
|
|
1332
|
+
let translateObject = {
|
|
1333
|
+
ref: lookup.resourceName,
|
|
1334
|
+
translations: [],
|
|
1335
|
+
};
|
|
1221
1336
|
translations.push(translateObject);
|
|
1222
|
-
lookup.model
|
|
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] =
|
|
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(
|
|
1373
|
+
return callback("Invalid ref property of " +
|
|
1374
|
+
thisColumnTranslation.ref +
|
|
1375
|
+
" in columnTranslations " +
|
|
1376
|
+
thisColumnTranslation.field);
|
|
1257
1377
|
}
|
|
1258
1378
|
}
|
|
1259
|
-
if (!thisColumnTranslation.translations &&
|
|
1260
|
-
|
|
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(
|
|
1388
|
+
return callback("A column translation needs a field property");
|
|
1265
1389
|
}
|
|
1266
1390
|
}
|
|
1267
1391
|
if (callFuncs) {
|
|
1268
|
-
toDo[
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
|
1307
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
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
|
|
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(
|
|
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(
|
|
1557
|
+
setSelectType("E", "I");
|
|
1420
1558
|
}
|
|
1421
1559
|
else if (projectParam[p] === 1) {
|
|
1422
|
-
setSelectType(
|
|
1560
|
+
setSelectType("I", "E");
|
|
1423
1561
|
}
|
|
1424
1562
|
else {
|
|
1425
|
-
throw new Error(
|
|
1563
|
+
throw new Error("Invalid projection: " + projectParam);
|
|
1426
1564
|
}
|
|
1427
1565
|
});
|
|
1428
|
-
if (type && type ===
|
|
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 &&
|
|
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
|
|
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
|
-
})
|
|
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(
|
|
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
|
|
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(
|
|
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[
|
|
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[
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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] ===
|
|
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
|
|
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 ===
|
|
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 &&
|
|
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
|
|
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({
|
|
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
|
|
1945
|
+
case "allow":
|
|
1804
1946
|
// old behaviour - no attempt to maintain data integrity
|
|
1805
1947
|
return doc.deleteOne();
|
|
1806
|
-
case
|
|
1948
|
+
case "cascade":
|
|
1807
1949
|
res.status(400).send('"cascade" option not yet supported');
|
|
1808
1950
|
break;
|
|
1809
1951
|
default:
|
|
1810
|
-
return that
|
|
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
|
|
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] ===
|
|
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
|
|
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
|
|
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(
|
|
2196
|
+
this.stack = new global.Error("").stack;
|
|
2051
2197
|
}
|
|
2052
2198
|
}
|