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