@formio/appserver 2.0.0-rc.1 → 2.0.0-rc.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/lib/actions/index.d.ts +3 -156
- package/lib/actions/login/LoginAction.d.ts +2 -108
- package/lib/actions/login/LoginAction.js +124 -213
- package/lib/actions/role/RoleAction.d.ts +2 -61
- package/lib/actions/role/RoleAction.js +13 -20
- package/lib/actions/save/SaveAction.d.ts +2 -28
- package/lib/actions/save/SaveAction.js +27 -45
- package/lib/modules/index.d.ts +1 -3
- package/lib/modules/index.js +3 -5
- package/lib/modules/mongodb/db.d.ts +127 -0
- package/lib/modules/mongodb/db.js +832 -0
- package/lib/server.d.ts +5 -174
- package/lib/server.js +61 -58
- package/package.json +25 -25
- package/lib/modules/auth/auth.d.ts +0 -5
- package/lib/modules/auth/auth.js +0 -70
- package/lib/prepare/access.d.ts +0 -3
- package/lib/prepare/access.js +0 -62
- package/lib/prepare/bcrypt.d.ts +0 -3
- package/lib/prepare/bcrypt.js +0 -25
- package/lib/prepare/index.d.ts +0 -11
- package/lib/prepare/index.js +0 -60
- package/lib/prepare/password.d.ts +0 -3
- package/lib/prepare/password.js +0 -27
- package/lib/prepare/persist.d.ts +0 -3
- package/lib/prepare/persist.js +0 -24
- package/lib/prepare/protect.d.ts +0 -3
- package/lib/prepare/protect.js +0 -28
- package/lib/prepare/reference.d.ts +0 -3
- package/lib/prepare/reference.js +0 -71
- package/lib/types/action.d.ts +0 -7
- package/lib/types/action.js +0 -2
- package/lib/types/form.d.ts +0 -4
- package/lib/types/form.js +0 -2
- package/lib/types/index.d.ts +0 -5
- package/lib/types/index.js +0 -21
- package/lib/types/project.d.ts +0 -4
- package/lib/types/project.js +0 -2
- package/lib/types/server.d.ts +0 -102
- package/lib/types/server.js +0 -2
- package/lib/types/submission.d.ts +0 -47
- package/lib/types/submission.js +0 -2
- package/server.js +0 -1
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.Database = void 0;
|
|
24
|
+
const mongodb_1 = require("mongodb");
|
|
25
|
+
const lodash_1 = require("lodash");
|
|
26
|
+
const core_1 = require("@formio/core");
|
|
27
|
+
const debug = require('debug')('formio:db');
|
|
28
|
+
const error = require('debug')('formio:error');
|
|
29
|
+
class Database {
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.prefix = '';
|
|
33
|
+
this.authQuery = null;
|
|
34
|
+
this.db = null;
|
|
35
|
+
this.client = null; // Add this line
|
|
36
|
+
this.currentCollectionName = 'submissions';
|
|
37
|
+
this.currentCollection = null;
|
|
38
|
+
this.defaultCollection = null;
|
|
39
|
+
}
|
|
40
|
+
ObjectId(id) {
|
|
41
|
+
if (id === null || id === void 0 ? void 0 : id.hasOwnProperty('$in')) {
|
|
42
|
+
return { $in: id['$in'].map((_id) => this.ObjectId(_id)) };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return id ? (new mongodb_1.ObjectId(id)) : null;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
collectionName(name) {
|
|
52
|
+
return `${this.prefix}${name}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Connect to the database.
|
|
56
|
+
* @returns
|
|
57
|
+
*/
|
|
58
|
+
connect() {
|
|
59
|
+
return __awaiter(this, arguments, void 0, function* (prefix = '') {
|
|
60
|
+
this.prefix = prefix ? `${prefix}.` : '';
|
|
61
|
+
try {
|
|
62
|
+
debug('db.connect()');
|
|
63
|
+
let config = {};
|
|
64
|
+
if (this.config.config) {
|
|
65
|
+
config = (typeof this.config.config === 'string') ? JSON.parse(this.config.config) : this.config.config;
|
|
66
|
+
}
|
|
67
|
+
this.client = new mongodb_1.MongoClient(this.config.url, config); // Store the client
|
|
68
|
+
yield this.client.connect();
|
|
69
|
+
this.db = yield this.client.db();
|
|
70
|
+
if (this.config.dropOnConnect) {
|
|
71
|
+
yield this.db.dropDatabase();
|
|
72
|
+
}
|
|
73
|
+
this.addIndex(this.collection('project'), 'name');
|
|
74
|
+
this.defaultCollection = this.collection('submissions');
|
|
75
|
+
yield this.setupIndexes(this.defaultCollection);
|
|
76
|
+
debug('Connected to database');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
error(err.message);
|
|
80
|
+
process.exit();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Add a close method
|
|
85
|
+
close() {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
if (this.client) {
|
|
88
|
+
yield this.client.close();
|
|
89
|
+
this.client = null;
|
|
90
|
+
this.db = null;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
collection(name) {
|
|
95
|
+
return this.db ? this.db.collection(this.collectionName(name)) : null;
|
|
96
|
+
}
|
|
97
|
+
// Generic save record method.
|
|
98
|
+
save(collectionName, item) {
|
|
99
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
if (!this.db) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const collection = this.collection(collectionName);
|
|
104
|
+
if (!collection) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
let result;
|
|
108
|
+
try {
|
|
109
|
+
debug('db.save()', collectionName);
|
|
110
|
+
if (item._id) {
|
|
111
|
+
yield collection.updateOne({ _id: this.ObjectId(item._id) }, { $set: (0, lodash_1.omit)(item, '_id') });
|
|
112
|
+
return yield collection.findOne({ _id: this.ObjectId(item._id) });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
result = yield collection.insertOne(item);
|
|
116
|
+
return yield collection.findOne({ _id: this.ObjectId(result.insertedId) });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
error(err.message);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
saveOne(collectionName, item) {
|
|
126
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
+
if (!this.db) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const collection = this.collection(collectionName);
|
|
131
|
+
if (!collection) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (!item._id) {
|
|
135
|
+
// Try to load an existing record if one exists.
|
|
136
|
+
let existing = yield this.load(collectionName);
|
|
137
|
+
if (existing && !existing._id) {
|
|
138
|
+
existing._id = new mongodb_1.ObjectId();
|
|
139
|
+
const result = yield collection.insertOne(existing);
|
|
140
|
+
existing = yield collection.findOne({ _id: this.ObjectId(result.insertedId) });
|
|
141
|
+
}
|
|
142
|
+
item._id = existing === null || existing === void 0 ? void 0 : existing._id;
|
|
143
|
+
}
|
|
144
|
+
return yield this.save(collectionName, item);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Generic load record method.
|
|
148
|
+
load(collectionName_1) {
|
|
149
|
+
return __awaiter(this, arguments, void 0, function* (collectionName, dbQuery = {}) {
|
|
150
|
+
if (!this.db) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
debug('db.load()', collectionName);
|
|
154
|
+
const collection = this.collection(collectionName);
|
|
155
|
+
if (!collection) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
let item;
|
|
159
|
+
try {
|
|
160
|
+
item = yield collection.findOne(dbQuery);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
error(err.message);
|
|
164
|
+
}
|
|
165
|
+
return item;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Provides a suplemental query for authentication purposes.
|
|
169
|
+
indexAuth(query) {
|
|
170
|
+
this.authQuery = query || null;
|
|
171
|
+
}
|
|
172
|
+
getQueryValue(name, value, selector) {
|
|
173
|
+
if (name === 'created' || name === 'modified') {
|
|
174
|
+
return new Date(value);
|
|
175
|
+
}
|
|
176
|
+
if ((typeof value === 'string') && (!selector || (selector === 'eq' || selector === 'ne'))) {
|
|
177
|
+
const lcValue = value.toLowerCase();
|
|
178
|
+
if (lcValue === 'null') {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (lcValue === '"null"') {
|
|
182
|
+
return 'null';
|
|
183
|
+
}
|
|
184
|
+
if (lcValue === 'true') {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (lcValue === '"true"') {
|
|
188
|
+
return 'true';
|
|
189
|
+
}
|
|
190
|
+
if (lcValue === 'false') {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
if (lcValue === '"false"') {
|
|
194
|
+
return 'false';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Converts the url query to query parameters.
|
|
201
|
+
* @param query
|
|
202
|
+
* @returns
|
|
203
|
+
*/
|
|
204
|
+
dbQuery(query = {}) {
|
|
205
|
+
const dbQuery = {};
|
|
206
|
+
const { limit, skip, sort } = query, filters = __rest(query, ["limit", "skip", "sort"]);
|
|
207
|
+
if (limit !== undefined) {
|
|
208
|
+
dbQuery.limit = limit ? Number(limit) : 10;
|
|
209
|
+
}
|
|
210
|
+
if (skip !== undefined) {
|
|
211
|
+
dbQuery.skip = skip ? Number(skip) : 0;
|
|
212
|
+
}
|
|
213
|
+
if (sort) {
|
|
214
|
+
dbQuery.sort = {};
|
|
215
|
+
const sortParts = sort.split(',');
|
|
216
|
+
sortParts.forEach((part) => {
|
|
217
|
+
const desc = part.charAt(0) === '-';
|
|
218
|
+
const field = desc ? part.substring(1) : part;
|
|
219
|
+
dbQuery.sort[field] = desc ? -1 : 1;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Iterate through each filter.
|
|
223
|
+
Object.entries(filters).forEach(([name, value]) => {
|
|
224
|
+
// Get the filter object.
|
|
225
|
+
const filter = (0, lodash_1.zipObject)(['name', 'selector'], name.split('__'));
|
|
226
|
+
if (filter.selector) {
|
|
227
|
+
var filterQuery = dbQuery[filter.name];
|
|
228
|
+
// Init the filter.
|
|
229
|
+
if (!filterQuery) {
|
|
230
|
+
filterQuery = {};
|
|
231
|
+
}
|
|
232
|
+
if (filter.selector === 'exists') {
|
|
233
|
+
value = ((value === 'true') || (value === '1')) ? true : value;
|
|
234
|
+
value = ((value === 'false') || (value === '0')) ? false : value;
|
|
235
|
+
value = !!value;
|
|
236
|
+
}
|
|
237
|
+
else if (filter.selector === 'regex') {
|
|
238
|
+
const parts = value.match(/\/?([^/]+)\/?([^/]+)?/);
|
|
239
|
+
if (!parts) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
value = new RegExp((0, lodash_1.escapeRegExp)(parts[1]), (parts[2] || 'i'));
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
error(err);
|
|
247
|
+
value = null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Special case for in filter with multiple values.
|
|
251
|
+
else if (['in', 'nin'].includes(filter.selector)) {
|
|
252
|
+
value = Array.isArray(value) ? value : value.split(',');
|
|
253
|
+
value = value.map((item) => this.getQueryValue(filter.name, item, filter.selector));
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Set the selector for this filter name.
|
|
257
|
+
value = this.getQueryValue(filter.name, value, filter.selector);
|
|
258
|
+
}
|
|
259
|
+
filterQuery[`$${filter.selector}`] = value;
|
|
260
|
+
dbQuery[filter.name] = filterQuery;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Set the find query to this value.
|
|
265
|
+
value = this.getQueryValue(filter.name, value, filter.selector);
|
|
266
|
+
dbQuery[filter.name] = value;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
// If an authQuery is provided, merge it in.
|
|
271
|
+
if (this.authQuery && (0, lodash_1.isPlainObject)(this.authQuery)) {
|
|
272
|
+
return (0, lodash_1.merge)(dbQuery, this.authQuery);
|
|
273
|
+
}
|
|
274
|
+
// Return the dbQuery.
|
|
275
|
+
return dbQuery;
|
|
276
|
+
}
|
|
277
|
+
// Generic delete record method.
|
|
278
|
+
remove(collectionName_1) {
|
|
279
|
+
return __awaiter(this, arguments, void 0, function* (collectionName, dbQuery = {}) {
|
|
280
|
+
if (!this.db) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
debug('db.remove()', collectionName);
|
|
284
|
+
const collection = this.collection(collectionName);
|
|
285
|
+
if (!collection) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
let result;
|
|
289
|
+
try {
|
|
290
|
+
result = yield collection.deleteOne(dbQuery);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
error(err.message);
|
|
294
|
+
}
|
|
295
|
+
return result ? result.deletedCount : null;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Ensures the form collection is created an returns it.
|
|
300
|
+
* @returns
|
|
301
|
+
*/
|
|
302
|
+
formCollection(form) {
|
|
303
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
304
|
+
if (this.db && form && form.settings && form.settings.collection) {
|
|
305
|
+
if (this.currentCollection && this.currentCollectionName === form.settings.collection) {
|
|
306
|
+
return this.currentCollection;
|
|
307
|
+
}
|
|
308
|
+
let collection = null;
|
|
309
|
+
try {
|
|
310
|
+
debug('db.createCollection()', form.settings.collection);
|
|
311
|
+
yield this.db.createCollection(this.collectionName(form.settings.collection));
|
|
312
|
+
collection = this.collection(form.settings.collection);
|
|
313
|
+
this.setupIndexes(collection, form);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
error(err.message);
|
|
317
|
+
}
|
|
318
|
+
this.currentCollection = collection || this.collection(form.settings.collection);
|
|
319
|
+
this.currentCollectionName = form.settings.collection;
|
|
320
|
+
return this.currentCollection;
|
|
321
|
+
}
|
|
322
|
+
return this.defaultCollection;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Setup indexes.
|
|
327
|
+
*/
|
|
328
|
+
setupIndexes(collection, form) {
|
|
329
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
330
|
+
if (!collection) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this.addIndex(collection, 'project');
|
|
334
|
+
this.addIndex(collection, 'form');
|
|
335
|
+
this.addIndex(collection, 'deleted');
|
|
336
|
+
this.addIndex(collection, 'modified');
|
|
337
|
+
// If TTL index is needed add that here.
|
|
338
|
+
if (this.config.ttl) {
|
|
339
|
+
try {
|
|
340
|
+
collection.createIndex({
|
|
341
|
+
created: 1
|
|
342
|
+
}, {
|
|
343
|
+
background: true,
|
|
344
|
+
expireAfterSeconds: this.config.ttl
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
error('Cannot add TTL index', err.message);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
this.addIndex(collection, 'created');
|
|
353
|
+
}
|
|
354
|
+
if (form && form.components) {
|
|
355
|
+
core_1.Utils.eachComponent(form.components, (component, path) => {
|
|
356
|
+
if (component.dbIndex) {
|
|
357
|
+
this.addIndex(collection, `data.${path}`);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Add a field index
|
|
365
|
+
*/
|
|
366
|
+
addIndex(collection, path) {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
if (!collection) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const index = {};
|
|
372
|
+
index[path] = 1;
|
|
373
|
+
try {
|
|
374
|
+
debug('db.addIndex()', path);
|
|
375
|
+
collection.createIndex(index, { background: true });
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
error(`Cannot add index ${path}`, err.message);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Adds new indexes to the forms submission collection.
|
|
384
|
+
* @param indexes
|
|
385
|
+
* @returns
|
|
386
|
+
*/
|
|
387
|
+
addIndexes(form, indexes) {
|
|
388
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
389
|
+
const collection = yield this.formCollection(form);
|
|
390
|
+
if (!collection) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
for (const path of indexes) {
|
|
394
|
+
yield this.addIndex(collection, `data.${path}`);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Remove a field index.
|
|
400
|
+
*/
|
|
401
|
+
removeIndex(collection, path) {
|
|
402
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
403
|
+
const index = {};
|
|
404
|
+
index[path] = 1;
|
|
405
|
+
try {
|
|
406
|
+
debug('db.removeIndex()', path);
|
|
407
|
+
yield collection.dropIndex(index);
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
error(`Cannot remove index ${path}`, err.message);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Removes indexes to the forms submission collection.
|
|
416
|
+
* @param indexes
|
|
417
|
+
* @returns
|
|
418
|
+
*/
|
|
419
|
+
removeIndexes(form, indexes) {
|
|
420
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
421
|
+
const collection = yield this.formCollection(form);
|
|
422
|
+
if (!collection) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
for (const path of indexes) {
|
|
426
|
+
yield this.removeIndex(collection, `data.${path}`);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Fetch a list of submissions from a table/collection.
|
|
432
|
+
* @param {*} table
|
|
433
|
+
* @param {*} query
|
|
434
|
+
*/
|
|
435
|
+
index(form_1) {
|
|
436
|
+
return __awaiter(this, arguments, void 0, function* (form, urlQuery = {}, collection = null) {
|
|
437
|
+
let items = [], skip = 0, limit = 10, count = 0;
|
|
438
|
+
try {
|
|
439
|
+
limit = urlQuery.limit || 10;
|
|
440
|
+
skip = urlQuery.skip || 0;
|
|
441
|
+
const sort = urlQuery.sort || { created: -1 };
|
|
442
|
+
const includeDeleted = urlQuery.includeDeleted || false;
|
|
443
|
+
let project = {};
|
|
444
|
+
if (urlQuery.select) {
|
|
445
|
+
if (typeof urlQuery.select === 'string') {
|
|
446
|
+
const fields = urlQuery.select.split(',');
|
|
447
|
+
fields.forEach((field) => {
|
|
448
|
+
project[field] = true;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
Object.keys(urlQuery.select).forEach((field) => {
|
|
453
|
+
project[field] = true;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
delete urlQuery.limit;
|
|
458
|
+
delete urlQuery.skip;
|
|
459
|
+
delete urlQuery.sort;
|
|
460
|
+
delete urlQuery.select;
|
|
461
|
+
delete urlQuery.includeDeleted;
|
|
462
|
+
debug('db.index()', urlQuery);
|
|
463
|
+
const dbQuery = this.dbQuery(urlQuery);
|
|
464
|
+
items = (yield this.find(form, dbQuery, limit, skip, sort, project, collection, includeDeleted)) || [];
|
|
465
|
+
count = yield this.count(form, dbQuery, collection, includeDeleted);
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
error(err.message);
|
|
469
|
+
}
|
|
470
|
+
return { items: items, limit, skip, count };
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Perform a count of the amount of documents within a collection.
|
|
475
|
+
* @param form
|
|
476
|
+
* @param query
|
|
477
|
+
* @returns
|
|
478
|
+
*/
|
|
479
|
+
count(form_1) {
|
|
480
|
+
return __awaiter(this, arguments, void 0, function* (form, dbQuery = {}, inCollection = null, includeDeleted = false) {
|
|
481
|
+
let count = 0;
|
|
482
|
+
try {
|
|
483
|
+
debug('db.count()', dbQuery);
|
|
484
|
+
const collection = inCollection || (yield this.formCollection(form));
|
|
485
|
+
if (!collection) {
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
count = yield collection.countDocuments(this.query(form, dbQuery, includeDeleted));
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
error(err.message);
|
|
492
|
+
}
|
|
493
|
+
return count;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Create a new record.
|
|
498
|
+
*/
|
|
499
|
+
create(form_1, record_1) {
|
|
500
|
+
return __awaiter(this, arguments, void 0, function* (form, record, allowFields = []) {
|
|
501
|
+
record.deleted = null;
|
|
502
|
+
record.created = new Date();
|
|
503
|
+
record.modified = new Date();
|
|
504
|
+
if (form && form._id) {
|
|
505
|
+
record.form = this.ObjectId(form._id);
|
|
506
|
+
}
|
|
507
|
+
if (form.project && form.project) {
|
|
508
|
+
record.project = this.ObjectId(form.project);
|
|
509
|
+
}
|
|
510
|
+
if (record.owner) {
|
|
511
|
+
record.owner = this.ObjectId(record.owner);
|
|
512
|
+
}
|
|
513
|
+
const collection = yield this.formCollection(form);
|
|
514
|
+
try {
|
|
515
|
+
debug('db.create()', record);
|
|
516
|
+
const result = yield collection.insertOne((0, lodash_1.pick)(record, [
|
|
517
|
+
'data',
|
|
518
|
+
'metadata',
|
|
519
|
+
'modified',
|
|
520
|
+
'created',
|
|
521
|
+
'deleted',
|
|
522
|
+
'form',
|
|
523
|
+
'project',
|
|
524
|
+
'owner',
|
|
525
|
+
'access',
|
|
526
|
+
'state'
|
|
527
|
+
].concat(allowFields)));
|
|
528
|
+
if (!result.insertedId) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
return yield this.read(form, result.insertedId);
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
error(err.message);
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Return a query for a single mongo record.
|
|
541
|
+
* @param {*} form
|
|
542
|
+
* @param {*} id
|
|
543
|
+
* @returns
|
|
544
|
+
*/
|
|
545
|
+
query(form, query = {}, includeAll = false) {
|
|
546
|
+
if (query._id) {
|
|
547
|
+
query._id = this.ObjectId(query._id);
|
|
548
|
+
}
|
|
549
|
+
if (query.owner) {
|
|
550
|
+
query.owner = this.ObjectId(query.owner);
|
|
551
|
+
}
|
|
552
|
+
if (query.form) {
|
|
553
|
+
query.form = this.ObjectId(query.form);
|
|
554
|
+
}
|
|
555
|
+
else if (!includeAll && form && form._id) {
|
|
556
|
+
query.form = this.ObjectId(form._id);
|
|
557
|
+
}
|
|
558
|
+
if (!includeAll && form.project) {
|
|
559
|
+
query.project = this.ObjectId(form.project);
|
|
560
|
+
}
|
|
561
|
+
if (!includeAll) {
|
|
562
|
+
query.deleted = { $eq: null };
|
|
563
|
+
}
|
|
564
|
+
// Handle nested queries.
|
|
565
|
+
['$or', '$and'].forEach((key) => {
|
|
566
|
+
if (query[key]) {
|
|
567
|
+
query[key].forEach((subQuery) => this.query(form, subQuery, true));
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
return query;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Find many records that match a query.
|
|
574
|
+
*/
|
|
575
|
+
find(form_1) {
|
|
576
|
+
return __awaiter(this, arguments, void 0, function* (form, dbQuery = {}, limit = 10, skip = 0, sort = { created: -1 }, select = {}, inCollection = null, includeDeleted = false) {
|
|
577
|
+
try {
|
|
578
|
+
debug('db.find()', dbQuery);
|
|
579
|
+
const collection = inCollection || (yield this.formCollection(form));
|
|
580
|
+
if (!collection) {
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
return yield collection.find(this.query(form, dbQuery, includeDeleted)).limit(limit).skip(skip).sort(sort).project(select).toArray();
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
error(err.message);
|
|
587
|
+
}
|
|
588
|
+
return [];
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Find a record for a provided query.
|
|
593
|
+
*/
|
|
594
|
+
findOne(form_1) {
|
|
595
|
+
return __awaiter(this, arguments, void 0, function* (form, dbQuery = {}, options = {}) {
|
|
596
|
+
try {
|
|
597
|
+
debug('db.findOne()', dbQuery);
|
|
598
|
+
const collection = yield this.formCollection(form);
|
|
599
|
+
if (!collection) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const doc = yield collection.findOne(this.query(form, dbQuery));
|
|
603
|
+
if (!doc) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
return doc;
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
error(err.message);
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Read a single submission from a form
|
|
616
|
+
*
|
|
617
|
+
* @param {*} table
|
|
618
|
+
* @param {*} query
|
|
619
|
+
*/
|
|
620
|
+
read(form, id) {
|
|
621
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
622
|
+
try {
|
|
623
|
+
debug('db.read()', id);
|
|
624
|
+
if (!id) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const collection = yield this.formCollection(form);
|
|
628
|
+
if (!collection) {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
const doc = yield collection.findOne(this.query(form, { _id: this.ObjectId(id) }));
|
|
632
|
+
if (!doc) {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
return doc;
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
error(err.message);
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Update a single submission of a form.
|
|
645
|
+
*
|
|
646
|
+
* @param {*} table
|
|
647
|
+
* @param {*} query
|
|
648
|
+
*/
|
|
649
|
+
update(form_1, id_1, update_1) {
|
|
650
|
+
return __awaiter(this, arguments, void 0, function* (form, id, update, allowFields = []) {
|
|
651
|
+
if (!id || !update) {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
update.modified = new Date();
|
|
655
|
+
try {
|
|
656
|
+
debug('db.update()', id, update);
|
|
657
|
+
const collection = yield this.formCollection(form);
|
|
658
|
+
if (!collection) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const result = yield collection.updateOne(this.query(form, { _id: this.ObjectId(id) }), {
|
|
662
|
+
$set: (0, lodash_1.pick)(update, ['data', 'metadata', 'modified', 'state'].concat(allowFields))
|
|
663
|
+
});
|
|
664
|
+
if (result.modifiedCount === 0) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
return yield this.read(form, id);
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
error(err.message);
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Delete a record from the database.
|
|
677
|
+
*/
|
|
678
|
+
delete(form_1, id_1) {
|
|
679
|
+
return __awaiter(this, arguments, void 0, function* (form, id, hard = false) {
|
|
680
|
+
if (!id) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
debug('db.delete()', id, hard ? '(hard)' : '(soft)');
|
|
685
|
+
const collection = yield this.formCollection(form);
|
|
686
|
+
if (!collection) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
if (hard) {
|
|
690
|
+
// Hard delete - completely remove the record from the database
|
|
691
|
+
const result = yield collection.deleteOne(this.query(form, { _id: this.ObjectId(id) }, true));
|
|
692
|
+
return result.deletedCount > 0;
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
// Soft delete - set deleted timestamp
|
|
696
|
+
const result = yield collection.updateOne(this.query(form, { _id: this.ObjectId(id) }), {
|
|
697
|
+
$set: {
|
|
698
|
+
deleted: new Date(),
|
|
699
|
+
modified: new Date()
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
return result.modifiedCount > 0;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
error(err.message);
|
|
707
|
+
}
|
|
708
|
+
return false;
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
addPathQueryParams(pathQueryParams, query, path) {
|
|
712
|
+
const pathArray = path.split(/\[\d+\]?./);
|
|
713
|
+
const needValuesInArray = pathArray.length > 1;
|
|
714
|
+
let pathToValue = path;
|
|
715
|
+
if (needValuesInArray) {
|
|
716
|
+
pathToValue = pathArray.shift();
|
|
717
|
+
if (!pathToValue) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const pathQueryObj = {};
|
|
721
|
+
(0, lodash_1.reduce)(pathArray, (pathQueryPath, pathPart, index) => {
|
|
722
|
+
const isLastPathPart = index === (pathArray.length - 1);
|
|
723
|
+
const obj = (0, lodash_1.get)(pathQueryObj, pathQueryPath, pathQueryObj);
|
|
724
|
+
const addedPath = `$elemMatch['${pathPart}']`;
|
|
725
|
+
(0, lodash_1.set)(obj, addedPath, isLastPathPart ? pathQueryParams : {});
|
|
726
|
+
return pathQueryPath ? `${pathQueryPath}.${addedPath}` : addedPath;
|
|
727
|
+
}, '');
|
|
728
|
+
query[pathToValue] = pathQueryObj;
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
query[pathToValue] = pathQueryParams;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Performs a unique query on the database to see if a value is unique.
|
|
736
|
+
* - If the value is unique, it returns true.
|
|
737
|
+
* - If the value is not unique, it returns the id of the non-unique submission.
|
|
738
|
+
* - If an error occurs, or uniqueness cannot be determined, it will throw an error.
|
|
739
|
+
*
|
|
740
|
+
* @param form
|
|
741
|
+
* @param context
|
|
742
|
+
* @param value
|
|
743
|
+
* @returns
|
|
744
|
+
*/
|
|
745
|
+
isUnique(form, component, submission, componentPath, value) {
|
|
746
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
747
|
+
const path = `data.${componentPath}`;
|
|
748
|
+
if (!form) {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
// Build the query
|
|
752
|
+
const query = { form: form._id };
|
|
753
|
+
let options = {};
|
|
754
|
+
if ((0, lodash_1.isString)(value)) {
|
|
755
|
+
if (component.dbIndex) {
|
|
756
|
+
this.addPathQueryParams(value, query, path);
|
|
757
|
+
}
|
|
758
|
+
else if (component.type === 'email' ||
|
|
759
|
+
(component.type === 'textfield' &&
|
|
760
|
+
component.validate &&
|
|
761
|
+
component.validate.pattern === '[A-Za-z0-9]+')) {
|
|
762
|
+
this.addPathQueryParams(value, query, path);
|
|
763
|
+
options = { collation: { locale: 'en', strength: 2 } };
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
this.addPathQueryParams({
|
|
767
|
+
$regex: new RegExp(`^${(0, core_1.escapeRegExCharacters)(value)}$`),
|
|
768
|
+
$options: 'i'
|
|
769
|
+
}, query, path);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// FOR-213 - Pluck the unique location id
|
|
773
|
+
else if ((0, lodash_1.isPlainObject)(value) &&
|
|
774
|
+
value.address &&
|
|
775
|
+
value.address['address_components'] &&
|
|
776
|
+
value.address['place_id']) {
|
|
777
|
+
this.addPathQueryParams({
|
|
778
|
+
$regex: new RegExp(`^${(0, core_1.escapeRegExCharacters)(value.address['place_id'])}$`),
|
|
779
|
+
$options: 'i'
|
|
780
|
+
}, query, `${path}.address.place_id`);
|
|
781
|
+
}
|
|
782
|
+
// Compare the contents of arrays vs the order.
|
|
783
|
+
else if ((0, lodash_1.isArray)(value)) {
|
|
784
|
+
this.addPathQueryParams({ $all: value }, query, path);
|
|
785
|
+
}
|
|
786
|
+
else if ((0, lodash_1.isObject)(value) || (0, lodash_1.isNumber)(value)) {
|
|
787
|
+
this.addPathQueryParams({ $eq: value }, query, path);
|
|
788
|
+
}
|
|
789
|
+
// Only search for non-deleted items
|
|
790
|
+
query.deleted = { $eq: null };
|
|
791
|
+
query.state = 'submitted';
|
|
792
|
+
// Perform the query.
|
|
793
|
+
let result = null;
|
|
794
|
+
try {
|
|
795
|
+
result = yield this.findOne(form, query, options);
|
|
796
|
+
if (!(result === null || result === void 0 ? void 0 : result._id) || ((result === null || result === void 0 ? void 0 : result._id.toString()) === (submission === null || submission === void 0 ? void 0 : submission._id))) {
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
return result._id.toString();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch (err) {
|
|
804
|
+
if (options.collation) {
|
|
805
|
+
// presume this error comes from db compatibility, try again as regex
|
|
806
|
+
delete query[path];
|
|
807
|
+
this.addPathQueryParams({
|
|
808
|
+
$regex: new RegExp(`^${(0, core_1.escapeRegExCharacters)(value)}$`),
|
|
809
|
+
$options: 'i'
|
|
810
|
+
}, query, path);
|
|
811
|
+
try {
|
|
812
|
+
result = yield this.findOne(form, query);
|
|
813
|
+
if (!(result === null || result === void 0 ? void 0 : result._id) || ((result === null || result === void 0 ? void 0 : result._id.toString()) === (submission === null || submission === void 0 ? void 0 : submission._id))) {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
component.conflictId = result._id.toString();
|
|
818
|
+
return component.conflictId;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
catch (err) {
|
|
822
|
+
throw err;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
throw err;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
exports.Database = Database;
|