forms-angular 0.12.0-beta.193 → 0.12.0-beta.194
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/client/forms-angular.js +661 -199
- package/dist/client/forms-angular.min.js +1 -1
- package/dist/client/index.d.ts +98 -8
- package/dist/server/data_form.js +714 -539
- package/dist/server/index.d.ts +19 -3
- package/package.json +11 -11
package/dist/server/data_form.js
CHANGED
|
@@ -1,64 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __extends = (this && this.__extends) || (function () {
|
|
3
|
-
var extendStatics = function (d, b) {
|
|
4
|
-
extendStatics = Object.setPrototypeOf ||
|
|
5
|
-
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
-
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
-
return extendStatics(d, b);
|
|
8
|
-
};
|
|
9
|
-
return function (d, b) {
|
|
10
|
-
if (typeof b !== "function" && b !== null)
|
|
11
|
-
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
-
extendStatics(d, b);
|
|
13
|
-
function __() { this.constructor = d; }
|
|
14
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
-
};
|
|
16
|
-
})();
|
|
17
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
28
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
-
function step(op) {
|
|
31
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
-
while (_) try {
|
|
33
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
-
switch (op[0]) {
|
|
36
|
-
case 0: case 1: t = op; break;
|
|
37
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
-
default:
|
|
41
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
-
if (t[2]) _.ops.pop();
|
|
46
|
-
_.trys.pop(); continue;
|
|
47
|
-
}
|
|
48
|
-
op = body.call(thisArg, _);
|
|
49
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
3
|
exports.FormsAngular = void 0;
|
|
55
4
|
// This part of forms-angular borrows _very_ heavily from https://github.com/Alexandre-Strzelewicz/angular-bridge
|
|
56
5
|
// (now https://github.com/Unitech/angular-bridge
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
6
|
+
const _ = require('lodash');
|
|
7
|
+
const util = require('util');
|
|
8
|
+
const extend = require('node.extend');
|
|
9
|
+
const async = require('async');
|
|
10
|
+
let debug = false;
|
|
62
11
|
function logTheAPICalls(req, res, next) {
|
|
63
12
|
void (res);
|
|
64
13
|
console.log('API : ' + req.method + ' ' + req.url + ' [ ' + JSON.stringify(req.body) + ' ]');
|
|
@@ -66,8 +15,8 @@ function logTheAPICalls(req, res, next) {
|
|
|
66
15
|
}
|
|
67
16
|
function processArgs(options, array) {
|
|
68
17
|
if (options.authentication) {
|
|
69
|
-
|
|
70
|
-
for (
|
|
18
|
+
let authArray = _.isArray(options.authentication) ? options.authentication : [options.authentication];
|
|
19
|
+
for (let i = authArray.length - 1; i >= 0; i--) {
|
|
71
20
|
array.splice(1, 0, authArray[i]);
|
|
72
21
|
}
|
|
73
22
|
}
|
|
@@ -77,8 +26,8 @@ function processArgs(options, array) {
|
|
|
77
26
|
array[0] = options.urlPrefix + array[0];
|
|
78
27
|
return array;
|
|
79
28
|
}
|
|
80
|
-
|
|
81
|
-
|
|
29
|
+
class FormsAngular {
|
|
30
|
+
constructor(mongoose, app, options) {
|
|
82
31
|
this.mongoose = mongoose;
|
|
83
32
|
this.app = app;
|
|
84
33
|
app.locals.formsAngular = app.locals.formsAngular || [];
|
|
@@ -90,7 +39,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
90
39
|
}, options || {});
|
|
91
40
|
this.resources = [];
|
|
92
41
|
this.searchFunc = async.forEach;
|
|
93
|
-
|
|
42
|
+
const search = 'search/', schema = 'schema/', report = 'report/', resourceName = ':resourceName', id = '/:id';
|
|
94
43
|
this.app.get.apply(this.app, processArgs(this.options, ['models', this.models()]));
|
|
95
44
|
this.app.get.apply(this.app, processArgs(this.options, [search + resourceName, this.search()]));
|
|
96
45
|
this.app.get.apply(this.app, processArgs(this.options, [schema + resourceName, this.schema()]));
|
|
@@ -99,50 +48,53 @@ var FormsAngular = /** @class */ (function () {
|
|
|
99
48
|
this.app.get.apply(this.app, processArgs(this.options, [report + resourceName + '/:reportName', this.report()]));
|
|
100
49
|
this.app.get.apply(this.app, processArgs(this.options, [resourceName, this.collectionGet()]));
|
|
101
50
|
this.app.post.apply(this.app, processArgs(this.options, [resourceName, this.collectionPost()]));
|
|
51
|
+
// return the List attributes for all records - used by record-handler's setUpLookupOptions() method, for cases
|
|
52
|
+
// where there's a lookup that doesn't use the fngajax option
|
|
53
|
+
this.app.get.apply(this.app, processArgs(this.options, [resourceName + '/listAll', this.entityListAll()]));
|
|
102
54
|
this.app.get.apply(this.app, processArgs(this.options, [resourceName + id, this.entityGet()]));
|
|
103
55
|
this.app.post.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()])); // You can POST or PUT to update data
|
|
104
56
|
this.app.put.apply(this.app, processArgs(this.options, [resourceName + id, this.entityPut()]));
|
|
105
57
|
this.app.delete.apply(this.app, processArgs(this.options, [resourceName + id, this.entityDelete()]));
|
|
106
|
-
// return the List attributes for a record - used by
|
|
58
|
+
// return the List attributes for a record - used by fng-ui-select
|
|
107
59
|
this.app.get.apply(this.app, processArgs(this.options, [resourceName + id + '/list', this.entityList()]));
|
|
108
60
|
this.app.get.apply(this.app, processArgs(this.options, ['search', this.searchAll()]));
|
|
109
|
-
for (
|
|
61
|
+
for (let pluginName in this.options.plugins) {
|
|
110
62
|
if (this.options.plugins.hasOwnProperty(pluginName)) {
|
|
111
|
-
|
|
63
|
+
let pluginObj = this.options.plugins[pluginName];
|
|
112
64
|
this.options.plugins[pluginName] = Object.assign(this.options.plugins[pluginName], pluginObj.plugin(this, processArgs, pluginObj.options));
|
|
113
65
|
}
|
|
114
66
|
}
|
|
115
67
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return doc[keyList[i]];
|
|
123
|
-
}
|
|
68
|
+
getFirstMatchingField(resource, doc, keyList, type) {
|
|
69
|
+
for (let i = 0; i < keyList.length; i++) {
|
|
70
|
+
let fieldDetails = resource.model.schema['tree'][keyList[i]];
|
|
71
|
+
if (fieldDetails.type && (!type || fieldDetails.type.name === type) && keyList[i] !== '_id') {
|
|
72
|
+
resource.options.listFields = [{ field: keyList[i] }];
|
|
73
|
+
return doc ? doc[keyList[i]] : keyList[i];
|
|
124
74
|
}
|
|
125
75
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
76
|
+
}
|
|
77
|
+
getListFields(resource, doc, cb) {
|
|
78
|
+
const that = this;
|
|
79
|
+
let display = '';
|
|
80
|
+
let listFields = resource.options.listFields;
|
|
129
81
|
if (listFields) {
|
|
130
82
|
async.map(listFields, function (aField, cbm) {
|
|
131
83
|
if (typeof doc[aField.field] !== 'undefined') {
|
|
132
84
|
if (aField.params) {
|
|
133
85
|
if (aField.params.ref) {
|
|
134
|
-
|
|
86
|
+
let fieldOptions = resource.model.schema['paths'][aField.field].options;
|
|
135
87
|
if (typeof fieldOptions.ref === 'string') {
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
88
|
+
let lookupResource = that.getResource(fieldOptions.ref);
|
|
89
|
+
if (lookupResource) {
|
|
90
|
+
let hiddenFields = that.generateHiddenFields(lookupResource, false);
|
|
139
91
|
hiddenFields.__v = false;
|
|
140
|
-
|
|
92
|
+
lookupResource.model.findOne({ _id: doc[aField.field] }).select(hiddenFields).exec(function (err, doc2) {
|
|
141
93
|
if (err) {
|
|
142
94
|
cbm(err);
|
|
143
95
|
}
|
|
144
96
|
else {
|
|
145
|
-
that.getListFields(
|
|
97
|
+
that.getListFields(lookupResource, doc2, cbm);
|
|
146
98
|
}
|
|
147
99
|
});
|
|
148
100
|
}
|
|
@@ -152,7 +104,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
152
104
|
}
|
|
153
105
|
}
|
|
154
106
|
else if (aField.params.params === 'timestamp') {
|
|
155
|
-
|
|
107
|
+
let date = that.extractTimestampFromMongoID(doc[aField.field]);
|
|
156
108
|
cbm(null, date.toLocaleDateString() + ' ' + date.toLocaleTimeString());
|
|
157
109
|
}
|
|
158
110
|
}
|
|
@@ -178,29 +130,61 @@ var FormsAngular = /** @class */ (function () {
|
|
|
178
130
|
});
|
|
179
131
|
}
|
|
180
132
|
else {
|
|
181
|
-
|
|
133
|
+
const keyList = Object.keys(resource.model.schema['tree']);
|
|
182
134
|
// No list field specified - use the first String field,
|
|
183
|
-
display = getFirstMatchingField(keyList, 'String') ||
|
|
135
|
+
display = this.getFirstMatchingField(resource, doc, keyList, 'String') ||
|
|
184
136
|
// and if there aren't any then just take the first field
|
|
185
|
-
getFirstMatchingField(keyList);
|
|
137
|
+
this.getFirstMatchingField(resource, doc, keyList);
|
|
186
138
|
cb(null, display.trim());
|
|
187
139
|
}
|
|
188
|
-
}
|
|
140
|
+
}
|
|
141
|
+
;
|
|
142
|
+
// generate a Mongo projection that can be used to restrict a query to return only those fields from the given
|
|
143
|
+
// resource that are identified as "list" fields (i.e., ones that should appear whenever records of that type are
|
|
144
|
+
// displayed in a list)
|
|
145
|
+
generateListFieldProjection(resource) {
|
|
146
|
+
const projection = {};
|
|
147
|
+
const listFields = resource.options?.listFields;
|
|
148
|
+
// resource.options.listFields will identify all of the fields from resource that have a value for .list.
|
|
149
|
+
// generally, that value will be "true", identifying the corresponding field as one which should be
|
|
150
|
+
// included whenever records of that type appear in a list.
|
|
151
|
+
// occasionally, it will instead be "{ ref: true }"", which means something entirely different -
|
|
152
|
+
// this means that the field requires a lookup translation before it can be displayed on a form.
|
|
153
|
+
// for our purposes, we're interested in only the first of these two cases, so we'll ignore anything where
|
|
154
|
+
// field.params.ref has a truthy value
|
|
155
|
+
if (listFields) {
|
|
156
|
+
for (const field of listFields) {
|
|
157
|
+
if (!field.params?.ref) {
|
|
158
|
+
projection[field.field] = 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const keyList = Object.keys(resource.model.schema['tree']);
|
|
164
|
+
const firstField = (
|
|
165
|
+
// No list field specified - use the first String field,
|
|
166
|
+
this.getFirstMatchingField(resource, undefined, keyList, 'String') ||
|
|
167
|
+
// and if there aren't any then just take the first field
|
|
168
|
+
this.getFirstMatchingField(resource, undefined, keyList));
|
|
169
|
+
projection[firstField] = 1;
|
|
170
|
+
}
|
|
171
|
+
return projection;
|
|
172
|
+
}
|
|
189
173
|
;
|
|
190
|
-
|
|
174
|
+
newResource(model, options) {
|
|
191
175
|
options = options || {};
|
|
192
176
|
options.suppressDeprecatedMessage = true;
|
|
193
|
-
|
|
177
|
+
let passModel = model;
|
|
194
178
|
if (typeof model !== 'function') {
|
|
195
179
|
passModel = model.model;
|
|
196
180
|
}
|
|
197
181
|
this.addResource(passModel.modelName, passModel, options);
|
|
198
|
-
}
|
|
182
|
+
}
|
|
199
183
|
;
|
|
200
184
|
// Add a resource, specifying the model and any options.
|
|
201
185
|
// Models may include their own options, which means they can be passed through from the model file
|
|
202
|
-
|
|
203
|
-
|
|
186
|
+
addResource(resourceName, model, options) {
|
|
187
|
+
let resource = {
|
|
204
188
|
resourceName: resourceName,
|
|
205
189
|
options: options || {}
|
|
206
190
|
};
|
|
@@ -212,7 +196,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
212
196
|
}
|
|
213
197
|
else {
|
|
214
198
|
resource.model = model.model;
|
|
215
|
-
for (
|
|
199
|
+
for (const prop in model) {
|
|
216
200
|
if (model.hasOwnProperty(prop) && prop !== 'model') {
|
|
217
201
|
resource.options[prop] = model[prop];
|
|
218
202
|
}
|
|
@@ -230,30 +214,89 @@ var FormsAngular = /** @class */ (function () {
|
|
|
230
214
|
else {
|
|
231
215
|
this.resources.push(resource);
|
|
232
216
|
}
|
|
233
|
-
}
|
|
217
|
+
}
|
|
234
218
|
;
|
|
235
|
-
|
|
219
|
+
getResource(name) {
|
|
236
220
|
return _.find(this.resources, function (resource) {
|
|
237
221
|
return resource.resourceName === name;
|
|
238
222
|
});
|
|
239
|
-
}
|
|
223
|
+
}
|
|
240
224
|
;
|
|
241
|
-
|
|
225
|
+
getResourceFromCollection(name) {
|
|
242
226
|
return _.find(this.resources, function (resource) {
|
|
243
227
|
return resource.model.collection.collectionName === name;
|
|
244
228
|
});
|
|
245
|
-
}
|
|
229
|
+
}
|
|
246
230
|
;
|
|
247
|
-
|
|
248
|
-
|
|
231
|
+
// Using the given (already-populated) AmbiguousRecordStore, generate text suitable for
|
|
232
|
+
// disambiguation of each ambiguous record, and pass that to the given disambiguateItemCallback
|
|
233
|
+
// so our caller can decorate the ambiguous record in whatever way it deems appropriate.
|
|
234
|
+
//
|
|
235
|
+
// The ambiguousRecordStore provided to this function (generated either by a call to
|
|
236
|
+
// buildSingleResourceAmbiguousRecordStore() or buildMultiResourceAmbiguousRecordStore()) will
|
|
237
|
+
// already be grouping records by the resource that should be used to disambiguate them, with
|
|
238
|
+
// the name of that resource being the primary index property of the store.
|
|
239
|
+
//
|
|
240
|
+
// The disambiguation text will be the concatenation (space-seperated) of the list fields for
|
|
241
|
+
// the doc from that resource whose _id matches the value of record[disambiguationField].
|
|
242
|
+
//
|
|
243
|
+
// allRecords should include all of the ambiguous records (also held by AmbiguousRecordStore)
|
|
244
|
+
// as well as those found not to be ambiguous. The final act of this function will be to delete
|
|
245
|
+
// the disambiguation field from those records - it is only going to be there for the purpose
|
|
246
|
+
// of disambiguation, and should not be returned by our caller once disambiguation is complete.
|
|
247
|
+
//
|
|
248
|
+
// The scary-looking templating used here ensures that the objects in allRecords (and also
|
|
249
|
+
// ambiguousRecordStore) include an (optional) string property with the name identified by
|
|
250
|
+
// disambiguationField. For the avoidance of doubt, "prop" here could be anything - "foo in f"
|
|
251
|
+
// would achieve the same result.
|
|
252
|
+
disambiguate(allRecords, ambiguousRecordStore, disambiguationField, disambiguateItemCallback, completionCallback) {
|
|
253
|
+
const that = this;
|
|
254
|
+
async.map(Object.keys(ambiguousRecordStore), function (resourceName, cbm) {
|
|
255
|
+
const resource = that.getResource(resourceName);
|
|
256
|
+
const projection = that.generateListFieldProjection(resource);
|
|
257
|
+
resource.model
|
|
258
|
+
.find({ _id: { $in: ambiguousRecordStore[resourceName].map((sr) => sr[disambiguationField]) } })
|
|
259
|
+
.select(projection)
|
|
260
|
+
.lean()
|
|
261
|
+
.then((disambiguationRecs) => {
|
|
262
|
+
for (const ambiguousResult of ambiguousRecordStore[resourceName]) {
|
|
263
|
+
const disambiguator = disambiguationRecs.find((d) => d._id.toString() === ambiguousResult[disambiguationField].toString());
|
|
264
|
+
if (disambiguator) {
|
|
265
|
+
let suffix = "";
|
|
266
|
+
for (const listField in projection) {
|
|
267
|
+
if (disambiguator[listField]) {
|
|
268
|
+
if (suffix) {
|
|
269
|
+
suffix += " ";
|
|
270
|
+
}
|
|
271
|
+
suffix += disambiguator[listField];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (suffix) {
|
|
275
|
+
disambiguateItemCallback(ambiguousResult, suffix);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
cbm(null);
|
|
280
|
+
})
|
|
281
|
+
.catch((err) => {
|
|
282
|
+
cbm(err);
|
|
283
|
+
});
|
|
284
|
+
}, (err) => {
|
|
285
|
+
for (const record of allRecords) {
|
|
286
|
+
delete record[disambiguationField];
|
|
287
|
+
}
|
|
288
|
+
completionCallback(err);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
internalSearch(req, resourcesToSearch, includeResourceInResults, limit, callback) {
|
|
249
292
|
if (typeof req.query === 'undefined') {
|
|
250
293
|
req.query = {};
|
|
251
294
|
}
|
|
252
|
-
|
|
253
|
-
|
|
295
|
+
const timestamps = { sentAt: req.query.sentAt, startedAt: new Date().valueOf(), completedAt: undefined };
|
|
296
|
+
let searches = [], resourceCount = resourcesToSearch.length, searchFor = req.query.q || '', filter = req.query.f;
|
|
254
297
|
function translate(string, array, context) {
|
|
255
298
|
if (array) {
|
|
256
|
-
|
|
299
|
+
let translation = _.find(array, function (fromTo) {
|
|
257
300
|
return fromTo.from === string && (!fromTo.context || fromTo.context === context);
|
|
258
301
|
});
|
|
259
302
|
if (translation) {
|
|
@@ -264,11 +307,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
264
307
|
}
|
|
265
308
|
// return a string that determines the sort order of the resultObject
|
|
266
309
|
function calcResultValue(obj) {
|
|
267
|
-
function padLeft(score, reqLength, str) {
|
|
268
|
-
if (str === void 0) { str = '0'; }
|
|
310
|
+
function padLeft(score, reqLength, str = '0') {
|
|
269
311
|
return new Array(1 + reqLength - String(score).length).join(str) + score;
|
|
270
312
|
}
|
|
271
|
-
|
|
313
|
+
let sortString = '';
|
|
272
314
|
sortString += padLeft(obj.addHits || 9, 1);
|
|
273
315
|
sortString += padLeft(obj.searchImportance || 99, 2);
|
|
274
316
|
sortString += padLeft(obj.weighting || 9999, 4);
|
|
@@ -279,9 +321,9 @@ var FormsAngular = /** @class */ (function () {
|
|
|
279
321
|
filter = JSON.parse(filter);
|
|
280
322
|
}
|
|
281
323
|
// See if we are narrowing down the resources
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
324
|
+
let collectionName;
|
|
325
|
+
let collectionNameLower;
|
|
326
|
+
let colonPos = searchFor.indexOf(':');
|
|
285
327
|
switch (colonPos) {
|
|
286
328
|
case -1:
|
|
287
329
|
// Original behaviour = do nothing different
|
|
@@ -298,19 +340,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
298
340
|
}
|
|
299
341
|
break;
|
|
300
342
|
}
|
|
301
|
-
for (
|
|
302
|
-
|
|
303
|
-
if (resourceCount === 1 || (resource.options.searchImportance !== false && (!collectionName || collectionName === resource.resourceName ||
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
for (
|
|
307
|
-
|
|
308
|
-
|
|
343
|
+
for (let i = 0; i < resourceCount; i++) {
|
|
344
|
+
let resource = resourcesToSearch[i];
|
|
345
|
+
if (resourceCount === 1 || (resource.options.searchImportance !== false && (!collectionName || collectionName === resource.resourceName || resource.options?.synonyms?.find(s => s.name?.toLowerCase() === collectionNameLower)))) {
|
|
346
|
+
let schema = resource.model.schema;
|
|
347
|
+
let indexedFields = [];
|
|
348
|
+
for (let j = 0; j < schema._indexes.length; j++) {
|
|
349
|
+
let attributes = schema._indexes[j][0];
|
|
350
|
+
let field = Object.keys(attributes)[0];
|
|
309
351
|
if (indexedFields.indexOf(field) === -1) {
|
|
310
352
|
indexedFields.push(field);
|
|
311
353
|
}
|
|
312
354
|
}
|
|
313
|
-
for (
|
|
355
|
+
for (let path in schema.paths) {
|
|
314
356
|
if (path !== '_id' && schema.paths.hasOwnProperty(path)) {
|
|
315
357
|
if (schema.paths[path]._index && !schema.paths[path].options.noSearch) {
|
|
316
358
|
if (indexedFields.indexOf(path) === -1) {
|
|
@@ -322,10 +364,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
322
364
|
if (indexedFields.length === 0) {
|
|
323
365
|
console.log('ERROR: Searching on a collection with no indexes ' + resource.resourceName);
|
|
324
366
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (
|
|
328
|
-
|
|
367
|
+
let synonymObj = resource.options?.synonyms?.find(s => s.name.toLowerCase() === collectionNameLower);
|
|
368
|
+
const synonymFilter = synonymObj?.filter;
|
|
369
|
+
for (let m = 0; m < indexedFields.length; m++) {
|
|
370
|
+
let searchObj = { resource: resource, field: indexedFields[m] };
|
|
329
371
|
if (synonymFilter) {
|
|
330
372
|
searchObj.filter = synonymFilter;
|
|
331
373
|
}
|
|
@@ -333,19 +375,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
333
375
|
}
|
|
334
376
|
}
|
|
335
377
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
378
|
+
const that = this;
|
|
379
|
+
let results = [];
|
|
380
|
+
let moreCount = 0;
|
|
381
|
+
let searchCriteria;
|
|
382
|
+
let searchStrings;
|
|
383
|
+
let multiMatchPossible = false;
|
|
342
384
|
if (searchFor === '?') {
|
|
343
385
|
// interpret this as a wildcard (so there is no way to search for ?
|
|
344
386
|
searchCriteria = null;
|
|
345
387
|
}
|
|
346
388
|
else {
|
|
347
389
|
// Support for searching anywhere in a field by starting with *
|
|
348
|
-
|
|
390
|
+
let startAnchor = '^';
|
|
349
391
|
if (searchFor.slice(0, 1) === '*') {
|
|
350
392
|
startAnchor = '';
|
|
351
393
|
searchFor = searchFor.slice(1);
|
|
@@ -356,19 +398,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
356
398
|
if (multiMatchPossible) {
|
|
357
399
|
searchStrings = searchFor.split(' ');
|
|
358
400
|
}
|
|
359
|
-
|
|
401
|
+
let modifiedSearchStr = multiMatchPossible ? searchStrings.join('|') : searchFor;
|
|
360
402
|
searchFor = searchFor.toLowerCase(); // For later case-insensitive comparison
|
|
361
403
|
// Removed the logic that preserved spaces when collection was specified because Louise asked me to.
|
|
362
|
-
searchCriteria = { $regex:
|
|
404
|
+
searchCriteria = { $regex: `${startAnchor}(${modifiedSearchStr})`, $options: 'i' };
|
|
363
405
|
}
|
|
364
|
-
|
|
406
|
+
let handleSearchResultsFromIndex = function (err, docs, item, cb) {
|
|
365
407
|
function handleSingleSearchResult(aDoc, cbdoc) {
|
|
366
|
-
|
|
408
|
+
let thisId = aDoc._id.toString(), resultObject, resultPos;
|
|
367
409
|
function handleResultsInList() {
|
|
368
410
|
if (multiMatchPossible) {
|
|
369
411
|
resultObject.matched = resultObject.matched || [];
|
|
370
412
|
// record the index of string that matched, so we don't count it against another field
|
|
371
|
-
for (
|
|
413
|
+
for (let i = 0; i < searchStrings.length; i++) {
|
|
372
414
|
if (aDoc[item.field].toLowerCase().indexOf(searchStrings[i]) === 0) {
|
|
373
415
|
resultObject.matched.push(i);
|
|
374
416
|
break;
|
|
@@ -395,7 +437,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
395
437
|
// If they have already matched then improve their weighting
|
|
396
438
|
if (multiMatchPossible) {
|
|
397
439
|
// record the index of string that matched, so we don't count it against another field
|
|
398
|
-
for (
|
|
440
|
+
for (let i = 0; i < searchStrings.length; i++) {
|
|
399
441
|
if (!resultObject.matched.includes(i) && aDoc[item.field].toLowerCase().indexOf(searchStrings[i]) === 0) {
|
|
400
442
|
resultObject.matched.push(i);
|
|
401
443
|
resultObject.addHits = Math.max((resultObject.addHits || 9) - 1, 0);
|
|
@@ -411,19 +453,28 @@ var FormsAngular = /** @class */ (function () {
|
|
|
411
453
|
}
|
|
412
454
|
else {
|
|
413
455
|
// Otherwise add them new...
|
|
414
|
-
|
|
415
|
-
if (multiMatchPossible)
|
|
456
|
+
let addHits;
|
|
457
|
+
if (multiMatchPossible) {
|
|
416
458
|
// If they match the whole search phrase in one index they get smaller addHits (so they sort higher)
|
|
417
459
|
if (aDoc[item.field].toLowerCase().indexOf(searchFor) === 0) {
|
|
418
|
-
|
|
460
|
+
addHits = 7;
|
|
419
461
|
}
|
|
462
|
+
}
|
|
463
|
+
let disambiguationId;
|
|
464
|
+
const opts = item.resource.options;
|
|
465
|
+
const disambiguationResource = opts.disambiguation?.resource;
|
|
466
|
+
if (disambiguationResource) {
|
|
467
|
+
disambiguationId = aDoc[opts.disambiguation.field]?.toString();
|
|
468
|
+
}
|
|
420
469
|
// Use special listings format if defined
|
|
421
|
-
|
|
470
|
+
let specialListingFormat = item.resource.options.searchResultFormat;
|
|
422
471
|
if (specialListingFormat) {
|
|
423
472
|
specialListingFormat.apply(aDoc, [req])
|
|
424
|
-
.then(
|
|
473
|
+
.then((resultObj) => {
|
|
425
474
|
resultObject = resultObj;
|
|
426
|
-
resultObject.addHits =
|
|
475
|
+
resultObject.addHits = addHits;
|
|
476
|
+
resultObject.disambiguationResource = disambiguationResource;
|
|
477
|
+
resultObject.disambiguationId = disambiguationId;
|
|
427
478
|
handleResultsInList();
|
|
428
479
|
});
|
|
429
480
|
}
|
|
@@ -436,7 +487,9 @@ var FormsAngular = /** @class */ (function () {
|
|
|
436
487
|
resultObject = {
|
|
437
488
|
id: aDoc._id,
|
|
438
489
|
weighting: 9999,
|
|
439
|
-
addHits
|
|
490
|
+
addHits,
|
|
491
|
+
disambiguationResource,
|
|
492
|
+
disambiguationId,
|
|
440
493
|
text: description
|
|
441
494
|
};
|
|
442
495
|
if (resourceCount > 1 || includeResourceInResults) {
|
|
@@ -456,14 +509,14 @@ var FormsAngular = /** @class */ (function () {
|
|
|
456
509
|
}
|
|
457
510
|
};
|
|
458
511
|
this.searchFunc(searches, function (item, cb) {
|
|
459
|
-
|
|
460
|
-
|
|
512
|
+
let searchDoc = {};
|
|
513
|
+
let searchFilter = filter || item.filter;
|
|
461
514
|
if (searchFilter) {
|
|
462
515
|
that.hackVariables(searchFilter);
|
|
463
516
|
extend(searchDoc, searchFilter);
|
|
464
517
|
if (searchFilter[item.field]) {
|
|
465
518
|
delete searchDoc[item.field];
|
|
466
|
-
|
|
519
|
+
let obj1 = {}, obj2 = {};
|
|
467
520
|
obj1[item.field] = searchFilter[item.field];
|
|
468
521
|
obj2[item.field] = searchCriteria;
|
|
469
522
|
searchDoc['$and'] = [obj1, obj2];
|
|
@@ -512,13 +565,27 @@ var FormsAngular = /** @class */ (function () {
|
|
|
512
565
|
moreCount += results.length - limit;
|
|
513
566
|
results.splice(limit);
|
|
514
567
|
}
|
|
515
|
-
|
|
516
|
-
|
|
568
|
+
that.disambiguate(results, that.buildMultiResourceAmbiguousRecordStore(results, ["text"], "disambiguationResource"), "disambiguationId", (item, disambiguationText) => {
|
|
569
|
+
item.text += ` (${disambiguationText})`;
|
|
570
|
+
}, (err) => {
|
|
571
|
+
if (err) {
|
|
572
|
+
callback(err);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
// the disambiguate() call will have deleted the disambiguationIds but we're responsible for
|
|
576
|
+
// the disambiguationResources, which we shouldn't be returning to the client
|
|
577
|
+
for (const result of results) {
|
|
578
|
+
delete result.disambiguationResource;
|
|
579
|
+
}
|
|
580
|
+
timestamps.completedAt = new Date().valueOf();
|
|
581
|
+
callback(null, { results, moreCount, timestamps });
|
|
582
|
+
}
|
|
583
|
+
});
|
|
517
584
|
}
|
|
518
585
|
});
|
|
519
|
-
}
|
|
586
|
+
}
|
|
520
587
|
;
|
|
521
|
-
|
|
588
|
+
wrapInternalSearch(req, res, resourcesToSearch, includeResourceInResults, limit) {
|
|
522
589
|
this.internalSearch(req, resourcesToSearch, includeResourceInResults, limit, function (err, resultsObject) {
|
|
523
590
|
if (err) {
|
|
524
591
|
res.status(400, err);
|
|
@@ -527,46 +594,46 @@ var FormsAngular = /** @class */ (function () {
|
|
|
527
594
|
res.send(resultsObject);
|
|
528
595
|
}
|
|
529
596
|
});
|
|
530
|
-
}
|
|
597
|
+
}
|
|
531
598
|
;
|
|
532
|
-
|
|
599
|
+
search() {
|
|
533
600
|
return _.bind(function (req, res, next) {
|
|
534
601
|
if (!(req.resource = this.getResource(req.params.resourceName))) {
|
|
535
602
|
return next();
|
|
536
603
|
}
|
|
537
604
|
this.wrapInternalSearch(req, res, [req.resource], false, 0);
|
|
538
605
|
}, this);
|
|
539
|
-
}
|
|
606
|
+
}
|
|
540
607
|
;
|
|
541
|
-
|
|
608
|
+
searchAll() {
|
|
542
609
|
return _.bind(function (req, res) {
|
|
543
610
|
this.wrapInternalSearch(req, res, this.resources, true, 10);
|
|
544
611
|
}, this);
|
|
545
|
-
}
|
|
612
|
+
}
|
|
546
613
|
;
|
|
547
|
-
|
|
548
|
-
|
|
614
|
+
models() {
|
|
615
|
+
const that = this;
|
|
549
616
|
return function (req, res) {
|
|
550
617
|
// TODO: Make this less wasteful - we only need to send the resourceNames of the resources
|
|
551
618
|
// Check for optional modelFilter and call it with the request and current list. Otherwise just return the list.
|
|
552
619
|
res.send(that.options.modelFilter ? that.options.modelFilter.call(null, req, that.resources) : that.resources);
|
|
553
620
|
};
|
|
554
|
-
}
|
|
621
|
+
}
|
|
555
622
|
;
|
|
556
|
-
|
|
557
|
-
res.statusMessage =
|
|
558
|
-
res.status(400).end(
|
|
559
|
-
}
|
|
623
|
+
renderError(err, redirectUrl, req, res) {
|
|
624
|
+
res.statusMessage = err?.message || err;
|
|
625
|
+
res.status(400).end(err?.message || err);
|
|
626
|
+
}
|
|
560
627
|
;
|
|
561
|
-
|
|
628
|
+
redirect(address, req, res) {
|
|
562
629
|
res.send(address);
|
|
563
|
-
}
|
|
630
|
+
}
|
|
564
631
|
;
|
|
565
|
-
|
|
566
|
-
|
|
632
|
+
applySchemaSubset(vanilla, schema) {
|
|
633
|
+
let outPath;
|
|
567
634
|
if (schema) {
|
|
568
635
|
outPath = {};
|
|
569
|
-
for (
|
|
636
|
+
for (let fld in schema) {
|
|
570
637
|
if (schema.hasOwnProperty(fld)) {
|
|
571
638
|
if (vanilla[fld]) {
|
|
572
639
|
outPath[fld] = _.cloneDeep(vanilla[fld]);
|
|
@@ -586,7 +653,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
586
653
|
}
|
|
587
654
|
}
|
|
588
655
|
outPath[fld].options = outPath[fld].options || {};
|
|
589
|
-
for (
|
|
656
|
+
for (const override in schema[fld]) {
|
|
590
657
|
if (schema[fld].hasOwnProperty(override)) {
|
|
591
658
|
if (override.slice(0, 1) !== '_') {
|
|
592
659
|
if (schema[fld].hasOwnProperty(override)) {
|
|
@@ -605,19 +672,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
605
672
|
outPath = vanilla;
|
|
606
673
|
}
|
|
607
674
|
return outPath;
|
|
608
|
-
}
|
|
675
|
+
}
|
|
609
676
|
;
|
|
610
|
-
|
|
611
|
-
|
|
677
|
+
preprocess(resource, paths, formSchema) {
|
|
678
|
+
let outPath = {}, hiddenFields = [], listFields = [];
|
|
612
679
|
if (resource && resource.options && resource.options.idIsList) {
|
|
613
680
|
paths['_id'].options = paths['_id'].options || {};
|
|
614
681
|
paths['_id'].options.list = resource.options.idIsList;
|
|
615
682
|
}
|
|
616
|
-
for (
|
|
683
|
+
for (let element in paths) {
|
|
617
684
|
if (paths.hasOwnProperty(element) && element !== '__v') {
|
|
618
685
|
// check for schemas
|
|
619
686
|
if (paths[element].schema) {
|
|
620
|
-
|
|
687
|
+
let subSchemaInfo = this.preprocess(null, paths[element].schema.paths);
|
|
621
688
|
outPath[element] = { schema: subSchemaInfo.paths };
|
|
622
689
|
if (paths[element].options.form) {
|
|
623
690
|
outPath[element].options = { form: extend(true, {}, paths[element].options.form) };
|
|
@@ -631,10 +698,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
631
698
|
}
|
|
632
699
|
else {
|
|
633
700
|
// check for arrays
|
|
634
|
-
|
|
701
|
+
let realType = paths[element].caster ? paths[element].caster : paths[element];
|
|
635
702
|
if (!realType.instance) {
|
|
636
703
|
if (realType.options.type) {
|
|
637
|
-
|
|
704
|
+
let type = realType.options.type(), typeType = typeof type;
|
|
638
705
|
if (typeType === 'string') {
|
|
639
706
|
realType.instance = (!isNaN(Date.parse(type))) ? 'Date' : 'String';
|
|
640
707
|
}
|
|
@@ -650,9 +717,9 @@ var FormsAngular = /** @class */ (function () {
|
|
|
650
717
|
if (paths[element].options.match) {
|
|
651
718
|
outPath[element].options.match = paths[element].options.match.source || paths[element].options.match;
|
|
652
719
|
}
|
|
653
|
-
|
|
720
|
+
let schemaListInfo = paths[element].options.list;
|
|
654
721
|
if (schemaListInfo) {
|
|
655
|
-
|
|
722
|
+
let listFieldInfo = { field: element };
|
|
656
723
|
if (typeof schemaListInfo === 'object' && Object.keys(schemaListInfo).length > 0) {
|
|
657
724
|
listFieldInfo.params = schemaListInfo;
|
|
658
725
|
}
|
|
@@ -662,7 +729,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
662
729
|
}
|
|
663
730
|
}
|
|
664
731
|
outPath = this.applySchemaSubset(outPath, formSchema);
|
|
665
|
-
|
|
732
|
+
let returnObj = { paths: outPath };
|
|
666
733
|
if (hiddenFields.length > 0) {
|
|
667
734
|
returnObj.hide = hiddenFields;
|
|
668
735
|
}
|
|
@@ -670,113 +737,101 @@ var FormsAngular = /** @class */ (function () {
|
|
|
670
737
|
returnObj.listFields = listFields;
|
|
671
738
|
}
|
|
672
739
|
return returnObj;
|
|
673
|
-
}
|
|
740
|
+
}
|
|
674
741
|
;
|
|
675
|
-
|
|
742
|
+
schema() {
|
|
676
743
|
return _.bind(function (req, res) {
|
|
677
744
|
if (!(req.resource = this.getResource(req.params.resourceName))) {
|
|
678
745
|
return res.status(404).end();
|
|
679
746
|
}
|
|
680
|
-
|
|
747
|
+
let formSchema = null;
|
|
681
748
|
if (req.params.formName) {
|
|
682
749
|
formSchema = req.resource.model.schema.statics['form'](req.params.formName, req);
|
|
683
750
|
}
|
|
684
|
-
|
|
751
|
+
let paths = this.preprocess(req.resource, req.resource.model.schema.paths, formSchema).paths;
|
|
685
752
|
res.send(paths);
|
|
686
753
|
}, this);
|
|
687
|
-
}
|
|
754
|
+
}
|
|
688
755
|
;
|
|
689
|
-
|
|
690
|
-
return _.bind(function (req, res, next) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
else {
|
|
722
|
-
fields = {};
|
|
723
|
-
for (key in req.resource.model.schema.paths) {
|
|
724
|
-
if (req.resource.model.schema.paths.hasOwnProperty(key)) {
|
|
725
|
-
if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
|
|
726
|
-
if (key.indexOf('.') === -1) {
|
|
727
|
-
fields[key] = 1;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
reportSchema = {
|
|
733
|
-
pipeline: [
|
|
734
|
-
{ $project: fields }
|
|
735
|
-
], drilldown: req.params.resourceName + '/|_id|/edit'
|
|
736
|
-
};
|
|
756
|
+
report() {
|
|
757
|
+
return _.bind(async function (req, res, next) {
|
|
758
|
+
if (!(req.resource = this.getResource(req.params.resourceName))) {
|
|
759
|
+
return next();
|
|
760
|
+
}
|
|
761
|
+
const self = this;
|
|
762
|
+
if (typeof req.query === 'undefined') {
|
|
763
|
+
req.query = {};
|
|
764
|
+
}
|
|
765
|
+
let reportSchema;
|
|
766
|
+
if (req.params.reportName) {
|
|
767
|
+
reportSchema = await req.resource.model.schema.statics['report'](req.params.reportName, req);
|
|
768
|
+
}
|
|
769
|
+
else if (req.query.r) {
|
|
770
|
+
switch (req.query.r[0]) {
|
|
771
|
+
case '[':
|
|
772
|
+
reportSchema = { pipeline: JSON.parse(req.query.r) };
|
|
773
|
+
break;
|
|
774
|
+
case '{':
|
|
775
|
+
reportSchema = JSON.parse(req.query.r);
|
|
776
|
+
break;
|
|
777
|
+
default:
|
|
778
|
+
return self.renderError(new Error('Invalid "r" parameter'), null, req, res);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
let fields = {};
|
|
783
|
+
for (let key in req.resource.model.schema.paths) {
|
|
784
|
+
if (req.resource.model.schema.paths.hasOwnProperty(key)) {
|
|
785
|
+
if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
|
|
786
|
+
if (key.indexOf('.') === -1) {
|
|
787
|
+
fields[key] = 1;
|
|
737
788
|
}
|
|
738
|
-
|
|
739
|
-
case 3:
|
|
740
|
-
schemaCopy = {};
|
|
741
|
-
extend(schemaCopy, reportSchema);
|
|
742
|
-
schemaCopy.params = schemaCopy.params || [];
|
|
743
|
-
self.reportInternal(req, req.resource, schemaCopy, function (err, result) {
|
|
744
|
-
if (err) {
|
|
745
|
-
res.send({ success: false, error: err.message || err });
|
|
746
|
-
}
|
|
747
|
-
else {
|
|
748
|
-
res.send(result);
|
|
749
|
-
}
|
|
750
|
-
});
|
|
751
|
-
return [2 /*return*/];
|
|
789
|
+
}
|
|
752
790
|
}
|
|
753
|
-
}
|
|
791
|
+
}
|
|
792
|
+
reportSchema = {
|
|
793
|
+
pipeline: [
|
|
794
|
+
{ $project: fields }
|
|
795
|
+
], drilldown: req.params.resourceName + '/|_id|/edit'
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
// Replace parameters in pipeline
|
|
799
|
+
let schemaCopy = {};
|
|
800
|
+
extend(schemaCopy, reportSchema);
|
|
801
|
+
schemaCopy.params = schemaCopy.params || [];
|
|
802
|
+
self.reportInternal(req, req.resource, schemaCopy, function (err, result) {
|
|
803
|
+
if (err) {
|
|
804
|
+
res.send({ success: false, error: err.message || err });
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
res.send(result);
|
|
808
|
+
}
|
|
754
809
|
});
|
|
755
810
|
}, this);
|
|
756
|
-
}
|
|
811
|
+
}
|
|
757
812
|
;
|
|
758
|
-
|
|
759
|
-
for (
|
|
813
|
+
hackVariablesInPipeline(runPipeline) {
|
|
814
|
+
for (let pipelineSection = 0; pipelineSection < runPipeline.length; pipelineSection++) {
|
|
760
815
|
if (runPipeline[pipelineSection]['$match']) {
|
|
761
816
|
this.hackVariables(runPipeline[pipelineSection]['$match']);
|
|
762
817
|
}
|
|
763
818
|
}
|
|
764
|
-
}
|
|
819
|
+
}
|
|
765
820
|
;
|
|
766
|
-
|
|
821
|
+
hackVariables(obj) {
|
|
767
822
|
// Replace variables that cannot be serialised / deserialised. Bit of a hack, but needs must...
|
|
768
823
|
// Anything formatted 1800-01-01T00:00:00.000Z or 1800-01-01T00:00:00.000+0000 is converted to a Date
|
|
769
824
|
// Only handles the cases I need for now
|
|
770
825
|
// TODO: handle arrays etc
|
|
771
|
-
for (
|
|
826
|
+
for (const prop in obj) {
|
|
772
827
|
if (obj.hasOwnProperty(prop)) {
|
|
773
828
|
if (typeof obj[prop] === 'string') {
|
|
774
|
-
|
|
829
|
+
const dateTest = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})(Z|[+ -]\d{4})$/.exec(obj[prop]);
|
|
775
830
|
if (dateTest) {
|
|
776
831
|
obj[prop] = new Date(dateTest[1] + 'Z');
|
|
777
832
|
}
|
|
778
833
|
else {
|
|
779
|
-
|
|
834
|
+
const objectIdTest = /^([0-9a-fA-F]{24})$/.exec(obj[prop]);
|
|
780
835
|
if (objectIdTest) {
|
|
781
836
|
obj[prop] = new this.mongoose.Types.ObjectId(objectIdTest[1]);
|
|
782
837
|
}
|
|
@@ -787,20 +842,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
787
842
|
}
|
|
788
843
|
}
|
|
789
844
|
}
|
|
790
|
-
}
|
|
845
|
+
}
|
|
791
846
|
;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
var doneHiddenFields = false;
|
|
847
|
+
sanitisePipeline(aggregationParam, hiddenFields, findFuncQry) {
|
|
848
|
+
let that = this;
|
|
849
|
+
let array = Array.isArray(aggregationParam) ? aggregationParam : [aggregationParam];
|
|
850
|
+
let retVal = [];
|
|
851
|
+
let doneHiddenFields = false;
|
|
798
852
|
if (findFuncQry) {
|
|
799
853
|
retVal.unshift({ $match: findFuncQry });
|
|
800
854
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
855
|
+
for (let pipelineSection = 0; pipelineSection < array.length; pipelineSection++) {
|
|
856
|
+
let stage = array[pipelineSection];
|
|
857
|
+
let keys = Object.keys(stage);
|
|
804
858
|
if (keys.length !== 1) {
|
|
805
859
|
throw new Error('Invalid pipeline instruction');
|
|
806
860
|
}
|
|
@@ -812,18 +866,18 @@ var FormsAngular = /** @class */ (function () {
|
|
|
812
866
|
/*
|
|
813
867
|
Sanitise the pipeline we are doing a union with, removing hidden fields from that collection
|
|
814
868
|
*/
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
869
|
+
const unionCollectionName = stage.$unionWith.coll;
|
|
870
|
+
const unionResource = that.getResourceFromCollection(unionCollectionName);
|
|
871
|
+
let unionHiddenLookupFields = {};
|
|
818
872
|
if (unionResource) {
|
|
819
|
-
if (
|
|
820
|
-
unionHiddenLookupFields =
|
|
873
|
+
if (unionResource.options?.hide?.length > 0) {
|
|
874
|
+
unionHiddenLookupFields = this.generateHiddenFields(unionResource, false);
|
|
821
875
|
}
|
|
822
876
|
}
|
|
823
877
|
stage.$unionWith.pipeline = that.sanitisePipeline(stage.$unionWith.pipeline, unionHiddenLookupFields, findFuncQry);
|
|
824
878
|
break;
|
|
825
879
|
case '$match':
|
|
826
|
-
|
|
880
|
+
this.hackVariables(array[pipelineSection]['$match']);
|
|
827
881
|
retVal.push(array[pipelineSection]);
|
|
828
882
|
if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
|
|
829
883
|
// We can now project out the hidden fields (we wait for the $match to make sure we don't break
|
|
@@ -835,20 +889,20 @@ var FormsAngular = /** @class */ (function () {
|
|
|
835
889
|
break;
|
|
836
890
|
case '$lookup':
|
|
837
891
|
// hide any hiddenfields in the lookup collection
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
if ((collectionName +
|
|
892
|
+
const collectionName = stage.$lookup.from;
|
|
893
|
+
const lookupField = stage.$lookup.as;
|
|
894
|
+
if ((collectionName + lookupField).indexOf('$') !== -1) {
|
|
841
895
|
throw new Error('No support for lookups where the "from" or "as" is anything other than a simple string');
|
|
842
896
|
}
|
|
843
|
-
|
|
897
|
+
const resource = that.getResourceFromCollection(collectionName);
|
|
844
898
|
if (resource) {
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
Object.keys(hiddenLookupFields).forEach(
|
|
849
|
-
|
|
899
|
+
if (resource.options?.hide?.length > 0) {
|
|
900
|
+
const hiddenLookupFields = this.generateHiddenFields(resource, false);
|
|
901
|
+
let hiddenFieldsObj = {};
|
|
902
|
+
Object.keys(hiddenLookupFields).forEach(hf => {
|
|
903
|
+
hiddenFieldsObj[`${lookupField}.${hf}`] = false;
|
|
850
904
|
});
|
|
851
|
-
retVal.push({ $project:
|
|
905
|
+
retVal.push({ $project: hiddenFieldsObj });
|
|
852
906
|
}
|
|
853
907
|
}
|
|
854
908
|
break;
|
|
@@ -859,21 +913,17 @@ var FormsAngular = /** @class */ (function () {
|
|
|
859
913
|
if (stage) {
|
|
860
914
|
retVal.push(stage);
|
|
861
915
|
}
|
|
862
|
-
};
|
|
863
|
-
var this_1 = this;
|
|
864
|
-
for (var pipelineSection = 0; pipelineSection < array.length; pipelineSection++) {
|
|
865
|
-
_loop_1(pipelineSection);
|
|
866
916
|
}
|
|
867
917
|
if (!doneHiddenFields && Object.keys(hiddenFields) && Object.keys(hiddenFields).length > 0) {
|
|
868
918
|
// If there was no $match we still need to hide the hidden fields
|
|
869
919
|
retVal.unshift({ $project: hiddenFields });
|
|
870
920
|
}
|
|
871
921
|
return retVal;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
922
|
+
}
|
|
923
|
+
reportInternal(req, resource, schema, callback) {
|
|
924
|
+
let runPipelineStr;
|
|
925
|
+
let runPipelineObj;
|
|
926
|
+
let self = this;
|
|
877
927
|
if (typeof req.query === 'undefined') {
|
|
878
928
|
req.query = {};
|
|
879
929
|
}
|
|
@@ -883,7 +933,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
883
933
|
}
|
|
884
934
|
else {
|
|
885
935
|
runPipelineStr = JSON.stringify(schema.pipeline);
|
|
886
|
-
for (
|
|
936
|
+
for (let param in req.query) {
|
|
887
937
|
if (req.query.hasOwnProperty(param)) {
|
|
888
938
|
if (req.query[param]) {
|
|
889
939
|
if (param !== 'r') { // we don't want to copy the whole report schema (again!)
|
|
@@ -891,7 +941,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
891
941
|
schema.params[param].value = req.query[param];
|
|
892
942
|
}
|
|
893
943
|
else {
|
|
894
|
-
callback(
|
|
944
|
+
callback(`No such parameter as ${param} - try one of ${Object.keys(schema.params).join()}`);
|
|
895
945
|
}
|
|
896
946
|
}
|
|
897
947
|
}
|
|
@@ -900,7 +950,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
900
950
|
// Replace parameters with the value
|
|
901
951
|
if (runPipelineStr) {
|
|
902
952
|
runPipelineStr = runPipelineStr.replace(/"\(.+?\)"/g, function (match) {
|
|
903
|
-
|
|
953
|
+
let sparam = schema.params[match.slice(2, -2)];
|
|
904
954
|
if (sparam !== undefined) {
|
|
905
955
|
if (sparam.type === 'number') {
|
|
906
956
|
return sparam.value;
|
|
@@ -916,27 +966,27 @@ var FormsAngular = /** @class */ (function () {
|
|
|
916
966
|
}
|
|
917
967
|
}
|
|
918
968
|
else {
|
|
919
|
-
callback(
|
|
969
|
+
callback(`No such parameter as ${match.slice(2, -2)} - try one of ${Object.keys(schema.params).join()}`);
|
|
920
970
|
}
|
|
921
971
|
});
|
|
922
972
|
}
|
|
923
973
|
runPipelineObj = JSON.parse(runPipelineStr);
|
|
924
|
-
|
|
925
|
-
|
|
974
|
+
let hiddenFields = self.generateHiddenFields(resource, false);
|
|
975
|
+
let toDo = {
|
|
926
976
|
runAggregation: function (cb) {
|
|
927
|
-
runPipelineObj = self.sanitisePipeline(runPipelineObj,
|
|
977
|
+
runPipelineObj = self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj);
|
|
928
978
|
resource.model.aggregate(runPipelineObj, cb);
|
|
929
979
|
}
|
|
930
980
|
};
|
|
931
|
-
|
|
981
|
+
let translations = []; // array of form {ref:'lookupname',translations:[{value:xx, display:' '}]}
|
|
932
982
|
// if we need to do any column translations add the function to the tasks list
|
|
933
983
|
if (schema.columnTranslations) {
|
|
934
984
|
toDo.applyTranslations = ['runAggregation', function (results, cb) {
|
|
935
985
|
function doATranslate(column, theTranslation) {
|
|
936
986
|
results['runAggregation'].forEach(function (resultRow) {
|
|
937
|
-
|
|
987
|
+
let valToTranslate = resultRow[column.field];
|
|
938
988
|
valToTranslate = (valToTranslate ? valToTranslate.toString() : '');
|
|
939
|
-
|
|
989
|
+
let thisTranslation = _.find(theTranslation.translations, function (option) {
|
|
940
990
|
return valToTranslate === option.value.toString();
|
|
941
991
|
});
|
|
942
992
|
resultRow[column.field] = thisTranslation ? thisTranslation.display : ' * Missing columnTranslation * ';
|
|
@@ -947,7 +997,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
947
997
|
doATranslate(columnTranslation, columnTranslation);
|
|
948
998
|
}
|
|
949
999
|
if (columnTranslation.ref) {
|
|
950
|
-
|
|
1000
|
+
let theTranslation = _.find(translations, function (translation) {
|
|
951
1001
|
return (translation.ref === columnTranslation.ref);
|
|
952
1002
|
});
|
|
953
1003
|
if (theTranslation) {
|
|
@@ -960,9 +1010,9 @@ var FormsAngular = /** @class */ (function () {
|
|
|
960
1010
|
});
|
|
961
1011
|
cb(null, null);
|
|
962
1012
|
}];
|
|
963
|
-
|
|
964
|
-
for (
|
|
965
|
-
|
|
1013
|
+
let callFuncs = false;
|
|
1014
|
+
for (let i = 0; i < schema.columnTranslations.length; i++) {
|
|
1015
|
+
let thisColumnTranslation = schema.columnTranslations[i];
|
|
966
1016
|
if (thisColumnTranslation.field) {
|
|
967
1017
|
// if any of the column translations are adhoc funcs, set up the tasks to perform them
|
|
968
1018
|
if (thisColumnTranslation.fn) {
|
|
@@ -970,28 +1020,28 @@ var FormsAngular = /** @class */ (function () {
|
|
|
970
1020
|
}
|
|
971
1021
|
// if this column translation is a "ref", set up the tasks to look up the values and populate the translations
|
|
972
1022
|
if (thisColumnTranslation.ref) {
|
|
973
|
-
|
|
1023
|
+
let lookup = self.getResource(thisColumnTranslation.ref);
|
|
974
1024
|
if (lookup) {
|
|
975
1025
|
if (!toDo[thisColumnTranslation.ref]) {
|
|
976
|
-
|
|
977
|
-
|
|
1026
|
+
let getFunc = function (ref) {
|
|
1027
|
+
let lookup = ref;
|
|
978
1028
|
return function (cb) {
|
|
979
|
-
|
|
980
|
-
|
|
1029
|
+
let translateObject = { ref: lookup.resourceName, translations: [] };
|
|
1030
|
+
translations.push(translateObject);
|
|
981
1031
|
lookup.model.find({}, {}, { lean: true }, function (err, findResults) {
|
|
982
1032
|
if (err) {
|
|
983
1033
|
cb(err);
|
|
984
1034
|
}
|
|
985
1035
|
else {
|
|
986
1036
|
// TODO - this ref func can probably be done away with now that list fields can have ref
|
|
987
|
-
|
|
1037
|
+
let j = 0;
|
|
988
1038
|
async.whilst(function (cbtest) {
|
|
989
|
-
cbtest(null,
|
|
1039
|
+
cbtest(null, j < findResults.length);
|
|
990
1040
|
}, function (cbres) {
|
|
991
|
-
|
|
992
|
-
translateObject.translations[
|
|
993
|
-
|
|
994
|
-
|
|
1041
|
+
let theResult = findResults[j];
|
|
1042
|
+
translateObject.translations[j] = translateObject.translations[j] || {};
|
|
1043
|
+
let theTranslation = translateObject.translations[j];
|
|
1044
|
+
j++;
|
|
995
1045
|
self.getListFields(lookup, theResult, function (err, description) {
|
|
996
1046
|
if (err) {
|
|
997
1047
|
cbres(err);
|
|
@@ -1026,8 +1076,8 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1026
1076
|
if (callFuncs) {
|
|
1027
1077
|
toDo['callFunctions'] = ['runAggregation', function (results, cb) {
|
|
1028
1078
|
async.each(results.runAggregation, function (row, cb) {
|
|
1029
|
-
for (
|
|
1030
|
-
|
|
1079
|
+
for (let i = 0; i < schema.columnTranslations.length; i++) {
|
|
1080
|
+
let thisColumnTranslation = schema.columnTranslations[i];
|
|
1031
1081
|
if (thisColumnTranslation.fn) {
|
|
1032
1082
|
thisColumnTranslation.fn(row, cb);
|
|
1033
1083
|
}
|
|
@@ -1055,13 +1105,13 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1055
1105
|
});
|
|
1056
1106
|
}
|
|
1057
1107
|
});
|
|
1058
|
-
}
|
|
1108
|
+
}
|
|
1059
1109
|
;
|
|
1060
|
-
|
|
1110
|
+
saveAndRespond(req, res, hiddenFields) {
|
|
1061
1111
|
function internalSave(doc) {
|
|
1062
1112
|
doc.save(function (err, doc2) {
|
|
1063
1113
|
if (err) {
|
|
1064
|
-
|
|
1114
|
+
let err2 = { status: 'err' };
|
|
1065
1115
|
if (!err.errors) {
|
|
1066
1116
|
err2.message = err.message;
|
|
1067
1117
|
}
|
|
@@ -1075,12 +1125,12 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1075
1125
|
}
|
|
1076
1126
|
else {
|
|
1077
1127
|
doc2 = doc2.toObject();
|
|
1078
|
-
for (
|
|
1128
|
+
for (const hiddenField in hiddenFields) {
|
|
1079
1129
|
if (hiddenFields.hasOwnProperty(hiddenField) && hiddenFields[hiddenField]) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
for (
|
|
1130
|
+
let parts = hiddenField.split('.');
|
|
1131
|
+
let lastPart = parts.length - 1;
|
|
1132
|
+
let target = doc2;
|
|
1133
|
+
for (let i = 0; i < lastPart; i++) {
|
|
1084
1134
|
if (target.hasOwnProperty(parts[i])) {
|
|
1085
1135
|
target = target[parts[i]];
|
|
1086
1136
|
}
|
|
@@ -1094,7 +1144,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1094
1144
|
}
|
|
1095
1145
|
});
|
|
1096
1146
|
}
|
|
1097
|
-
|
|
1147
|
+
let doc = req.doc;
|
|
1098
1148
|
if (typeof req.resource.options.onSave === 'function') {
|
|
1099
1149
|
req.resource.options.onSave(doc, req, function (err) {
|
|
1100
1150
|
if (err) {
|
|
@@ -1106,19 +1156,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1106
1156
|
else {
|
|
1107
1157
|
internalSave(doc);
|
|
1108
1158
|
}
|
|
1109
|
-
}
|
|
1159
|
+
}
|
|
1110
1160
|
;
|
|
1111
1161
|
/**
|
|
1112
1162
|
* All entities REST functions have to go through this first.
|
|
1113
1163
|
*/
|
|
1114
|
-
|
|
1164
|
+
processCollection(req) {
|
|
1115
1165
|
req.resource = this.getResource(req.params.resourceName);
|
|
1116
|
-
}
|
|
1166
|
+
}
|
|
1117
1167
|
;
|
|
1118
1168
|
/**
|
|
1119
1169
|
* Renders a view with the list of docs, which may be modified by query parameters
|
|
1120
1170
|
*/
|
|
1121
|
-
|
|
1171
|
+
collectionGet() {
|
|
1122
1172
|
return _.bind(function (req, res, next) {
|
|
1123
1173
|
this.processCollection(req);
|
|
1124
1174
|
if (!req.resource) {
|
|
@@ -1128,20 +1178,20 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1128
1178
|
req.query = {};
|
|
1129
1179
|
}
|
|
1130
1180
|
try {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1181
|
+
const aggregationParam = req.query.a ? JSON.parse(req.query.a) : null;
|
|
1182
|
+
const findParam = req.query.f ? JSON.parse(req.query.f) : {};
|
|
1183
|
+
const projectParam = req.query.p ? JSON.parse(req.query.p) : {};
|
|
1184
|
+
const limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
|
|
1185
|
+
const skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
|
|
1186
|
+
const orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
|
|
1137
1187
|
// Dates in aggregation must be Dates
|
|
1138
1188
|
if (aggregationParam) {
|
|
1139
1189
|
this.hackVariablesInPipeline(aggregationParam);
|
|
1140
1190
|
}
|
|
1141
|
-
|
|
1191
|
+
const self = this;
|
|
1142
1192
|
this.filteredFind(req.resource, req, aggregationParam, findParam, projectParam, orderParam, limitParam, skipParam, function (err, docs) {
|
|
1143
1193
|
if (err) {
|
|
1144
|
-
return
|
|
1194
|
+
return self.renderError(err, null, req, res);
|
|
1145
1195
|
}
|
|
1146
1196
|
else {
|
|
1147
1197
|
res.send(docs);
|
|
@@ -1152,10 +1202,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1152
1202
|
res.status(400).send(e.message);
|
|
1153
1203
|
}
|
|
1154
1204
|
}, this);
|
|
1155
|
-
}
|
|
1205
|
+
}
|
|
1156
1206
|
;
|
|
1157
|
-
|
|
1158
|
-
|
|
1207
|
+
generateProjection(hiddenFields, projectParam) {
|
|
1208
|
+
let type;
|
|
1159
1209
|
function setSelectType(typeChar, checkChar) {
|
|
1160
1210
|
if (type === checkChar) {
|
|
1161
1211
|
throw new Error('Cannot mix include and exclude fields in select');
|
|
@@ -1164,11 +1214,11 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1164
1214
|
type = typeChar;
|
|
1165
1215
|
}
|
|
1166
1216
|
}
|
|
1167
|
-
|
|
1217
|
+
let retVal = hiddenFields;
|
|
1168
1218
|
if (projectParam) {
|
|
1169
|
-
|
|
1219
|
+
let projection = Object.keys(projectParam);
|
|
1170
1220
|
if (projection.length > 0) {
|
|
1171
|
-
projection.forEach(
|
|
1221
|
+
projection.forEach(p => {
|
|
1172
1222
|
if (projectParam[p] === 0) {
|
|
1173
1223
|
setSelectType('E', 'I');
|
|
1174
1224
|
}
|
|
@@ -1186,7 +1236,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1186
1236
|
else {
|
|
1187
1237
|
// We are selecting fields - make sure none are hidden
|
|
1188
1238
|
retVal = projectParam;
|
|
1189
|
-
for (
|
|
1239
|
+
for (let h in hiddenFields) {
|
|
1190
1240
|
if (hiddenFields.hasOwnProperty(h)) {
|
|
1191
1241
|
delete retVal[h];
|
|
1192
1242
|
}
|
|
@@ -1195,21 +1245,21 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1195
1245
|
}
|
|
1196
1246
|
}
|
|
1197
1247
|
return retVal;
|
|
1198
|
-
}
|
|
1248
|
+
}
|
|
1199
1249
|
;
|
|
1200
|
-
|
|
1250
|
+
doFindFunc(req, resource, cb) {
|
|
1201
1251
|
if (resource.options.findFunc) {
|
|
1202
1252
|
resource.options.findFunc(req, cb);
|
|
1203
1253
|
}
|
|
1204
1254
|
else {
|
|
1205
1255
|
cb(null);
|
|
1206
1256
|
}
|
|
1207
|
-
}
|
|
1257
|
+
}
|
|
1208
1258
|
;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1259
|
+
filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
|
|
1260
|
+
const that = this;
|
|
1261
|
+
let hiddenFields = this.generateHiddenFields(resource, false);
|
|
1262
|
+
let stashAggregationResults;
|
|
1213
1263
|
function doAggregation(queryObj, cb) {
|
|
1214
1264
|
if (aggregationParam) {
|
|
1215
1265
|
aggregationParam = that.sanitisePipeline(aggregationParam, hiddenFields, queryObj);
|
|
@@ -1239,7 +1289,7 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1239
1289
|
callback(null, []);
|
|
1240
1290
|
}
|
|
1241
1291
|
else {
|
|
1242
|
-
|
|
1292
|
+
let query = resource.model.find(queryObj);
|
|
1243
1293
|
if (idArray.length > 0) {
|
|
1244
1294
|
query = query.where('_id').in(idArray);
|
|
1245
1295
|
}
|
|
@@ -1258,10 +1308,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1258
1308
|
}
|
|
1259
1309
|
query.exec(function (err, docs) {
|
|
1260
1310
|
if (!err && stashAggregationResults) {
|
|
1261
|
-
docs.forEach(
|
|
1311
|
+
docs.forEach(obj => {
|
|
1262
1312
|
// Add any fields from the aggregation results whose field name starts __ to the mongoose Document
|
|
1263
|
-
|
|
1264
|
-
Object.keys(aggObj).forEach(
|
|
1313
|
+
let aggObj = stashAggregationResults.find(a => a._id.toString() === obj._id.toString());
|
|
1314
|
+
Object.keys(aggObj).forEach(k => {
|
|
1265
1315
|
if (k.slice(0, 2) === '__') {
|
|
1266
1316
|
obj[k] = aggObj[k];
|
|
1267
1317
|
}
|
|
@@ -1274,9 +1324,9 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1274
1324
|
});
|
|
1275
1325
|
}
|
|
1276
1326
|
});
|
|
1277
|
-
}
|
|
1327
|
+
}
|
|
1278
1328
|
;
|
|
1279
|
-
|
|
1329
|
+
collectionPost() {
|
|
1280
1330
|
return _.bind(function (req, res, next) {
|
|
1281
1331
|
this.processCollection(req);
|
|
1282
1332
|
if (!req.resource) {
|
|
@@ -1286,36 +1336,36 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1286
1336
|
if (!req.body) {
|
|
1287
1337
|
throw new Error('Nothing submitted.');
|
|
1288
1338
|
}
|
|
1289
|
-
|
|
1339
|
+
let cleansedBody = this.cleanseRequest(req);
|
|
1290
1340
|
req.doc = new req.resource.model(cleansedBody);
|
|
1291
1341
|
this.saveAndRespond(req, res);
|
|
1292
1342
|
}, this);
|
|
1293
|
-
}
|
|
1343
|
+
}
|
|
1294
1344
|
;
|
|
1295
1345
|
/**
|
|
1296
1346
|
* Generate an object of fields to not expose
|
|
1297
1347
|
**/
|
|
1298
|
-
|
|
1299
|
-
|
|
1348
|
+
generateHiddenFields(resource, state) {
|
|
1349
|
+
let hiddenFields = {};
|
|
1300
1350
|
if (resource.options['hide'] !== undefined) {
|
|
1301
1351
|
resource.options.hide.forEach(function (dt) {
|
|
1302
1352
|
hiddenFields[dt] = state;
|
|
1303
1353
|
});
|
|
1304
1354
|
}
|
|
1305
1355
|
return hiddenFields;
|
|
1306
|
-
}
|
|
1356
|
+
}
|
|
1307
1357
|
;
|
|
1308
1358
|
/** Sec issue
|
|
1309
1359
|
* Cleanse incoming data to avoid overwrite and POST request forgery
|
|
1310
1360
|
* (name may seem weird but it was in French, so it is some small improvement!)
|
|
1311
1361
|
*/
|
|
1312
|
-
|
|
1313
|
-
|
|
1362
|
+
cleanseRequest(req) {
|
|
1363
|
+
let reqData = req.body, resource = req.resource;
|
|
1314
1364
|
delete reqData.__v; // Don't mess with Mongoose internal field (https://github.com/LearnBoost/mongoose/issues/1933)
|
|
1315
1365
|
if (typeof resource.options['hide'] === 'undefined') {
|
|
1316
1366
|
return reqData;
|
|
1317
1367
|
}
|
|
1318
|
-
|
|
1368
|
+
let hiddenFields = resource.options.hide;
|
|
1319
1369
|
_.each(reqData, function (num, key) {
|
|
1320
1370
|
_.each(hiddenFields, function (fi) {
|
|
1321
1371
|
if (fi === key) {
|
|
@@ -1324,20 +1374,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1324
1374
|
});
|
|
1325
1375
|
});
|
|
1326
1376
|
return reqData;
|
|
1327
|
-
}
|
|
1377
|
+
}
|
|
1328
1378
|
;
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1379
|
+
generateQueryForEntity(req, resource, id, cb) {
|
|
1380
|
+
let that = this;
|
|
1381
|
+
let hiddenFields = this.generateHiddenFields(resource, false);
|
|
1332
1382
|
hiddenFields.__v = false;
|
|
1333
1383
|
that.doFindFunc(req, resource, function (err, queryObj) {
|
|
1334
|
-
var _a;
|
|
1335
1384
|
if (err) {
|
|
1336
1385
|
cb(err);
|
|
1337
1386
|
}
|
|
1338
1387
|
else {
|
|
1339
|
-
|
|
1340
|
-
|
|
1388
|
+
const idSel = { _id: id };
|
|
1389
|
+
let crit;
|
|
1341
1390
|
if (queryObj) {
|
|
1342
1391
|
if (queryObj._id) {
|
|
1343
1392
|
crit = { $and: [idSel, { _id: queryObj._id }] };
|
|
@@ -1353,16 +1402,16 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1353
1402
|
else {
|
|
1354
1403
|
crit = idSel;
|
|
1355
1404
|
}
|
|
1356
|
-
cb(null, resource.model.findOne(crit).select(that.generateProjection(hiddenFields,
|
|
1405
|
+
cb(null, resource.model.findOne(crit).select(that.generateProjection(hiddenFields, req.query?.p)));
|
|
1357
1406
|
}
|
|
1358
1407
|
});
|
|
1359
|
-
}
|
|
1408
|
+
}
|
|
1360
1409
|
;
|
|
1361
1410
|
/*
|
|
1362
1411
|
* Entity request goes here first
|
|
1363
1412
|
* It retrieves the resource
|
|
1364
1413
|
*/
|
|
1365
|
-
|
|
1414
|
+
processEntity(req, res, next) {
|
|
1366
1415
|
if (!(req.resource = this.getResource(req.params.resourceName))) {
|
|
1367
1416
|
next();
|
|
1368
1417
|
return;
|
|
@@ -1393,14 +1442,14 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1393
1442
|
});
|
|
1394
1443
|
}
|
|
1395
1444
|
});
|
|
1396
|
-
}
|
|
1445
|
+
}
|
|
1397
1446
|
;
|
|
1398
1447
|
/**
|
|
1399
1448
|
* Gets a single entity
|
|
1400
1449
|
*
|
|
1401
1450
|
* @return {Function} The function to use as route
|
|
1402
1451
|
*/
|
|
1403
|
-
|
|
1452
|
+
entityGet() {
|
|
1404
1453
|
return _.bind(function (req, res, next) {
|
|
1405
1454
|
this.processEntity(req, res, function () {
|
|
1406
1455
|
if (!req.resource) {
|
|
@@ -1416,10 +1465,10 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1416
1465
|
}
|
|
1417
1466
|
});
|
|
1418
1467
|
}, this);
|
|
1419
|
-
}
|
|
1468
|
+
}
|
|
1420
1469
|
;
|
|
1421
|
-
|
|
1422
|
-
|
|
1470
|
+
replaceHiddenFields(record, data) {
|
|
1471
|
+
const self = this;
|
|
1423
1472
|
if (record) {
|
|
1424
1473
|
record._replacingHiddenFields = true;
|
|
1425
1474
|
_.each(data, function (value, name) {
|
|
@@ -1432,11 +1481,11 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1432
1481
|
});
|
|
1433
1482
|
delete record._replacingHiddenFields;
|
|
1434
1483
|
}
|
|
1435
|
-
}
|
|
1484
|
+
}
|
|
1436
1485
|
;
|
|
1437
|
-
|
|
1486
|
+
entityPut() {
|
|
1438
1487
|
return _.bind(function (req, res, next) {
|
|
1439
|
-
|
|
1488
|
+
const that = this;
|
|
1440
1489
|
this.processEntity(req, res, function () {
|
|
1441
1490
|
if (!req.resource) {
|
|
1442
1491
|
next();
|
|
@@ -1445,19 +1494,19 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1445
1494
|
if (!req.body) {
|
|
1446
1495
|
throw new Error('Nothing submitted.');
|
|
1447
1496
|
}
|
|
1448
|
-
|
|
1497
|
+
let cleansedBody = that.cleanseRequest(req);
|
|
1449
1498
|
// Merge
|
|
1450
|
-
for (
|
|
1499
|
+
for (let prop in cleansedBody) {
|
|
1451
1500
|
if (cleansedBody.hasOwnProperty(prop)) {
|
|
1452
1501
|
req.doc.set(prop, cleansedBody[prop] === '' ? undefined : cleansedBody[prop]);
|
|
1453
1502
|
}
|
|
1454
1503
|
}
|
|
1455
1504
|
if (req.resource.options.hide !== undefined) {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
req.resource.model.findById(req.doc._id,
|
|
1505
|
+
let hiddenFields = that.generateHiddenFields(req.resource, true);
|
|
1506
|
+
hiddenFields._id = false;
|
|
1507
|
+
req.resource.model.findById(req.doc._id, hiddenFields, { lean: true }, function (err, data) {
|
|
1459
1508
|
that.replaceHiddenFields(req.doc, data);
|
|
1460
|
-
that.saveAndRespond(req, res,
|
|
1509
|
+
that.saveAndRespond(req, res, hiddenFields);
|
|
1461
1510
|
});
|
|
1462
1511
|
}
|
|
1463
1512
|
else {
|
|
@@ -1465,187 +1514,313 @@ var FormsAngular = /** @class */ (function () {
|
|
|
1465
1514
|
}
|
|
1466
1515
|
});
|
|
1467
1516
|
}, this);
|
|
1468
|
-
}
|
|
1517
|
+
}
|
|
1469
1518
|
;
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
return _.bind(function (req, res, next) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1519
|
+
entityDelete() {
|
|
1520
|
+
let that = this;
|
|
1521
|
+
return _.bind(async function (req, res, next) {
|
|
1522
|
+
function generateDependencyList(resource) {
|
|
1523
|
+
if (resource.options.dependents === undefined) {
|
|
1524
|
+
resource.options.dependents = that.resources.reduce(function (acc, r) {
|
|
1525
|
+
function searchPaths(schema, prefix) {
|
|
1526
|
+
var fldList = [];
|
|
1527
|
+
for (var fld in schema.paths) {
|
|
1528
|
+
if (schema.paths.hasOwnProperty(fld)) {
|
|
1529
|
+
var parts = fld.split('.');
|
|
1530
|
+
var schemaType = schema.tree;
|
|
1531
|
+
while (parts.length > 0) {
|
|
1532
|
+
schemaType = schemaType[parts.shift()];
|
|
1533
|
+
}
|
|
1534
|
+
if (schemaType.type) {
|
|
1535
|
+
if (schemaType.type.name === 'ObjectId' && schemaType.ref === resource.resourceName) {
|
|
1536
|
+
fldList.push(prefix + fld);
|
|
1485
1537
|
}
|
|
1486
|
-
if (schemaType.type) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1490
|
-
else if (_.isArray(schemaType.type)) {
|
|
1491
|
-
schemaType.type.forEach(function (t) {
|
|
1492
|
-
searchPaths(t, prefix + fld + '.');
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1538
|
+
else if (_.isArray(schemaType.type)) {
|
|
1539
|
+
schemaType.type.forEach(function (t) {
|
|
1540
|
+
searchPaths(t, prefix + fld + '.');
|
|
1541
|
+
});
|
|
1495
1542
|
}
|
|
1496
1543
|
}
|
|
1497
1544
|
}
|
|
1498
|
-
if (fldList.length > 0) {
|
|
1499
|
-
acc.push({ resource: r, keys: fldList });
|
|
1500
|
-
}
|
|
1501
1545
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
}, []);
|
|
1505
|
-
for (var pluginName in that.options.plugins) {
|
|
1506
|
-
var thisPlugin = that.options.plugins[pluginName];
|
|
1507
|
-
if (thisPlugin.dependencyChecks && thisPlugin.dependencyChecks[resource.resourceName]) {
|
|
1508
|
-
resource.options.dependents = resource.options.dependents.concat(thisPlugin.dependencyChecks[resource.resourceName]);
|
|
1546
|
+
if (fldList.length > 0) {
|
|
1547
|
+
acc.push({ resource: r, keys: fldList });
|
|
1509
1548
|
}
|
|
1510
1549
|
}
|
|
1550
|
+
searchPaths(r.model.schema, '');
|
|
1551
|
+
return acc;
|
|
1552
|
+
}, []);
|
|
1553
|
+
for (let pluginName in that.options.plugins) {
|
|
1554
|
+
let thisPlugin = that.options.plugins[pluginName];
|
|
1555
|
+
if (thisPlugin.dependencyChecks && thisPlugin.dependencyChecks[resource.resourceName]) {
|
|
1556
|
+
resource.options.dependents = resource.options.dependents.concat(thisPlugin.dependencyChecks[resource.resourceName]);
|
|
1557
|
+
}
|
|
1511
1558
|
}
|
|
1512
1559
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
key: key
|
|
1535
|
-
});
|
|
1536
|
-
});
|
|
1537
|
-
});
|
|
1538
|
-
return [2 /*return*/, Promise.all(promises_1.map(function (p) { return p.p; }))
|
|
1539
|
-
.then(function (results) {
|
|
1540
|
-
results.forEach(function (r, i) {
|
|
1541
|
-
if (r.length > 0) {
|
|
1542
|
-
throw new ForeignKeyError(resource.resourceName, promises_1[i].collection.resource.resourceName, promises_1[i].key, r[0]._id);
|
|
1543
|
-
}
|
|
1544
|
-
});
|
|
1545
|
-
return doc.remove();
|
|
1546
|
-
})];
|
|
1547
|
-
}
|
|
1548
|
-
return [2 /*return*/];
|
|
1549
|
-
});
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
function runDeletion(doc, resource) {
|
|
1553
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1554
|
-
return __generator(this, function (_a) {
|
|
1555
|
-
return [2 /*return*/, new Promise(function (resolve) {
|
|
1556
|
-
if (resource.options.onRemove) {
|
|
1557
|
-
resource.options.onRemove(doc, req, function (err) {
|
|
1558
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1559
|
-
return __generator(this, function (_a) {
|
|
1560
|
-
if (err) {
|
|
1561
|
-
throw err;
|
|
1562
|
-
}
|
|
1563
|
-
resolve(removeDoc(doc, resource));
|
|
1564
|
-
return [2 /*return*/];
|
|
1565
|
-
});
|
|
1566
|
-
});
|
|
1567
|
-
});
|
|
1568
|
-
}
|
|
1569
|
-
else {
|
|
1570
|
-
resolve(removeDoc(doc, resource));
|
|
1571
|
-
}
|
|
1572
|
-
})];
|
|
1560
|
+
}
|
|
1561
|
+
async function removeDoc(doc, resource) {
|
|
1562
|
+
switch (resource.options.handleRemove) {
|
|
1563
|
+
case 'allow':
|
|
1564
|
+
// old behaviour - no attempt to maintain data integrity
|
|
1565
|
+
return doc.remove();
|
|
1566
|
+
case 'cascade':
|
|
1567
|
+
generateDependencyList(resource);
|
|
1568
|
+
res.status(400).send('"cascade" option not yet supported');
|
|
1569
|
+
break;
|
|
1570
|
+
default:
|
|
1571
|
+
generateDependencyList(resource);
|
|
1572
|
+
let promises = [];
|
|
1573
|
+
resource.options.dependents.forEach(collection => {
|
|
1574
|
+
collection.keys.forEach(key => {
|
|
1575
|
+
promises.push({
|
|
1576
|
+
p: collection.resource.model.find({ [key]: doc._id }).limit(1).exec(),
|
|
1577
|
+
collection,
|
|
1578
|
+
key
|
|
1579
|
+
});
|
|
1580
|
+
});
|
|
1573
1581
|
});
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
var doc, e_1;
|
|
1580
|
-
return __generator(this, function (_a) {
|
|
1581
|
-
switch (_a.label) {
|
|
1582
|
-
case 0:
|
|
1583
|
-
if (!req.resource) {
|
|
1584
|
-
next();
|
|
1585
|
-
return [2 /*return*/];
|
|
1586
|
-
}
|
|
1587
|
-
doc = req.doc;
|
|
1588
|
-
_a.label = 1;
|
|
1589
|
-
case 1:
|
|
1590
|
-
_a.trys.push([1, 3, , 4]);
|
|
1591
|
-
return [4 /*yield*/, runDeletion(doc, req.resource)];
|
|
1592
|
-
case 2:
|
|
1593
|
-
void (_a.sent());
|
|
1594
|
-
res.status(200).send();
|
|
1595
|
-
return [3 /*break*/, 4];
|
|
1596
|
-
case 3:
|
|
1597
|
-
e_1 = _a.sent();
|
|
1598
|
-
if (e_1 instanceof ForeignKeyError) {
|
|
1599
|
-
res.status(400).send(e_1.message);
|
|
1600
|
-
}
|
|
1601
|
-
else {
|
|
1602
|
-
res.status(500).send(e_1.message);
|
|
1603
|
-
}
|
|
1604
|
-
return [3 /*break*/, 4];
|
|
1605
|
-
case 4: return [2 /*return*/];
|
|
1582
|
+
return Promise.all(promises.map(p => p.p))
|
|
1583
|
+
.then((results) => {
|
|
1584
|
+
results.forEach((r, i) => {
|
|
1585
|
+
if (r.length > 0) {
|
|
1586
|
+
throw new ForeignKeyError(resource.resourceName, promises[i].collection.resource.resourceName, promises[i].key, r[0]._id);
|
|
1606
1587
|
}
|
|
1607
1588
|
});
|
|
1589
|
+
return doc.remove();
|
|
1608
1590
|
});
|
|
1609
|
-
|
|
1610
|
-
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
async function runDeletion(doc, resource) {
|
|
1594
|
+
return new Promise((resolve) => {
|
|
1595
|
+
if (resource.options.onRemove) {
|
|
1596
|
+
resource.options.onRemove(doc, req, async function (err) {
|
|
1597
|
+
if (err) {
|
|
1598
|
+
throw err;
|
|
1599
|
+
}
|
|
1600
|
+
resolve(removeDoc(doc, resource));
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
resolve(removeDoc(doc, resource));
|
|
1605
|
+
}
|
|
1611
1606
|
});
|
|
1607
|
+
}
|
|
1608
|
+
this.processEntity(req, res, async function () {
|
|
1609
|
+
if (!req.resource) {
|
|
1610
|
+
next();
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
let doc = req.doc;
|
|
1614
|
+
try {
|
|
1615
|
+
void await runDeletion(doc, req.resource);
|
|
1616
|
+
res.status(200).send();
|
|
1617
|
+
}
|
|
1618
|
+
catch (e) {
|
|
1619
|
+
if (e instanceof ForeignKeyError) {
|
|
1620
|
+
res.status(400).send(e.message);
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
res.status(500).send(e.message);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1612
1626
|
});
|
|
1613
1627
|
}, this);
|
|
1614
|
-
}
|
|
1628
|
+
}
|
|
1615
1629
|
;
|
|
1616
|
-
|
|
1630
|
+
entityList() {
|
|
1617
1631
|
return _.bind(function (req, res, next) {
|
|
1618
|
-
|
|
1632
|
+
const that = this;
|
|
1619
1633
|
this.processEntity(req, res, function () {
|
|
1620
1634
|
if (!req.resource) {
|
|
1621
1635
|
return next();
|
|
1622
1636
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1637
|
+
const returnRawParam = req.query?.returnRaw ? !!JSON.parse(req.query.returnRaw) : false;
|
|
1638
|
+
if (returnRawParam) {
|
|
1639
|
+
const result = { _id: req.doc._id };
|
|
1640
|
+
for (const field of req.resource.options.listFields) {
|
|
1641
|
+
result[field.field] = req.doc[field.field];
|
|
1642
|
+
}
|
|
1643
|
+
return res.send(result);
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
that.getListFields(req.resource, req.doc, function (err, display) {
|
|
1647
|
+
if (err) {
|
|
1648
|
+
return res.status(500).send(err);
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
return res.send({ list: display });
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
}, this);
|
|
1657
|
+
}
|
|
1658
|
+
;
|
|
1659
|
+
// To disambiguate the contents of items - assumed to be the results of a single resource lookup or search -
|
|
1660
|
+
// pass the result of this function as the second argument to the disambiguate() function.
|
|
1661
|
+
// equalityProps should identify the property(s) of the items that must ALL be equal for two items to
|
|
1662
|
+
// be considered ambiguous.
|
|
1663
|
+
// disambiguationResourceName should identify the resource whose list field(s) should be used to generate
|
|
1664
|
+
// the disambiguation text for ambiguous results later.
|
|
1665
|
+
buildSingleResourceAmbiguousRecordStore(items, equalityProps, disambiguationResourceName) {
|
|
1666
|
+
const ambiguousResults = [];
|
|
1667
|
+
for (let i = 0; i < items.length - 1; i++) {
|
|
1668
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
1669
|
+
if (!equalityProps.some((p) => items[i][p] !== items[j][p])) {
|
|
1670
|
+
if (!ambiguousResults.includes(items[i])) {
|
|
1671
|
+
ambiguousResults.push(items[i]);
|
|
1672
|
+
}
|
|
1673
|
+
if (!ambiguousResults.includes(items[j])) {
|
|
1674
|
+
ambiguousResults.push(items[j]);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return { [disambiguationResourceName]: ambiguousResults };
|
|
1680
|
+
}
|
|
1681
|
+
// An alternative to buildSingleResourceAmbiguousRecordStore() for use when disambiguating the results of a
|
|
1682
|
+
// multi-resource lookup or search. In this case, all items need to include the name of the resource that
|
|
1683
|
+
// will be used (when necessary) to yield their disambiguation text later. The property of items that holds
|
|
1684
|
+
// that resource name should be identified by the disambiguationResourceNameProp parameter.
|
|
1685
|
+
// The scary-looking templating used here ensures that the items really do all have an (optional) string
|
|
1686
|
+
// property with the name identified by disambiguationResourceNameProp. For the avoidance of doubt, "prop"
|
|
1687
|
+
// here could be anything - "foo in f" would achieve the same result.
|
|
1688
|
+
buildMultiResourceAmbiguousRecordStore(items, equalityProps, disambiguationResourceNameProp) {
|
|
1689
|
+
const store = {};
|
|
1690
|
+
for (let i = 0; i < items.length - 1; i++) {
|
|
1691
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
1692
|
+
if (items[i][disambiguationResourceNameProp] &&
|
|
1693
|
+
items[i][disambiguationResourceNameProp] === items[j][disambiguationResourceNameProp] &&
|
|
1694
|
+
!equalityProps.some((p) => items[i][p] !== items[j][p])) {
|
|
1695
|
+
if (!store[items[i][disambiguationResourceNameProp]]) {
|
|
1696
|
+
store[items[i][disambiguationResourceNameProp]] = [];
|
|
1697
|
+
}
|
|
1698
|
+
if (!store[items[i][disambiguationResourceNameProp]].includes(items[i])) {
|
|
1699
|
+
store[items[i][disambiguationResourceNameProp]].push(items[i]);
|
|
1700
|
+
}
|
|
1701
|
+
if (!store[items[i][disambiguationResourceNameProp]].includes(items[j])) {
|
|
1702
|
+
store[items[i][disambiguationResourceNameProp]].push(items[j]);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return store;
|
|
1708
|
+
}
|
|
1709
|
+
// return just the id and list fields for all of the records from req.resource.
|
|
1710
|
+
// list fields are those whose schema entry has a value for the "list" attribute (except where this is { ref: true })
|
|
1711
|
+
// if the resource has no explicit list fields identified, the first string field will be returned. if the resource
|
|
1712
|
+
// doesn't have any string fields either, the first (non-id) field will be returned.
|
|
1713
|
+
// usually, we will respond with an array of ILookupItem objects, where the .text property of those objects is the concatenation
|
|
1714
|
+
// of all of the document's list fields (space-seperated).
|
|
1715
|
+
// to request the documents without this transformation applied, include "c=true" in the query string.
|
|
1716
|
+
// the query string can also be used to filter and order the response, by providing values for "f" (find), "l" (limit),
|
|
1717
|
+
// "s" (skip) and/or "o" (order).
|
|
1718
|
+
// results will be disambiguated if req.resource includes disambiguation parameters in its resource options.
|
|
1719
|
+
// where c=true, the disambiguation will be added as a suffix to the .text property of the returned ILookupItem objects.
|
|
1720
|
+
// otherwise, if the resource has just one list field, the disambiguation will be appended to the values of that field, and if
|
|
1721
|
+
// it has multiple list fields, it will be returned as an additional property of the returned (untransformed) objects
|
|
1722
|
+
internalEntityListAll(req, callback) {
|
|
1723
|
+
const projection = this.generateListFieldProjection(req.resource);
|
|
1724
|
+
const listFields = Object.keys(projection);
|
|
1725
|
+
const aggregationParam = req.query.a ? JSON.parse(req.query.a) : null;
|
|
1726
|
+
const findParam = req.query.f ? JSON.parse(req.query.f) : {};
|
|
1727
|
+
const limitParam = req.query.l ? JSON.parse(req.query.l) : 0;
|
|
1728
|
+
const skipParam = req.query.s ? JSON.parse(req.query.s) : 0;
|
|
1729
|
+
const orderParam = req.query.o ? JSON.parse(req.query.o) : req.resource.options.listOrder;
|
|
1730
|
+
const concatenateParam = req.query.c ? JSON.parse(req.query.c) : true;
|
|
1731
|
+
const resOpts = req.resource.options;
|
|
1732
|
+
let disambiguationField;
|
|
1733
|
+
let disambiguationResourceName;
|
|
1734
|
+
if (resOpts?.disambiguation) {
|
|
1735
|
+
disambiguationField = resOpts.disambiguation.field;
|
|
1736
|
+
if (disambiguationField) {
|
|
1737
|
+
projection[disambiguationField] = 1;
|
|
1738
|
+
disambiguationResourceName = resOpts.disambiguation.resource;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
const that = this;
|
|
1742
|
+
this.filteredFind(req.resource, req, aggregationParam, findParam, projection, orderParam, limitParam, skipParam, function (err, docs) {
|
|
1743
|
+
if (err) {
|
|
1744
|
+
return callback(err);
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
docs = docs.map((d) => d.toObject());
|
|
1748
|
+
if (concatenateParam) {
|
|
1749
|
+
const transformed = docs.map((doc) => {
|
|
1750
|
+
let text = "";
|
|
1751
|
+
for (const field of listFields) {
|
|
1752
|
+
if (doc[field]) {
|
|
1753
|
+
if (text !== "") {
|
|
1754
|
+
text += " ";
|
|
1755
|
+
}
|
|
1756
|
+
text += doc[field];
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
const disambiguationId = disambiguationField ? doc[disambiguationField] : undefined;
|
|
1760
|
+
return { id: doc._id, text, disambiguationId };
|
|
1761
|
+
});
|
|
1762
|
+
if (disambiguationResourceName) {
|
|
1763
|
+
that.disambiguate(transformed, that.buildSingleResourceAmbiguousRecordStore(transformed, ["text"], disambiguationResourceName), "disambiguationId", (item, disambiguationText) => {
|
|
1764
|
+
item.text += ` (${disambiguationText})`;
|
|
1765
|
+
}, (err) => {
|
|
1766
|
+
callback(err, transformed);
|
|
1767
|
+
});
|
|
1626
1768
|
}
|
|
1627
1769
|
else {
|
|
1628
|
-
return
|
|
1770
|
+
return callback(null, transformed);
|
|
1629
1771
|
}
|
|
1630
|
-
}
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
if (disambiguationResourceName) {
|
|
1775
|
+
that.disambiguate(docs, that.buildSingleResourceAmbiguousRecordStore(docs, listFields, disambiguationResourceName), disambiguationField, (item, disambiguationText) => {
|
|
1776
|
+
if (listFields.length === 1) {
|
|
1777
|
+
item[listFields[0]] += ` (${disambiguationText})`;
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
// store the text against hard-coded property name "disambiguation", rather than (say) using
|
|
1781
|
+
// item[disambiguationResourceName], because if disambiguationResourceName === disambiguationField,
|
|
1782
|
+
// that value would end up being deleted again when the this.disambiguate() call (which we have
|
|
1783
|
+
// been called from) does its final tidy-up and deletes [disambiguationField] from all of the items in docs
|
|
1784
|
+
item.disambiguation = disambiguationText;
|
|
1785
|
+
}
|
|
1786
|
+
}, (err) => {
|
|
1787
|
+
callback(err, docs);
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
else {
|
|
1791
|
+
return callback(null, docs);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
;
|
|
1798
|
+
entityListAll() {
|
|
1799
|
+
return _.bind(function (req, res, next) {
|
|
1800
|
+
if (!(req.resource = this.getResource(req.params.resourceName))) {
|
|
1801
|
+
return next();
|
|
1802
|
+
}
|
|
1803
|
+
this.internalEntityListAll(req, function (err, resultsObject) {
|
|
1804
|
+
if (err) {
|
|
1805
|
+
res.status(400, err);
|
|
1806
|
+
}
|
|
1807
|
+
else {
|
|
1808
|
+
res.send(resultsObject);
|
|
1809
|
+
}
|
|
1631
1810
|
});
|
|
1632
1811
|
}, this);
|
|
1633
|
-
}
|
|
1812
|
+
}
|
|
1634
1813
|
;
|
|
1635
|
-
|
|
1636
|
-
|
|
1814
|
+
extractTimestampFromMongoID(record) {
|
|
1815
|
+
let timestamp = record.toString().substring(0, 8);
|
|
1637
1816
|
return new Date(parseInt(timestamp, 16) * 1000);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
}());
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1641
1819
|
exports.FormsAngular = FormsAngular;
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
_this.stack = new global.Error('').stack;
|
|
1648
|
-
return _this;
|
|
1820
|
+
class ForeignKeyError extends global.Error {
|
|
1821
|
+
constructor(resourceName, foreignKeyOnResource, foreignItem, id) {
|
|
1822
|
+
super(`Cannot delete this ${resourceName}, as it is the ${foreignItem} on ${foreignKeyOnResource} ${id}`);
|
|
1823
|
+
this.name = "ForeignKeyError";
|
|
1824
|
+
this.stack = new global.Error('').stack;
|
|
1649
1825
|
}
|
|
1650
|
-
|
|
1651
|
-
}(global.Error));
|
|
1826
|
+
}
|