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