@emulators/mongoatlas 0.4.0
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/LICENSE +202 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +969 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
// src/store.ts
|
|
2
|
+
function getMongoAtlasStore(store) {
|
|
3
|
+
return {
|
|
4
|
+
clusters: store.collection("mongoatlas.clusters", ["cluster_id", "name"]),
|
|
5
|
+
databases: store.collection("mongoatlas.databases", ["cluster_id", "name"]),
|
|
6
|
+
collections: store.collection("mongoatlas.collections", ["cluster_id", "database", "name"]),
|
|
7
|
+
documents: store.collection("mongoatlas.documents", ["cluster_id", "doc_id"]),
|
|
8
|
+
projects: store.collection("mongoatlas.projects", ["group_id"]),
|
|
9
|
+
users: store.collection("mongoatlas.users", ["user_id", "username"])
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/helpers.ts
|
|
14
|
+
import { randomBytes } from "crypto";
|
|
15
|
+
function generateObjectId() {
|
|
16
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString(16).padStart(8, "0");
|
|
17
|
+
const random = randomBytes(8).toString("hex").slice(0, 16);
|
|
18
|
+
return (timestamp + random).slice(0, 24);
|
|
19
|
+
}
|
|
20
|
+
function generateClusterId() {
|
|
21
|
+
return randomBytes(12).toString("hex");
|
|
22
|
+
}
|
|
23
|
+
function generateGroupId() {
|
|
24
|
+
return randomBytes(12).toString("hex");
|
|
25
|
+
}
|
|
26
|
+
function generateUserId() {
|
|
27
|
+
return randomBytes(12).toString("hex");
|
|
28
|
+
}
|
|
29
|
+
function mongoOk(c, data, status = 200) {
|
|
30
|
+
return c.json(data, status);
|
|
31
|
+
}
|
|
32
|
+
function mongoError(c, errorCode, detail, status = 400) {
|
|
33
|
+
return c.json({ error: status, errorCode, detail }, status);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/routes/data-api.ts
|
|
37
|
+
function dataApiRoutes(ctx) {
|
|
38
|
+
const { app, store } = ctx;
|
|
39
|
+
const ms = () => getMongoAtlasStore(store);
|
|
40
|
+
app.post("/app/data-api/v1/action/findOne", async (c) => {
|
|
41
|
+
const body = await c.req.json();
|
|
42
|
+
if (!body.dataSource || !body.database || !body.collection) {
|
|
43
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
|
|
44
|
+
}
|
|
45
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
46
|
+
if (!cluster) {
|
|
47
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
48
|
+
}
|
|
49
|
+
const docs = ms().documents.all().filter(
|
|
50
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
51
|
+
);
|
|
52
|
+
const matched = matchFilter(docs, body.filter) ?? docs;
|
|
53
|
+
const doc = matched[0] ?? null;
|
|
54
|
+
const projected = doc ? applyProjection(doc.data, body.projection) : null;
|
|
55
|
+
return mongoOk(c, { document: projected });
|
|
56
|
+
});
|
|
57
|
+
app.post("/app/data-api/v1/action/find", async (c) => {
|
|
58
|
+
const body = await c.req.json();
|
|
59
|
+
if (!body.dataSource || !body.database || !body.collection) {
|
|
60
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
|
|
61
|
+
}
|
|
62
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
63
|
+
if (!cluster) {
|
|
64
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
65
|
+
}
|
|
66
|
+
let docs = ms().documents.all().filter(
|
|
67
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
68
|
+
);
|
|
69
|
+
docs = matchFilter(docs, body.filter) ?? docs;
|
|
70
|
+
if (body.sort) {
|
|
71
|
+
docs = sortBySpec(docs, body.sort, (d) => d.data);
|
|
72
|
+
}
|
|
73
|
+
if (body.skip) {
|
|
74
|
+
docs = docs.slice(body.skip);
|
|
75
|
+
}
|
|
76
|
+
if (body.limit) {
|
|
77
|
+
docs = docs.slice(0, body.limit);
|
|
78
|
+
}
|
|
79
|
+
const documents = docs.map((d) => applyProjection(d.data, body.projection));
|
|
80
|
+
return mongoOk(c, { documents });
|
|
81
|
+
});
|
|
82
|
+
app.post("/app/data-api/v1/action/insertOne", async (c) => {
|
|
83
|
+
const body = await c.req.json();
|
|
84
|
+
if (!body.dataSource || !body.database || !body.collection || !body.document) {
|
|
85
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, collection, and document are required");
|
|
86
|
+
}
|
|
87
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
88
|
+
if (!cluster) {
|
|
89
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
90
|
+
}
|
|
91
|
+
ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
|
|
92
|
+
const docId = body.document._id ?? generateObjectId();
|
|
93
|
+
const data = { ...body.document, _id: docId };
|
|
94
|
+
ms().documents.insert({
|
|
95
|
+
cluster_id: cluster.cluster_id,
|
|
96
|
+
database: body.database,
|
|
97
|
+
collection: body.collection,
|
|
98
|
+
doc_id: docId,
|
|
99
|
+
data
|
|
100
|
+
});
|
|
101
|
+
return mongoOk(c, { insertedId: docId }, 201);
|
|
102
|
+
});
|
|
103
|
+
app.post("/app/data-api/v1/action/insertMany", async (c) => {
|
|
104
|
+
const body = await c.req.json();
|
|
105
|
+
if (!body.dataSource || !body.database || !body.collection || !body.documents) {
|
|
106
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, collection, and documents are required");
|
|
107
|
+
}
|
|
108
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
109
|
+
if (!cluster) {
|
|
110
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
111
|
+
}
|
|
112
|
+
ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
|
|
113
|
+
const insertedIds = [];
|
|
114
|
+
for (const doc of body.documents) {
|
|
115
|
+
const docId = doc._id ?? generateObjectId();
|
|
116
|
+
const data = { ...doc, _id: docId };
|
|
117
|
+
ms().documents.insert({
|
|
118
|
+
cluster_id: cluster.cluster_id,
|
|
119
|
+
database: body.database,
|
|
120
|
+
collection: body.collection,
|
|
121
|
+
doc_id: docId,
|
|
122
|
+
data
|
|
123
|
+
});
|
|
124
|
+
insertedIds.push(docId);
|
|
125
|
+
}
|
|
126
|
+
return mongoOk(c, { insertedIds }, 201);
|
|
127
|
+
});
|
|
128
|
+
app.post("/app/data-api/v1/action/updateOne", async (c) => {
|
|
129
|
+
const body = await c.req.json();
|
|
130
|
+
if (!body.dataSource || !body.database || !body.collection || !body.update) {
|
|
131
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, collection, and update are required");
|
|
132
|
+
}
|
|
133
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
134
|
+
if (!cluster) {
|
|
135
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
136
|
+
}
|
|
137
|
+
const docs = ms().documents.all().filter(
|
|
138
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
139
|
+
);
|
|
140
|
+
const matched = matchFilter(docs, body.filter) ?? docs;
|
|
141
|
+
const doc = matched[0];
|
|
142
|
+
if (doc) {
|
|
143
|
+
const updatedData = applyUpdate(doc.data, body.update);
|
|
144
|
+
ms().documents.update(doc.id, { data: updatedData });
|
|
145
|
+
return mongoOk(c, { matchedCount: 1, modifiedCount: 1 });
|
|
146
|
+
}
|
|
147
|
+
if (body.upsert) {
|
|
148
|
+
ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
|
|
149
|
+
const docId = generateObjectId();
|
|
150
|
+
const baseDoc = extractEqualityFields(body.filter ?? {});
|
|
151
|
+
const data = applyUpdate({ _id: docId, ...baseDoc }, body.update);
|
|
152
|
+
ms().documents.insert({
|
|
153
|
+
cluster_id: cluster.cluster_id,
|
|
154
|
+
database: body.database,
|
|
155
|
+
collection: body.collection,
|
|
156
|
+
doc_id: docId,
|
|
157
|
+
data
|
|
158
|
+
});
|
|
159
|
+
return mongoOk(c, { matchedCount: 0, modifiedCount: 0, upsertedId: docId });
|
|
160
|
+
}
|
|
161
|
+
return mongoOk(c, { matchedCount: 0, modifiedCount: 0 });
|
|
162
|
+
});
|
|
163
|
+
app.post("/app/data-api/v1/action/updateMany", async (c) => {
|
|
164
|
+
const body = await c.req.json();
|
|
165
|
+
if (!body.dataSource || !body.database || !body.collection || !body.update) {
|
|
166
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, collection, and update are required");
|
|
167
|
+
}
|
|
168
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
169
|
+
if (!cluster) {
|
|
170
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
171
|
+
}
|
|
172
|
+
const docs = ms().documents.all().filter(
|
|
173
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
174
|
+
);
|
|
175
|
+
const matched = matchFilter(docs, body.filter) ?? docs;
|
|
176
|
+
let modifiedCount = 0;
|
|
177
|
+
for (const doc of matched) {
|
|
178
|
+
const updatedData = applyUpdate(doc.data, body.update);
|
|
179
|
+
ms().documents.update(doc.id, { data: updatedData });
|
|
180
|
+
modifiedCount++;
|
|
181
|
+
}
|
|
182
|
+
if (matched.length === 0 && body.upsert) {
|
|
183
|
+
ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
|
|
184
|
+
const docId = generateObjectId();
|
|
185
|
+
const baseDoc = extractEqualityFields(body.filter ?? {});
|
|
186
|
+
const data = applyUpdate({ _id: docId, ...baseDoc }, body.update);
|
|
187
|
+
ms().documents.insert({
|
|
188
|
+
cluster_id: cluster.cluster_id,
|
|
189
|
+
database: body.database,
|
|
190
|
+
collection: body.collection,
|
|
191
|
+
doc_id: docId,
|
|
192
|
+
data
|
|
193
|
+
});
|
|
194
|
+
return mongoOk(c, { matchedCount: 0, modifiedCount: 0, upsertedId: docId });
|
|
195
|
+
}
|
|
196
|
+
return mongoOk(c, { matchedCount: matched.length, modifiedCount });
|
|
197
|
+
});
|
|
198
|
+
app.post("/app/data-api/v1/action/deleteOne", async (c) => {
|
|
199
|
+
const body = await c.req.json();
|
|
200
|
+
if (!body.dataSource || !body.database || !body.collection) {
|
|
201
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
|
|
202
|
+
}
|
|
203
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
204
|
+
if (!cluster) {
|
|
205
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
206
|
+
}
|
|
207
|
+
const docs = ms().documents.all().filter(
|
|
208
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
209
|
+
);
|
|
210
|
+
const matched = matchFilter(docs, body.filter) ?? docs;
|
|
211
|
+
const doc = matched[0];
|
|
212
|
+
if (doc) {
|
|
213
|
+
ms().documents.delete(doc.id);
|
|
214
|
+
return mongoOk(c, { deletedCount: 1 });
|
|
215
|
+
}
|
|
216
|
+
return mongoOk(c, { deletedCount: 0 });
|
|
217
|
+
});
|
|
218
|
+
app.post("/app/data-api/v1/action/deleteMany", async (c) => {
|
|
219
|
+
const body = await c.req.json();
|
|
220
|
+
if (!body.dataSource || !body.database || !body.collection) {
|
|
221
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
|
|
222
|
+
}
|
|
223
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
224
|
+
if (!cluster) {
|
|
225
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
226
|
+
}
|
|
227
|
+
const docs = ms().documents.all().filter(
|
|
228
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
229
|
+
);
|
|
230
|
+
const matched = matchFilter(docs, body.filter) ?? docs;
|
|
231
|
+
let deletedCount = 0;
|
|
232
|
+
for (const doc of matched) {
|
|
233
|
+
ms().documents.delete(doc.id);
|
|
234
|
+
deletedCount++;
|
|
235
|
+
}
|
|
236
|
+
return mongoOk(c, { deletedCount });
|
|
237
|
+
});
|
|
238
|
+
app.post("/app/data-api/v1/action/aggregate", async (c) => {
|
|
239
|
+
const body = await c.req.json();
|
|
240
|
+
if (!body.dataSource || !body.database || !body.collection) {
|
|
241
|
+
return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
|
|
242
|
+
}
|
|
243
|
+
const cluster = ms().clusters.findOneBy("name", body.dataSource);
|
|
244
|
+
if (!cluster) {
|
|
245
|
+
return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
|
|
246
|
+
}
|
|
247
|
+
let docs = ms().documents.all().filter(
|
|
248
|
+
(d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
|
|
249
|
+
);
|
|
250
|
+
const pipeline = body.pipeline ?? [];
|
|
251
|
+
let results = docs.map((d) => d.data);
|
|
252
|
+
for (const stage of pipeline) {
|
|
253
|
+
if ("$match" in stage) {
|
|
254
|
+
const filter = stage.$match;
|
|
255
|
+
results = results.filter((d) => matchesFilter(d, filter));
|
|
256
|
+
} else if ("$limit" in stage) {
|
|
257
|
+
results = results.slice(0, stage.$limit);
|
|
258
|
+
} else if ("$skip" in stage) {
|
|
259
|
+
results = results.slice(stage.$skip);
|
|
260
|
+
} else if ("$sort" in stage) {
|
|
261
|
+
const sortSpec = stage.$sort;
|
|
262
|
+
results = sortBySpec(results, sortSpec, (d) => d);
|
|
263
|
+
} else if ("$project" in stage) {
|
|
264
|
+
const projection = stage.$project;
|
|
265
|
+
results = results.map((d) => applyProjection(d, projection));
|
|
266
|
+
} else if ("$count" in stage) {
|
|
267
|
+
const fieldName = stage.$count;
|
|
268
|
+
results = [{ [fieldName]: results.length }];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return mongoOk(c, { documents: results });
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function ensureCollectionExists(ms, clusterId, database, collection) {
|
|
275
|
+
const existing = ms().collections.all().find(
|
|
276
|
+
(col) => col.cluster_id === clusterId && col.database === database && col.name === collection
|
|
277
|
+
);
|
|
278
|
+
if (!existing) {
|
|
279
|
+
const dbExists = ms().databases.all().find(
|
|
280
|
+
(db) => db.cluster_id === clusterId && db.name === database
|
|
281
|
+
);
|
|
282
|
+
if (!dbExists) {
|
|
283
|
+
ms().databases.insert({ cluster_id: clusterId, name: database });
|
|
284
|
+
}
|
|
285
|
+
ms().collections.insert({ cluster_id: clusterId, database, name: collection });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function matchesFilter(data, filter) {
|
|
289
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
290
|
+
if (key === "$and") {
|
|
291
|
+
const conditions = value;
|
|
292
|
+
if (!conditions.every((cond) => matchesFilter(data, cond))) return false;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (key === "$or") {
|
|
296
|
+
const conditions = value;
|
|
297
|
+
if (!conditions.some((cond) => matchesFilter(data, cond))) return false;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (key === "$nor") {
|
|
301
|
+
const conditions = value;
|
|
302
|
+
if (conditions.some((cond) => matchesFilter(data, cond))) return false;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const docValue = getNestedValue(data, key);
|
|
306
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
307
|
+
const ops = value;
|
|
308
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
309
|
+
switch (op) {
|
|
310
|
+
case "$eq":
|
|
311
|
+
if (docValue !== opVal) return false;
|
|
312
|
+
break;
|
|
313
|
+
case "$ne":
|
|
314
|
+
if (docValue === opVal) return false;
|
|
315
|
+
break;
|
|
316
|
+
case "$gt":
|
|
317
|
+
if (typeof docValue !== "number" || typeof opVal !== "number" || docValue <= opVal) return false;
|
|
318
|
+
break;
|
|
319
|
+
case "$gte":
|
|
320
|
+
if (typeof docValue !== "number" || typeof opVal !== "number" || docValue < opVal) return false;
|
|
321
|
+
break;
|
|
322
|
+
case "$lt":
|
|
323
|
+
if (typeof docValue !== "number" || typeof opVal !== "number" || docValue >= opVal) return false;
|
|
324
|
+
break;
|
|
325
|
+
case "$lte":
|
|
326
|
+
if (typeof docValue !== "number" || typeof opVal !== "number" || docValue > opVal) return false;
|
|
327
|
+
break;
|
|
328
|
+
case "$in":
|
|
329
|
+
if (!Array.isArray(opVal) || !opVal.includes(docValue)) return false;
|
|
330
|
+
break;
|
|
331
|
+
case "$nin":
|
|
332
|
+
if (!Array.isArray(opVal) || opVal.includes(docValue)) return false;
|
|
333
|
+
break;
|
|
334
|
+
case "$exists":
|
|
335
|
+
if (opVal && docValue === void 0) return false;
|
|
336
|
+
if (!opVal && docValue !== void 0) return false;
|
|
337
|
+
break;
|
|
338
|
+
case "$regex": {
|
|
339
|
+
const pattern = opVal;
|
|
340
|
+
const flags = ops.$options ?? "";
|
|
341
|
+
try {
|
|
342
|
+
if (pattern.length > 1e3) return false;
|
|
343
|
+
const re = new RegExp(pattern, flags);
|
|
344
|
+
if (typeof docValue !== "string" || !re.test(docValue)) return false;
|
|
345
|
+
} catch {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
if (docValue !== value) return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
function getNestedValue(obj, path) {
|
|
359
|
+
const parts = path.split(".");
|
|
360
|
+
if (hasDangerousKey(parts)) return void 0;
|
|
361
|
+
let current = obj;
|
|
362
|
+
for (const part of parts) {
|
|
363
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
364
|
+
current = current[part];
|
|
365
|
+
}
|
|
366
|
+
return current;
|
|
367
|
+
}
|
|
368
|
+
function matchFilter(docs, filter) {
|
|
369
|
+
if (!filter || Object.keys(filter).length === 0) return null;
|
|
370
|
+
return docs.filter((d) => matchesFilter(d.data, filter));
|
|
371
|
+
}
|
|
372
|
+
function applyProjection(data, projection) {
|
|
373
|
+
if (!projection || Object.keys(projection).length === 0) return data;
|
|
374
|
+
const hasInclusions = Object.values(projection).some((v) => v === 1 || v === true);
|
|
375
|
+
if (hasInclusions) {
|
|
376
|
+
const result2 = {};
|
|
377
|
+
if (projection._id !== 0 && projection._id !== false) {
|
|
378
|
+
result2._id = data._id;
|
|
379
|
+
}
|
|
380
|
+
for (const [key, val] of Object.entries(projection)) {
|
|
381
|
+
if (key === "_id") continue;
|
|
382
|
+
if (val === 1 || val === true) {
|
|
383
|
+
result2[key] = data[key];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return result2;
|
|
387
|
+
}
|
|
388
|
+
const result = { ...data };
|
|
389
|
+
for (const [key, val] of Object.entries(projection)) {
|
|
390
|
+
if (val === 0 || val === false) {
|
|
391
|
+
delete result[key];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
function applyUpdate(data, update) {
|
|
397
|
+
const result = { ...data };
|
|
398
|
+
if ("$set" in update) {
|
|
399
|
+
const setFields = update.$set;
|
|
400
|
+
for (const [key, value] of Object.entries(setFields)) {
|
|
401
|
+
setNestedValue(result, key, value);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if ("$unset" in update) {
|
|
405
|
+
const unsetFields = update.$unset;
|
|
406
|
+
for (const key of Object.keys(unsetFields)) {
|
|
407
|
+
const parts = key.split(".");
|
|
408
|
+
if (hasDangerousKey(parts)) continue;
|
|
409
|
+
if (parts.length === 1) {
|
|
410
|
+
delete result[key];
|
|
411
|
+
} else {
|
|
412
|
+
let current = result;
|
|
413
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
414
|
+
if (current[parts[i]] === null || current[parts[i]] === void 0 || typeof current[parts[i]] !== "object") break;
|
|
415
|
+
current = current[parts[i]];
|
|
416
|
+
}
|
|
417
|
+
delete current[parts[parts.length - 1]];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if ("$inc" in update) {
|
|
422
|
+
const incFields = update.$inc;
|
|
423
|
+
for (const [key, value] of Object.entries(incFields)) {
|
|
424
|
+
const current = getNestedValue(result, key) ?? 0;
|
|
425
|
+
setNestedValue(result, key, current + value);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if ("$push" in update) {
|
|
429
|
+
const pushFields = update.$push;
|
|
430
|
+
for (const [key, value] of Object.entries(pushFields)) {
|
|
431
|
+
const current = getNestedValue(result, key);
|
|
432
|
+
if (!Array.isArray(current)) {
|
|
433
|
+
setNestedValue(result, key, [value]);
|
|
434
|
+
} else {
|
|
435
|
+
setNestedValue(result, key, [...current, value]);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if ("$pull" in update) {
|
|
440
|
+
const pullFields = update.$pull;
|
|
441
|
+
for (const [key, value] of Object.entries(pullFields)) {
|
|
442
|
+
const current = getNestedValue(result, key);
|
|
443
|
+
if (Array.isArray(current)) {
|
|
444
|
+
setNestedValue(result, key, current.filter((item) => item !== value));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if ("$rename" in update) {
|
|
449
|
+
const renameFields = update.$rename;
|
|
450
|
+
for (const [oldKey, newKey] of Object.entries(renameFields)) {
|
|
451
|
+
const oldParts = oldKey.split(".");
|
|
452
|
+
const newParts = newKey.split(".");
|
|
453
|
+
if (hasDangerousKey(oldParts) || hasDangerousKey(newParts)) continue;
|
|
454
|
+
const value = getNestedValue(result, oldKey);
|
|
455
|
+
if (value !== void 0) {
|
|
456
|
+
setNestedValue(result, newKey, value);
|
|
457
|
+
if (oldParts.length === 1) {
|
|
458
|
+
delete result[oldKey];
|
|
459
|
+
} else {
|
|
460
|
+
let current = result;
|
|
461
|
+
for (let i = 0; i < oldParts.length - 1; i++) {
|
|
462
|
+
if (typeof current[oldParts[i]] !== "object" || current[oldParts[i]] === null) break;
|
|
463
|
+
current = current[oldParts[i]];
|
|
464
|
+
}
|
|
465
|
+
delete current[oldParts[oldParts.length - 1]];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const hasOperators = Object.keys(update).some((k) => k.startsWith("$"));
|
|
471
|
+
if (!hasOperators) {
|
|
472
|
+
const id = result._id;
|
|
473
|
+
for (const key of Object.keys(result)) {
|
|
474
|
+
delete result[key];
|
|
475
|
+
}
|
|
476
|
+
result._id = id;
|
|
477
|
+
Object.assign(result, update);
|
|
478
|
+
}
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
482
|
+
function hasDangerousKey(parts) {
|
|
483
|
+
return parts.some((p) => DANGEROUS_KEYS.has(p));
|
|
484
|
+
}
|
|
485
|
+
function setNestedValue(obj, path, value) {
|
|
486
|
+
const parts = path.split(".");
|
|
487
|
+
if (hasDangerousKey(parts)) return;
|
|
488
|
+
let current = obj;
|
|
489
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
490
|
+
if (!(parts[i] in current) || typeof current[parts[i]] !== "object") {
|
|
491
|
+
current[parts[i]] = {};
|
|
492
|
+
}
|
|
493
|
+
current = current[parts[i]];
|
|
494
|
+
}
|
|
495
|
+
current[parts[parts.length - 1]] = value;
|
|
496
|
+
}
|
|
497
|
+
function sortBySpec(docs, sortSpec, accessor) {
|
|
498
|
+
return [...docs].sort((a, b) => {
|
|
499
|
+
for (const [key, direction] of Object.entries(sortSpec)) {
|
|
500
|
+
const aVal = getNestedValue(accessor(a), key);
|
|
501
|
+
const bVal = getNestedValue(accessor(b), key);
|
|
502
|
+
if (aVal === bVal) continue;
|
|
503
|
+
if (aVal === void 0) return direction;
|
|
504
|
+
if (bVal === void 0) return -direction;
|
|
505
|
+
if (aVal < bVal) return -direction;
|
|
506
|
+
if (aVal > bVal) return direction;
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function extractEqualityFields(filter) {
|
|
512
|
+
const result = {};
|
|
513
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
514
|
+
if (key.startsWith("$")) continue;
|
|
515
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
516
|
+
const ops = value;
|
|
517
|
+
if (Object.keys(ops).some((k) => k.startsWith("$"))) continue;
|
|
518
|
+
}
|
|
519
|
+
result[key] = value;
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/routes/admin.ts
|
|
525
|
+
function adminRoutes(ctx) {
|
|
526
|
+
const { app, store } = ctx;
|
|
527
|
+
const ms = () => getMongoAtlasStore(store);
|
|
528
|
+
app.get("/api/atlas/v2/groups", (c) => {
|
|
529
|
+
const projects = ms().projects.all();
|
|
530
|
+
return mongoOk(c, {
|
|
531
|
+
results: projects.map(formatProject),
|
|
532
|
+
totalCount: projects.length
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
app.get("/api/atlas/v2/groups/:groupId", (c) => {
|
|
536
|
+
const groupId = c.req.param("groupId");
|
|
537
|
+
const project = ms().projects.findOneBy("group_id", groupId);
|
|
538
|
+
if (!project) {
|
|
539
|
+
return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
|
|
540
|
+
}
|
|
541
|
+
return mongoOk(c, formatProject(project));
|
|
542
|
+
});
|
|
543
|
+
app.post("/api/atlas/v2/groups", async (c) => {
|
|
544
|
+
const body = await c.req.json();
|
|
545
|
+
if (!body.name?.trim()) {
|
|
546
|
+
return mongoError(c, "INVALID_PARAMETER", "name is required");
|
|
547
|
+
}
|
|
548
|
+
const existing = ms().projects.all().find((p) => p.name === body.name);
|
|
549
|
+
if (existing) {
|
|
550
|
+
return mongoError(c, "DUPLICATE_GROUP_NAME", `Group name '${body.name}' already exists.`, 409);
|
|
551
|
+
}
|
|
552
|
+
const groupId = generateGroupId();
|
|
553
|
+
const project = ms().projects.insert({
|
|
554
|
+
group_id: groupId,
|
|
555
|
+
name: body.name,
|
|
556
|
+
org_id: body.orgId ?? "default_org",
|
|
557
|
+
cluster_count: 0
|
|
558
|
+
});
|
|
559
|
+
return mongoOk(c, formatProject(project), 201);
|
|
560
|
+
});
|
|
561
|
+
app.delete("/api/atlas/v2/groups/:groupId", (c) => {
|
|
562
|
+
const groupId = c.req.param("groupId");
|
|
563
|
+
const project = ms().projects.findOneBy("group_id", groupId);
|
|
564
|
+
if (!project) {
|
|
565
|
+
return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
|
|
566
|
+
}
|
|
567
|
+
const clusters = ms().clusters.all().filter((cl) => cl.group_id === groupId);
|
|
568
|
+
for (const cluster of clusters) {
|
|
569
|
+
deleteClusterData(ms, cluster.cluster_id);
|
|
570
|
+
ms().clusters.delete(cluster.id);
|
|
571
|
+
}
|
|
572
|
+
ms().projects.delete(project.id);
|
|
573
|
+
return c.body(null, 204);
|
|
574
|
+
});
|
|
575
|
+
app.get("/api/atlas/v2/groups/:groupId/clusters", (c) => {
|
|
576
|
+
const groupId = c.req.param("groupId");
|
|
577
|
+
const project = ms().projects.findOneBy("group_id", groupId);
|
|
578
|
+
if (!project) {
|
|
579
|
+
return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
|
|
580
|
+
}
|
|
581
|
+
const clusters = ms().clusters.all().filter((cl) => cl.group_id === groupId);
|
|
582
|
+
return mongoOk(c, {
|
|
583
|
+
results: clusters.map(formatCluster),
|
|
584
|
+
totalCount: clusters.length
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName", (c) => {
|
|
588
|
+
const groupId = c.req.param("groupId");
|
|
589
|
+
const clusterName = c.req.param("clusterName");
|
|
590
|
+
const cluster = ms().clusters.all().find(
|
|
591
|
+
(cl) => cl.group_id === groupId && cl.name === clusterName
|
|
592
|
+
);
|
|
593
|
+
if (!cluster) {
|
|
594
|
+
return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
|
|
595
|
+
}
|
|
596
|
+
return mongoOk(c, formatCluster(cluster));
|
|
597
|
+
});
|
|
598
|
+
app.post("/api/atlas/v2/groups/:groupId/clusters", async (c) => {
|
|
599
|
+
const groupId = c.req.param("groupId");
|
|
600
|
+
const project = ms().projects.findOneBy("group_id", groupId);
|
|
601
|
+
if (!project) {
|
|
602
|
+
return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
|
|
603
|
+
}
|
|
604
|
+
const body = await c.req.json();
|
|
605
|
+
if (!body.name?.trim()) {
|
|
606
|
+
return mongoError(c, "INVALID_PARAMETER", "name is required");
|
|
607
|
+
}
|
|
608
|
+
const existing = ms().clusters.all().find(
|
|
609
|
+
(cl) => cl.group_id === groupId && cl.name === body.name
|
|
610
|
+
);
|
|
611
|
+
if (existing) {
|
|
612
|
+
return mongoError(c, "DUPLICATE_CLUSTER_NAME", `Cluster '${body.name}' already exists.`, 409);
|
|
613
|
+
}
|
|
614
|
+
const clusterId = generateClusterId();
|
|
615
|
+
const cluster = ms().clusters.insert({
|
|
616
|
+
cluster_id: clusterId,
|
|
617
|
+
name: body.name,
|
|
618
|
+
group_id: groupId,
|
|
619
|
+
state: "IDLE",
|
|
620
|
+
mongo_uri: `mongodb+srv://${body.name}.emulate.mongodb.net`,
|
|
621
|
+
connection_strings: {
|
|
622
|
+
standard: `mongodb://${body.name}.emulate.mongodb.net:27017`,
|
|
623
|
+
standard_srv: `mongodb+srv://${body.name}.emulate.mongodb.net`
|
|
624
|
+
},
|
|
625
|
+
provider_settings: {
|
|
626
|
+
provider_name: body.providerSettings?.providerName ?? "AWS",
|
|
627
|
+
instance_size_name: body.providerSettings?.instanceSizeName ?? "M10",
|
|
628
|
+
region_name: body.providerSettings?.regionName ?? "US_EAST_1"
|
|
629
|
+
},
|
|
630
|
+
cluster_type: body.clusterType ?? "REPLICASET",
|
|
631
|
+
disk_size_gb: body.diskSizeGB ?? 10,
|
|
632
|
+
mongodb_version: body.mongoDBMajorVersion ?? "8.0"
|
|
633
|
+
});
|
|
634
|
+
ms().projects.update(project.id, { cluster_count: project.cluster_count + 1 });
|
|
635
|
+
return mongoOk(c, formatCluster(cluster), 201);
|
|
636
|
+
});
|
|
637
|
+
app.patch("/api/atlas/v2/groups/:groupId/clusters/:clusterName", async (c) => {
|
|
638
|
+
const groupId = c.req.param("groupId");
|
|
639
|
+
const clusterName = c.req.param("clusterName");
|
|
640
|
+
const cluster = ms().clusters.all().find(
|
|
641
|
+
(cl) => cl.group_id === groupId && cl.name === clusterName
|
|
642
|
+
);
|
|
643
|
+
if (!cluster) {
|
|
644
|
+
return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
|
|
645
|
+
}
|
|
646
|
+
const body = await c.req.json();
|
|
647
|
+
const updates = {};
|
|
648
|
+
if (body.providerSettings) {
|
|
649
|
+
updates.provider_settings = {
|
|
650
|
+
provider_name: cluster.provider_settings.provider_name,
|
|
651
|
+
instance_size_name: body.providerSettings.instanceSizeName ?? cluster.provider_settings.instance_size_name,
|
|
652
|
+
region_name: body.providerSettings.regionName ?? cluster.provider_settings.region_name
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (body.diskSizeGB !== void 0) {
|
|
656
|
+
updates.disk_size_gb = body.diskSizeGB;
|
|
657
|
+
}
|
|
658
|
+
const updated = ms().clusters.update(cluster.id, updates);
|
|
659
|
+
return mongoOk(c, formatCluster(updated));
|
|
660
|
+
});
|
|
661
|
+
app.delete("/api/atlas/v2/groups/:groupId/clusters/:clusterName", (c) => {
|
|
662
|
+
const groupId = c.req.param("groupId");
|
|
663
|
+
const clusterName = c.req.param("clusterName");
|
|
664
|
+
const cluster = ms().clusters.all().find(
|
|
665
|
+
(cl) => cl.group_id === groupId && cl.name === clusterName
|
|
666
|
+
);
|
|
667
|
+
if (!cluster) {
|
|
668
|
+
return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
|
|
669
|
+
}
|
|
670
|
+
deleteClusterData(ms, cluster.cluster_id);
|
|
671
|
+
ms().clusters.delete(cluster.id);
|
|
672
|
+
const project = ms().projects.findOneBy("group_id", groupId);
|
|
673
|
+
if (project) {
|
|
674
|
+
ms().projects.update(project.id, { cluster_count: Math.max(0, project.cluster_count - 1) });
|
|
675
|
+
}
|
|
676
|
+
return c.body(null, 204);
|
|
677
|
+
});
|
|
678
|
+
app.get("/api/atlas/v2/groups/:groupId/databaseUsers", (c) => {
|
|
679
|
+
const groupId = c.req.param("groupId");
|
|
680
|
+
const users = ms().users.all().filter((u) => u.group_id === groupId);
|
|
681
|
+
return mongoOk(c, {
|
|
682
|
+
results: users.map(formatUser),
|
|
683
|
+
totalCount: users.length
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
app.get("/api/atlas/v2/groups/:groupId/databaseUsers/admin/:username", (c) => {
|
|
687
|
+
const groupId = c.req.param("groupId");
|
|
688
|
+
const username = c.req.param("username");
|
|
689
|
+
const user = ms().users.all().find((u) => u.group_id === groupId && u.username === username);
|
|
690
|
+
if (!user) {
|
|
691
|
+
return mongoError(c, "USER_NOT_FOUND", `Database user '${username}' not found.`, 404);
|
|
692
|
+
}
|
|
693
|
+
return mongoOk(c, formatUser(user));
|
|
694
|
+
});
|
|
695
|
+
app.post("/api/atlas/v2/groups/:groupId/databaseUsers", async (c) => {
|
|
696
|
+
const groupId = c.req.param("groupId");
|
|
697
|
+
const body = await c.req.json();
|
|
698
|
+
if (!body.username?.trim()) {
|
|
699
|
+
return mongoError(c, "INVALID_PARAMETER", "username is required");
|
|
700
|
+
}
|
|
701
|
+
const existing = ms().users.all().find((u) => u.group_id === groupId && u.username === body.username);
|
|
702
|
+
if (existing) {
|
|
703
|
+
return mongoError(c, "DUPLICATE_USER", `User '${body.username}' already exists.`, 409);
|
|
704
|
+
}
|
|
705
|
+
const userId = generateUserId();
|
|
706
|
+
const user = ms().users.insert({
|
|
707
|
+
user_id: userId,
|
|
708
|
+
username: body.username,
|
|
709
|
+
group_id: groupId,
|
|
710
|
+
roles: (body.roles ?? []).map((r) => ({ database_name: r.databaseName, role_name: r.roleName }))
|
|
711
|
+
});
|
|
712
|
+
return mongoOk(c, formatUser(user), 201);
|
|
713
|
+
});
|
|
714
|
+
app.delete("/api/atlas/v2/groups/:groupId/databaseUsers/admin/:username", (c) => {
|
|
715
|
+
const groupId = c.req.param("groupId");
|
|
716
|
+
const username = c.req.param("username");
|
|
717
|
+
const user = ms().users.all().find((u) => u.group_id === groupId && u.username === username);
|
|
718
|
+
if (!user) {
|
|
719
|
+
return mongoError(c, "USER_NOT_FOUND", `Database user '${username}' not found.`, 404);
|
|
720
|
+
}
|
|
721
|
+
ms().users.delete(user.id);
|
|
722
|
+
return c.body(null, 204);
|
|
723
|
+
});
|
|
724
|
+
app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName/databases", (c) => {
|
|
725
|
+
const groupId = c.req.param("groupId");
|
|
726
|
+
const clusterName = c.req.param("clusterName");
|
|
727
|
+
const cluster = ms().clusters.all().find(
|
|
728
|
+
(cl) => cl.group_id === groupId && cl.name === clusterName
|
|
729
|
+
);
|
|
730
|
+
if (!cluster) {
|
|
731
|
+
return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
|
|
732
|
+
}
|
|
733
|
+
const databases = ms().databases.all().filter((db) => db.cluster_id === cluster.cluster_id);
|
|
734
|
+
return mongoOk(c, {
|
|
735
|
+
results: databases.map((db) => ({ databaseName: db.name })),
|
|
736
|
+
totalCount: databases.length
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName/databases/:databaseName/collections", (c) => {
|
|
740
|
+
const groupId = c.req.param("groupId");
|
|
741
|
+
const clusterName = c.req.param("clusterName");
|
|
742
|
+
const databaseName = c.req.param("databaseName");
|
|
743
|
+
const cluster = ms().clusters.all().find(
|
|
744
|
+
(cl) => cl.group_id === groupId && cl.name === clusterName
|
|
745
|
+
);
|
|
746
|
+
if (!cluster) {
|
|
747
|
+
return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
|
|
748
|
+
}
|
|
749
|
+
const collections = ms().collections.all().filter(
|
|
750
|
+
(col) => col.cluster_id === cluster.cluster_id && col.database === databaseName
|
|
751
|
+
);
|
|
752
|
+
return mongoOk(c, {
|
|
753
|
+
results: collections.map((col) => ({ collectionName: col.name, databaseName })),
|
|
754
|
+
totalCount: collections.length
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
function deleteClusterData(ms, clusterId) {
|
|
759
|
+
const docs = ms().documents.all().filter((d) => d.cluster_id === clusterId);
|
|
760
|
+
for (const doc of docs) ms().documents.delete(doc.id);
|
|
761
|
+
const cols = ms().collections.all().filter((col) => col.cluster_id === clusterId);
|
|
762
|
+
for (const col of cols) ms().collections.delete(col.id);
|
|
763
|
+
const dbs = ms().databases.all().filter((db) => db.cluster_id === clusterId);
|
|
764
|
+
for (const db of dbs) ms().databases.delete(db.id);
|
|
765
|
+
}
|
|
766
|
+
function formatProject(p) {
|
|
767
|
+
return {
|
|
768
|
+
id: p.group_id,
|
|
769
|
+
name: p.name,
|
|
770
|
+
orgId: p.org_id,
|
|
771
|
+
clusterCount: p.cluster_count,
|
|
772
|
+
created: p.created_at
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function formatCluster(cl) {
|
|
776
|
+
return {
|
|
777
|
+
id: cl.cluster_id,
|
|
778
|
+
name: cl.name,
|
|
779
|
+
groupId: cl.group_id,
|
|
780
|
+
stateName: cl.state,
|
|
781
|
+
mongoURI: cl.mongo_uri,
|
|
782
|
+
connectionStrings: {
|
|
783
|
+
standard: cl.connection_strings.standard,
|
|
784
|
+
standardSrv: cl.connection_strings.standard_srv
|
|
785
|
+
},
|
|
786
|
+
providerSettings: {
|
|
787
|
+
providerName: cl.provider_settings.provider_name,
|
|
788
|
+
instanceSizeName: cl.provider_settings.instance_size_name,
|
|
789
|
+
regionName: cl.provider_settings.region_name
|
|
790
|
+
},
|
|
791
|
+
clusterType: cl.cluster_type,
|
|
792
|
+
diskSizeGB: cl.disk_size_gb,
|
|
793
|
+
mongoDBVersion: cl.mongodb_version,
|
|
794
|
+
created: cl.created_at
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function formatUser(u) {
|
|
798
|
+
return {
|
|
799
|
+
username: u.username,
|
|
800
|
+
groupId: u.group_id,
|
|
801
|
+
databaseName: "admin",
|
|
802
|
+
roles: u.roles.map((r) => ({ databaseName: r.database_name, roleName: r.role_name }))
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/index.ts
|
|
807
|
+
function seedDefaults(store, _baseUrl) {
|
|
808
|
+
const ms = getMongoAtlasStore(store);
|
|
809
|
+
const groupId = generateGroupId();
|
|
810
|
+
ms.projects.insert({
|
|
811
|
+
group_id: groupId,
|
|
812
|
+
name: "Project0",
|
|
813
|
+
org_id: "default_org",
|
|
814
|
+
cluster_count: 1
|
|
815
|
+
});
|
|
816
|
+
const clusterId = generateClusterId();
|
|
817
|
+
ms.clusters.insert({
|
|
818
|
+
cluster_id: clusterId,
|
|
819
|
+
name: "Cluster0",
|
|
820
|
+
group_id: groupId,
|
|
821
|
+
state: "IDLE",
|
|
822
|
+
mongo_uri: "mongodb+srv://Cluster0.emulate.mongodb.net",
|
|
823
|
+
connection_strings: {
|
|
824
|
+
standard: "mongodb://Cluster0.emulate.mongodb.net:27017",
|
|
825
|
+
standard_srv: "mongodb+srv://Cluster0.emulate.mongodb.net"
|
|
826
|
+
},
|
|
827
|
+
provider_settings: {
|
|
828
|
+
provider_name: "AWS",
|
|
829
|
+
instance_size_name: "M10",
|
|
830
|
+
region_name: "US_EAST_1"
|
|
831
|
+
},
|
|
832
|
+
cluster_type: "REPLICASET",
|
|
833
|
+
disk_size_gb: 10,
|
|
834
|
+
mongodb_version: "8.0"
|
|
835
|
+
});
|
|
836
|
+
ms.users.insert({
|
|
837
|
+
user_id: generateUserId(),
|
|
838
|
+
username: "admin",
|
|
839
|
+
group_id: groupId,
|
|
840
|
+
roles: [{ database_name: "admin", role_name: "atlasAdmin" }]
|
|
841
|
+
});
|
|
842
|
+
ms.databases.insert({ cluster_id: clusterId, name: "test" });
|
|
843
|
+
ms.collections.insert({ cluster_id: clusterId, database: "test", name: "items" });
|
|
844
|
+
}
|
|
845
|
+
function seedFromConfig(store, _baseUrl, config) {
|
|
846
|
+
const ms = getMongoAtlasStore(store);
|
|
847
|
+
const projectIdMap = /* @__PURE__ */ new Map();
|
|
848
|
+
if (config.projects) {
|
|
849
|
+
for (const p of config.projects) {
|
|
850
|
+
const existing = ms.projects.all().find((ep) => ep.name === p.name);
|
|
851
|
+
if (existing) {
|
|
852
|
+
projectIdMap.set(p.name, existing.group_id);
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const groupId = generateGroupId();
|
|
856
|
+
ms.projects.insert({
|
|
857
|
+
group_id: groupId,
|
|
858
|
+
name: p.name,
|
|
859
|
+
org_id: p.org_id ?? "default_org",
|
|
860
|
+
cluster_count: 0
|
|
861
|
+
});
|
|
862
|
+
projectIdMap.set(p.name, groupId);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const defaultProject = ms.projects.all()[0];
|
|
866
|
+
if (defaultProject) {
|
|
867
|
+
projectIdMap.set(defaultProject.name, defaultProject.group_id);
|
|
868
|
+
}
|
|
869
|
+
const clusterIdMap = /* @__PURE__ */ new Map();
|
|
870
|
+
if (config.clusters) {
|
|
871
|
+
for (const cl of config.clusters) {
|
|
872
|
+
const groupId = projectIdMap.get(cl.project);
|
|
873
|
+
if (!groupId) continue;
|
|
874
|
+
const existing = ms.clusters.all().find(
|
|
875
|
+
(ec) => ec.group_id === groupId && ec.name === cl.name
|
|
876
|
+
);
|
|
877
|
+
if (existing) {
|
|
878
|
+
clusterIdMap.set(cl.name, existing.cluster_id);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const clusterId = generateClusterId();
|
|
882
|
+
ms.clusters.insert({
|
|
883
|
+
cluster_id: clusterId,
|
|
884
|
+
name: cl.name,
|
|
885
|
+
group_id: groupId,
|
|
886
|
+
state: "IDLE",
|
|
887
|
+
mongo_uri: `mongodb+srv://${cl.name}.emulate.mongodb.net`,
|
|
888
|
+
connection_strings: {
|
|
889
|
+
standard: `mongodb://${cl.name}.emulate.mongodb.net:27017`,
|
|
890
|
+
standard_srv: `mongodb+srv://${cl.name}.emulate.mongodb.net`
|
|
891
|
+
},
|
|
892
|
+
provider_settings: {
|
|
893
|
+
provider_name: cl.provider ?? "AWS",
|
|
894
|
+
instance_size_name: cl.instance_size ?? "M10",
|
|
895
|
+
region_name: cl.region ?? "US_EAST_1"
|
|
896
|
+
},
|
|
897
|
+
cluster_type: "REPLICASET",
|
|
898
|
+
disk_size_gb: cl.disk_size_gb ?? 10,
|
|
899
|
+
mongodb_version: cl.mongodb_version ?? "8.0"
|
|
900
|
+
});
|
|
901
|
+
clusterIdMap.set(cl.name, clusterId);
|
|
902
|
+
const project = ms.projects.findOneBy("group_id", groupId);
|
|
903
|
+
if (project) {
|
|
904
|
+
ms.projects.update(project.id, { cluster_count: project.cluster_count + 1 });
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const defaultCluster = ms.clusters.all()[0];
|
|
909
|
+
if (defaultCluster) {
|
|
910
|
+
clusterIdMap.set(defaultCluster.name, defaultCluster.cluster_id);
|
|
911
|
+
}
|
|
912
|
+
if (config.database_users) {
|
|
913
|
+
for (const u of config.database_users) {
|
|
914
|
+
const groupId = projectIdMap.get(u.project);
|
|
915
|
+
if (!groupId) continue;
|
|
916
|
+
const existing = ms.users.all().find(
|
|
917
|
+
(eu) => eu.group_id === groupId && eu.username === u.username
|
|
918
|
+
);
|
|
919
|
+
if (existing) continue;
|
|
920
|
+
ms.users.insert({
|
|
921
|
+
user_id: generateUserId(),
|
|
922
|
+
username: u.username,
|
|
923
|
+
group_id: groupId,
|
|
924
|
+
roles: u.roles ?? [{ database_name: "admin", role_name: "readWriteAnyDatabase" }]
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (config.databases) {
|
|
929
|
+
for (const db of config.databases) {
|
|
930
|
+
const clusterId = clusterIdMap.get(db.cluster);
|
|
931
|
+
if (!clusterId) continue;
|
|
932
|
+
const existingDb = ms.databases.all().find(
|
|
933
|
+
(edb) => edb.cluster_id === clusterId && edb.name === db.name
|
|
934
|
+
);
|
|
935
|
+
if (!existingDb) {
|
|
936
|
+
ms.databases.insert({ cluster_id: clusterId, name: db.name });
|
|
937
|
+
}
|
|
938
|
+
if (db.collections) {
|
|
939
|
+
for (const colName of db.collections) {
|
|
940
|
+
const existingCol = ms.collections.all().find(
|
|
941
|
+
(ec) => ec.cluster_id === clusterId && ec.database === db.name && ec.name === colName
|
|
942
|
+
);
|
|
943
|
+
if (!existingCol) {
|
|
944
|
+
ms.collections.insert({ cluster_id: clusterId, database: db.name, name: colName });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
var mongoatlasPlugin = {
|
|
952
|
+
name: "mongoatlas",
|
|
953
|
+
register(app, store, webhooks, baseUrl, tokenMap) {
|
|
954
|
+
const ctx = { app, store, webhooks, baseUrl, tokenMap };
|
|
955
|
+
adminRoutes(ctx);
|
|
956
|
+
dataApiRoutes(ctx);
|
|
957
|
+
},
|
|
958
|
+
seed(store, baseUrl) {
|
|
959
|
+
seedDefaults(store, baseUrl);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
var index_default = mongoatlasPlugin;
|
|
963
|
+
export {
|
|
964
|
+
index_default as default,
|
|
965
|
+
getMongoAtlasStore,
|
|
966
|
+
mongoatlasPlugin,
|
|
967
|
+
seedFromConfig
|
|
968
|
+
};
|
|
969
|
+
//# sourceMappingURL=index.js.map
|