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.
@@ -1,5 +1,57 @@
1
- 'use strict';
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 DataForm = function (mongoose, app, options) {
29
- this.app = app;
30
- app.locals.formsAngular = app.locals.formsAngular || [];
31
- app.locals.formsAngular.push(this);
32
- this.mongoose = mongoose;
33
- mongoose.set('debug', debug);
34
- mongoose.Promise = global.Promise;
35
- this.options = _.extend({
36
- urlPrefix: '/api/'
37
- }, options || {});
38
- this.resources = [];
39
- this.searchFunc = async.forEach;
40
- this.registerRoutes();
41
- this.app.get.apply(this.app, processArgs(this.options, ['search', this.searchAll()]));
42
- for (var pluginName in this.options.plugins) {
43
- var pluginObj = this.options.plugins[pluginName];
44
- this[pluginName] = pluginObj.plugin(this, processArgs, pluginObj.options);
45
- }
46
- };
47
- module.exports = exports = DataForm;
48
- DataForm.prototype.extractTimestampFromMongoID = function (record) {
49
- var timestamp = record.toString().substring(0, 8);
50
- return new Date(parseInt(timestamp, 16) * 1000);
51
- };
52
- DataForm.prototype.getListFields = function (resource, doc, cb) {
53
- function getFirstMatchingField(keyList, type) {
54
- for (var i = 0; i < keyList.length; i++) {
55
- var fieldDetails = resource.model.schema['tree'][keyList[i]];
56
- if (fieldDetails.type && (!type || fieldDetails.type.name === type) && keyList[i] !== '_id') {
57
- resource.options.listFields = [{ field: keyList[i] }];
58
- return doc[keyList[i]];
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
- var that = this;
63
- var display = '';
64
- var listFields = resource.options.listFields;
65
- if (listFields) {
66
- async.map(listFields, function (aField, cbm) {
67
- if (typeof doc[aField.field] !== 'undefined') {
68
- if (aField.params) {
69
- if (aField.params.ref) {
70
- var fieldOptions = resource.model.schema['paths'][aField.field].options;
71
- if (typeof fieldOptions.ref === 'string') {
72
- fieldOptions.ref = { type: 'lookup', collection: fieldOptions.ref };
73
- console.log('Deprecation warning: invalid ref should be ' + JSON.stringify(fieldOptions.ref));
74
- }
75
- if (fieldOptions.ref.type === 'lookup') {
76
- var lookupResource_1 = that.getResource(fieldOptions.ref.collection);
77
- if (lookupResource_1) {
78
- var hiddenFields = that.generateHiddenFields(lookupResource_1, false);
79
- hiddenFields.__v = 0;
80
- lookupResource_1.model.findOne({ _id: doc[aField.field] }).select(hiddenFields).exec(function (err, doc2) {
81
- if (err) {
82
- cbm(err);
83
- }
84
- else {
85
- that.getListFields(lookupResource_1, doc2, cbm);
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
- throw new Error('No support for ref type ' + aField.params.ref.type);
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 if (aField.params.params === 'timestamp') {
95
- var date = this.extractTimestampFromMongoID(doc[aField.field]);
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, doc[aField.field]);
164
+ cbm(null, '');
101
165
  }
102
- }
103
- else {
104
- cbm(null, '');
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
- console.log('No results ' + listFields);
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
- if (!resource.options.suppressDeprecatedMessage) {
166
- console.log('addResource is deprecated - see https://github.com/forms-angular/forms-angular/issues/39');
167
- }
168
- if (typeof model === 'function') {
169
- resource.model = model;
170
- }
171
- else {
172
- resource.model = model.model;
173
- for (var prop in model) {
174
- if (model.hasOwnProperty(prop) && prop !== 'model') {
175
- resource.options[prop] = model[prop];
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
- extend(resource.options, this.preprocess(resource, resource.model.schema['paths'], null));
180
- if (resource.options.searchImportance) {
181
- this.searchFunc = async.forEachSeries;
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
- return string;
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
- var sortString = '';
225
- sortString += padLeft(obj.addHits || 9, 1);
226
- sortString += padLeft(obj.searchImportance || 99, 2);
227
- sortString += padLeft(obj.weighting || 9999, 4);
228
- sortString += obj.text;
229
- return sortString;
230
- }
231
- if (filter) {
232
- filter = JSON.parse(filter);
233
- }
234
- for (var i = 0; i < resourceCount; i++) {
235
- var resource = resourcesToSearch[i];
236
- if (resourceCount === 1 || resource.options.searchImportance !== false) {
237
- var schema = resource.model.schema;
238
- var indexedFields = [];
239
- for (var j = 0; j < schema._indexes.length; j++) {
240
- var attributes = schema._indexes[j][0];
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
- for (var path in schema.paths) {
247
- if (path !== '_id' && schema.paths.hasOwnProperty(path)) {
248
- if (schema.paths[path]._index && !schema.paths[path].options.noSearch) {
249
- if (indexedFields.indexOf(path) === -1) {
250
- indexedFields.push(path);
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
- if (indexedFields.length === 0) {
256
- console.log('ERROR: Searching on a collection with no indexes ' + resource.resourceName);
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
- for (var m = 0; m < indexedFields.length; m++) {
259
- searches.push({ resource: resource, field: indexedFields[m] });
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
- var that = this;
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
- resultObject.addHits = Math.max((resultObject.addHits || 9) - 1, 1);
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
- resultObject = specialListingFormat.apply(aDoc);
307
- handleResultsInList();
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
- }, function (err) {
329
- cb(err);
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
- searchDoc[item.field] = searchCriteria;
455
+ cb(err);
350
456
  }
351
- }
352
- else {
353
- searchDoc[item.field] = searchCriteria;
354
- }
355
- // The +60 in the next line is an arbitrary safety zone for situations where items that match the string
356
- // in more than one index get filtered out.
357
- // TODO : Figure out a better way to deal with this
358
- if (item.resource.options.searchFunc) {
359
- item.resource.options.searchFunc(item.resource, req, null, searchDoc, item.resource.options.searchOrder, limit + 60, null, function (err, docs) {
360
- handleSearchResultsFromIndex(err, docs, item, cb);
361
- });
362
- }
363
- else {
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
- outPath[fld] = vanilla[fld];
427
- if (vanilla[fld].schema) {
428
- outPath[fld].schema = this.applySchemaSubset(outPath[fld].schema, schema[fld].schema);
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
- else {
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
- // check for arrays
465
- var realType = paths[element].caster ? paths[element].caster : paths[element];
466
- if (!realType.instance) {
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
- outPath = this.applySchemaSubset(outPath, formSchema);
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
- else {
544
- var fields = {};
545
- for (var key in req.resource.model.schema.paths) {
546
- if (req.resource.model.schema.paths.hasOwnProperty(key)) {
547
- if (key !== '__v' && !req.resource.model.schema.paths[key].options.secure) {
548
- if (key.indexOf('.') === -1) {
549
- fields[key] = 1;
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
- reportSchema = {
555
- pipeline: [
556
- { $project: fields }
557
- ], drilldown: req.params.resourceName + '/|_id|/edit'
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
- self.renderError(err, null, req, res, next);
524
+ res.status(400, err);
567
525
  }
568
526
  else {
569
- res.send(result);
527
+ res.send(resultsObject);
570
528
  }
571
529
  });
572
- }, this);
573
- };
574
- DataForm.prototype.hackVariablesInPipeline = function (runPipeline) {
575
- for (var pipelineSection = 0; pipelineSection < runPipeline.length; pipelineSection++) {
576
- if (runPipeline[pipelineSection]['$match']) {
577
- this.hackVariables(runPipeline[pipelineSection]['$match']);
578
- }
579
- }
580
- };
581
- DataForm.prototype.hackVariables = function (obj) {
582
- // Replace variables that cannot be serialised / deserialised. Bit of a hack, but needs must...
583
- // Anything formatted 1800-01-01T00:00:00.000Z or 1800-01-01T00:00:00.000+0000 is converted to a Date
584
- // Only handles the cases I need for now
585
- // TODO: handle arrays etc
586
- for (var prop in obj) {
587
- if (obj.hasOwnProperty(prop)) {
588
- if (typeof obj[prop] === 'string') {
589
- var dateTest = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})(Z|[+ -]\d{4})$/.exec(obj[prop]);
590
- if (dateTest) {
591
- obj[prop] = new Date(dateTest[1] + 'Z');
592
- }
593
- else {
594
- var objectIdTest = /^([0-9a-fA-F]{24})$/.exec(obj[prop]);
595
- if (objectIdTest) {
596
- obj[prop] = new this.mongoose.Types.ObjectId(objectIdTest[1]);
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
- runPipelineStr = JSON.stringify(schema.pipeline);
619
- for (var param in req.query) {
620
- if (req.query[param]) {
621
- if (param !== 'r') { // we don't want to copy the whole report schema (again!)
622
- schema.params[param].value = req.query[param];
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
- // Replace parameters with the value
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
- else if (_.isObject(sparam.value)) {
634
- return JSON.stringify(sparam.value);
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
- else if (sparam.value[0] === '{') {
637
- return sparam.value;
646
+ outPath[element] = extend(true, {}, paths[element]);
647
+ if (paths[element].options.secure) {
648
+ hiddenFields.push(element);
638
649
  }
639
- else {
640
- return '"' + sparam.value + '"';
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
- // Don't send the 'secure' fields
645
- var hiddenFields = self.generateHiddenFields(resource, false);
646
- for (var hiddenField in hiddenFields) {
647
- if (hiddenFields.hasOwnProperty(hiddenField)) {
648
- if (runPipelineStr.indexOf(hiddenField) !== -1) {
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
- runPipelineObj = JSON.parse(runPipelineStr);
654
- if (!_.isArray(runPipelineObj)) {
655
- runPipelineObj = [runPipelineObj];
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
- self.hackVariablesInPipeline(runPipelineObj);
658
- // Add the findFunc query to the pipeline
659
- if (queryObj) {
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 toDo = {
663
- runAggregation: function (cb) {
664
- resource.model.aggregate(runPipelineObj, cb);
665
- }
666
- };
667
- var translations_1 = []; // array of form {ref:'lookupname',translations:[{value:xx, display:' '}]}
668
- // if we need to do any column translations add the function to the tasks list
669
- if (schema.columnTranslations) {
670
- toDo.applyTranslations = ['runAggregation', function (results, cb) {
671
- function doATranslate(column, theTranslation) {
672
- results['runAggregation'].forEach(function (resultRow) {
673
- var valToTranslate = resultRow[column.field];
674
- valToTranslate = (valToTranslate ? valToTranslate.toString() : '');
675
- var thisTranslation = _.find(theTranslation.translations, function (option) {
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
- if (columnTranslation.ref) {
686
- var theTranslation = _.find(translations_1, function (translation) {
687
- return (translation.ref === columnTranslation.ref);
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
- cb(null, null);
698
- }];
699
- var callFuncs = false;
700
- for (var i = 0; i < schema.columnTranslations.length; i++) {
701
- var thisColumnTranslation = schema.columnTranslations[i];
702
- if (thisColumnTranslation.field) {
703
- // if any of the column translations are adhoc funcs, set up the tasks to perform them
704
- if (thisColumnTranslation.fn) {
705
- callFuncs = true;
706
- }
707
- // if this column translation is a "ref", set up the tasks to look up the values and populate the translations
708
- if (thisColumnTranslation.ref) {
709
- var lookup = self.getResource(thisColumnTranslation.ref);
710
- if (lookup) {
711
- if (!toDo[thisColumnTranslation.ref]) {
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
- return callback('Invalid ref property of ' + thisColumnTranslation.ref + ' in columnTranslations ' + thisColumnTranslation.field);
752
- }
753
- }
754
- if (!thisColumnTranslation.translations && !thisColumnTranslation.ref && !thisColumnTranslation.fn) {
755
- return callback('A column translation needs a ref, fn or a translations property - ' + thisColumnTranslation.field + ' has neither');
756
- }
757
- }
758
- else {
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
- }, function () {
772
- cb(null);
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
- toDo.applyTranslations.unshift('callFunctions'); // Make sure we do function before translating its result
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
- DataForm.prototype.saveAndRespond = function (req, res, hiddenFields) {
791
- function internalSave(doc) {
792
- doc.save(function (err, doc2) {
793
- if (err) {
794
- var err2 = { status: 'err' };
795
- if (!err.errors) {
796
- err2.message = err.message;
797
- }
798
- else {
799
- extend(err2, err);
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 (debug) {
802
- console.log('Error saving record: ' + JSON.stringify(err2));
785
+ else if (_.isObject(obj[prop])) {
786
+ this.hackVariables(obj[prop]);
803
787
  }
804
- res.status(400).send(err2);
805
788
  }
806
- else {
807
- doc2 = doc2.toObject();
808
- for (var hiddenField in hiddenFields) {
809
- if (hiddenFields.hasOwnProperty(hiddenField) && hiddenFields[hiddenField]) {
810
- var parts = hiddenField.split('.');
811
- var lastPart = parts.length - 1;
812
- var target = doc2;
813
- for (var i = 0; i < lastPart; i++) {
814
- if (target.hasOwnProperty(parts[i])) {
815
- target = target[parts[i]];
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
- if (target.hasOwnProperty(parts[lastPart])) {
819
- delete target[parts[lastPart]];
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
- res.send(doc2);
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
- internalSave(doc);
834
- });
835
- }
836
- else {
837
- internalSave(doc);
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
- try {
859
- var aggregationParam = req.query.a ? JSON.parse(req.query.a) : null;
860
- var findParam = req.query.f ? JSON.parse(req.query.f) : {};
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
- var self_1 = this;
869
- this.filteredFind(req.resource, req, aggregationParam, findParam, orderParam, limitParam, skipParam, function (err, docs) {
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
- return self_1.renderError(err, null, req, res, next);
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
- res.send(docs);
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
- catch (e) {
879
- res.send(e);
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
- else {
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
- cb([]);
911
- }
912
- }
913
- doAggregation(function (idArray) {
914
- if (aggregationParam && idArray.length === 0) {
915
- callback(null, []);
1107
+ internalSave(doc);
916
1108
  }
917
- else {
918
- that.doFindFunc(req, resource, function (err, queryObj) {
919
- if (err) {
920
- callback(err);
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
- else {
923
- var query = resource.model.find(queryObj);
924
- if (idArray.length > 0) {
925
- query = query.where('_id').in(idArray);
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
- query = query.find(findParam).select(hiddenFields);
928
- if (limit) {
929
- query = query.limit(limit);
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 (skip) {
932
- query = query.skip(skip);
1175
+ else if (projectParam[p] === 1) {
1176
+ setSelectType('I', 'E');
933
1177
  }
934
- if (sortOrder) {
935
- query = query.sort(sortOrder);
1178
+ else {
1179
+ throw new Error('Invalid projection: ' + projectParam);
936
1180
  }
937
- query.exec(function (err, docs) {
938
- if (!err && stashAggregationResults) {
939
- docs.forEach(function (obj) {
940
- // Add any fields from the aggregation results whose field name starts __ to the mongoose Document
941
- var aggObj = stashAggregationResults.find(function (a) { return a._id.toString() === obj._id.toString(); });
942
- Object.keys(aggObj).forEach(function (k) {
943
- if (k.slice(0, 2) === '__') {
944
- obj[k] = aggObj[k];
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
- callback(err, docs);
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
- * Entity request goes there first
1019
- * It retrieves the resource
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
- query.exec(function (err, doc) {
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
- * Gets a single entity
1055
- *
1056
- * @return {Function} The function to use as route
1057
- */
1058
- DataForm.prototype.entityGet = function () {
1059
- return _.bind(function (req, res, next) {
1060
- this.processEntity(req, res, function () {
1061
- if (!req.resource) {
1062
- return next();
1063
- }
1064
- if (req.resource.options.onAccess) {
1065
- req.resource.options.onAccess(req, function () {
1066
- return res.send(req.doc);
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
- return res.send(req.doc);
1229
+ cb([]);
1071
1230
  }
1072
- });
1073
- }, this);
1074
- };
1075
- DataForm.prototype.replaceHiddenFields = function (record, data) {
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
- record[name] = value;
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
- delete record._replacingHiddenFields;
1088
- }
1089
- };
1090
- DataForm.prototype.entityPut = function () {
1091
- return _.bind(function (req, res, next) {
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 = that.cleanseRequest(req);
1102
- // Merge
1103
- _.each(cleansedBody, function (value, name) {
1104
- req.doc[name] = (value === '') ? undefined : value;
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
- if (req.resource.options.hide !== undefined) {
1107
- var hiddenFields_1 = that.generateHiddenFields(req.resource, true);
1108
- hiddenFields_1._id = false;
1109
- req.resource.model.findById(req.doc._id, hiddenFields_1, { lean: true }, function (err, data) {
1110
- that.replaceHiddenFields(req.doc, data);
1111
- that.saveAndRespond(req, res, hiddenFields_1);
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
- that.saveAndRespond(req, res);
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
- }, this);
1119
- };
1120
- DataForm.prototype.entityDelete = function () {
1121
- return _.bind(function (req, res, next) {
1122
- function internalRemove(doc) {
1123
- doc.remove(function (err) {
1124
- if (err) {
1125
- return res.send({ success: false });
1126
- }
1127
- return res.send({ success: true });
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.processEntity(req, res, function () {
1131
- if (!req.resource) {
1132
- next();
1133
- return;
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
- var doc = req.doc;
1136
- if (typeof req.resource.options.onRemove === 'function') {
1137
- req.resource.options.onRemove(doc, req, function (err) {
1377
+ else {
1378
+ query.exec(function (err, doc) {
1138
1379
  if (err) {
1139
- throw err;
1380
+ return res.status(400).send({
1381
+ success: false,
1382
+ err: util.inspect(err)
1383
+ });
1140
1384
  }
1141
- internalRemove(doc);
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
- }, this);
1149
- };
1150
- DataForm.prototype.entityList = function () {
1151
- return _.bind(function (req, res, next) {
1152
- var that = this;
1153
- this.processEntity(req, res, function () {
1154
- if (!req.resource) {
1155
- return next();
1156
- }
1157
- that.getListFields(req.resource, req.doc, function (err, display) {
1158
- if (err) {
1159
- return res.status(500).send(err);
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({ list: display });
1415
+ return res.status(200).send(req.doc);
1163
1416
  }
1164
1417
  });
1165
- });
1166
- }, this);
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));