@resolveio/server-lib 22.1.18 → 22.1.20
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/managers/websocket.manager.d.ts +4 -0
- package/managers/websocket.manager.js +21 -0
- package/managers/websocket.manager.js.map +1 -1
- package/methods/mongo-explorer.js +874 -29
- package/methods/mongo-explorer.js.map +1 -1
- package/methods.ts +14 -8
- package/package.json +1 -1
- package/server-app.d.ts +18 -0
- package/server-app.js +610 -280
- package/server-app.js.map +1 -1
|
@@ -35,14 +35,62 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
39
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
40
|
+
if (!m) return o;
|
|
41
|
+
var i = m.call(o), r, ar = [], e;
|
|
42
|
+
try {
|
|
43
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
44
|
+
}
|
|
45
|
+
catch (error) { e = { error: error }; }
|
|
46
|
+
finally {
|
|
47
|
+
try {
|
|
48
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
49
|
+
}
|
|
50
|
+
finally { if (e) throw e.error; }
|
|
51
|
+
}
|
|
52
|
+
return ar;
|
|
53
|
+
};
|
|
54
|
+
var __values = (this && this.__values) || function(o) {
|
|
55
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
56
|
+
if (m) return m.call(o);
|
|
57
|
+
if (o && typeof o.length === "number") return {
|
|
58
|
+
next: function () {
|
|
59
|
+
if (o && i >= o.length) o = void 0;
|
|
60
|
+
return { value: o && o[i++], done: !o };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
64
|
+
};
|
|
65
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
66
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
67
|
+
if (ar || !(i in from)) {
|
|
68
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
69
|
+
ar[i] = from[i];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
73
|
+
};
|
|
38
74
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
75
|
exports.loadMongoExplorerMethods = loadMongoExplorerMethods;
|
|
40
76
|
var simpl_schema_1 = require("simpl-schema");
|
|
41
77
|
var user_collection_1 = require("../collections/user.collection");
|
|
42
78
|
var resolveio_server_app_1 = require("../resolveio-server-app");
|
|
79
|
+
var openai_client_1 = require("../services/openai-client");
|
|
43
80
|
var common_1 = require("../util/common");
|
|
44
81
|
var DEFAULT_LIMIT = 100;
|
|
45
82
|
var MAX_LIMIT = 2000;
|
|
83
|
+
var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
84
|
+
var MAX_REVIEW_STRING_LENGTH = 500;
|
|
85
|
+
var MAX_REVIEW_ARRAY_ITEMS = 10;
|
|
86
|
+
var MAX_REVIEW_OBJECT_KEYS = 40;
|
|
87
|
+
var MAX_REVIEW_DEPTH = 4;
|
|
88
|
+
var MAX_REVIEW_LIST_ITEMS = 6;
|
|
89
|
+
var MAX_REVIEW_CHANGED_FIELDS = 60;
|
|
90
|
+
var DEFAULT_RISK_REVIEW_TIMEOUT_MS = 30000;
|
|
91
|
+
var DEFAULT_RISK_REVIEW_MAX_TOKENS = 700;
|
|
92
|
+
var DEFAULT_RISK_REVIEW_MODEL = 'gpt-5.3-codex';
|
|
93
|
+
var SENSITIVE_REVIEW_FIELD_REGEX = /(password|secret|token|api[_-]?key|salt|hash|email|phone|address|ssn|services|roles)/i;
|
|
46
94
|
function parseMongoExplorerWriteUsers() {
|
|
47
95
|
var raw = typeof process.env.MONGO_EXPLORER_WRITE_USERS === 'string' ? process.env.MONGO_EXPLORER_WRITE_USERS.trim() : '';
|
|
48
96
|
if (!raw) {
|
|
@@ -65,6 +113,24 @@ function parseMongoExplorerWriteUsers() {
|
|
|
65
113
|
.map(function (value) { return value.trim(); })
|
|
66
114
|
.filter(Boolean);
|
|
67
115
|
}
|
|
116
|
+
function normalizeExplorerMode(mode) {
|
|
117
|
+
var normalizedMode = String(mode || '').trim().toLowerCase();
|
|
118
|
+
if (normalizedMode === 'resolveio') {
|
|
119
|
+
return 'resolveio';
|
|
120
|
+
}
|
|
121
|
+
return 'aicoder';
|
|
122
|
+
}
|
|
123
|
+
function isAdminUser(user) {
|
|
124
|
+
var _a;
|
|
125
|
+
if (!user) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
var username = String(user.username || '').trim().toLowerCase();
|
|
129
|
+
return username === 'admin' || ((_a = user.roles) === null || _a === void 0 ? void 0 : _a.super_admin) === true;
|
|
130
|
+
}
|
|
131
|
+
function allowUnschemaizedWrites() {
|
|
132
|
+
return process.env.MONGO_EXPLORER_ALLOW_UNSCHEMATIZED_WRITE === 'true';
|
|
133
|
+
}
|
|
68
134
|
function resolveDatabaseName(database) {
|
|
69
135
|
var _a, _b;
|
|
70
136
|
var defaultDb = ((_a = resolveio_server_app_1.ResolveIOServer.getServerConfig()) === null || _a === void 0 ? void 0 : _a.DATABASE) || '';
|
|
@@ -93,6 +159,172 @@ function resolveCollectionHandle(database, collection) {
|
|
|
93
159
|
dbCollection: db.collection(collection)
|
|
94
160
|
};
|
|
95
161
|
}
|
|
162
|
+
function getSchemaDefinition(collectionRef) {
|
|
163
|
+
if (!collectionRef || !collectionRef.simplschema || typeof collectionRef.simplschema.schema !== 'function') {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
var schema = collectionRef.simplschema.schema();
|
|
167
|
+
return schema && typeof schema === 'object' ? schema : {};
|
|
168
|
+
}
|
|
169
|
+
function getSchemaTypeName(definition) {
|
|
170
|
+
var _a, _b;
|
|
171
|
+
var typeDefs = (_a = definition === null || definition === void 0 ? void 0 : definition.type) === null || _a === void 0 ? void 0 : _a.definitions;
|
|
172
|
+
if (!Array.isArray(typeDefs) || !typeDefs.length) {
|
|
173
|
+
return 'Any';
|
|
174
|
+
}
|
|
175
|
+
var firstType = (_b = typeDefs[0]) === null || _b === void 0 ? void 0 : _b.type;
|
|
176
|
+
if (!firstType) {
|
|
177
|
+
return 'Any';
|
|
178
|
+
}
|
|
179
|
+
if (firstType === String) {
|
|
180
|
+
return 'String';
|
|
181
|
+
}
|
|
182
|
+
if (firstType === Number) {
|
|
183
|
+
return 'Number';
|
|
184
|
+
}
|
|
185
|
+
if (firstType === Boolean) {
|
|
186
|
+
return 'Boolean';
|
|
187
|
+
}
|
|
188
|
+
if (firstType === Date) {
|
|
189
|
+
return 'Date';
|
|
190
|
+
}
|
|
191
|
+
if (firstType === Object) {
|
|
192
|
+
return 'Object';
|
|
193
|
+
}
|
|
194
|
+
if (firstType === Array) {
|
|
195
|
+
return 'Array';
|
|
196
|
+
}
|
|
197
|
+
var typeName = firstType.name || String(firstType);
|
|
198
|
+
if (typeName === 'Integer') {
|
|
199
|
+
return 'Number';
|
|
200
|
+
}
|
|
201
|
+
return typeName;
|
|
202
|
+
}
|
|
203
|
+
function toLabel(path) {
|
|
204
|
+
var last = path.split('.').pop() || path;
|
|
205
|
+
return last
|
|
206
|
+
.replace(/\$/g, '')
|
|
207
|
+
.replace(/_/g, ' ')
|
|
208
|
+
.replace(/\s+/g, ' ')
|
|
209
|
+
.trim();
|
|
210
|
+
}
|
|
211
|
+
function isProtectedFieldSegment(segment) {
|
|
212
|
+
if (!segment) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (segment === '_id') {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
var normalizedSegment = segment.replace(/\[\d+\]/g, '');
|
|
219
|
+
return normalizedSegment.toLowerCase().startsWith('id_');
|
|
220
|
+
}
|
|
221
|
+
function isProtectedFieldPath(path) {
|
|
222
|
+
var segments = String(path || '').split('.').filter(Boolean);
|
|
223
|
+
return segments.some(function (segment) { return isProtectedFieldSegment(segment); });
|
|
224
|
+
}
|
|
225
|
+
function safeStringify(value) {
|
|
226
|
+
try {
|
|
227
|
+
return JSON.stringify(value);
|
|
228
|
+
}
|
|
229
|
+
catch (_a) {
|
|
230
|
+
return String(value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function collectProtectedFieldPaths(value, prefix, out) {
|
|
234
|
+
if (prefix === void 0) { prefix = ''; }
|
|
235
|
+
if (out === void 0) { out = {}; }
|
|
236
|
+
if (Array.isArray(value)) {
|
|
237
|
+
value.forEach(function (item, index) {
|
|
238
|
+
var nextPrefix = prefix ? "".concat(prefix, "[").concat(index, "]") : "[".concat(index, "]");
|
|
239
|
+
collectProtectedFieldPaths(item, nextPrefix, out);
|
|
240
|
+
});
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
if (!value || typeof value !== 'object') {
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
Object.keys(value).forEach(function (key) {
|
|
247
|
+
var nextPrefix = prefix ? "".concat(prefix, ".").concat(key) : key;
|
|
248
|
+
if (isProtectedFieldSegment(key)) {
|
|
249
|
+
out[nextPrefix] = value[key];
|
|
250
|
+
}
|
|
251
|
+
collectProtectedFieldPaths(value[key], nextPrefix, out);
|
|
252
|
+
});
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
function ensureProtectedFieldsUnchanged(originalDoc, updatedDoc) {
|
|
256
|
+
var originalProtected = collectProtectedFieldPaths(originalDoc || {});
|
|
257
|
+
var updatedProtected = collectProtectedFieldPaths(updatedDoc || {});
|
|
258
|
+
var violations = [];
|
|
259
|
+
Object.keys(originalProtected).forEach(function (path) {
|
|
260
|
+
if (!Object.prototype.hasOwnProperty.call(updatedProtected, path)) {
|
|
261
|
+
violations.push(path);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
var originalValue = safeStringify(originalProtected[path]);
|
|
265
|
+
var updatedValue = safeStringify(updatedProtected[path]);
|
|
266
|
+
if (originalValue !== updatedValue) {
|
|
267
|
+
violations.push(path);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
Object.keys(updatedProtected).forEach(function (path) {
|
|
271
|
+
if (!Object.prototype.hasOwnProperty.call(originalProtected, path)) {
|
|
272
|
+
violations.push(path);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (violations.length) {
|
|
276
|
+
var preview = violations.slice(0, 4).join(', ');
|
|
277
|
+
throw new Error("Mongo Explorer: Protected fields cannot be edited (".concat(preview).concat(violations.length > 4 ? ', ...' : '', ")"));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function coerceDateValue(value) {
|
|
281
|
+
if (value instanceof Date) {
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
if (typeof value !== 'string' || !ISO_DATE_REGEX.test(value)) {
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
var parsed = new Date(value);
|
|
288
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed;
|
|
289
|
+
}
|
|
290
|
+
function coerceDatePath(target, segments) {
|
|
291
|
+
if (!target || !segments.length) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
var _a = __read(segments), segment = _a[0], rest = _a.slice(1);
|
|
295
|
+
if (segment === '$') {
|
|
296
|
+
if (Array.isArray(target)) {
|
|
297
|
+
target.forEach(function (item) { return coerceDatePath(item, rest); });
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (!Object.prototype.hasOwnProperty.call(target, segment)) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (rest.length === 0) {
|
|
305
|
+
var updatedValue = coerceDateValue(target[segment]);
|
|
306
|
+
if (updatedValue !== target[segment]) {
|
|
307
|
+
target[segment] = updatedValue;
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
coerceDatePath(target[segment], rest);
|
|
312
|
+
}
|
|
313
|
+
function coerceDateFields(collectionRef, doc) {
|
|
314
|
+
if (!collectionRef || !collectionRef.simplschema || !doc) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
var schema = getSchemaDefinition(collectionRef);
|
|
318
|
+
Object.keys(schema).forEach(function (schemaKey) {
|
|
319
|
+
var _a;
|
|
320
|
+
var definition = schema[schemaKey];
|
|
321
|
+
var typeDefs = (_a = definition === null || definition === void 0 ? void 0 : definition.type) === null || _a === void 0 ? void 0 : _a.definitions;
|
|
322
|
+
if (!Array.isArray(typeDefs) || !typeDefs.some(function (typeDef) { return typeDef.type === Date; })) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
coerceDatePath(doc, schemaKey.split('.'));
|
|
326
|
+
});
|
|
327
|
+
}
|
|
96
328
|
function normalizeFindOptions(options) {
|
|
97
329
|
var normalized = options || {};
|
|
98
330
|
var projection = normalized.projection && Object.keys(normalized.projection).length ? normalized.projection : undefined;
|
|
@@ -130,9 +362,9 @@ function userHasView(user, view) {
|
|
|
130
362
|
}
|
|
131
363
|
return false;
|
|
132
364
|
}
|
|
133
|
-
function ensureWriteAccess(context, permissionView) {
|
|
365
|
+
function ensureWriteAccess(context, permissionView, mode) {
|
|
134
366
|
return __awaiter(this, void 0, void 0, function () {
|
|
135
|
-
var idUser, user, allowedUsers, username_1, email_1, id_1, isAllowed, normalizedPermission;
|
|
367
|
+
var idUser, user, normalizedMode, allowedUsers, username_1, email_1, id_1, isAllowed, normalizedPermission;
|
|
136
368
|
var _a;
|
|
137
369
|
return __generator(this, function (_b) {
|
|
138
370
|
switch (_b.label) {
|
|
@@ -150,6 +382,10 @@ function ensureWriteAccess(context, permissionView) {
|
|
|
150
382
|
if (user.readonly) {
|
|
151
383
|
throw new Error('Mongo Explorer: Readonly user');
|
|
152
384
|
}
|
|
385
|
+
normalizedMode = normalizeExplorerMode(mode);
|
|
386
|
+
if (normalizedMode === 'resolveio' && !isAdminUser(user)) {
|
|
387
|
+
throw new Error('Mongo Explorer: ResolveIO mode is read only for non-admin users');
|
|
388
|
+
}
|
|
153
389
|
allowedUsers = parseMongoExplorerWriteUsers();
|
|
154
390
|
if (allowedUsers.length) {
|
|
155
391
|
username_1 = String(user.username || '').trim().toLowerCase();
|
|
@@ -181,6 +417,437 @@ function ensureWriteAccess(context, permissionView) {
|
|
|
181
417
|
});
|
|
182
418
|
});
|
|
183
419
|
}
|
|
420
|
+
function buildDeleteSafetyCollections(collectionRef) {
|
|
421
|
+
var schema = getSchemaDefinition(collectionRef);
|
|
422
|
+
return Object.keys(schema).filter(function (path) { return isProtectedFieldPath(path) && path !== '_id'; });
|
|
423
|
+
}
|
|
424
|
+
function buildDeleteImpact(database, sourceCollection, docId) {
|
|
425
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
426
|
+
var mainDb, mongoManager, collections, impacts, unresolved, collections_1, collections_1_1, collectionRef, collectionName, referencePaths, query, docs, _a, e_1_1;
|
|
427
|
+
var e_1, _b;
|
|
428
|
+
var _c;
|
|
429
|
+
return __generator(this, function (_d) {
|
|
430
|
+
switch (_d.label) {
|
|
431
|
+
case 0:
|
|
432
|
+
mainDb = ((_c = resolveio_server_app_1.ResolveIOServer.getServerConfig()) === null || _c === void 0 ? void 0 : _c.DATABASE) || '';
|
|
433
|
+
if (database !== mainDb) {
|
|
434
|
+
return [2 /*return*/, {
|
|
435
|
+
has_references: false,
|
|
436
|
+
unresolved: true,
|
|
437
|
+
collections: []
|
|
438
|
+
}];
|
|
439
|
+
}
|
|
440
|
+
mongoManager = resolveio_server_app_1.ResolveIOServer.getMongoManager();
|
|
441
|
+
collections = (mongoManager === null || mongoManager === void 0 ? void 0 : mongoManager.collections) ? mongoManager.collections() : [];
|
|
442
|
+
impacts = [];
|
|
443
|
+
unresolved = false;
|
|
444
|
+
_d.label = 1;
|
|
445
|
+
case 1:
|
|
446
|
+
_d.trys.push([1, 8, 9, 10]);
|
|
447
|
+
collections_1 = __values(collections), collections_1_1 = collections_1.next();
|
|
448
|
+
_d.label = 2;
|
|
449
|
+
case 2:
|
|
450
|
+
if (!!collections_1_1.done) return [3 /*break*/, 7];
|
|
451
|
+
collectionRef = collections_1_1.value;
|
|
452
|
+
collectionName = collectionRef === null || collectionRef === void 0 ? void 0 : collectionRef.collectionName;
|
|
453
|
+
if (!collectionName) {
|
|
454
|
+
return [3 /*break*/, 6];
|
|
455
|
+
}
|
|
456
|
+
if (collectionName === "".concat(sourceCollection, ".versions")) {
|
|
457
|
+
return [3 /*break*/, 6];
|
|
458
|
+
}
|
|
459
|
+
referencePaths = buildDeleteSafetyCollections(collectionRef);
|
|
460
|
+
if (!referencePaths.length) {
|
|
461
|
+
return [3 /*break*/, 6];
|
|
462
|
+
}
|
|
463
|
+
query = {
|
|
464
|
+
$or: referencePaths.map(function (path) {
|
|
465
|
+
var _a;
|
|
466
|
+
return (_a = {}, _a[path] = docId, _a);
|
|
467
|
+
})
|
|
468
|
+
};
|
|
469
|
+
_d.label = 3;
|
|
470
|
+
case 3:
|
|
471
|
+
_d.trys.push([3, 5, , 6]);
|
|
472
|
+
return [4 /*yield*/, collectionRef.find(query, { projection: { _id: 1 }, limit: 3 })];
|
|
473
|
+
case 4:
|
|
474
|
+
docs = _d.sent();
|
|
475
|
+
if (Array.isArray(docs) && docs.length) {
|
|
476
|
+
impacts.push({
|
|
477
|
+
collection: collectionName,
|
|
478
|
+
references: docs.length,
|
|
479
|
+
sample_ids: docs.map(function (doc) { return String((doc === null || doc === void 0 ? void 0 : doc._id) || ''); }).filter(Boolean)
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return [3 /*break*/, 6];
|
|
483
|
+
case 5:
|
|
484
|
+
_a = _d.sent();
|
|
485
|
+
unresolved = true;
|
|
486
|
+
return [3 /*break*/, 6];
|
|
487
|
+
case 6:
|
|
488
|
+
collections_1_1 = collections_1.next();
|
|
489
|
+
return [3 /*break*/, 2];
|
|
490
|
+
case 7: return [3 /*break*/, 10];
|
|
491
|
+
case 8:
|
|
492
|
+
e_1_1 = _d.sent();
|
|
493
|
+
e_1 = { error: e_1_1 };
|
|
494
|
+
return [3 /*break*/, 10];
|
|
495
|
+
case 9:
|
|
496
|
+
try {
|
|
497
|
+
if (collections_1_1 && !collections_1_1.done && (_b = collections_1.return)) _b.call(collections_1);
|
|
498
|
+
}
|
|
499
|
+
finally { if (e_1) throw e_1.error; }
|
|
500
|
+
return [7 /*endfinally*/];
|
|
501
|
+
case 10: return [2 /*return*/, {
|
|
502
|
+
has_references: impacts.length > 0,
|
|
503
|
+
unresolved: unresolved,
|
|
504
|
+
collections: impacts.sort(function (a, b) { return a.collection.localeCompare(b.collection); })
|
|
505
|
+
}];
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function normalizeOptionalString(value) {
|
|
511
|
+
if (typeof value === 'string') {
|
|
512
|
+
var trimmed = value.trim();
|
|
513
|
+
return trimmed.length ? trimmed : undefined;
|
|
514
|
+
}
|
|
515
|
+
if (value === null || value === undefined) {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
var normalized = String(value).trim();
|
|
519
|
+
return normalized.length ? normalized : undefined;
|
|
520
|
+
}
|
|
521
|
+
function normalizePositiveNumber(value, fallback) {
|
|
522
|
+
var parsed = Number(value);
|
|
523
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
524
|
+
return fallback;
|
|
525
|
+
}
|
|
526
|
+
return parsed;
|
|
527
|
+
}
|
|
528
|
+
function parseBooleanValue(value, fallback) {
|
|
529
|
+
if (typeof value === 'boolean') {
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
var normalized = String(value || '').trim().toLowerCase();
|
|
533
|
+
if (!normalized.length) {
|
|
534
|
+
return fallback;
|
|
535
|
+
}
|
|
536
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
return fallback;
|
|
543
|
+
}
|
|
544
|
+
function truncateForRiskReview(value, maxLength) {
|
|
545
|
+
if (maxLength === void 0) { maxLength = MAX_REVIEW_STRING_LENGTH; }
|
|
546
|
+
var normalized = String(value || '');
|
|
547
|
+
if (normalized.length <= maxLength) {
|
|
548
|
+
return normalized;
|
|
549
|
+
}
|
|
550
|
+
var diff = normalized.length - maxLength;
|
|
551
|
+
return "".concat(normalized.slice(0, maxLength), "...[+").concat(diff, " chars]");
|
|
552
|
+
}
|
|
553
|
+
function sanitizeForRiskReview(value, depth) {
|
|
554
|
+
if (depth === void 0) { depth = 0; }
|
|
555
|
+
if (value === null || value === undefined) {
|
|
556
|
+
return value;
|
|
557
|
+
}
|
|
558
|
+
if (depth > MAX_REVIEW_DEPTH) {
|
|
559
|
+
return '[Truncated depth]';
|
|
560
|
+
}
|
|
561
|
+
if (value instanceof Date) {
|
|
562
|
+
return value.toISOString();
|
|
563
|
+
}
|
|
564
|
+
if (typeof value === 'string') {
|
|
565
|
+
return truncateForRiskReview(value);
|
|
566
|
+
}
|
|
567
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
568
|
+
return value;
|
|
569
|
+
}
|
|
570
|
+
if (Array.isArray(value)) {
|
|
571
|
+
var sanitized = value
|
|
572
|
+
.slice(0, MAX_REVIEW_ARRAY_ITEMS)
|
|
573
|
+
.map(function (item) { return sanitizeForRiskReview(item, depth + 1); });
|
|
574
|
+
if (value.length > MAX_REVIEW_ARRAY_ITEMS) {
|
|
575
|
+
sanitized.push("[".concat(value.length - MAX_REVIEW_ARRAY_ITEMS, " more item(s)]"));
|
|
576
|
+
}
|
|
577
|
+
return sanitized;
|
|
578
|
+
}
|
|
579
|
+
if (typeof value === 'object') {
|
|
580
|
+
var output_1 = {};
|
|
581
|
+
var keys = Object.keys(value);
|
|
582
|
+
var limitedKeys = keys.slice(0, MAX_REVIEW_OBJECT_KEYS);
|
|
583
|
+
limitedKeys.forEach(function (key) {
|
|
584
|
+
if (SENSITIVE_REVIEW_FIELD_REGEX.test(key)) {
|
|
585
|
+
output_1[key] = '[REDACTED]';
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
output_1[key] = sanitizeForRiskReview(value[key], depth + 1);
|
|
589
|
+
});
|
|
590
|
+
if (keys.length > MAX_REVIEW_OBJECT_KEYS) {
|
|
591
|
+
output_1.__truncated_keys = keys.length - MAX_REVIEW_OBJECT_KEYS;
|
|
592
|
+
}
|
|
593
|
+
return output_1;
|
|
594
|
+
}
|
|
595
|
+
return truncateForRiskReview(String(value));
|
|
596
|
+
}
|
|
597
|
+
function pickChangedFields(beforeDocument, afterDocument) {
|
|
598
|
+
var e_2, _a;
|
|
599
|
+
if (!beforeDocument || typeof beforeDocument !== 'object' || !afterDocument || typeof afterDocument !== 'object') {
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
var changed = [];
|
|
603
|
+
var fieldNames = new Set(__spreadArray(__spreadArray([], __read(Object.keys(beforeDocument)), false), __read(Object.keys(afterDocument)), false));
|
|
604
|
+
try {
|
|
605
|
+
for (var fieldNames_1 = __values(fieldNames), fieldNames_1_1 = fieldNames_1.next(); !fieldNames_1_1.done; fieldNames_1_1 = fieldNames_1.next()) {
|
|
606
|
+
var field = fieldNames_1_1.value;
|
|
607
|
+
if (changed.length >= MAX_REVIEW_CHANGED_FIELDS) {
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
var beforeValue = safeStringify(beforeDocument[field]);
|
|
611
|
+
var afterValue = safeStringify(afterDocument[field]);
|
|
612
|
+
if (beforeValue !== afterValue) {
|
|
613
|
+
changed.push(field);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
618
|
+
finally {
|
|
619
|
+
try {
|
|
620
|
+
if (fieldNames_1_1 && !fieldNames_1_1.done && (_a = fieldNames_1.return)) _a.call(fieldNames_1);
|
|
621
|
+
}
|
|
622
|
+
finally { if (e_2) throw e_2.error; }
|
|
623
|
+
}
|
|
624
|
+
return changed;
|
|
625
|
+
}
|
|
626
|
+
function normalizeRiskLevel(value) {
|
|
627
|
+
var normalized = String(value || '').trim().toLowerCase();
|
|
628
|
+
if (normalized === 'low') {
|
|
629
|
+
return 'low';
|
|
630
|
+
}
|
|
631
|
+
if (normalized === 'medium') {
|
|
632
|
+
return 'medium';
|
|
633
|
+
}
|
|
634
|
+
if (normalized === 'high') {
|
|
635
|
+
return 'high';
|
|
636
|
+
}
|
|
637
|
+
if (normalized === 'critical') {
|
|
638
|
+
return 'critical';
|
|
639
|
+
}
|
|
640
|
+
return 'high';
|
|
641
|
+
}
|
|
642
|
+
function normalizeRiskList(value) {
|
|
643
|
+
if (!Array.isArray(value)) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
return value
|
|
647
|
+
.map(function (item) { return normalizeOptionalString(item); })
|
|
648
|
+
.filter(function (item) { return !!item; })
|
|
649
|
+
.slice(0, MAX_REVIEW_LIST_ITEMS)
|
|
650
|
+
.map(function (item) { return truncateForRiskReview(item, 220); });
|
|
651
|
+
}
|
|
652
|
+
function buildFallbackRiskReview(operation, summary, model) {
|
|
653
|
+
if (model === void 0) { model = ''; }
|
|
654
|
+
return {
|
|
655
|
+
operation: operation,
|
|
656
|
+
risk_level: 'high',
|
|
657
|
+
should_block: true,
|
|
658
|
+
summary: truncateForRiskReview(summary, 220),
|
|
659
|
+
reasons: [
|
|
660
|
+
'AI review could not produce a reliable result.',
|
|
661
|
+
'Proceed only with explicit approval and manual dependency checks.'
|
|
662
|
+
],
|
|
663
|
+
suggested_checks: [
|
|
664
|
+
'Confirm related records and id_* references before writing.',
|
|
665
|
+
'Validate that schema-required fields and date values remain valid.'
|
|
666
|
+
],
|
|
667
|
+
confidence: 0,
|
|
668
|
+
model: model || '',
|
|
669
|
+
request_id: '',
|
|
670
|
+
review_status: 'fallback',
|
|
671
|
+
reviewed_at: new Date().toISOString()
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function buildDisabledRiskReview(operation) {
|
|
675
|
+
return {
|
|
676
|
+
operation: operation,
|
|
677
|
+
risk_level: 'low',
|
|
678
|
+
should_block: false,
|
|
679
|
+
summary: 'AI risk review is disabled by configuration.',
|
|
680
|
+
reasons: [],
|
|
681
|
+
suggested_checks: [],
|
|
682
|
+
confidence: 1,
|
|
683
|
+
model: '',
|
|
684
|
+
request_id: '',
|
|
685
|
+
review_status: 'disabled',
|
|
686
|
+
reviewed_at: new Date().toISOString()
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function parseRiskReviewPayload(content) {
|
|
690
|
+
var normalized = String(content || '').trim();
|
|
691
|
+
if (!normalized.length) {
|
|
692
|
+
return {};
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
return JSON.parse(normalized);
|
|
696
|
+
}
|
|
697
|
+
catch (_a) {
|
|
698
|
+
// Fall through and try to parse a JSON object embedded in plain text.
|
|
699
|
+
}
|
|
700
|
+
var start = normalized.indexOf('{');
|
|
701
|
+
var end = normalized.lastIndexOf('}');
|
|
702
|
+
if (start !== -1 && end > start) {
|
|
703
|
+
var candidate = normalized.slice(start, end + 1);
|
|
704
|
+
try {
|
|
705
|
+
return JSON.parse(candidate);
|
|
706
|
+
}
|
|
707
|
+
catch (_b) {
|
|
708
|
+
return {};
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return {};
|
|
712
|
+
}
|
|
713
|
+
function normalizeRiskReview(operation, payload, model, requestId) {
|
|
714
|
+
var riskLevel = normalizeRiskLevel(payload === null || payload === void 0 ? void 0 : payload.risk_level);
|
|
715
|
+
var normalizedConfidence = Number(payload === null || payload === void 0 ? void 0 : payload.confidence);
|
|
716
|
+
var confidence = Number.isFinite(normalizedConfidence)
|
|
717
|
+
? Math.max(0, Math.min(1, normalizedConfidence))
|
|
718
|
+
: 0.6;
|
|
719
|
+
var summary = normalizeOptionalString(payload === null || payload === void 0 ? void 0 : payload.summary)
|
|
720
|
+
|| "Codex review marked this operation as ".concat(riskLevel, " risk.");
|
|
721
|
+
var reasons = normalizeRiskList(payload === null || payload === void 0 ? void 0 : payload.reasons);
|
|
722
|
+
var suggestedChecks = normalizeRiskList(payload === null || payload === void 0 ? void 0 : payload.suggested_checks);
|
|
723
|
+
var shouldBlock = (payload === null || payload === void 0 ? void 0 : payload.should_block) === true || riskLevel === 'critical';
|
|
724
|
+
return {
|
|
725
|
+
operation: operation,
|
|
726
|
+
risk_level: riskLevel,
|
|
727
|
+
should_block: !!shouldBlock,
|
|
728
|
+
summary: truncateForRiskReview(summary, 220),
|
|
729
|
+
reasons: reasons,
|
|
730
|
+
suggested_checks: suggestedChecks,
|
|
731
|
+
confidence: confidence,
|
|
732
|
+
model: model || '',
|
|
733
|
+
request_id: requestId || '',
|
|
734
|
+
review_status: 'ok',
|
|
735
|
+
reviewed_at: new Date().toISOString()
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function resolveRiskReviewSettings() {
|
|
739
|
+
var _a, _b, _c, _d, _e;
|
|
740
|
+
var serverConfig = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
|
|
741
|
+
var enabled = parseBooleanValue((_a = serverConfig['MONGO_EXPLORER_ENABLE_AI_RISK_REVIEW']) !== null && _a !== void 0 ? _a : process.env.MONGO_EXPLORER_ENABLE_AI_RISK_REVIEW, true);
|
|
742
|
+
var model = normalizeOptionalString(serverConfig['MONGO_EXPLORER_RISK_REVIEW_MODEL']
|
|
743
|
+
|| process.env.MONGO_EXPLORER_RISK_REVIEW_MODEL
|
|
744
|
+
|| serverConfig['AI_ASSISTANT_CODEX_MODEL']
|
|
745
|
+
|| process.env.AI_ASSISTANT_CODEX_MODEL
|
|
746
|
+
|| serverConfig['AI_DASHBOARD_CODEX_MODEL']
|
|
747
|
+
|| process.env.AI_DASHBOARD_CODEX_MODEL
|
|
748
|
+
|| serverConfig['OPENAI_MODEL']
|
|
749
|
+
|| process.env.OPENAI_MODEL) || DEFAULT_RISK_REVIEW_MODEL;
|
|
750
|
+
var fallbackModel = normalizeOptionalString(serverConfig['MONGO_EXPLORER_RISK_REVIEW_FALLBACK_MODEL']
|
|
751
|
+
|| process.env.MONGO_EXPLORER_RISK_REVIEW_FALLBACK_MODEL
|
|
752
|
+
|| serverConfig['AI_ASSISTANT_CODEX_FALLBACK_MODEL']
|
|
753
|
+
|| process.env.AI_ASSISTANT_CODEX_FALLBACK_MODEL
|
|
754
|
+
|| serverConfig['AI_DASHBOARD_CODEX_FALLBACK_MODEL']
|
|
755
|
+
|| process.env.AI_DASHBOARD_CODEX_FALLBACK_MODEL);
|
|
756
|
+
var timeoutMs = normalizePositiveNumber((_b = serverConfig['MONGO_EXPLORER_RISK_REVIEW_TIMEOUT_MS']) !== null && _b !== void 0 ? _b : process.env.MONGO_EXPLORER_RISK_REVIEW_TIMEOUT_MS, DEFAULT_RISK_REVIEW_TIMEOUT_MS);
|
|
757
|
+
var maxTokens = normalizePositiveNumber((_c = serverConfig['MONGO_EXPLORER_RISK_REVIEW_MAX_TOKENS']) !== null && _c !== void 0 ? _c : process.env.MONGO_EXPLORER_RISK_REVIEW_MAX_TOKENS, DEFAULT_RISK_REVIEW_MAX_TOKENS);
|
|
758
|
+
var maxRetries = normalizePositiveNumber((_d = serverConfig['OPENAI_MAX_RETRIES']) !== null && _d !== void 0 ? _d : process.env.OPENAI_MAX_RETRIES, 1);
|
|
759
|
+
var retryDelayMs = normalizePositiveNumber((_e = serverConfig['OPENAI_RETRY_DELAY_MS']) !== null && _e !== void 0 ? _e : process.env.OPENAI_RETRY_DELAY_MS, 750);
|
|
760
|
+
var apiKey = normalizeOptionalString(serverConfig['OPENAI_API_KEY'] || process.env.OPENAI_API_KEY) || '';
|
|
761
|
+
var baseUrl = normalizeOptionalString(serverConfig['OPENAI_BASE_URL'] || process.env.OPENAI_BASE_URL);
|
|
762
|
+
return {
|
|
763
|
+
enabled: enabled,
|
|
764
|
+
apiKey: apiKey,
|
|
765
|
+
baseUrl: baseUrl,
|
|
766
|
+
model: model,
|
|
767
|
+
fallbackModel: fallbackModel,
|
|
768
|
+
timeoutMs: timeoutMs,
|
|
769
|
+
maxTokens: maxTokens,
|
|
770
|
+
maxRetries: maxRetries,
|
|
771
|
+
retryDelayMs: retryDelayMs
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function buildRiskReviewPrompt(input) {
|
|
775
|
+
var payload = {
|
|
776
|
+
database: input.database,
|
|
777
|
+
collection: input.collection,
|
|
778
|
+
operation: input.operation,
|
|
779
|
+
mode: input.mode,
|
|
780
|
+
changed_fields: pickChangedFields(input.before_document, input.after_document),
|
|
781
|
+
delete_impact: sanitizeForRiskReview(input.delete_impact || null),
|
|
782
|
+
before_document: sanitizeForRiskReview(input.before_document || null),
|
|
783
|
+
after_document: sanitizeForRiskReview(input.after_document || null)
|
|
784
|
+
};
|
|
785
|
+
return [
|
|
786
|
+
'Review this MongoDB write operation for runtime and data integrity risk.',
|
|
787
|
+
'Focus on foreign-key style id_* dependencies, schema compatibility, destructive impact, and financial/operational record safety.',
|
|
788
|
+
'Return JSON only.',
|
|
789
|
+
JSON.stringify(payload, null, 2)
|
|
790
|
+
].join('\n');
|
|
791
|
+
}
|
|
792
|
+
function reviewOperationRisk(input) {
|
|
793
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
794
|
+
var settings, client, systemPrompt, response, payload, err_1, detail;
|
|
795
|
+
return __generator(this, function (_a) {
|
|
796
|
+
switch (_a.label) {
|
|
797
|
+
case 0:
|
|
798
|
+
settings = resolveRiskReviewSettings();
|
|
799
|
+
if (!settings.enabled) {
|
|
800
|
+
return [2 /*return*/, buildDisabledRiskReview(input.operation)];
|
|
801
|
+
}
|
|
802
|
+
if (!settings.apiKey) {
|
|
803
|
+
return [2 /*return*/, buildFallbackRiskReview(input.operation, 'AI risk review unavailable: OPENAI_API_KEY is missing.')];
|
|
804
|
+
}
|
|
805
|
+
client = new openai_client_1.OpenAIClient({
|
|
806
|
+
apiKey: settings.apiKey,
|
|
807
|
+
baseUrl: settings.baseUrl,
|
|
808
|
+
model: settings.model,
|
|
809
|
+
fallbackModel: settings.fallbackModel,
|
|
810
|
+
temperature: 0.1,
|
|
811
|
+
maxTokens: settings.maxTokens,
|
|
812
|
+
maxRetries: (0, common_1.round)(settings.maxRetries),
|
|
813
|
+
retryDelayMs: (0, common_1.round)(settings.retryDelayMs),
|
|
814
|
+
responseFormat: 'json'
|
|
815
|
+
});
|
|
816
|
+
systemPrompt = [
|
|
817
|
+
'You are a MongoDB operation safety reviewer for a production SaaS application.',
|
|
818
|
+
'Respond with a single JSON object only.',
|
|
819
|
+
'Required keys:',
|
|
820
|
+
'risk_level ("low" | "medium" | "high" | "critical"),',
|
|
821
|
+
'should_block (boolean),',
|
|
822
|
+
'summary (string),',
|
|
823
|
+
'reasons (string[]),',
|
|
824
|
+
'suggested_checks (string[]),',
|
|
825
|
+
'confidence (number between 0 and 1).',
|
|
826
|
+
'Keep summary concise (<= 220 chars).'
|
|
827
|
+
].join(' ');
|
|
828
|
+
_a.label = 1;
|
|
829
|
+
case 1:
|
|
830
|
+
_a.trys.push([1, 3, , 4]);
|
|
831
|
+
return [4 /*yield*/, client.chat([
|
|
832
|
+
{ role: 'system', content: systemPrompt },
|
|
833
|
+
{ role: 'user', content: buildRiskReviewPrompt(input) }
|
|
834
|
+
], {
|
|
835
|
+
timeoutMs: (0, common_1.round)(settings.timeoutMs),
|
|
836
|
+
responseFormat: 'json'
|
|
837
|
+
})];
|
|
838
|
+
case 2:
|
|
839
|
+
response = _a.sent();
|
|
840
|
+
payload = parseRiskReviewPayload(response.content);
|
|
841
|
+
return [2 /*return*/, normalizeRiskReview(input.operation, payload, response.model || settings.model, response.requestId || '')];
|
|
842
|
+
case 3:
|
|
843
|
+
err_1 = _a.sent();
|
|
844
|
+
detail = (err_1 === null || err_1 === void 0 ? void 0 : err_1.message) ? String(err_1.message) : 'Unknown AI review error';
|
|
845
|
+
return [2 /*return*/, buildFallbackRiskReview(input.operation, "AI risk review failed: ".concat(truncateForRiskReview(detail, 160)), settings.model)];
|
|
846
|
+
case 4: return [2 /*return*/];
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
}
|
|
184
851
|
function loadMongoExplorerMethods(methodManager) {
|
|
185
852
|
methodManager.methods({
|
|
186
853
|
mongoExplorerListCollections: {
|
|
@@ -259,6 +926,47 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
259
926
|
});
|
|
260
927
|
}
|
|
261
928
|
},
|
|
929
|
+
mongoExplorerGetCollectionSchema: {
|
|
930
|
+
check: new simpl_schema_1.default({
|
|
931
|
+
database: {
|
|
932
|
+
type: String,
|
|
933
|
+
optional: true
|
|
934
|
+
},
|
|
935
|
+
collection: {
|
|
936
|
+
type: String
|
|
937
|
+
}
|
|
938
|
+
}),
|
|
939
|
+
function: function (database, collection) {
|
|
940
|
+
var dbName = resolveDatabaseName(database);
|
|
941
|
+
var managerCollection = resolveCollectionHandle(dbName, collection).managerCollection;
|
|
942
|
+
if (!managerCollection) {
|
|
943
|
+
return Promise.resolve({
|
|
944
|
+
collection: collection,
|
|
945
|
+
has_schema: false,
|
|
946
|
+
fields: []
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
var schema = getSchemaDefinition(managerCollection);
|
|
950
|
+
var fields = Object.keys(schema)
|
|
951
|
+
.sort(function (a, b) { return a.localeCompare(b); })
|
|
952
|
+
.map(function (path) {
|
|
953
|
+
var definition = schema[path];
|
|
954
|
+
return {
|
|
955
|
+
path: path,
|
|
956
|
+
label: toLabel(path),
|
|
957
|
+
type: getSchemaTypeName(definition),
|
|
958
|
+
optional: (definition === null || definition === void 0 ? void 0 : definition.optional) === true,
|
|
959
|
+
protected: isProtectedFieldPath(path),
|
|
960
|
+
depth: path.split('.').filter(function (segment) { return segment !== '$'; }).length - 1
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
return Promise.resolve({
|
|
964
|
+
collection: collection,
|
|
965
|
+
has_schema: true,
|
|
966
|
+
fields: fields
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
},
|
|
262
970
|
mongoExplorerAggregate: {
|
|
263
971
|
bypassSession: true,
|
|
264
972
|
check: new simpl_schema_1.default({
|
|
@@ -306,14 +1014,18 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
306
1014
|
permissionView: {
|
|
307
1015
|
type: String,
|
|
308
1016
|
optional: true
|
|
1017
|
+
},
|
|
1018
|
+
mode: {
|
|
1019
|
+
type: String,
|
|
1020
|
+
optional: true
|
|
309
1021
|
}
|
|
310
1022
|
}),
|
|
311
|
-
function: function (database, command, permissionView) {
|
|
1023
|
+
function: function (database, command, permissionView, mode) {
|
|
312
1024
|
return __awaiter(this, void 0, void 0, function () {
|
|
313
1025
|
var db;
|
|
314
1026
|
return __generator(this, function (_a) {
|
|
315
1027
|
switch (_a.label) {
|
|
316
|
-
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView)];
|
|
1028
|
+
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView, mode)];
|
|
317
1029
|
case 1:
|
|
318
1030
|
_a.sent();
|
|
319
1031
|
db = resolveDatabase(database);
|
|
@@ -339,14 +1051,18 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
339
1051
|
permissionView: {
|
|
340
1052
|
type: String,
|
|
341
1053
|
optional: true
|
|
1054
|
+
},
|
|
1055
|
+
mode: {
|
|
1056
|
+
type: String,
|
|
1057
|
+
optional: true
|
|
342
1058
|
}
|
|
343
1059
|
}),
|
|
344
|
-
function: function (database, collection, document, permissionView) {
|
|
1060
|
+
function: function (database, collection, document, permissionView, mode) {
|
|
345
1061
|
return __awaiter(this, void 0, void 0, function () {
|
|
346
1062
|
var dbName, _a, managerCollection, dbCollection;
|
|
347
1063
|
return __generator(this, function (_b) {
|
|
348
1064
|
switch (_b.label) {
|
|
349
|
-
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView)];
|
|
1065
|
+
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView, mode)];
|
|
350
1066
|
case 1:
|
|
351
1067
|
_b.sent();
|
|
352
1068
|
dbName = resolveDatabaseName(database);
|
|
@@ -354,6 +1070,9 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
354
1070
|
if (!document || typeof document !== 'object') {
|
|
355
1071
|
throw new Error('Mongo Explorer: Document is required');
|
|
356
1072
|
}
|
|
1073
|
+
if (!managerCollection && !allowUnschemaizedWrites()) {
|
|
1074
|
+
throw new Error('Mongo Explorer: Writes require schema-backed collections. Set MONGO_EXPLORER_ALLOW_UNSCHEMATIZED_WRITE=true to bypass.');
|
|
1075
|
+
}
|
|
357
1076
|
if (!document._id) {
|
|
358
1077
|
document._id = (0, common_1.objectIdHexString)();
|
|
359
1078
|
}
|
|
@@ -361,6 +1080,7 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
361
1080
|
document.__v = 0;
|
|
362
1081
|
}
|
|
363
1082
|
if (!managerCollection) return [3 /*break*/, 3];
|
|
1083
|
+
coerceDateFields(managerCollection, document);
|
|
364
1084
|
return [4 /*yield*/, managerCollection.insertOne(document)];
|
|
365
1085
|
case 2:
|
|
366
1086
|
_b.sent();
|
|
@@ -391,31 +1111,114 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
391
1111
|
permissionView: {
|
|
392
1112
|
type: String,
|
|
393
1113
|
optional: true
|
|
1114
|
+
},
|
|
1115
|
+
mode: {
|
|
1116
|
+
type: String,
|
|
1117
|
+
optional: true
|
|
394
1118
|
}
|
|
395
1119
|
}),
|
|
396
|
-
function: function (database, collection, document, permissionView) {
|
|
1120
|
+
function: function (database, collection, document, permissionView, mode) {
|
|
397
1121
|
return __awaiter(this, void 0, void 0, function () {
|
|
398
|
-
var dbName, _a, managerCollection, dbCollection;
|
|
399
|
-
return __generator(this, function (
|
|
400
|
-
switch (
|
|
401
|
-
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView)];
|
|
1122
|
+
var dbName, _a, managerCollection, dbCollection, currentDoc, _b;
|
|
1123
|
+
return __generator(this, function (_c) {
|
|
1124
|
+
switch (_c.label) {
|
|
1125
|
+
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView, mode)];
|
|
402
1126
|
case 1:
|
|
403
|
-
|
|
1127
|
+
_c.sent();
|
|
404
1128
|
dbName = resolveDatabaseName(database);
|
|
405
1129
|
_a = resolveCollectionHandle(dbName, collection), managerCollection = _a.managerCollection, dbCollection = _a.dbCollection;
|
|
406
1130
|
if (!document || typeof document !== 'object' || !document._id) {
|
|
407
1131
|
throw new Error('Mongo Explorer: Document with _id is required');
|
|
408
1132
|
}
|
|
409
1133
|
if (!managerCollection) return [3 /*break*/, 3];
|
|
410
|
-
return [4 /*yield*/, managerCollection.
|
|
1134
|
+
return [4 /*yield*/, managerCollection.findOne({ _id: document._id })];
|
|
411
1135
|
case 2:
|
|
412
|
-
_b.sent();
|
|
1136
|
+
_b = _c.sent();
|
|
413
1137
|
return [3 /*break*/, 5];
|
|
414
|
-
case 3: return [4 /*yield*/, dbCollection.
|
|
1138
|
+
case 3: return [4 /*yield*/, dbCollection.findOne({ _id: document._id })];
|
|
415
1139
|
case 4:
|
|
416
|
-
_b.sent();
|
|
417
|
-
|
|
418
|
-
case 5:
|
|
1140
|
+
_b = _c.sent();
|
|
1141
|
+
_c.label = 5;
|
|
1142
|
+
case 5:
|
|
1143
|
+
currentDoc = _b;
|
|
1144
|
+
if (!currentDoc) {
|
|
1145
|
+
throw new Error('Mongo Explorer: Document not found');
|
|
1146
|
+
}
|
|
1147
|
+
ensureProtectedFieldsUnchanged(currentDoc, document);
|
|
1148
|
+
if (!managerCollection && !allowUnschemaizedWrites()) {
|
|
1149
|
+
throw new Error('Mongo Explorer: Writes require schema-backed collections. Set MONGO_EXPLORER_ALLOW_UNSCHEMATIZED_WRITE=true to bypass.');
|
|
1150
|
+
}
|
|
1151
|
+
if (!managerCollection) return [3 /*break*/, 7];
|
|
1152
|
+
coerceDateFields(managerCollection, document);
|
|
1153
|
+
return [4 /*yield*/, managerCollection.replaceOne({ _id: document._id }, document)];
|
|
1154
|
+
case 6:
|
|
1155
|
+
_c.sent();
|
|
1156
|
+
return [3 /*break*/, 9];
|
|
1157
|
+
case 7: return [4 /*yield*/, dbCollection.replaceOne({ _id: document._id }, document)];
|
|
1158
|
+
case 8:
|
|
1159
|
+
_c.sent();
|
|
1160
|
+
_c.label = 9;
|
|
1161
|
+
case 9: return [2 /*return*/, true];
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
mongoExplorerReviewOperationRisk: {
|
|
1168
|
+
check: new simpl_schema_1.default({
|
|
1169
|
+
database: {
|
|
1170
|
+
type: String,
|
|
1171
|
+
optional: true
|
|
1172
|
+
},
|
|
1173
|
+
collection: {
|
|
1174
|
+
type: String
|
|
1175
|
+
},
|
|
1176
|
+
operation: {
|
|
1177
|
+
type: String,
|
|
1178
|
+
allowedValues: ['insert', 'replace', 'delete', 'command']
|
|
1179
|
+
},
|
|
1180
|
+
before_document: {
|
|
1181
|
+
type: Object,
|
|
1182
|
+
blackbox: true,
|
|
1183
|
+
optional: true
|
|
1184
|
+
},
|
|
1185
|
+
after_document: {
|
|
1186
|
+
type: Object,
|
|
1187
|
+
blackbox: true,
|
|
1188
|
+
optional: true
|
|
1189
|
+
},
|
|
1190
|
+
delete_impact: {
|
|
1191
|
+
type: Object,
|
|
1192
|
+
blackbox: true,
|
|
1193
|
+
optional: true
|
|
1194
|
+
},
|
|
1195
|
+
permissionView: {
|
|
1196
|
+
type: String,
|
|
1197
|
+
optional: true
|
|
1198
|
+
},
|
|
1199
|
+
mode: {
|
|
1200
|
+
type: String,
|
|
1201
|
+
optional: true
|
|
1202
|
+
}
|
|
1203
|
+
}),
|
|
1204
|
+
function: function (database, collection, operation, before_document, after_document, delete_impact, permissionView, mode) {
|
|
1205
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1206
|
+
var dbName;
|
|
1207
|
+
return __generator(this, function (_a) {
|
|
1208
|
+
switch (_a.label) {
|
|
1209
|
+
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView, mode)];
|
|
1210
|
+
case 1:
|
|
1211
|
+
_a.sent();
|
|
1212
|
+
dbName = resolveDatabaseName(database);
|
|
1213
|
+
return [2 /*return*/, reviewOperationRisk({
|
|
1214
|
+
database: dbName,
|
|
1215
|
+
collection: collection,
|
|
1216
|
+
operation: operation,
|
|
1217
|
+
mode: normalizeExplorerMode(mode),
|
|
1218
|
+
before_document: before_document,
|
|
1219
|
+
after_document: after_document,
|
|
1220
|
+
delete_impact: delete_impact
|
|
1221
|
+
})];
|
|
419
1222
|
}
|
|
420
1223
|
});
|
|
421
1224
|
});
|
|
@@ -436,33 +1239,75 @@ function loadMongoExplorerMethods(methodManager) {
|
|
|
436
1239
|
permissionView: {
|
|
437
1240
|
type: String,
|
|
438
1241
|
optional: true
|
|
1242
|
+
},
|
|
1243
|
+
force: {
|
|
1244
|
+
type: Boolean,
|
|
1245
|
+
optional: true
|
|
1246
|
+
},
|
|
1247
|
+
mode: {
|
|
1248
|
+
type: String,
|
|
1249
|
+
optional: true
|
|
439
1250
|
}
|
|
440
1251
|
}),
|
|
441
|
-
function: function (
|
|
442
|
-
return __awaiter(this,
|
|
443
|
-
var dbName, _a, managerCollection, dbCollection, deleteFilter;
|
|
1252
|
+
function: function (database_1, collection_1, doc_id_1, permissionView_1) {
|
|
1253
|
+
return __awaiter(this, arguments, void 0, function (database, collection, doc_id, permissionView, force, mode) {
|
|
1254
|
+
var dbName, impact, collectionPreview, _a, managerCollection, dbCollection, deleteFilter;
|
|
1255
|
+
if (force === void 0) { force = false; }
|
|
444
1256
|
return __generator(this, function (_b) {
|
|
445
1257
|
switch (_b.label) {
|
|
446
|
-
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView)];
|
|
1258
|
+
case 0: return [4 /*yield*/, ensureWriteAccess(this, permissionView, mode)];
|
|
447
1259
|
case 1:
|
|
448
1260
|
_b.sent();
|
|
449
1261
|
dbName = resolveDatabaseName(database);
|
|
1262
|
+
return [4 /*yield*/, buildDeleteImpact(dbName, collection, doc_id)];
|
|
1263
|
+
case 2:
|
|
1264
|
+
impact = _b.sent();
|
|
1265
|
+
if (!force && (impact.unresolved || impact.has_references)) {
|
|
1266
|
+
collectionPreview = impact.collections.slice(0, 3).map(function (item) { return item.collection; }).join(', ');
|
|
1267
|
+
if (impact.unresolved) {
|
|
1268
|
+
throw new Error('Mongo Explorer: Delete blocked because dependency checks could not complete. Use force delete after review.');
|
|
1269
|
+
}
|
|
1270
|
+
throw new Error("Mongo Explorer: Delete blocked. Document is referenced by ".concat(collectionPreview).concat(impact.collections.length > 3 ? ', ...' : ''));
|
|
1271
|
+
}
|
|
450
1272
|
_a = resolveCollectionHandle(dbName, collection), managerCollection = _a.managerCollection, dbCollection = _a.dbCollection;
|
|
451
1273
|
deleteFilter = { _id: doc_id };
|
|
452
|
-
if (!managerCollection) return [3 /*break*/,
|
|
1274
|
+
if (!managerCollection) return [3 /*break*/, 4];
|
|
453
1275
|
return [4 /*yield*/, managerCollection.deleteOne(deleteFilter)];
|
|
454
|
-
case
|
|
1276
|
+
case 3:
|
|
455
1277
|
_b.sent();
|
|
456
|
-
return [3 /*break*/,
|
|
457
|
-
case
|
|
458
|
-
case
|
|
1278
|
+
return [3 /*break*/, 6];
|
|
1279
|
+
case 4: return [4 /*yield*/, dbCollection.deleteOne(deleteFilter)];
|
|
1280
|
+
case 5:
|
|
459
1281
|
_b.sent();
|
|
460
|
-
_b.label =
|
|
461
|
-
case
|
|
1282
|
+
_b.label = 6;
|
|
1283
|
+
case 6: return [2 /*return*/, true];
|
|
462
1284
|
}
|
|
463
1285
|
});
|
|
464
1286
|
});
|
|
465
1287
|
}
|
|
1288
|
+
},
|
|
1289
|
+
mongoExplorerDeleteImpact: {
|
|
1290
|
+
check: new simpl_schema_1.default({
|
|
1291
|
+
database: {
|
|
1292
|
+
type: String,
|
|
1293
|
+
optional: true
|
|
1294
|
+
},
|
|
1295
|
+
collection: {
|
|
1296
|
+
type: String
|
|
1297
|
+
},
|
|
1298
|
+
doc_id: {
|
|
1299
|
+
type: String
|
|
1300
|
+
}
|
|
1301
|
+
}),
|
|
1302
|
+
function: function (database, collection, doc_id) {
|
|
1303
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1304
|
+
var dbName;
|
|
1305
|
+
return __generator(this, function (_a) {
|
|
1306
|
+
dbName = resolveDatabaseName(database);
|
|
1307
|
+
return [2 /*return*/, buildDeleteImpact(dbName, collection, doc_id)];
|
|
1308
|
+
});
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
466
1311
|
}
|
|
467
1312
|
});
|
|
468
1313
|
}
|