@flowerforce/flowerbase 1.7.5-beta.4 → 1.7.5-beta.6
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/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +0 -3
- package/dist/monitoring/routes/collections.d.ts.map +1 -1
- package/dist/monitoring/routes/collections.js +89 -0
- package/dist/monitoring/utils.d.ts +3 -1
- package/dist/monitoring/utils.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +0 -14
- package/package.json +1 -1
- package/src/features/functions/controller.ts +0 -3
- package/src/monitoring/routes/__tests__/collections.test.ts +163 -0
- package/src/monitoring/routes/collections.ts +99 -0
- package/src/monitoring/ui.collections.js +100 -6
- package/src/monitoring/ui.css +9 -3
- package/src/monitoring/ui.functions.js +2 -1
- package/src/monitoring/ui.html +16 -0
- package/src/monitoring/utils.ts +3 -1
- package/src/services/mongodb-atlas/index.ts +108 -123
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAmHhD,eAAO,MAAM,iCAAiC,GAAI,OAAO,OAAO,KAAG,OA0BlE,CAAA;AAID,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,OAgD9D,CAAA;AAqHD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAmHhD,eAAO,MAAM,iCAAiC,GAAI,OAAO,OAAO,KAAG,OA0BlE,CAAA;AAID,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,OAgD9D,CAAA;AAqHD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,kBAoRjC,CAAA"}
|
|
@@ -359,7 +359,6 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
359
359
|
const config = bson_1.EJSON.deserialize(decodedConfig);
|
|
360
360
|
const [_a] = config.arguments, { database, collection } = _a, watchArgsInput = __rest(_a, ["database", "collection"]);
|
|
361
361
|
const watchArgs = isRecord(watchArgsInput) ? watchArgsInput : {};
|
|
362
|
-
console.log("🚀 ~ functionsController ~ watchArgs:", watchArgs);
|
|
363
362
|
const headers = {
|
|
364
363
|
'Content-Type': 'text/event-stream',
|
|
365
364
|
'Cache-Control': 'no-cache',
|
|
@@ -420,7 +419,6 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
420
419
|
}
|
|
421
420
|
});
|
|
422
421
|
const onHubChange = (change) => __awaiter(void 0, void 0, void 0, function* () {
|
|
423
|
-
console.log("🚀 ~ onHubChange ~ change:", change);
|
|
424
422
|
const subscribers = Array.from(currentHub.subscribers.values());
|
|
425
423
|
yield Promise.all(subscribers.map((subscriber) => __awaiter(void 0, void 0, void 0, function* () {
|
|
426
424
|
var _a, _b, _c;
|
|
@@ -444,7 +442,6 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
444
442
|
.db(currentHub.database)
|
|
445
443
|
.collection(currentHub.collection)
|
|
446
444
|
.findOne(readQuery);
|
|
447
|
-
console.log("🚀 ~ onHubChange ~ readableDoc:", readableDoc);
|
|
448
445
|
if (!isReadableDocumentResult(readableDoc))
|
|
449
446
|
return;
|
|
450
447
|
subscriberRes.write(`data: ${serializeEjson(change)}\n\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../../src/monitoring/routes/collections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAK9C,OAAO,EAGL,qBAAqB,EAMtB,MAAM,UAAU,CAAA;AAEjB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,qBAAqB,EAAE,CAAA;IAC1C,oBAAoB,EAAE,MAAM,CAAA;IAC5B,oBAAoB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAC7D,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,KAAK,eAAe,EAAE,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../../src/monitoring/routes/collections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAK9C,OAAO,EAGL,qBAAqB,EAMtB,MAAM,UAAU,CAAA;AAEjB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,qBAAqB,EAAE,CAAA;IAC1C,oBAAoB,EAAE,MAAM,CAAA;IAC5B,oBAAoB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAC7D,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,KAAK,eAAe,EAAE,MAAM,oBAAoB,SAqSxF,CAAA"}
|
|
@@ -193,5 +193,94 @@ const registerCollectionRoutes = (app, deps) => {
|
|
|
193
193
|
return { error: details.message, stack: details.stack };
|
|
194
194
|
}
|
|
195
195
|
}));
|
|
196
|
+
app.post(`${prefix}/api/collections/insert`, (req, reply) => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
+
const body = req.body;
|
|
198
|
+
const collection = body === null || body === void 0 ? void 0 : body.collection;
|
|
199
|
+
if (!collection) {
|
|
200
|
+
reply.code(400);
|
|
201
|
+
return { error: 'Missing collection name' };
|
|
202
|
+
}
|
|
203
|
+
const mode = (body === null || body === void 0 ? void 0 : body.mode) === 'insertMany' ? 'insertMany' : 'insertOne';
|
|
204
|
+
if (mode === 'insertOne') {
|
|
205
|
+
if (!(0, utils_1.isPlainObject)(body === null || body === void 0 ? void 0 : body.document)) {
|
|
206
|
+
reply.code(400);
|
|
207
|
+
return { error: 'Document must be an object' };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
if (!Array.isArray(body === null || body === void 0 ? void 0 : body.documents) || !body.documents.length) {
|
|
212
|
+
reply.code(400);
|
|
213
|
+
return { error: 'Documents must be a non-empty array' };
|
|
214
|
+
}
|
|
215
|
+
const invalid = body.documents.some((entry) => !(0, utils_1.isPlainObject)(entry));
|
|
216
|
+
if (invalid) {
|
|
217
|
+
reply.code(400);
|
|
218
|
+
return { error: 'Every document must be an object' };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const rules = state_1.StateManager.select('rules');
|
|
222
|
+
const services = state_1.StateManager.select('services');
|
|
223
|
+
const resolvedUser = yield (0, utils_1.resolveUserContext)(app, body === null || body === void 0 ? void 0 : body.userId);
|
|
224
|
+
const runAsSystem = (body === null || body === void 0 ? void 0 : body.runAsSystem) !== false;
|
|
225
|
+
const recordHistory = (body === null || body === void 0 ? void 0 : body.recordHistory) !== false;
|
|
226
|
+
try {
|
|
227
|
+
const mongoService = services['mongodb-atlas'](app, {
|
|
228
|
+
rules,
|
|
229
|
+
user: resolvedUser !== null && resolvedUser !== void 0 ? resolvedUser : {},
|
|
230
|
+
run_as_system: runAsSystem
|
|
231
|
+
});
|
|
232
|
+
const collectionService = mongoService.db(constants_1.DB_NAME).collection(collection);
|
|
233
|
+
if (mode === 'insertMany') {
|
|
234
|
+
const documents = body.documents;
|
|
235
|
+
const result = yield collectionService.insertMany(documents);
|
|
236
|
+
const insertedIds = Array.isArray(result === null || result === void 0 ? void 0 : result.insertedIds)
|
|
237
|
+
? result.insertedIds
|
|
238
|
+
: Object.values((result === null || result === void 0 ? void 0 : result.insertedIds) || {});
|
|
239
|
+
if (recordHistory) {
|
|
240
|
+
addCollectionHistory({
|
|
241
|
+
ts: Date.now(),
|
|
242
|
+
collection,
|
|
243
|
+
mode: 'insertMany',
|
|
244
|
+
documents: (0, utils_1.sanitize)(documents),
|
|
245
|
+
runAsSystem,
|
|
246
|
+
user: (0, utils_1.getUserInfo)(resolvedUser),
|
|
247
|
+
page: 1
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
mode: 'insertMany',
|
|
252
|
+
acknowledged: !!(result === null || result === void 0 ? void 0 : result.acknowledged),
|
|
253
|
+
insertedCount: insertedIds.length,
|
|
254
|
+
insertedIds: (0, utils_1.sanitize)(insertedIds),
|
|
255
|
+
count: insertedIds.length
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const document = body.document;
|
|
259
|
+
const result = yield collectionService.insertOne(document);
|
|
260
|
+
const insertedId = result === null || result === void 0 ? void 0 : result.insertedId;
|
|
261
|
+
if (recordHistory) {
|
|
262
|
+
addCollectionHistory({
|
|
263
|
+
ts: Date.now(),
|
|
264
|
+
collection,
|
|
265
|
+
mode: 'insertOne',
|
|
266
|
+
document: (0, utils_1.sanitize)(document),
|
|
267
|
+
runAsSystem,
|
|
268
|
+
user: (0, utils_1.getUserInfo)(resolvedUser),
|
|
269
|
+
page: 1
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
mode: 'insertOne',
|
|
274
|
+
acknowledged: !!(result === null || result === void 0 ? void 0 : result.acknowledged),
|
|
275
|
+
insertedId: (0, utils_1.sanitize)(insertedId),
|
|
276
|
+
count: insertedId ? 1 : 0
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
const details = (0, utils_1.getErrorDetails)(error);
|
|
281
|
+
reply.code(500);
|
|
282
|
+
return { error: details.message, stack: details.stack };
|
|
283
|
+
}
|
|
284
|
+
}));
|
|
196
285
|
};
|
|
197
286
|
exports.registerCollectionRoutes = registerCollectionRoutes;
|
|
@@ -23,9 +23,11 @@ export type FunctionHistoryItem = {
|
|
|
23
23
|
export type CollectionHistoryItem = {
|
|
24
24
|
ts: number;
|
|
25
25
|
collection: string;
|
|
26
|
-
mode: 'query' | 'aggregate';
|
|
26
|
+
mode: 'query' | 'aggregate' | 'insertOne' | 'insertMany';
|
|
27
27
|
query?: unknown;
|
|
28
28
|
pipeline?: unknown;
|
|
29
|
+
document?: unknown;
|
|
30
|
+
documents?: unknown;
|
|
29
31
|
sort?: Record<string, unknown>;
|
|
30
32
|
runAsSystem: boolean;
|
|
31
33
|
user?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/monitoring/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AAIxD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,OAAO,GAAG,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/monitoring/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAGL,oBAAoB,EAErB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAGL,oBAAoB,EAErB,MAAM,SAAS,CAAA;AAm0ChB,QAAA,MAAM,YAAY,EAAE,oBA6BlB,CAAA;AAEF,eAAe,YAAY,CAAA"}
|
|
@@ -908,20 +908,8 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
908
908
|
// Override the .on() method to apply validation before emitting events
|
|
909
909
|
result.on = (eventType, listener) => {
|
|
910
910
|
return originalOn(eventType, (change) => __awaiter(void 0, void 0, void 0, function* () {
|
|
911
|
-
var _a, _b, _c, _d;
|
|
912
911
|
const { document, updatedFieldsStatus, updatedFields, hasFullDocument, hasWinningRole } = yield isValidChange(change);
|
|
913
912
|
const filteredChange = Object.assign(Object.assign({}, change), { fullDocument: document, updateDescription: Object.assign(Object.assign({}, change.updateDescription), { updatedFields: updatedFieldsStatus ? updatedFields : {} }) });
|
|
914
|
-
console.log('[flowerbase watch] delivered change', {
|
|
915
|
-
collection: collName,
|
|
916
|
-
operationType: change === null || change === void 0 ? void 0 : change.operationType,
|
|
917
|
-
eventType,
|
|
918
|
-
hasFullDocument,
|
|
919
|
-
hasWinningRole,
|
|
920
|
-
updatedFieldsStatus,
|
|
921
|
-
documentKey: ((_c = (_b = (_a = change === null || change === void 0 ? void 0 : change.documentKey) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString) === null || _c === void 0 ? void 0 : _c.call(_b)) ||
|
|
922
|
-
((_d = change === null || change === void 0 ? void 0 : change.documentKey) === null || _d === void 0 ? void 0 : _d._id) ||
|
|
923
|
-
null
|
|
924
|
-
});
|
|
925
913
|
listener(filteredChange);
|
|
926
914
|
}));
|
|
927
915
|
};
|
|
@@ -1041,7 +1029,6 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
1041
1029
|
// Retrieve the document to check permissions before updating
|
|
1042
1030
|
const result = yield collection.find({ $and: formattedQuery }).toArray();
|
|
1043
1031
|
if (!result) {
|
|
1044
|
-
console.log('check1 In updateMany --> (!result)');
|
|
1045
1032
|
throw new Error('Update not permitted');
|
|
1046
1033
|
}
|
|
1047
1034
|
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
@@ -1062,7 +1049,6 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
1062
1049
|
// Ensure no unauthorized changes are made
|
|
1063
1050
|
const areDocumentsEqual = docsToCheck.every((doc, index) => areUpdatedFieldsAllowed(filteredItems[index], doc, updatedPaths));
|
|
1064
1051
|
if (!areDocumentsEqual) {
|
|
1065
|
-
console.log('check1 In updateMany --> (!areDocumentsEqual)');
|
|
1066
1052
|
throw new Error('Update not permitted');
|
|
1067
1053
|
}
|
|
1068
1054
|
const res = yield collection.updateMany({ $and: formattedQuery }, normalizedData, options);
|
package/package.json
CHANGED
|
@@ -440,7 +440,6 @@ export const functionsController: FunctionController = async (
|
|
|
440
440
|
|
|
441
441
|
const [{ database, collection, ...watchArgsInput }] = config.arguments
|
|
442
442
|
const watchArgs = isRecord(watchArgsInput) ? watchArgsInput : {}
|
|
443
|
-
console.log("🚀 ~ functionsController ~ watchArgs:", watchArgs)
|
|
444
443
|
|
|
445
444
|
const headers = {
|
|
446
445
|
'Content-Type': 'text/event-stream',
|
|
@@ -511,7 +510,6 @@ export const functionsController: FunctionController = async (
|
|
|
511
510
|
}
|
|
512
511
|
|
|
513
512
|
const onHubChange = async (change: Document) => {
|
|
514
|
-
console.log("🚀 ~ onHubChange ~ change:", change)
|
|
515
513
|
const subscribers = Array.from(currentHub.subscribers.values())
|
|
516
514
|
await Promise.all(subscribers.map(async (subscriber) => {
|
|
517
515
|
const subscriberRes = subscriber.response
|
|
@@ -538,7 +536,6 @@ export const functionsController: FunctionController = async (
|
|
|
538
536
|
.db(currentHub.database)
|
|
539
537
|
.collection(currentHub.collection)
|
|
540
538
|
.findOne(readQuery)
|
|
541
|
-
console.log("🚀 ~ onHubChange ~ readableDoc:", readableDoc)
|
|
542
539
|
|
|
543
540
|
if (!isReadableDocumentResult(readableDoc)) return
|
|
544
541
|
subscriberRes.write(`data: ${serializeEjson(change)}\n\n`)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import Fastify, { FastifyInstance } from 'fastify'
|
|
2
|
+
import { registerCollectionRoutes } from '../collections'
|
|
3
|
+
import { StateManager } from '../../../state'
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../state', () => ({
|
|
6
|
+
StateManager: {
|
|
7
|
+
select: jest.fn()
|
|
8
|
+
}
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
describe('monitoring collections routes', () => {
|
|
12
|
+
let app: FastifyInstance
|
|
13
|
+
let addCollectionHistory: jest.Mock
|
|
14
|
+
let selectMock: jest.Mock
|
|
15
|
+
let insertOne: jest.Mock
|
|
16
|
+
let insertMany: jest.Mock
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
app = Fastify()
|
|
20
|
+
addCollectionHistory = jest.fn()
|
|
21
|
+
selectMock = StateManager.select as unknown as jest.Mock
|
|
22
|
+
insertOne = jest.fn()
|
|
23
|
+
insertMany = jest.fn()
|
|
24
|
+
|
|
25
|
+
const services = {
|
|
26
|
+
'mongodb-atlas': jest.fn(() => ({
|
|
27
|
+
db: jest.fn(() => ({
|
|
28
|
+
collection: jest.fn(() => ({
|
|
29
|
+
insertOne,
|
|
30
|
+
insertMany
|
|
31
|
+
}))
|
|
32
|
+
}))
|
|
33
|
+
}))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
selectMock.mockImplementation((key: string) => {
|
|
37
|
+
if (key === 'rules') return {}
|
|
38
|
+
if (key === 'services') return services
|
|
39
|
+
return {}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
registerCollectionRoutes(app, {
|
|
43
|
+
prefix: '/monit',
|
|
44
|
+
collectionHistory: [],
|
|
45
|
+
maxCollectionHistory: 20,
|
|
46
|
+
addCollectionHistory
|
|
47
|
+
})
|
|
48
|
+
await app.ready()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
await app.close()
|
|
53
|
+
jest.clearAllMocks()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('POST /collections/insert should insert one document', async () => {
|
|
57
|
+
insertOne.mockResolvedValue({
|
|
58
|
+
acknowledged: true,
|
|
59
|
+
insertedId: 'id-1'
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const response = await app.inject({
|
|
63
|
+
method: 'POST',
|
|
64
|
+
url: '/monit/api/collections/insert',
|
|
65
|
+
payload: {
|
|
66
|
+
collection: 'todos',
|
|
67
|
+
mode: 'insertOne',
|
|
68
|
+
document: { title: 'Task 1' },
|
|
69
|
+
runAsSystem: true
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(response.statusCode).toBe(200)
|
|
74
|
+
expect(JSON.parse(response.body)).toEqual({
|
|
75
|
+
mode: 'insertOne',
|
|
76
|
+
acknowledged: true,
|
|
77
|
+
insertedId: 'id-1',
|
|
78
|
+
count: 1
|
|
79
|
+
})
|
|
80
|
+
expect(insertOne).toHaveBeenCalledWith({ title: 'Task 1' })
|
|
81
|
+
expect(addCollectionHistory).toHaveBeenCalledWith(expect.objectContaining({
|
|
82
|
+
collection: 'todos',
|
|
83
|
+
mode: 'insertOne',
|
|
84
|
+
document: { title: 'Task 1' },
|
|
85
|
+
runAsSystem: true,
|
|
86
|
+
page: 1
|
|
87
|
+
}))
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('POST /collections/insert should insert many documents', async () => {
|
|
91
|
+
insertMany.mockResolvedValue({
|
|
92
|
+
acknowledged: true,
|
|
93
|
+
insertedIds: {
|
|
94
|
+
0: 'id-1',
|
|
95
|
+
1: 'id-2'
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const response = await app.inject({
|
|
100
|
+
method: 'POST',
|
|
101
|
+
url: '/monit/api/collections/insert',
|
|
102
|
+
payload: {
|
|
103
|
+
collection: 'todos',
|
|
104
|
+
mode: 'insertMany',
|
|
105
|
+
documents: [{ title: 'Task 1' }, { title: 'Task 2' }],
|
|
106
|
+
runAsSystem: false
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(response.statusCode).toBe(200)
|
|
111
|
+
expect(JSON.parse(response.body)).toEqual({
|
|
112
|
+
mode: 'insertMany',
|
|
113
|
+
acknowledged: true,
|
|
114
|
+
insertedCount: 2,
|
|
115
|
+
insertedIds: ['id-1', 'id-2'],
|
|
116
|
+
count: 2
|
|
117
|
+
})
|
|
118
|
+
expect(insertMany).toHaveBeenCalledWith([{ title: 'Task 1' }, { title: 'Task 2' }])
|
|
119
|
+
expect(addCollectionHistory).toHaveBeenCalledWith(expect.objectContaining({
|
|
120
|
+
collection: 'todos',
|
|
121
|
+
mode: 'insertMany',
|
|
122
|
+
documents: [{ title: 'Task 1' }, { title: 'Task 2' }],
|
|
123
|
+
runAsSystem: false,
|
|
124
|
+
page: 1
|
|
125
|
+
}))
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('POST /collections/insert should validate insertOne payload', async () => {
|
|
129
|
+
const response = await app.inject({
|
|
130
|
+
method: 'POST',
|
|
131
|
+
url: '/monit/api/collections/insert',
|
|
132
|
+
payload: {
|
|
133
|
+
collection: 'todos',
|
|
134
|
+
mode: 'insertOne',
|
|
135
|
+
document: ['invalid']
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
expect(response.statusCode).toBe(400)
|
|
140
|
+
expect(JSON.parse(response.body)).toEqual({
|
|
141
|
+
error: 'Document must be an object'
|
|
142
|
+
})
|
|
143
|
+
expect(insertOne).not.toHaveBeenCalled()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('POST /collections/insert should validate insertMany payload', async () => {
|
|
147
|
+
const response = await app.inject({
|
|
148
|
+
method: 'POST',
|
|
149
|
+
url: '/monit/api/collections/insert',
|
|
150
|
+
payload: {
|
|
151
|
+
collection: 'todos',
|
|
152
|
+
mode: 'insertMany',
|
|
153
|
+
documents: [{ title: 'Task 1' }, 'invalid']
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
expect(response.statusCode).toBe(400)
|
|
158
|
+
expect(JSON.parse(response.body)).toEqual({
|
|
159
|
+
error: 'Every document must be an object'
|
|
160
|
+
})
|
|
161
|
+
expect(insertMany).not.toHaveBeenCalled()
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -215,4 +215,103 @@ export const registerCollectionRoutes = (app: FastifyInstance, deps: CollectionR
|
|
|
215
215
|
return { error: details.message, stack: details.stack }
|
|
216
216
|
}
|
|
217
217
|
})
|
|
218
|
+
|
|
219
|
+
app.post(`${prefix}/api/collections/insert`, async (req, reply) => {
|
|
220
|
+
const body = req.body as {
|
|
221
|
+
collection?: string
|
|
222
|
+
mode?: 'insertOne' | 'insertMany'
|
|
223
|
+
document?: unknown
|
|
224
|
+
documents?: unknown
|
|
225
|
+
recordHistory?: boolean
|
|
226
|
+
runAsSystem?: boolean
|
|
227
|
+
userId?: string
|
|
228
|
+
}
|
|
229
|
+
const collection = body?.collection
|
|
230
|
+
if (!collection) {
|
|
231
|
+
reply.code(400)
|
|
232
|
+
return { error: 'Missing collection name' }
|
|
233
|
+
}
|
|
234
|
+
const mode = body?.mode === 'insertMany' ? 'insertMany' : 'insertOne'
|
|
235
|
+
if (mode === 'insertOne') {
|
|
236
|
+
if (!isPlainObject(body?.document)) {
|
|
237
|
+
reply.code(400)
|
|
238
|
+
return { error: 'Document must be an object' }
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
if (!Array.isArray(body?.documents) || !body.documents.length) {
|
|
242
|
+
reply.code(400)
|
|
243
|
+
return { error: 'Documents must be a non-empty array' }
|
|
244
|
+
}
|
|
245
|
+
const invalid = body.documents.some((entry) => !isPlainObject(entry))
|
|
246
|
+
if (invalid) {
|
|
247
|
+
reply.code(400)
|
|
248
|
+
return { error: 'Every document must be an object' }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const rules = StateManager.select('rules') as Rules
|
|
253
|
+
const services = StateManager.select('services') as typeof coreServices
|
|
254
|
+
const resolvedUser = await resolveUserContext(app, body?.userId)
|
|
255
|
+
const runAsSystem = body?.runAsSystem !== false
|
|
256
|
+
const recordHistory = body?.recordHistory !== false
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const mongoService = services['mongodb-atlas'](app, {
|
|
260
|
+
rules,
|
|
261
|
+
user: resolvedUser ?? {},
|
|
262
|
+
run_as_system: runAsSystem
|
|
263
|
+
})
|
|
264
|
+
const collectionService = mongoService.db(DB_NAME).collection(collection)
|
|
265
|
+
if (mode === 'insertMany') {
|
|
266
|
+
const documents = body.documents as Record<string, unknown>[]
|
|
267
|
+
const result = await collectionService.insertMany(documents)
|
|
268
|
+
const insertedIds = Array.isArray(result?.insertedIds)
|
|
269
|
+
? result.insertedIds
|
|
270
|
+
: Object.values(result?.insertedIds || {})
|
|
271
|
+
if (recordHistory) {
|
|
272
|
+
addCollectionHistory({
|
|
273
|
+
ts: Date.now(),
|
|
274
|
+
collection,
|
|
275
|
+
mode: 'insertMany',
|
|
276
|
+
documents: sanitize(documents),
|
|
277
|
+
runAsSystem,
|
|
278
|
+
user: getUserInfo(resolvedUser as Record<string, unknown> | undefined),
|
|
279
|
+
page: 1
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
mode: 'insertMany',
|
|
284
|
+
acknowledged: !!result?.acknowledged,
|
|
285
|
+
insertedCount: insertedIds.length,
|
|
286
|
+
insertedIds: sanitize(insertedIds),
|
|
287
|
+
count: insertedIds.length
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const document = body.document as Record<string, unknown>
|
|
292
|
+
const result = await collectionService.insertOne(document)
|
|
293
|
+
const insertedId = result?.insertedId
|
|
294
|
+
if (recordHistory) {
|
|
295
|
+
addCollectionHistory({
|
|
296
|
+
ts: Date.now(),
|
|
297
|
+
collection,
|
|
298
|
+
mode: 'insertOne',
|
|
299
|
+
document: sanitize(document),
|
|
300
|
+
runAsSystem,
|
|
301
|
+
user: getUserInfo(resolvedUser as Record<string, unknown> | undefined),
|
|
302
|
+
page: 1
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
mode: 'insertOne',
|
|
307
|
+
acknowledged: !!result?.acknowledged,
|
|
308
|
+
insertedId: sanitize(insertedId),
|
|
309
|
+
count: insertedId ? 1 : 0
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const details = getErrorDetails(error)
|
|
313
|
+
reply.code(500)
|
|
314
|
+
return { error: details.message, stack: details.stack }
|
|
315
|
+
}
|
|
316
|
+
})
|
|
218
317
|
}
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
dom.collectionQueryHighlight = document.getElementById('collectionQueryHighlight');
|
|
43
43
|
dom.collectionAggregate = document.getElementById('collectionAggregate');
|
|
44
44
|
dom.collectionAggregateHighlight = document.getElementById('collectionAggregateHighlight');
|
|
45
|
+
dom.collectionInsertOne = document.getElementById('collectionInsertOne');
|
|
46
|
+
dom.collectionInsertOneHighlight = document.getElementById('collectionInsertOneHighlight');
|
|
47
|
+
dom.collectionInsertMany = document.getElementById('collectionInsertMany');
|
|
48
|
+
dom.collectionInsertManyHighlight = document.getElementById('collectionInsertManyHighlight');
|
|
45
49
|
dom.runCollectionQuery = document.getElementById('runCollectionQuery');
|
|
46
50
|
dom.collectionResult = document.getElementById('collectionResult');
|
|
47
51
|
dom.collectionPrev = document.getElementById('collectionPrev');
|
|
@@ -71,6 +75,10 @@
|
|
|
71
75
|
collectionQueryHighlight,
|
|
72
76
|
collectionAggregate,
|
|
73
77
|
collectionAggregateHighlight,
|
|
78
|
+
collectionInsertOne,
|
|
79
|
+
collectionInsertOneHighlight,
|
|
80
|
+
collectionInsertMany,
|
|
81
|
+
collectionInsertManyHighlight,
|
|
74
82
|
runCollectionQuery,
|
|
75
83
|
collectionResult,
|
|
76
84
|
collectionPrev,
|
|
@@ -237,6 +245,22 @@
|
|
|
237
245
|
collectionAggregateHighlight.scrollLeft = collectionAggregate.scrollLeft;
|
|
238
246
|
};
|
|
239
247
|
|
|
248
|
+
const updateCollectionInsertOneHighlight = () => {
|
|
249
|
+
if (!collectionInsertOne || !collectionInsertOneHighlight) return;
|
|
250
|
+
const text = collectionInsertOne.value || '';
|
|
251
|
+
collectionInsertOneHighlight.innerHTML = highlightJson(text || '');
|
|
252
|
+
collectionInsertOneHighlight.scrollTop = collectionInsertOne.scrollTop;
|
|
253
|
+
collectionInsertOneHighlight.scrollLeft = collectionInsertOne.scrollLeft;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const updateCollectionInsertManyHighlight = () => {
|
|
257
|
+
if (!collectionInsertMany || !collectionInsertManyHighlight) return;
|
|
258
|
+
const text = collectionInsertMany.value || '';
|
|
259
|
+
collectionInsertManyHighlight.innerHTML = highlightJson(text || '');
|
|
260
|
+
collectionInsertManyHighlight.scrollTop = collectionInsertMany.scrollTop;
|
|
261
|
+
collectionInsertManyHighlight.scrollLeft = collectionInsertMany.scrollLeft;
|
|
262
|
+
};
|
|
263
|
+
|
|
240
264
|
const formatCellValue = (value) => {
|
|
241
265
|
if (value === null || value === undefined) return '';
|
|
242
266
|
if (typeof value === 'string') {
|
|
@@ -483,6 +507,14 @@
|
|
|
483
507
|
collectionAggregate.value = entry.pipeline ? JSON.stringify(entry.pipeline, null, 2) : '';
|
|
484
508
|
updateCollectionAggregateHighlight();
|
|
485
509
|
}
|
|
510
|
+
if (collectionInsertOne) {
|
|
511
|
+
collectionInsertOne.value = entry.document ? JSON.stringify(entry.document, null, 2) : '';
|
|
512
|
+
updateCollectionInsertOneHighlight();
|
|
513
|
+
}
|
|
514
|
+
if (collectionInsertMany) {
|
|
515
|
+
collectionInsertMany.value = entry.documents ? JSON.stringify(entry.documents, null, 2) : '';
|
|
516
|
+
updateCollectionInsertManyHighlight();
|
|
517
|
+
}
|
|
486
518
|
if (entry.user && entry.user.id) {
|
|
487
519
|
if (collectionUserInput) collectionUserInput.value = entry.user.id;
|
|
488
520
|
setSelectedCollectionUser({
|
|
@@ -549,14 +581,16 @@
|
|
|
549
581
|
setCollectionResult('Select a collection first', false);
|
|
550
582
|
return;
|
|
551
583
|
}
|
|
552
|
-
const
|
|
584
|
+
const mode = collectionMode ? collectionMode.value : 'query';
|
|
585
|
+
const supportsPaging = mode === 'query' || mode === 'aggregate';
|
|
586
|
+
const keepPage = supportsPaging && options && options.keepPage;
|
|
553
587
|
const recordHistory = !keepPage;
|
|
554
588
|
if (!keepPage) {
|
|
555
589
|
state.collectionPage = 1;
|
|
556
590
|
}
|
|
557
|
-
const shouldRefreshTotals = !keepPage || !state.collectionTotal;
|
|
591
|
+
const shouldRefreshTotals = supportsPaging && (!keepPage || !state.collectionTotal);
|
|
558
592
|
state.collectionHasMore = false;
|
|
559
|
-
if (shouldRefreshTotals) {
|
|
593
|
+
if (shouldRefreshTotals || !supportsPaging) {
|
|
560
594
|
state.collectionTotal = 0;
|
|
561
595
|
}
|
|
562
596
|
state.collectionLoading = true;
|
|
@@ -568,11 +602,10 @@
|
|
|
568
602
|
const userId = selectedUser
|
|
569
603
|
? String(selectedUser.id || (selectedUser.auth && selectedUser.auth._id) || '')
|
|
570
604
|
: fallbackUserId;
|
|
571
|
-
const mode = collectionMode ? collectionMode.value : 'query';
|
|
572
605
|
try {
|
|
573
|
-
const sortRaw = collectionSort ? collectionSort.value.trim() : '';
|
|
574
|
-
const sort = parseJsonObject(sortRaw, 'Sort');
|
|
575
606
|
if (mode === 'aggregate') {
|
|
607
|
+
const sortRaw = collectionSort ? collectionSort.value.trim() : '';
|
|
608
|
+
const sort = parseJsonObject(sortRaw, 'Sort');
|
|
576
609
|
const raw = collectionAggregate ? collectionAggregate.value.trim() : '';
|
|
577
610
|
const pipeline = raw ? JSON.parse(raw) : [];
|
|
578
611
|
if (!Array.isArray(pipeline)) {
|
|
@@ -607,7 +640,58 @@
|
|
|
607
640
|
updateCollectionPager();
|
|
608
641
|
setCollectionTab('query');
|
|
609
642
|
setCollectionResult(data, true);
|
|
643
|
+
} else if (mode === 'insertOne') {
|
|
644
|
+
const raw = collectionInsertOne ? collectionInsertOne.value.trim() : '';
|
|
645
|
+
const document = parseJsonObject(raw, 'Document');
|
|
646
|
+
if (!document) {
|
|
647
|
+
throw new Error('Document is required for insert one');
|
|
648
|
+
}
|
|
649
|
+
const data = await api('/collections/insert', {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
body: JSON.stringify({
|
|
652
|
+
collection: name,
|
|
653
|
+
mode,
|
|
654
|
+
document,
|
|
655
|
+
runAsSystem,
|
|
656
|
+
userId: userId || undefined,
|
|
657
|
+
recordHistory
|
|
658
|
+
})
|
|
659
|
+
});
|
|
660
|
+
state.collectionHasMore = false;
|
|
661
|
+
state.collectionPage = 1;
|
|
662
|
+
state.collectionTotal = typeof data.count === 'number' ? data.count : 1;
|
|
663
|
+
updateCollectionPager();
|
|
664
|
+
setCollectionTab('query');
|
|
665
|
+
setCollectionResult(data, true);
|
|
666
|
+
} else if (mode === 'insertMany') {
|
|
667
|
+
const raw = collectionInsertMany ? collectionInsertMany.value.trim() : '';
|
|
668
|
+
const documents = raw ? JSON.parse(raw) : [];
|
|
669
|
+
if (!Array.isArray(documents)) {
|
|
670
|
+
throw new Error('Documents must be a JSON array for insert many');
|
|
671
|
+
}
|
|
672
|
+
if (!documents.length) {
|
|
673
|
+
throw new Error('Documents are required for insert many');
|
|
674
|
+
}
|
|
675
|
+
const data = await api('/collections/insert', {
|
|
676
|
+
method: 'POST',
|
|
677
|
+
body: JSON.stringify({
|
|
678
|
+
collection: name,
|
|
679
|
+
mode,
|
|
680
|
+
documents,
|
|
681
|
+
runAsSystem,
|
|
682
|
+
userId: userId || undefined,
|
|
683
|
+
recordHistory
|
|
684
|
+
})
|
|
685
|
+
});
|
|
686
|
+
state.collectionHasMore = false;
|
|
687
|
+
state.collectionPage = 1;
|
|
688
|
+
state.collectionTotal = typeof data.count === 'number' ? data.count : documents.length;
|
|
689
|
+
updateCollectionPager();
|
|
690
|
+
setCollectionTab('query');
|
|
691
|
+
setCollectionResult(data, true);
|
|
610
692
|
} else {
|
|
693
|
+
const sortRaw = collectionSort ? collectionSort.value.trim() : '';
|
|
694
|
+
const sort = parseJsonObject(sortRaw, 'Sort');
|
|
611
695
|
const raw = collectionQuery ? collectionQuery.value.trim() : '';
|
|
612
696
|
const query = parseJsonObject(raw, 'Query') || {};
|
|
613
697
|
const data = await api('/collections/query', {
|
|
@@ -806,6 +890,14 @@
|
|
|
806
890
|
collectionAggregate.addEventListener('input', updateCollectionAggregateHighlight);
|
|
807
891
|
collectionAggregate.addEventListener('scroll', updateCollectionAggregateHighlight);
|
|
808
892
|
}
|
|
893
|
+
if (collectionInsertOne) {
|
|
894
|
+
collectionInsertOne.addEventListener('input', updateCollectionInsertOneHighlight);
|
|
895
|
+
collectionInsertOne.addEventListener('scroll', updateCollectionInsertOneHighlight);
|
|
896
|
+
}
|
|
897
|
+
if (collectionInsertMany) {
|
|
898
|
+
collectionInsertMany.addEventListener('input', updateCollectionInsertManyHighlight);
|
|
899
|
+
collectionInsertMany.addEventListener('scroll', updateCollectionInsertManyHighlight);
|
|
900
|
+
}
|
|
809
901
|
|
|
810
902
|
if (collectionList) {
|
|
811
903
|
collectionList.addEventListener('click', (event) => {
|
|
@@ -916,6 +1008,8 @@
|
|
|
916
1008
|
setCollectionResultView('json');
|
|
917
1009
|
updateCollectionQueryHighlight();
|
|
918
1010
|
updateCollectionAggregateHighlight();
|
|
1011
|
+
updateCollectionInsertOneHighlight();
|
|
1012
|
+
updateCollectionInsertManyHighlight();
|
|
919
1013
|
setRulesTabEnabled(false);
|
|
920
1014
|
};
|
|
921
1015
|
|
package/src/monitoring/ui.css
CHANGED
|
@@ -929,12 +929,17 @@ button.small {
|
|
|
929
929
|
align-items: stretch;
|
|
930
930
|
}
|
|
931
931
|
|
|
932
|
-
.collection-io
|
|
932
|
+
.collection-io [data-collection-mode] {
|
|
933
933
|
display: none;
|
|
934
|
+
min-height: 0;
|
|
934
935
|
}
|
|
935
936
|
|
|
936
|
-
.collection-io[data-mode="
|
|
937
|
-
|
|
937
|
+
.collection-io[data-mode="query"] [data-collection-mode="query"],
|
|
938
|
+
.collection-io[data-mode="aggregate"] [data-collection-mode="aggregate"],
|
|
939
|
+
.collection-io[data-mode="insertOne"] [data-collection-mode="insertOne"],
|
|
940
|
+
.collection-io[data-mode="insertMany"] [data-collection-mode="insertMany"] {
|
|
941
|
+
display: flex;
|
|
942
|
+
flex-direction: column;
|
|
938
943
|
}
|
|
939
944
|
|
|
940
945
|
.collection-io {
|
|
@@ -1039,6 +1044,7 @@ button.small {
|
|
|
1039
1044
|
border-right: 1px solid #2b2b2b;
|
|
1040
1045
|
user-select: none;
|
|
1041
1046
|
white-space: pre;
|
|
1047
|
+
overflow: hidden;
|
|
1042
1048
|
}
|
|
1043
1049
|
|
|
1044
1050
|
.code-surface {
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
functionHighlight.innerHTML = highlightCode(code);
|
|
79
79
|
}
|
|
80
80
|
if (functionGutter) {
|
|
81
|
-
const
|
|
81
|
+
const lineBreaks = code.match(/\r\n|\r|\n/g);
|
|
82
|
+
const lines = Math.max(1, (lineBreaks ? lineBreaks.length : 0) + 1);
|
|
82
83
|
let out = '';
|
|
83
84
|
for (let i = 1; i <= lines; i += 1) {
|
|
84
85
|
out += i + (i === lines ? '' : '\n');
|
package/src/monitoring/ui.html
CHANGED
|
@@ -371,6 +371,8 @@
|
|
|
371
371
|
<select id="collectionMode">
|
|
372
372
|
<option value="query" selected>query</option>
|
|
373
373
|
<option value="aggregate">aggregate</option>
|
|
374
|
+
<option value="insertOne">insert one</option>
|
|
375
|
+
<option value="insertMany">insert many</option>
|
|
374
376
|
</select>
|
|
375
377
|
</div>
|
|
376
378
|
<div class="control-group">
|
|
@@ -393,6 +395,20 @@
|
|
|
393
395
|
<textarea id="collectionAggregate" placeholder='[{ "$match": { "field": "value" } }]'></textarea>
|
|
394
396
|
</div>
|
|
395
397
|
</div>
|
|
398
|
+
<div class="io-column" data-collection-mode="insertOne">
|
|
399
|
+
<label for="collectionInsertOne">document</label>
|
|
400
|
+
<div class="json-editor">
|
|
401
|
+
<pre id="collectionInsertOneHighlight" class="json-highlight"></pre>
|
|
402
|
+
<textarea id="collectionInsertOne" placeholder='{ "field": "value" }'></textarea>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
<div class="io-column" data-collection-mode="insertMany">
|
|
406
|
+
<label for="collectionInsertMany">documents</label>
|
|
407
|
+
<div class="json-editor">
|
|
408
|
+
<pre id="collectionInsertManyHighlight" class="json-highlight"></pre>
|
|
409
|
+
<textarea id="collectionInsertMany" placeholder='[{ "field": "value" }]'></textarea>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
396
412
|
</div>
|
|
397
413
|
</div>
|
|
398
414
|
<div class="collection-actions">
|
package/src/monitoring/utils.ts
CHANGED
|
@@ -29,9 +29,11 @@ export type FunctionHistoryItem = {
|
|
|
29
29
|
export type CollectionHistoryItem = {
|
|
30
30
|
ts: number
|
|
31
31
|
collection: string
|
|
32
|
-
mode: 'query' | 'aggregate'
|
|
32
|
+
mode: 'query' | 'aggregate' | 'insertOne' | 'insertMany'
|
|
33
33
|
query?: unknown
|
|
34
34
|
pipeline?: unknown
|
|
35
|
+
document?: unknown
|
|
36
|
+
documents?: unknown
|
|
35
37
|
sort?: Record<string, unknown>
|
|
36
38
|
runAsSystem: boolean
|
|
37
39
|
user?: { id?: string; email?: string }
|
|
@@ -456,9 +456,9 @@ const getOperators: GetOperatorsFunction = (
|
|
|
456
456
|
const resolvedOptions =
|
|
457
457
|
projection || normalizedOptions
|
|
458
458
|
? {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
...(normalizedOptions ?? {}),
|
|
460
|
+
...(projection ? { projection } : {})
|
|
461
|
+
}
|
|
462
462
|
: undefined
|
|
463
463
|
const resolvedQuery = query ?? {}
|
|
464
464
|
if (!run_as_system) {
|
|
@@ -496,15 +496,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
496
496
|
})
|
|
497
497
|
const { status, document } = winningRole
|
|
498
498
|
? await checkValidation(
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
499
|
+
winningRole,
|
|
500
|
+
{
|
|
501
|
+
type: 'read',
|
|
502
|
+
roles,
|
|
503
|
+
cursor: result,
|
|
504
|
+
expansions: {}
|
|
505
|
+
},
|
|
506
|
+
user
|
|
507
|
+
)
|
|
508
508
|
: fallbackAccess(result)
|
|
509
509
|
|
|
510
510
|
// Return validated document or empty object if not permitted
|
|
@@ -557,15 +557,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
557
557
|
})
|
|
558
558
|
const { status } = winningRole
|
|
559
559
|
? await checkValidation(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
560
|
+
winningRole,
|
|
561
|
+
{
|
|
562
|
+
type: 'delete',
|
|
563
|
+
roles,
|
|
564
|
+
cursor: result,
|
|
565
|
+
expansions: {}
|
|
566
|
+
},
|
|
567
|
+
user
|
|
568
|
+
)
|
|
569
569
|
: fallbackAccess(result)
|
|
570
570
|
|
|
571
571
|
if (!status) {
|
|
@@ -612,15 +612,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
612
612
|
|
|
613
613
|
const { status, document } = winningRole
|
|
614
614
|
? await checkValidation(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
615
|
+
winningRole,
|
|
616
|
+
{
|
|
617
|
+
type: 'insert',
|
|
618
|
+
roles,
|
|
619
|
+
cursor: data,
|
|
620
|
+
expansions: {}
|
|
621
|
+
},
|
|
622
|
+
user
|
|
623
|
+
)
|
|
624
624
|
: fallbackAccess(data)
|
|
625
625
|
|
|
626
626
|
if (!status || !isEqual(data, document)) {
|
|
@@ -703,15 +703,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
703
703
|
// Validate update permissions
|
|
704
704
|
const { status, document } = winningRole
|
|
705
705
|
? await checkValidation(
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
706
|
+
winningRole,
|
|
707
|
+
{
|
|
708
|
+
type: 'write',
|
|
709
|
+
roles,
|
|
710
|
+
cursor: docToCheck,
|
|
711
|
+
expansions: {}
|
|
712
|
+
},
|
|
713
|
+
user
|
|
714
|
+
)
|
|
715
715
|
: fallbackAccess(docToCheck)
|
|
716
716
|
// Ensure no unauthorized changes are made
|
|
717
717
|
const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths)
|
|
@@ -818,15 +818,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
818
818
|
const readRole = getWinningRole(updateResult, user, roles)
|
|
819
819
|
const readResult = readRole
|
|
820
820
|
? await checkValidation(
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
821
|
+
readRole,
|
|
822
|
+
{
|
|
823
|
+
type: 'read',
|
|
824
|
+
roles,
|
|
825
|
+
cursor: updateResult,
|
|
826
|
+
expansions: {}
|
|
827
|
+
},
|
|
828
|
+
user
|
|
829
|
+
)
|
|
830
830
|
: fallbackAccess(updateResult)
|
|
831
831
|
|
|
832
832
|
const sanitizedDoc = readResult.status ? (readResult.document ?? updateResult) : {}
|
|
@@ -873,9 +873,9 @@ const getOperators: GetOperatorsFunction = (
|
|
|
873
873
|
const resolvedOptions =
|
|
874
874
|
projection || normalizedOptions
|
|
875
875
|
? {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
876
|
+
...(normalizedOptions ?? {}),
|
|
877
|
+
...(projection ? { projection } : {})
|
|
878
|
+
}
|
|
879
879
|
: undefined
|
|
880
880
|
if (!run_as_system) {
|
|
881
881
|
checkDenyOperation(normalizedRules, collection.collectionName, CRUD_OPERATIONS.READ)
|
|
@@ -906,15 +906,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
906
906
|
})
|
|
907
907
|
const { status, document } = winningRole
|
|
908
908
|
? await checkValidation(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
909
|
+
winningRole,
|
|
910
|
+
{
|
|
911
|
+
type: 'read',
|
|
912
|
+
roles,
|
|
913
|
+
cursor: currentDoc,
|
|
914
|
+
expansions: {}
|
|
915
|
+
},
|
|
916
|
+
user
|
|
917
|
+
)
|
|
918
918
|
: fallbackAccess(currentDoc)
|
|
919
919
|
|
|
920
920
|
return status ? document : undefined
|
|
@@ -1017,10 +1017,10 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1017
1017
|
|
|
1018
1018
|
const firstStep = watchFormattedQuery.length
|
|
1019
1019
|
? {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1020
|
+
$match: {
|
|
1021
|
+
$and: watchFormattedQuery
|
|
1023
1022
|
}
|
|
1023
|
+
}
|
|
1024
1024
|
: undefined
|
|
1025
1025
|
|
|
1026
1026
|
const formattedPipeline = [firstStep, ...extraMatches, ...pipeline].filter(Boolean) as Document[]
|
|
@@ -1041,29 +1041,29 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1041
1041
|
|
|
1042
1042
|
const fullDocumentValidation = winningRole
|
|
1043
1043
|
? await checkValidation(
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1044
|
+
winningRole,
|
|
1045
|
+
{
|
|
1046
|
+
type: 'read',
|
|
1047
|
+
roles,
|
|
1048
|
+
cursor: fullDocument,
|
|
1049
|
+
expansions: {}
|
|
1050
|
+
},
|
|
1051
|
+
user
|
|
1052
|
+
)
|
|
1053
1053
|
: fallbackAccess(fullDocument)
|
|
1054
1054
|
const { status, document } = fullDocumentValidation
|
|
1055
1055
|
|
|
1056
1056
|
const { status: updatedFieldsStatus, document: updatedFields } = winningRole
|
|
1057
1057
|
? await checkValidation(
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1058
|
+
winningRole,
|
|
1059
|
+
{
|
|
1060
|
+
type: 'read',
|
|
1061
|
+
roles,
|
|
1062
|
+
cursor: updateDescription?.updatedFields,
|
|
1063
|
+
expansions: {}
|
|
1064
|
+
},
|
|
1065
|
+
user
|
|
1066
|
+
)
|
|
1067
1067
|
: fallbackAccess(updateDescription?.updatedFields)
|
|
1068
1068
|
|
|
1069
1069
|
return {
|
|
@@ -1099,18 +1099,6 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1099
1099
|
}
|
|
1100
1100
|
}
|
|
1101
1101
|
|
|
1102
|
-
console.log('[flowerbase watch] delivered change', {
|
|
1103
|
-
collection: collName,
|
|
1104
|
-
operationType: change?.operationType,
|
|
1105
|
-
eventType,
|
|
1106
|
-
hasFullDocument,
|
|
1107
|
-
hasWinningRole,
|
|
1108
|
-
updatedFieldsStatus,
|
|
1109
|
-
documentKey:
|
|
1110
|
-
change?.documentKey?._id?.toString?.() ||
|
|
1111
|
-
change?.documentKey?._id ||
|
|
1112
|
-
null
|
|
1113
|
-
})
|
|
1114
1102
|
listener(filteredChange)
|
|
1115
1103
|
})
|
|
1116
1104
|
}
|
|
@@ -1210,15 +1198,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1210
1198
|
|
|
1211
1199
|
const { status, document } = winningRole
|
|
1212
1200
|
? await checkValidation(
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1201
|
+
winningRole,
|
|
1202
|
+
{
|
|
1203
|
+
type: 'insert',
|
|
1204
|
+
roles,
|
|
1205
|
+
cursor: currentDoc,
|
|
1206
|
+
expansions: {}
|
|
1207
|
+
},
|
|
1208
|
+
user
|
|
1209
|
+
)
|
|
1222
1210
|
: fallbackAccess(currentDoc)
|
|
1223
1211
|
|
|
1224
1212
|
return status ? document : undefined
|
|
@@ -1255,7 +1243,6 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1255
1243
|
// Retrieve the document to check permissions before updating
|
|
1256
1244
|
const result = await collection.find({ $and: formattedQuery }).toArray()
|
|
1257
1245
|
if (!result) {
|
|
1258
|
-
console.log('check1 In updateMany --> (!result)')
|
|
1259
1246
|
throw new Error('Update not permitted')
|
|
1260
1247
|
}
|
|
1261
1248
|
|
|
@@ -1271,15 +1258,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1271
1258
|
|
|
1272
1259
|
const { status, document } = winningRole
|
|
1273
1260
|
? await checkValidation(
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1261
|
+
winningRole,
|
|
1262
|
+
{
|
|
1263
|
+
type: 'write',
|
|
1264
|
+
roles,
|
|
1265
|
+
cursor: currentDoc,
|
|
1266
|
+
expansions: {}
|
|
1267
|
+
},
|
|
1268
|
+
user
|
|
1269
|
+
)
|
|
1283
1270
|
: fallbackAccess(currentDoc)
|
|
1284
1271
|
|
|
1285
1272
|
return status ? document : undefined
|
|
@@ -1292,8 +1279,6 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1292
1279
|
)
|
|
1293
1280
|
|
|
1294
1281
|
if (!areDocumentsEqual) {
|
|
1295
|
-
console.log('check1 In updateMany --> (!areDocumentsEqual)')
|
|
1296
|
-
|
|
1297
1282
|
throw new Error('Update not permitted')
|
|
1298
1283
|
}
|
|
1299
1284
|
|
|
@@ -1341,15 +1326,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1341
1326
|
|
|
1342
1327
|
const { status, document } = winningRole
|
|
1343
1328
|
? await checkValidation(
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1329
|
+
winningRole,
|
|
1330
|
+
{
|
|
1331
|
+
type: 'delete',
|
|
1332
|
+
roles,
|
|
1333
|
+
cursor: currentDoc,
|
|
1334
|
+
expansions: {}
|
|
1335
|
+
},
|
|
1336
|
+
user
|
|
1337
|
+
)
|
|
1353
1338
|
: fallbackAccess(currentDoc)
|
|
1354
1339
|
|
|
1355
1340
|
return status ? document : undefined
|