@flowerforce/flowerbase 1.7.3-beta.0 → 1.7.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +247 -61
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/context/helpers.d.ts +5 -2
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +2 -2
- package/dist/utils/context/index.d.ts +1 -0
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +62 -26
- package/dist/utils/context/interface.d.ts +1 -0
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +0 -1
- package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +274 -0
- package/src/services/mongodb-atlas/index.ts +281 -65
- package/src/services/mongodb-atlas/model.ts +3 -0
- package/src/utils/__tests__/contextExecuteCompatibility.test.ts +60 -0
- package/src/utils/__tests__/generateContextData.test.ts +3 -1
- package/src/utils/context/helpers.ts +2 -1
- package/src/utils/context/index.ts +94 -47
- package/src/utils/context/interface.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAyC,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAysCrF,QAAA,MAAM,YAAY,EAAE,oBA6BlB,CAAA;AAEF,eAAe,YAAY,CAAA"}
|
|
@@ -14,6 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
const get_1 = __importDefault(require("lodash/get"));
|
|
16
16
|
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
|
17
|
+
const set_1 = __importDefault(require("lodash/set"));
|
|
18
|
+
const unset_1 = __importDefault(require("lodash/unset"));
|
|
19
|
+
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
|
|
17
20
|
const utils_1 = require("../../monitoring/utils");
|
|
18
21
|
const machines_1 = require("../../utils/roles/machines");
|
|
19
22
|
const utils_2 = require("../../utils/roles/machines/utils");
|
|
@@ -39,6 +42,206 @@ const logService = (message, payload) => {
|
|
|
39
42
|
return;
|
|
40
43
|
console.log('[service-debug]', message, payload !== null && payload !== void 0 ? payload : '');
|
|
41
44
|
};
|
|
45
|
+
const findOptionKeys = new Set([
|
|
46
|
+
'sort',
|
|
47
|
+
'skip',
|
|
48
|
+
'limit',
|
|
49
|
+
'session',
|
|
50
|
+
'hint',
|
|
51
|
+
'maxTimeMS',
|
|
52
|
+
'collation',
|
|
53
|
+
'allowPartialResults',
|
|
54
|
+
'noCursorTimeout',
|
|
55
|
+
'batchSize',
|
|
56
|
+
'returnKey',
|
|
57
|
+
'showRecordId',
|
|
58
|
+
'comment',
|
|
59
|
+
'let',
|
|
60
|
+
'projection'
|
|
61
|
+
]);
|
|
62
|
+
const isPlainObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
63
|
+
const looksLikeFindOptions = (value) => {
|
|
64
|
+
if (!isPlainObject(value))
|
|
65
|
+
return false;
|
|
66
|
+
return Object.keys(value).some((key) => findOptionKeys.has(key));
|
|
67
|
+
};
|
|
68
|
+
const resolveFindArgs = (projectionOrOptions, options) => {
|
|
69
|
+
if (typeof options !== 'undefined') {
|
|
70
|
+
return {
|
|
71
|
+
projection: projectionOrOptions,
|
|
72
|
+
options
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (looksLikeFindOptions(projectionOrOptions)) {
|
|
76
|
+
const resolvedOptions = projectionOrOptions;
|
|
77
|
+
const projection = isPlainObject(resolvedOptions) && isPlainObject(resolvedOptions.projection)
|
|
78
|
+
? resolvedOptions.projection
|
|
79
|
+
: undefined;
|
|
80
|
+
return {
|
|
81
|
+
projection,
|
|
82
|
+
options: resolvedOptions
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
projection: projectionOrOptions,
|
|
87
|
+
options: undefined
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const normalizeInsertManyResult = (result) => {
|
|
91
|
+
if (!(result === null || result === void 0 ? void 0 : result.insertedIds) || Array.isArray(result.insertedIds))
|
|
92
|
+
return result;
|
|
93
|
+
return Object.assign(Object.assign({}, result), { insertedIds: Object.values(result.insertedIds) });
|
|
94
|
+
};
|
|
95
|
+
const hasAtomicOperators = (data) => Object.keys(data).some((key) => key.startsWith('$'));
|
|
96
|
+
const normalizeUpdatePayload = (data) => hasAtomicOperators(data) ? data : { $set: data };
|
|
97
|
+
const hasOperatorExpressions = (value) => isPlainObject(value) && Object.keys(value).some((key) => key.startsWith('$'));
|
|
98
|
+
const matchesPullCondition = (item, operand) => {
|
|
99
|
+
if (!isPlainObject(operand))
|
|
100
|
+
return (0, isEqual_1.default)(item, operand);
|
|
101
|
+
if (hasOperatorExpressions(operand)) {
|
|
102
|
+
if (Array.isArray(operand.$in)) {
|
|
103
|
+
return (operand.$in).some((candidate) => (0, isEqual_1.default)(candidate, item));
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return Object.entries(operand).every(([key, value]) => (0, isEqual_1.default)((0, get_1.default)(item, key), value));
|
|
108
|
+
};
|
|
109
|
+
const applyDocumentUpdateOperators = (baseDocument, update) => {
|
|
110
|
+
const updated = (0, cloneDeep_1.default)(baseDocument);
|
|
111
|
+
for (const [operator, payload] of Object.entries(update)) {
|
|
112
|
+
if (!isPlainObject(payload))
|
|
113
|
+
continue;
|
|
114
|
+
switch (operator) {
|
|
115
|
+
case '$set':
|
|
116
|
+
Object.entries(payload).forEach(([path, value]) => (0, set_1.default)(updated, path, value));
|
|
117
|
+
break;
|
|
118
|
+
case '$unset':
|
|
119
|
+
Object.keys(payload).forEach((path) => {
|
|
120
|
+
(0, unset_1.default)(updated, path);
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case '$inc':
|
|
124
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
125
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
126
|
+
const increment = typeof value === 'number' ? value : 0;
|
|
127
|
+
if (typeof currentValue === 'undefined') {
|
|
128
|
+
(0, set_1.default)(updated, path, increment);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (typeof currentValue !== 'number') {
|
|
132
|
+
throw new Error(`Cannot apply $inc to a non-numeric value at path "${path}"`);
|
|
133
|
+
}
|
|
134
|
+
(0, set_1.default)(updated, path, currentValue + increment);
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
case '$push':
|
|
138
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
139
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
140
|
+
const targetArray = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
141
|
+
if (isPlainObject(value) && Array.isArray(value.$each)) {
|
|
142
|
+
targetArray.push(...(value.$each));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
targetArray.push(value);
|
|
146
|
+
}
|
|
147
|
+
(0, set_1.default)(updated, path, targetArray);
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
case '$addToSet':
|
|
151
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
152
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
153
|
+
const targetArray = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
154
|
+
const valuesToAdd = isPlainObject(value) && Array.isArray(value.$each)
|
|
155
|
+
? value.$each
|
|
156
|
+
: [value];
|
|
157
|
+
valuesToAdd.forEach((entry) => {
|
|
158
|
+
if (!targetArray.some((existing) => (0, isEqual_1.default)(existing, entry))) {
|
|
159
|
+
targetArray.push(entry);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
(0, set_1.default)(updated, path, targetArray);
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
case '$pull':
|
|
166
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
167
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
168
|
+
if (!Array.isArray(currentValue))
|
|
169
|
+
return;
|
|
170
|
+
const filtered = currentValue.filter((entry) => !matchesPullCondition(entry, value));
|
|
171
|
+
(0, set_1.default)(updated, path, filtered);
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case '$pop':
|
|
175
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
176
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
177
|
+
if (!Array.isArray(currentValue) || !currentValue.length)
|
|
178
|
+
return;
|
|
179
|
+
const next = [...currentValue];
|
|
180
|
+
if (value === -1) {
|
|
181
|
+
next.shift();
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
next.pop();
|
|
185
|
+
}
|
|
186
|
+
(0, set_1.default)(updated, path, next);
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
case '$mul':
|
|
190
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
191
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
192
|
+
const factor = typeof value === 'number' ? value : 1;
|
|
193
|
+
if (typeof currentValue === 'undefined') {
|
|
194
|
+
(0, set_1.default)(updated, path, 0);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (typeof currentValue !== 'number') {
|
|
198
|
+
throw new Error(`Cannot apply $mul to a non-numeric value at path "${path}"`);
|
|
199
|
+
}
|
|
200
|
+
(0, set_1.default)(updated, path, currentValue * factor);
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
case '$min':
|
|
204
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
205
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
206
|
+
const comparableCurrent = currentValue;
|
|
207
|
+
const comparableValue = value;
|
|
208
|
+
if (typeof currentValue === 'undefined' || comparableCurrent > comparableValue) {
|
|
209
|
+
(0, set_1.default)(updated, path, value);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
case '$max':
|
|
214
|
+
Object.entries(payload).forEach(([path, value]) => {
|
|
215
|
+
const currentValue = (0, get_1.default)(updated, path);
|
|
216
|
+
const comparableCurrent = currentValue;
|
|
217
|
+
const comparableValue = value;
|
|
218
|
+
if (typeof currentValue === 'undefined' || comparableCurrent < comparableValue) {
|
|
219
|
+
(0, set_1.default)(updated, path, value);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
break;
|
|
223
|
+
case '$rename':
|
|
224
|
+
Object.entries(payload).forEach(([fromPath, toPath]) => {
|
|
225
|
+
if (typeof toPath !== 'string')
|
|
226
|
+
return;
|
|
227
|
+
const currentValue = (0, get_1.default)(updated, fromPath);
|
|
228
|
+
if (typeof currentValue === 'undefined')
|
|
229
|
+
return;
|
|
230
|
+
(0, set_1.default)(updated, toPath, currentValue);
|
|
231
|
+
(0, unset_1.default)(updated, fromPath);
|
|
232
|
+
});
|
|
233
|
+
break;
|
|
234
|
+
case '$currentDate':
|
|
235
|
+
Object.keys(payload).forEach((path) => (0, set_1.default)(updated, path, new Date()));
|
|
236
|
+
break;
|
|
237
|
+
case '$setOnInsert':
|
|
238
|
+
break;
|
|
239
|
+
default:
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return updated;
|
|
244
|
+
};
|
|
42
245
|
const getUpdatedPaths = (update) => {
|
|
43
246
|
const entries = Object.entries(update !== null && update !== void 0 ? update : {});
|
|
44
247
|
const hasOperators = entries.some(([key]) => key.startsWith('$'));
|
|
@@ -108,11 +311,12 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
108
311
|
* - Validates the result using `checkValidation` to ensure read permission.
|
|
109
312
|
* - If validation fails, returns an empty object; otherwise returns the validated document.
|
|
110
313
|
*/
|
|
111
|
-
findOne: (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (query = {},
|
|
314
|
+
findOne: (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (query = {}, projectionOrOptions, options) {
|
|
112
315
|
var _a;
|
|
113
316
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
317
|
+
const { projection, options: normalizedOptions } = resolveFindArgs(projectionOrOptions, options);
|
|
318
|
+
const resolvedOptions = projection || normalizedOptions
|
|
319
|
+
? Object.assign(Object.assign({}, (normalizedOptions !== null && normalizedOptions !== void 0 ? normalizedOptions : {})), (projection ? { projection } : {})) : undefined;
|
|
116
320
|
const resolvedQuery = query !== null && query !== void 0 ? query : {};
|
|
117
321
|
if (!run_as_system) {
|
|
118
322
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.READ);
|
|
@@ -303,6 +507,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
303
507
|
*/
|
|
304
508
|
updateOne: (query, data, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
305
509
|
try {
|
|
510
|
+
const normalizedData = normalizeUpdatePayload(data);
|
|
306
511
|
if (!run_as_system) {
|
|
307
512
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.UPDATE);
|
|
308
513
|
// Apply access control filters
|
|
@@ -313,28 +518,17 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
313
518
|
: formattedQuery;
|
|
314
519
|
const result = yield collection.findOne({ $and: safeQuery });
|
|
315
520
|
if (!result) {
|
|
521
|
+
if (options === null || options === void 0 ? void 0 : options.upsert) {
|
|
522
|
+
const upsertResult = yield collection.updateOne({ $and: safeQuery }, normalizedData, options);
|
|
523
|
+
emitMongoEvent('updateOne');
|
|
524
|
+
return upsertResult;
|
|
525
|
+
}
|
|
316
526
|
throw new Error('Update not permitted');
|
|
317
527
|
}
|
|
318
528
|
const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
|
|
319
529
|
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
// Flatten the update object to extract the actual fields being modified
|
|
323
|
-
// const docToCheck = hasOperators
|
|
324
|
-
// ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
|
|
325
|
-
// : data
|
|
326
|
-
const pipeline = [
|
|
327
|
-
{
|
|
328
|
-
$match: { $and: safeQuery }
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
$limit: 1
|
|
332
|
-
},
|
|
333
|
-
...Object.entries(data).map(([key, value]) => ({ [key]: value }))
|
|
334
|
-
];
|
|
335
|
-
const [docToCheck] = hasOperators
|
|
336
|
-
? yield collection.aggregate(pipeline).toArray()
|
|
337
|
-
: [data];
|
|
530
|
+
const updatedPaths = getUpdatedPaths(normalizedData);
|
|
531
|
+
const docToCheck = applyDocumentUpdateOperators(result, normalizedData);
|
|
338
532
|
// Validate update permissions
|
|
339
533
|
const { status, document } = winningRole
|
|
340
534
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
@@ -349,11 +543,11 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
349
543
|
if (!status || !areDocumentsEqual) {
|
|
350
544
|
throw new Error('Update not permitted');
|
|
351
545
|
}
|
|
352
|
-
const res = yield collection.updateOne({ $and: safeQuery },
|
|
546
|
+
const res = yield collection.updateOne({ $and: safeQuery }, normalizedData, options);
|
|
353
547
|
emitMongoEvent('updateOne');
|
|
354
548
|
return res;
|
|
355
549
|
}
|
|
356
|
-
const result = yield collection.updateOne(query,
|
|
550
|
+
const result = yield collection.updateOne(query, normalizedData, options);
|
|
357
551
|
emitMongoEvent('updateOne');
|
|
358
552
|
return result;
|
|
359
553
|
}
|
|
@@ -386,20 +580,19 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
386
580
|
throw new Error('Update not permitted');
|
|
387
581
|
}
|
|
388
582
|
const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
: [data];
|
|
583
|
+
const normalizedData = Array.isArray(data)
|
|
584
|
+
? data
|
|
585
|
+
: normalizeUpdatePayload(data);
|
|
586
|
+
const updatedPaths = Array.isArray(normalizedData)
|
|
587
|
+
? []
|
|
588
|
+
: getUpdatedPaths(normalizedData);
|
|
589
|
+
const [docToCheck] = Array.isArray(normalizedData)
|
|
590
|
+
? yield collection.aggregate([
|
|
591
|
+
{ $match: { $and: safeQuery } },
|
|
592
|
+
{ $limit: 1 },
|
|
593
|
+
...normalizedData
|
|
594
|
+
]).toArray()
|
|
595
|
+
: [applyDocumentUpdateOperators(result, normalizedData)];
|
|
403
596
|
const { status, document } = winningRole
|
|
404
597
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
405
598
|
type: 'write',
|
|
@@ -413,8 +606,8 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
413
606
|
throw new Error('Update not permitted');
|
|
414
607
|
}
|
|
415
608
|
const updateResult = options
|
|
416
|
-
? yield collection.findOneAndUpdate({ $and: safeQuery },
|
|
417
|
-
: yield collection.findOneAndUpdate({ $and: safeQuery },
|
|
609
|
+
? yield collection.findOneAndUpdate({ $and: safeQuery }, normalizedData, options)
|
|
610
|
+
: yield collection.findOneAndUpdate({ $and: safeQuery }, normalizedData);
|
|
418
611
|
if (!updateResult) {
|
|
419
612
|
emitMongoEvent('findOneAndUpdate');
|
|
420
613
|
return updateResult;
|
|
@@ -463,10 +656,11 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
463
656
|
*
|
|
464
657
|
* This ensures that both pre-query filtering and post-query validation are applied consistently.
|
|
465
658
|
*/
|
|
466
|
-
find: (query = {},
|
|
659
|
+
find: (query = {}, projectionOrOptions, options) => {
|
|
467
660
|
try {
|
|
468
|
-
const
|
|
469
|
-
|
|
661
|
+
const { projection, options: normalizedOptions } = resolveFindArgs(projectionOrOptions, options);
|
|
662
|
+
const resolvedOptions = projection || normalizedOptions
|
|
663
|
+
? Object.assign(Object.assign({}, (normalizedOptions !== null && normalizedOptions !== void 0 ? normalizedOptions : {})), (projection ? { projection } : {})) : undefined;
|
|
470
664
|
if (!run_as_system) {
|
|
471
665
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.READ);
|
|
472
666
|
// Pre-query filtering based on access control rules
|
|
@@ -722,12 +916,12 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
722
916
|
}
|
|
723
917
|
const result = yield collection.insertMany(documents, options);
|
|
724
918
|
emitMongoEvent('insertMany');
|
|
725
|
-
return result;
|
|
919
|
+
return normalizeInsertManyResult(result);
|
|
726
920
|
}
|
|
727
921
|
// If system mode is active, insert all documents without validation
|
|
728
922
|
const result = yield collection.insertMany(documents, options);
|
|
729
923
|
emitMongoEvent('insertMany');
|
|
730
|
-
return result;
|
|
924
|
+
return normalizeInsertManyResult(result);
|
|
731
925
|
}
|
|
732
926
|
catch (error) {
|
|
733
927
|
emitMongoEvent('insertMany', undefined, error);
|
|
@@ -736,6 +930,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
736
930
|
}),
|
|
737
931
|
updateMany: (query, data, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
738
932
|
try {
|
|
933
|
+
const normalizedData = normalizeUpdatePayload(data);
|
|
739
934
|
if (!run_as_system) {
|
|
740
935
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.UPDATE);
|
|
741
936
|
// Apply access control filters
|
|
@@ -747,21 +942,8 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
747
942
|
throw new Error('Update not permitted');
|
|
748
943
|
}
|
|
749
944
|
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
750
|
-
const
|
|
751
|
-
const
|
|
752
|
-
// Flatten the update object to extract the actual fields being modified
|
|
753
|
-
// const docToCheck = hasOperators
|
|
754
|
-
// ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
|
|
755
|
-
// : data
|
|
756
|
-
const pipeline = [
|
|
757
|
-
{
|
|
758
|
-
$match: { $and: formattedQuery }
|
|
759
|
-
},
|
|
760
|
-
...Object.entries(data).map(([key, value]) => ({ [key]: value }))
|
|
761
|
-
];
|
|
762
|
-
const docsToCheck = hasOperators
|
|
763
|
-
? yield collection.aggregate(pipeline).toArray()
|
|
764
|
-
: result;
|
|
945
|
+
const updatedPaths = getUpdatedPaths(normalizedData);
|
|
946
|
+
const docsToCheck = result.map((currentDoc) => applyDocumentUpdateOperators(currentDoc, normalizedData));
|
|
765
947
|
const filteredItems = yield Promise.all(docsToCheck.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
766
948
|
const winningRole = (0, utils_2.getWinningRole)(currentDoc, user, roles);
|
|
767
949
|
const { status, document } = winningRole
|
|
@@ -780,11 +962,11 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
780
962
|
console.log('check1 In updateMany --> (!areDocumentsEqual)');
|
|
781
963
|
throw new Error('Update not permitted');
|
|
782
964
|
}
|
|
783
|
-
const res = yield collection.updateMany({ $and: formattedQuery },
|
|
965
|
+
const res = yield collection.updateMany({ $and: formattedQuery }, normalizedData, options);
|
|
784
966
|
emitMongoEvent('updateMany');
|
|
785
967
|
return res;
|
|
786
968
|
}
|
|
787
|
-
const result = yield collection.updateMany(query,
|
|
969
|
+
const result = yield collection.updateMany(query, normalizedData, options);
|
|
788
970
|
emitMongoEvent('updateMany');
|
|
789
971
|
return result;
|
|
790
972
|
}
|
|
@@ -860,6 +1042,10 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
860
1042
|
};
|
|
861
1043
|
};
|
|
862
1044
|
const MongodbAtlas = (app, { rules, user, run_as_system, monitoring } = {}) => ({
|
|
1045
|
+
startSession: (options) => {
|
|
1046
|
+
const mongoClient = app.mongo.client;
|
|
1047
|
+
return mongoClient.startSession(options);
|
|
1048
|
+
},
|
|
863
1049
|
db: (dbName) => {
|
|
864
1050
|
return {
|
|
865
1051
|
collection: (collName) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import { Collection, Document, FindCursor, FindOneAndUpdateOptions, FindOneOptions, FindOptions, Filter as MongoFilter, UpdateFilter, WithId } from 'mongodb';
|
|
2
|
+
import { ClientSession, ClientSessionOptions, Collection, Document, FindCursor, FindOneAndUpdateOptions, FindOneOptions, FindOptions, Filter as MongoFilter, UpdateFilter, WithId } from 'mongodb';
|
|
3
3
|
import { User } from '../../auth/dtos';
|
|
4
4
|
import { Filter, Rules } from '../../features/rules/interface';
|
|
5
5
|
import { Role } from '../../utils/roles/interface';
|
|
@@ -14,6 +14,7 @@ export type MongodbAtlasFunction = (app: FastifyInstance, { rules, user, run_as_
|
|
|
14
14
|
db: (dbName: string) => {
|
|
15
15
|
collection: (collName: string) => ReturnType<GetOperatorsFunction>;
|
|
16
16
|
};
|
|
17
|
+
startSession: (options?: ClientSessionOptions) => ClientSession;
|
|
17
18
|
};
|
|
18
19
|
export type GetValidRuleParams<T extends Role | Filter> = {
|
|
19
20
|
filters: T[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EACL,UAAU,EACV,QAAQ,EACR,UAAU,EACV,uBAAuB,EACvB,cAAc,EACd,WAAW,EACX,MAAM,IAAI,WAAW,EACrB,YAAY,EACZ,MAAM,EACP,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAElD,MAAM,MAAM,oBAAoB,GAAG,CACjC,GAAG,EAAE,eAAe,EACpB,EACE,KAAK,EACL,IAAI,EACJ,aAAa,EACb,UAAU,EACX,EAAE;IACD,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACtC,KACE;IACH,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QACtB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC,oBAAoB,CAAC,CAAA;KACnE,CAAA;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ type JwtUtils = {
|
|
|
15
15
|
* @param currentFunction -> the function's name that should be called
|
|
16
16
|
* @param functionsList -> the list of all functions
|
|
17
17
|
*/
|
|
18
|
-
export declare const generateContextData: ({ user, services, app, rules, currentFunction, functionName, functionsList, GenerateContext, request }: GenerateContextDataParams) => {
|
|
18
|
+
export declare const generateContextData: ({ user, services, app, rules, currentFunction, functionName, functionsList, GenerateContext, GenerateContextSync, request }: GenerateContextDataParams) => {
|
|
19
19
|
BSON: typeof mongodb.BSON;
|
|
20
20
|
EJSON: {
|
|
21
21
|
parse: (text: string, options?: mongodb.BSON.EJSONOptions) => any;
|
|
@@ -57,6 +57,7 @@ export declare const generateContextData: ({ user, services, app, rules, current
|
|
|
57
57
|
db: (dbName: string) => {
|
|
58
58
|
collection: (collName: string) => ReturnType<import("../../services/mongodb-atlas/model").GetOperatorsFunction>;
|
|
59
59
|
};
|
|
60
|
+
startSession: (options?: mongodb.ClientSessionOptions) => mongodb.ClientSession;
|
|
60
61
|
} | {
|
|
61
62
|
get: <T = null>({ url, headers, resolveBody }: import("../../services/api/model").GetParams) => Promise<{
|
|
62
63
|
status: number;
|
|
@@ -204,6 +205,7 @@ export declare const generateContextData: ({ user, services, app, rules, current
|
|
|
204
205
|
db: (dbName: string) => {
|
|
205
206
|
collection: (collName: string) => ReturnType<import("../../services/mongodb-atlas/model").GetOperatorsFunction>;
|
|
206
207
|
};
|
|
208
|
+
startSession: (options?: mongodb.ClientSessionOptions) => mongodb.ClientSession;
|
|
207
209
|
} | {
|
|
208
210
|
get: <T = null>({ url, headers, resolveBody }: import("../../services/api/model").GetParams) => Promise<{
|
|
209
211
|
status: number;
|
|
@@ -350,6 +352,7 @@ export declare const generateContextData: ({ user, services, app, rules, current
|
|
|
350
352
|
db: (dbName: string) => {
|
|
351
353
|
collection: (collName: string) => ReturnType<import("../../services/mongodb-atlas/model").GetOperatorsFunction>;
|
|
352
354
|
};
|
|
355
|
+
startSession: (options?: mongodb.ClientSessionOptions) => mongodb.ClientSession;
|
|
353
356
|
} | {
|
|
354
357
|
get: <T = null>({ url, headers, resolveBody }: import("../../services/api/model").GetParams) => Promise<{
|
|
355
358
|
status: number;
|
|
@@ -493,7 +496,7 @@ export declare const generateContextData: ({ user, services, app, rules, current
|
|
|
493
496
|
};
|
|
494
497
|
} | undefined;
|
|
495
498
|
functions: {
|
|
496
|
-
execute: (name: keyof typeof functionsList, ...args: Arguments) =>
|
|
499
|
+
execute: (name: keyof typeof functionsList, ...args: Arguments) => unknown;
|
|
497
500
|
};
|
|
498
501
|
};
|
|
499
502
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAGvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAGvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,6HAWjC,yBAAyB;;;;;;;;;;;;;uBA4DP,SAAS;yBAGP,SAAS;;;;;;;;;;;;;;;;;;uBAcb,MAAM;;;;;;+BA5CU,MAAM,OAAO,QAAQ;;;;sCA3HrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsG+B,CAAC;iCAC5C,CAAD;;;;;;;;;;;;;;;;;;;kCAvGY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsG+B,CAAC;6BAC5C,CAAD;;;;;;;;;;;;;;;;;;kCAvGY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsG+B,CAAC;6BAC5C,CAAD;;;;;;;;;;;;;;;4BAyEiB,MAAM,OAAO,aAAa,WAAW,SAAS;;;CAgBrE,CAAA"}
|
|
@@ -108,7 +108,7 @@ const createJwtUtils = () => {
|
|
|
108
108
|
* @param currentFunction -> the function's name that should be called
|
|
109
109
|
* @param functionsList -> the list of all functions
|
|
110
110
|
*/
|
|
111
|
-
const generateContextData = ({ user, services, app, rules, currentFunction, functionName, functionsList, GenerateContext, request }) => {
|
|
111
|
+
const generateContextData = ({ user, services, app, rules, currentFunction, functionName, functionsList, GenerateContext, GenerateContextSync, request }) => {
|
|
112
112
|
var _a;
|
|
113
113
|
const BSON = mongodb_1.mongodb.BSON;
|
|
114
114
|
const Binary = BSON === null || BSON === void 0 ? void 0 : BSON.Binary;
|
|
@@ -177,7 +177,7 @@ const generateContextData = ({ user, services, app, rules, currentFunction, func
|
|
|
177
177
|
functions: {
|
|
178
178
|
execute: (name, ...args) => {
|
|
179
179
|
const currentFunction = functionsList[name];
|
|
180
|
-
return
|
|
180
|
+
return GenerateContextSync({
|
|
181
181
|
args,
|
|
182
182
|
app,
|
|
183
183
|
rules,
|
|
@@ -11,4 +11,5 @@ import { GenerateContextParams } from './interface';
|
|
|
11
11
|
* @param services -> the list of all services
|
|
12
12
|
*/
|
|
13
13
|
export declare function GenerateContext({ args, app, rules, user, currentFunction, functionsList, services, functionName, runAsSystem, deserializeArgs, enqueue, request }: GenerateContextParams): Promise<unknown>;
|
|
14
|
+
export declare function GenerateContextSync({ args, app, rules, user, currentFunction, functionsList, services, functionName, runAsSystem, deserializeArgs, request }: GenerateContextParams): unknown;
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4JnD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,EACpC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACP,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2G1C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CA4BjC"}
|