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