@harborclient/team-hub 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1239 -273
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -358,6 +358,7 @@ var USERS_COLLECTION = "users";
|
|
|
358
358
|
var API_TOKENS_COLLECTION = "apiTokens";
|
|
359
359
|
var COLLECTIONS_COLLECTION = "collections";
|
|
360
360
|
var ENVIRONMENTS_COLLECTION = "environments";
|
|
361
|
+
var SNIPPETS_COLLECTION = "snippets";
|
|
361
362
|
var FOLDERS_COLLECTION = "folders";
|
|
362
363
|
var REQUESTS_COLLECTION = "requests";
|
|
363
364
|
var AUDIT_LOG_COLLECTION = "auditLog";
|
|
@@ -384,7 +385,8 @@ function createSystemUserInput() {
|
|
|
384
385
|
name: SYSTEM_USER_NAME,
|
|
385
386
|
role: "admin",
|
|
386
387
|
collectionAccess: [],
|
|
387
|
-
environmentAccess: []
|
|
388
|
+
environmentAccess: [],
|
|
389
|
+
snippetAccess: []
|
|
388
390
|
};
|
|
389
391
|
}
|
|
390
392
|
|
|
@@ -398,7 +400,7 @@ var firestoreConfigSchema = z2.object({
|
|
|
398
400
|
|
|
399
401
|
// src/db/firestore/utils.ts
|
|
400
402
|
function parseAuditEntityType(value) {
|
|
401
|
-
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "folder" || value === "request") {
|
|
403
|
+
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request") {
|
|
402
404
|
return value;
|
|
403
405
|
}
|
|
404
406
|
throw new Error(`Invalid audit entity type: ${value}`);
|
|
@@ -427,6 +429,7 @@ function mapFirestoreUser(id, data) {
|
|
|
427
429
|
role: data.role,
|
|
428
430
|
collectionAccess: data.collectionAccess,
|
|
429
431
|
environmentAccess: data.environmentAccess,
|
|
432
|
+
snippetAccess: data.snippetAccess ?? [],
|
|
430
433
|
llmAccess: data.llmAccess ?? false,
|
|
431
434
|
llmModels: data.llmModels ?? [],
|
|
432
435
|
llmMonthlyTokenLimit: data.llmMonthlyTokenLimit ?? null,
|
|
@@ -492,6 +495,20 @@ function mapFirestoreEnvironment(id, data) {
|
|
|
492
495
|
deletionLocked: data.deletionLocked ?? false
|
|
493
496
|
};
|
|
494
497
|
}
|
|
498
|
+
function mapFirestoreSnippet(id, data) {
|
|
499
|
+
return {
|
|
500
|
+
id,
|
|
501
|
+
name: data.name,
|
|
502
|
+
code: data.code,
|
|
503
|
+
scope: data.scope,
|
|
504
|
+
sortOrder: data.sortOrder,
|
|
505
|
+
createdAt: data.createdAt,
|
|
506
|
+
updatedAt: data.updatedAt ?? data.createdAt,
|
|
507
|
+
createdByUserId: data.createdByUserId ?? null,
|
|
508
|
+
updatedByUserId: data.updatedByUserId ?? null,
|
|
509
|
+
deletionLocked: data.deletionLocked ?? false
|
|
510
|
+
};
|
|
511
|
+
}
|
|
495
512
|
function mapFirestoreFolder(id, data) {
|
|
496
513
|
return {
|
|
497
514
|
id,
|
|
@@ -699,6 +716,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
699
716
|
async migrate() {
|
|
700
717
|
await this.ensureSystemUser();
|
|
701
718
|
await this.migrateOrphanTokensToBootstrapUser();
|
|
719
|
+
await this.migrateSnippetAccessBackfill();
|
|
702
720
|
}
|
|
703
721
|
/**
|
|
704
722
|
* Returns the stable identifier of the internal system user, when provisioned.
|
|
@@ -745,6 +763,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
745
763
|
role: input.role,
|
|
746
764
|
collectionAccess: input.collectionAccess,
|
|
747
765
|
environmentAccess: input.environmentAccess,
|
|
766
|
+
snippetAccess: input.snippetAccess,
|
|
748
767
|
llmAccess: input.llmAccess ?? false,
|
|
749
768
|
llmModels: input.llmModels ?? [],
|
|
750
769
|
llmMonthlyTokenLimit: input.llmMonthlyTokenLimit ?? null,
|
|
@@ -816,6 +835,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
816
835
|
const role = input.role ?? existing.role;
|
|
817
836
|
const collectionAccess = input.collectionAccess ?? existing.collectionAccess;
|
|
818
837
|
const environmentAccess = input.environmentAccess ?? existing.environmentAccess;
|
|
838
|
+
const snippetAccess = input.snippetAccess ?? existing.snippetAccess;
|
|
819
839
|
const llmAccess = input.llmAccess ?? existing.llmAccess;
|
|
820
840
|
const llmModels = input.llmModels ?? existing.llmModels;
|
|
821
841
|
const llmMonthlyTokenLimit = input.llmMonthlyTokenLimit !== void 0 ? input.llmMonthlyTokenLimit : existing.llmMonthlyTokenLimit;
|
|
@@ -825,6 +845,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
825
845
|
role,
|
|
826
846
|
collectionAccess,
|
|
827
847
|
environmentAccess,
|
|
848
|
+
snippetAccess,
|
|
828
849
|
llmAccess,
|
|
829
850
|
llmModels,
|
|
830
851
|
llmMonthlyTokenLimit,
|
|
@@ -879,7 +900,8 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
879
900
|
name: BOOTSTRAP_USER_NAME,
|
|
880
901
|
role: "user",
|
|
881
902
|
collectionAccess: ["*"],
|
|
882
|
-
environmentAccess: ["*"]
|
|
903
|
+
environmentAccess: ["*"],
|
|
904
|
+
snippetAccess: ["*"]
|
|
883
905
|
},
|
|
884
906
|
systemUserId
|
|
885
907
|
);
|
|
@@ -893,6 +915,37 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
893
915
|
await batch.commit();
|
|
894
916
|
}
|
|
895
917
|
}
|
|
918
|
+
/**
|
|
919
|
+
* Grants wildcard snippet access to user accounts that already have wildcard collection access.
|
|
920
|
+
*/
|
|
921
|
+
async migrateSnippetAccessBackfill() {
|
|
922
|
+
const client = this.requireClient();
|
|
923
|
+
const snapshot = await client.collection(USERS_COLLECTION).where("role", "==", "user").get();
|
|
924
|
+
if (snapshot.docs.length === 0) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
let batch = client.batch();
|
|
928
|
+
let batchSize = 0;
|
|
929
|
+
for (const doc of snapshot.docs) {
|
|
930
|
+
const data = doc.data();
|
|
931
|
+
if ((data.snippetAccess?.length ?? 0) > 0) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
if (!data.collectionAccess?.includes("*")) {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
batch.update(doc.ref, { snippetAccess: ["*"] });
|
|
938
|
+
batchSize += 1;
|
|
939
|
+
if (batchSize >= WRITE_BATCH_LIMIT) {
|
|
940
|
+
await batch.commit();
|
|
941
|
+
batch = client.batch();
|
|
942
|
+
batchSize = 0;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (batchSize > 0) {
|
|
946
|
+
await batch.commit();
|
|
947
|
+
}
|
|
948
|
+
}
|
|
896
949
|
/**
|
|
897
950
|
* Inserts a new API token document.
|
|
898
951
|
*
|
|
@@ -1250,6 +1303,129 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
1250
1303
|
updatedByUserId: actingUserId
|
|
1251
1304
|
});
|
|
1252
1305
|
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Lists all snippets ordered by sort order then name.
|
|
1308
|
+
*/
|
|
1309
|
+
async listSnippets() {
|
|
1310
|
+
const snapshot = await this.requireClient().collection(SNIPPETS_COLLECTION).get();
|
|
1311
|
+
return snapshot.docs.map((doc) => mapFirestoreSnippet(doc.id, doc.data())).sort((left, right) => {
|
|
1312
|
+
if (left.sortOrder !== right.sortOrder) {
|
|
1313
|
+
return left.sortOrder - right.sortOrder;
|
|
1314
|
+
}
|
|
1315
|
+
return left.name.localeCompare(right.name);
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Creates a new snippet with the given fields.
|
|
1320
|
+
*
|
|
1321
|
+
* @param name - Display name for the snippet.
|
|
1322
|
+
* @param code - JavaScript source for the snippet.
|
|
1323
|
+
* @param scope - Execution scope for the snippet.
|
|
1324
|
+
* @param actingUserId - User performing the create action.
|
|
1325
|
+
*/
|
|
1326
|
+
async createSnippet(name, code, scope, actingUserId) {
|
|
1327
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
1328
|
+
const id = randomUUID2();
|
|
1329
|
+
const now = /* @__PURE__ */ new Date();
|
|
1330
|
+
const existing = await this.listSnippets();
|
|
1331
|
+
const maxOrder = existing.reduce((max, snippet) => Math.max(max, snippet.sortOrder), -1);
|
|
1332
|
+
const data = {
|
|
1333
|
+
name: trimmedName,
|
|
1334
|
+
code,
|
|
1335
|
+
scope,
|
|
1336
|
+
sortOrder: maxOrder + 1,
|
|
1337
|
+
createdAt: now,
|
|
1338
|
+
updatedAt: now,
|
|
1339
|
+
createdByUserId: actingUserId,
|
|
1340
|
+
updatedByUserId: actingUserId,
|
|
1341
|
+
deletionLocked: false
|
|
1342
|
+
};
|
|
1343
|
+
await this.requireClient().collection(SNIPPETS_COLLECTION).doc(id).set(data);
|
|
1344
|
+
await this.recordAuditEntry(actingUserId, "create", "snippet", id);
|
|
1345
|
+
return mapFirestoreSnippet(id, data);
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Updates a snippet's name, code, and scope. Sort order is left unchanged.
|
|
1349
|
+
*
|
|
1350
|
+
* @param actingUserId - User performing the update action.
|
|
1351
|
+
*/
|
|
1352
|
+
async updateSnippet(id, name, code, scope, actingUserId) {
|
|
1353
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
1354
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
1355
|
+
const docRef = this.requireClient().collection(SNIPPETS_COLLECTION).doc(id);
|
|
1356
|
+
const snapshot = await docRef.get();
|
|
1357
|
+
if (!snapshot.exists) {
|
|
1358
|
+
throw new Error("Snippet not found");
|
|
1359
|
+
}
|
|
1360
|
+
const existing = snapshot.data();
|
|
1361
|
+
const updated = {
|
|
1362
|
+
...existing,
|
|
1363
|
+
name: trimmedName,
|
|
1364
|
+
code,
|
|
1365
|
+
scope,
|
|
1366
|
+
updatedAt,
|
|
1367
|
+
updatedByUserId: actingUserId
|
|
1368
|
+
};
|
|
1369
|
+
await docRef.update({
|
|
1370
|
+
name: trimmedName,
|
|
1371
|
+
code,
|
|
1372
|
+
scope,
|
|
1373
|
+
updatedAt,
|
|
1374
|
+
updatedByUserId: actingUserId
|
|
1375
|
+
});
|
|
1376
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
1377
|
+
return mapFirestoreSnippet(id, updated);
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Deletes a snippet.
|
|
1381
|
+
*
|
|
1382
|
+
* @param id - Snippet ID to delete.
|
|
1383
|
+
* @param actingUserId - User performing the delete action.
|
|
1384
|
+
*/
|
|
1385
|
+
async deleteSnippet(id, actingUserId) {
|
|
1386
|
+
await this.recordAuditEntry(actingUserId, "delete", "snippet", id);
|
|
1387
|
+
await this.requireClient().collection(SNIPPETS_COLLECTION).doc(id).delete();
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Finds a snippet by stable identifier.
|
|
1391
|
+
*
|
|
1392
|
+
* @param id - Snippet ID to look up.
|
|
1393
|
+
*/
|
|
1394
|
+
async findSnippetById(id) {
|
|
1395
|
+
const snapshot = await this.requireClient().collection(SNIPPETS_COLLECTION).doc(id).get();
|
|
1396
|
+
if (!snapshot.exists) {
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
return mapFirestoreSnippet(id, snapshot.data());
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Updates whether non-admin users may delete a snippet.
|
|
1403
|
+
*
|
|
1404
|
+
* @param id - Snippet ID to update.
|
|
1405
|
+
* @param deletionLocked - When true, user-role tokens cannot delete the snippet.
|
|
1406
|
+
* @param actingUserId - Admin user performing the update.
|
|
1407
|
+
*/
|
|
1408
|
+
async setSnippetDeletionLocked(id, deletionLocked, actingUserId) {
|
|
1409
|
+
const docRef = this.requireClient().collection(SNIPPETS_COLLECTION).doc(id);
|
|
1410
|
+
const snapshot = await docRef.get();
|
|
1411
|
+
if (!snapshot.exists) {
|
|
1412
|
+
throw new Error("Snippet not found");
|
|
1413
|
+
}
|
|
1414
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
1415
|
+
await docRef.update({
|
|
1416
|
+
deletionLocked,
|
|
1417
|
+
updatedAt,
|
|
1418
|
+
updatedByUserId: actingUserId
|
|
1419
|
+
});
|
|
1420
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
1421
|
+
const existing = snapshot.data();
|
|
1422
|
+
return mapFirestoreSnippet(id, {
|
|
1423
|
+
...existing,
|
|
1424
|
+
deletionLocked,
|
|
1425
|
+
updatedAt,
|
|
1426
|
+
updatedByUserId: actingUserId
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1253
1429
|
/**
|
|
1254
1430
|
* Lists all saved requests in a collection.
|
|
1255
1431
|
*
|
|
@@ -1716,6 +1892,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
1716
1892
|
role: input.role,
|
|
1717
1893
|
collectionAccess: input.collectionAccess,
|
|
1718
1894
|
environmentAccess: input.environmentAccess,
|
|
1895
|
+
snippetAccess: input.snippetAccess,
|
|
1719
1896
|
llmAccess: false,
|
|
1720
1897
|
llmModels: [],
|
|
1721
1898
|
llmMonthlyTokenLimit: null,
|
|
@@ -1799,7 +1976,7 @@ function parseAuditAction(value) {
|
|
|
1799
1976
|
throw new Error(`Invalid audit action: ${value}`);
|
|
1800
1977
|
}
|
|
1801
1978
|
function parseAuditEntityType2(value) {
|
|
1802
|
-
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "folder" || value === "request") {
|
|
1979
|
+
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request") {
|
|
1803
1980
|
return value;
|
|
1804
1981
|
}
|
|
1805
1982
|
throw new Error(`Invalid audit entity type: ${value}`);
|
|
@@ -1876,6 +2053,26 @@ function mapEnvironmentSqlRow(row) {
|
|
|
1876
2053
|
deletionLocked: Boolean(row.deletion_locked)
|
|
1877
2054
|
};
|
|
1878
2055
|
}
|
|
2056
|
+
function parseSnippetScope(value) {
|
|
2057
|
+
if (value === "pre-request" || value === "post-request" || value === "any") {
|
|
2058
|
+
return value;
|
|
2059
|
+
}
|
|
2060
|
+
throw new Error(`Invalid snippet scope: ${value}`);
|
|
2061
|
+
}
|
|
2062
|
+
function mapSnippetSqlRow(row) {
|
|
2063
|
+
return {
|
|
2064
|
+
id: row.id,
|
|
2065
|
+
name: row.name,
|
|
2066
|
+
code: row.code,
|
|
2067
|
+
scope: parseSnippetScope(row.scope),
|
|
2068
|
+
sortOrder: row.sort_order,
|
|
2069
|
+
createdAt: row.created_at,
|
|
2070
|
+
updatedAt: row.updated_at ?? row.created_at,
|
|
2071
|
+
createdByUserId: row.created_by_user_id ?? null,
|
|
2072
|
+
updatedByUserId: row.updated_by_user_id ?? null,
|
|
2073
|
+
deletionLocked: Boolean(row.deletion_locked)
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
1879
2076
|
function mapFolderSqlRow(row) {
|
|
1880
2077
|
return {
|
|
1881
2078
|
id: row.id,
|
|
@@ -1960,6 +2157,22 @@ CREATE TABLE IF NOT EXISTS environments (
|
|
|
1960
2157
|
FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
|
|
1961
2158
|
)
|
|
1962
2159
|
`.trim();
|
|
2160
|
+
var SNIPPETS_MIGRATION_SQL = `
|
|
2161
|
+
CREATE TABLE IF NOT EXISTS snippets (
|
|
2162
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
2163
|
+
name VARCHAR(255) NOT NULL,
|
|
2164
|
+
code LONGTEXT NOT NULL,
|
|
2165
|
+
scope VARCHAR(32) NOT NULL DEFAULT 'any',
|
|
2166
|
+
sort_order INT NOT NULL DEFAULT 0,
|
|
2167
|
+
created_at DATETIME NOT NULL,
|
|
2168
|
+
updated_at DATETIME NOT NULL,
|
|
2169
|
+
created_by_user_id VARCHAR(36) NULL,
|
|
2170
|
+
updated_by_user_id VARCHAR(36) NULL,
|
|
2171
|
+
deletion_locked TINYINT(1) NOT NULL DEFAULT 0,
|
|
2172
|
+
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
|
|
2173
|
+
FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
|
|
2174
|
+
)
|
|
2175
|
+
`.trim();
|
|
1963
2176
|
var FOLDERS_MIGRATION_SQL = `
|
|
1964
2177
|
CREATE TABLE IF NOT EXISTS folders (
|
|
1965
2178
|
id VARCHAR(36) PRIMARY KEY,
|
|
@@ -2009,6 +2222,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|
|
2009
2222
|
role VARCHAR(16) NOT NULL,
|
|
2010
2223
|
collection_access LONGTEXT NOT NULL,
|
|
2011
2224
|
environment_access LONGTEXT NOT NULL,
|
|
2225
|
+
snippet_access LONGTEXT NOT NULL,
|
|
2012
2226
|
created_at DATETIME NOT NULL,
|
|
2013
2227
|
updated_at DATETIME NOT NULL,
|
|
2014
2228
|
created_by_user_id VARCHAR(36) NULL,
|
|
@@ -2124,11 +2338,23 @@ var ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL = `
|
|
|
2124
2338
|
ALTER TABLE environments
|
|
2125
2339
|
ADD COLUMN IF NOT EXISTS deletion_locked TINYINT(1) NOT NULL DEFAULT 0;
|
|
2126
2340
|
`.trim();
|
|
2341
|
+
var USERS_SNIPPET_ACCESS_MIGRATION_SQL = `
|
|
2342
|
+
ALTER TABLE users
|
|
2343
|
+
ADD COLUMN IF NOT EXISTS snippet_access LONGTEXT NOT NULL DEFAULT '[]';
|
|
2344
|
+
`.trim();
|
|
2345
|
+
var USERS_SNIPPET_ACCESS_BACKFILL_SQL = `
|
|
2346
|
+
UPDATE users
|
|
2347
|
+
SET snippet_access = '["*"]'
|
|
2348
|
+
WHERE role = 'user'
|
|
2349
|
+
AND snippet_access = '[]'
|
|
2350
|
+
AND collection_access LIKE '%"*"%';
|
|
2351
|
+
`.trim();
|
|
2127
2352
|
var MYSQL_MIGRATIONS = [
|
|
2128
2353
|
USERS_MIGRATION_SQL,
|
|
2129
2354
|
API_TOKENS_MIGRATION_SQL,
|
|
2130
2355
|
COLLECTIONS_MIGRATION_SQL,
|
|
2131
2356
|
ENVIRONMENTS_MIGRATION_SQL,
|
|
2357
|
+
SNIPPETS_MIGRATION_SQL,
|
|
2132
2358
|
FOLDERS_MIGRATION_SQL,
|
|
2133
2359
|
REQUESTS_MIGRATION_SQL,
|
|
2134
2360
|
AUDIT_LOG_MIGRATION_SQL,
|
|
@@ -2146,7 +2372,9 @@ var MYSQL_MIGRATIONS = [
|
|
|
2146
2372
|
LLM_USAGE_MIGRATION_SQL,
|
|
2147
2373
|
LLM_USAGE_LOG_MIGRATION_SQL,
|
|
2148
2374
|
COLLECTIONS_DELETION_LOCKED_MIGRATION_SQL,
|
|
2149
|
-
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL
|
|
2375
|
+
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL,
|
|
2376
|
+
USERS_SNIPPET_ACCESS_MIGRATION_SQL,
|
|
2377
|
+
USERS_SNIPPET_ACCESS_BACKFILL_SQL
|
|
2150
2378
|
];
|
|
2151
2379
|
|
|
2152
2380
|
// src/db/mysql/schemas.ts
|
|
@@ -2190,6 +2418,7 @@ function mapUserSqlRow(row) {
|
|
|
2190
2418
|
role: parseUserRole(row.role),
|
|
2191
2419
|
collectionAccess: parseAccessList(row.collection_access),
|
|
2192
2420
|
environmentAccess: parseAccessList(row.environment_access),
|
|
2421
|
+
snippetAccess: parseAccessList(row.snippet_access),
|
|
2193
2422
|
llmAccess: Boolean(row.llm_access),
|
|
2194
2423
|
llmModels: parseAccessList(row.llm_models),
|
|
2195
2424
|
llmMonthlyTokenLimit: row.llm_monthly_token_limit,
|
|
@@ -2202,9 +2431,10 @@ function mapUserSqlRow(row) {
|
|
|
2202
2431
|
function serializeAccessList(access) {
|
|
2203
2432
|
return JSON.stringify(access);
|
|
2204
2433
|
}
|
|
2205
|
-
var USER_SELECT_COLUMNS = `id, name, role, collection_access, environment_access, llm_access, llm_models, llm_monthly_token_limit, created_at, updated_at, created_by_user_id, updated_by_user_id`;
|
|
2434
|
+
var USER_SELECT_COLUMNS = `id, name, role, collection_access, environment_access, snippet_access, llm_access, llm_models, llm_monthly_token_limit, created_at, updated_at, created_by_user_id, updated_by_user_id`;
|
|
2206
2435
|
var COLLECTION_SELECT_COLUMNS = `id, name, variables, headers, auth, pre_request_script, post_request_script, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
|
|
2207
2436
|
var ENVIRONMENT_SELECT_COLUMNS = `id, name, variables, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
|
|
2437
|
+
var SNIPPET_SELECT_COLUMNS = `id, name, code, scope, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
|
|
2208
2438
|
var FOLDER_SELECT_COLUMNS = `id, collection_id, name, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id`;
|
|
2209
2439
|
var REQUEST_SELECT_COLUMNS = `id, collection_id, folder_id, name, method, url, headers, params, auth, body, body_type, pre_request_script, post_request_script, comment, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id`;
|
|
2210
2440
|
var API_TOKEN_SELECT_COLUMNS = `id, user_id, name, token_hash, token_prefix, created_at, last_used_at, revoked_at, created_by_user_id, updated_by_user_id`;
|
|
@@ -2247,6 +2477,7 @@ var LLM_USAGE_SELECT_COLUMNS = `id, user_id, period, prompt_tokens, completion_t
|
|
|
2247
2477
|
// src/db/mysql/MysqlDatabase.ts
|
|
2248
2478
|
var COLLECTION_SELECT = `SELECT ${COLLECTION_SELECT_COLUMNS} FROM collections`;
|
|
2249
2479
|
var ENVIRONMENT_SELECT = `SELECT ${ENVIRONMENT_SELECT_COLUMNS} FROM environments`;
|
|
2480
|
+
var SNIPPET_SELECT = `SELECT ${SNIPPET_SELECT_COLUMNS} FROM snippets`;
|
|
2250
2481
|
var USER_SELECT = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
|
|
2251
2482
|
var API_TOKEN_SELECT = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
|
|
2252
2483
|
var FOLDER_SELECT = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
|
|
@@ -2384,6 +2615,7 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2384
2615
|
role,
|
|
2385
2616
|
collection_access,
|
|
2386
2617
|
environment_access,
|
|
2618
|
+
snippet_access,
|
|
2387
2619
|
llm_access,
|
|
2388
2620
|
llm_models,
|
|
2389
2621
|
llm_monthly_token_limit,
|
|
@@ -2391,13 +2623,14 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2391
2623
|
updated_at,
|
|
2392
2624
|
created_by_user_id,
|
|
2393
2625
|
updated_by_user_id
|
|
2394
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2626
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2395
2627
|
[
|
|
2396
2628
|
id,
|
|
2397
2629
|
trimmedName,
|
|
2398
2630
|
input.role,
|
|
2399
2631
|
serializeAccessList(input.collectionAccess),
|
|
2400
2632
|
serializeAccessList(input.environmentAccess),
|
|
2633
|
+
serializeAccessList(input.snippetAccess),
|
|
2401
2634
|
input.llmAccess ? 1 : 0,
|
|
2402
2635
|
serializeAccessList(input.llmModels ?? []),
|
|
2403
2636
|
input.llmMonthlyTokenLimit ?? null,
|
|
@@ -2470,6 +2703,7 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2470
2703
|
const role = input.role ?? existing.role;
|
|
2471
2704
|
const collectionAccess = input.collectionAccess ?? existing.collectionAccess;
|
|
2472
2705
|
const environmentAccess = input.environmentAccess ?? existing.environmentAccess;
|
|
2706
|
+
const snippetAccess = input.snippetAccess ?? existing.snippetAccess;
|
|
2473
2707
|
const llmAccess = input.llmAccess ?? existing.llmAccess;
|
|
2474
2708
|
const llmModels = input.llmModels ?? existing.llmModels;
|
|
2475
2709
|
const llmMonthlyTokenLimit = input.llmMonthlyTokenLimit !== void 0 ? input.llmMonthlyTokenLimit : existing.llmMonthlyTokenLimit;
|
|
@@ -2480,6 +2714,7 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2480
2714
|
role = ?,
|
|
2481
2715
|
collection_access = ?,
|
|
2482
2716
|
environment_access = ?,
|
|
2717
|
+
snippet_access = ?,
|
|
2483
2718
|
llm_access = ?,
|
|
2484
2719
|
llm_models = ?,
|
|
2485
2720
|
llm_monthly_token_limit = ?,
|
|
@@ -2491,6 +2726,7 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2491
2726
|
role,
|
|
2492
2727
|
serializeAccessList(collectionAccess),
|
|
2493
2728
|
serializeAccessList(environmentAccess),
|
|
2729
|
+
serializeAccessList(snippetAccess),
|
|
2494
2730
|
llmAccess ? 1 : 0,
|
|
2495
2731
|
serializeAccessList(llmModels),
|
|
2496
2732
|
llmMonthlyTokenLimit,
|
|
@@ -2552,7 +2788,8 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2552
2788
|
name: BOOTSTRAP_USER_NAME,
|
|
2553
2789
|
role: "user",
|
|
2554
2790
|
collectionAccess: ["*"],
|
|
2555
|
-
environmentAccess: ["*"]
|
|
2791
|
+
environmentAccess: ["*"],
|
|
2792
|
+
snippetAccess: ["*"]
|
|
2556
2793
|
},
|
|
2557
2794
|
systemUserId
|
|
2558
2795
|
);
|
|
@@ -2967,6 +3204,143 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
2967
3204
|
}
|
|
2968
3205
|
return mapEnvironmentSqlRow(row);
|
|
2969
3206
|
}
|
|
3207
|
+
/**
|
|
3208
|
+
* Lists all snippets ordered by sort order then name.
|
|
3209
|
+
*/
|
|
3210
|
+
async listSnippets() {
|
|
3211
|
+
const rows = await this.queryRows(
|
|
3212
|
+
`${SNIPPET_SELECT} ORDER BY sort_order ASC, name ASC`
|
|
3213
|
+
);
|
|
3214
|
+
return rows.map(mapSnippetSqlRow);
|
|
3215
|
+
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Creates a new snippet with the given fields.
|
|
3218
|
+
*
|
|
3219
|
+
* @param name - Display name for the snippet.
|
|
3220
|
+
* @param code - JavaScript source for the snippet.
|
|
3221
|
+
* @param scope - Execution scope for the snippet.
|
|
3222
|
+
* @param actingUserId - User performing the create action.
|
|
3223
|
+
*/
|
|
3224
|
+
async createSnippet(name, code, scope, actingUserId) {
|
|
3225
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
3226
|
+
const id = randomUUID3();
|
|
3227
|
+
const now = /* @__PURE__ */ new Date();
|
|
3228
|
+
const maxRows = await this.queryRows(
|
|
3229
|
+
"SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM snippets"
|
|
3230
|
+
);
|
|
3231
|
+
const maxOrder = maxRows[0]?.max_order ?? -1;
|
|
3232
|
+
await this.executeStatement(
|
|
3233
|
+
`INSERT INTO snippets (
|
|
3234
|
+
id,
|
|
3235
|
+
name,
|
|
3236
|
+
code,
|
|
3237
|
+
scope,
|
|
3238
|
+
sort_order,
|
|
3239
|
+
created_at,
|
|
3240
|
+
updated_at,
|
|
3241
|
+
created_by_user_id,
|
|
3242
|
+
updated_by_user_id,
|
|
3243
|
+
deletion_locked
|
|
3244
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3245
|
+
[id, trimmedName, code, scope, maxOrder + 1, now, now, actingUserId, actingUserId, 0]
|
|
3246
|
+
);
|
|
3247
|
+
await this.recordAuditEntry(actingUserId, "create", "snippet", id);
|
|
3248
|
+
const rows = await this.queryRows(
|
|
3249
|
+
`${SNIPPET_SELECT} WHERE id = ?`,
|
|
3250
|
+
[id]
|
|
3251
|
+
);
|
|
3252
|
+
const row = rows[0];
|
|
3253
|
+
if (!row) {
|
|
3254
|
+
throw new Error("Snippet not found after insert");
|
|
3255
|
+
}
|
|
3256
|
+
return mapSnippetSqlRow(row);
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Updates a snippet's name, code, and scope. Sort order is left unchanged.
|
|
3260
|
+
*
|
|
3261
|
+
* @param actingUserId - User performing the update action.
|
|
3262
|
+
*/
|
|
3263
|
+
async updateSnippet(id, name, code, scope, actingUserId) {
|
|
3264
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
3265
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
3266
|
+
const result = await this.executeStatement(
|
|
3267
|
+
`UPDATE snippets
|
|
3268
|
+
SET name = ?,
|
|
3269
|
+
code = ?,
|
|
3270
|
+
scope = ?,
|
|
3271
|
+
updated_at = ?,
|
|
3272
|
+
updated_by_user_id = ?
|
|
3273
|
+
WHERE id = ?`,
|
|
3274
|
+
[trimmedName, code, scope, updatedAt, actingUserId, id]
|
|
3275
|
+
);
|
|
3276
|
+
if ((result.affectedRows ?? 0) === 0) {
|
|
3277
|
+
throw new Error("Snippet not found");
|
|
3278
|
+
}
|
|
3279
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
3280
|
+
const rows = await this.queryRows(
|
|
3281
|
+
`${SNIPPET_SELECT} WHERE id = ?`,
|
|
3282
|
+
[id]
|
|
3283
|
+
);
|
|
3284
|
+
const row = rows[0];
|
|
3285
|
+
if (!row) {
|
|
3286
|
+
throw new Error("Snippet not found");
|
|
3287
|
+
}
|
|
3288
|
+
return mapSnippetSqlRow(row);
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Deletes a snippet.
|
|
3292
|
+
*
|
|
3293
|
+
* @param id - Snippet ID to delete.
|
|
3294
|
+
* @param actingUserId - User performing the delete action.
|
|
3295
|
+
*/
|
|
3296
|
+
async deleteSnippet(id, actingUserId) {
|
|
3297
|
+
await this.recordAuditEntry(actingUserId, "delete", "snippet", id);
|
|
3298
|
+
await this.executeStatement("DELETE FROM snippets WHERE id = ?", [id]);
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Finds a snippet by stable identifier.
|
|
3302
|
+
*
|
|
3303
|
+
* @param id - Snippet ID to look up.
|
|
3304
|
+
*/
|
|
3305
|
+
async findSnippetById(id) {
|
|
3306
|
+
const rows = await this.queryRows(
|
|
3307
|
+
`${SNIPPET_SELECT} WHERE id = ?`,
|
|
3308
|
+
[id]
|
|
3309
|
+
);
|
|
3310
|
+
const row = rows[0];
|
|
3311
|
+
return row ? mapSnippetSqlRow(row) : null;
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Updates whether non-admin users may delete a snippet.
|
|
3315
|
+
*
|
|
3316
|
+
* @param id - Snippet ID to update.
|
|
3317
|
+
* @param deletionLocked - When true, user-role tokens cannot delete the snippet.
|
|
3318
|
+
* @param actingUserId - Admin user performing the update.
|
|
3319
|
+
*/
|
|
3320
|
+
async setSnippetDeletionLocked(id, deletionLocked, actingUserId) {
|
|
3321
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
3322
|
+
const result = await this.executeStatement(
|
|
3323
|
+
`UPDATE snippets
|
|
3324
|
+
SET deletion_locked = ?,
|
|
3325
|
+
updated_at = ?,
|
|
3326
|
+
updated_by_user_id = ?
|
|
3327
|
+
WHERE id = ?`,
|
|
3328
|
+
[deletionLocked ? 1 : 0, updatedAt, actingUserId, id]
|
|
3329
|
+
);
|
|
3330
|
+
if ((result.affectedRows ?? 0) === 0) {
|
|
3331
|
+
throw new Error("Snippet not found");
|
|
3332
|
+
}
|
|
3333
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
3334
|
+
const rows = await this.queryRows(
|
|
3335
|
+
`${SNIPPET_SELECT} WHERE id = ?`,
|
|
3336
|
+
[id]
|
|
3337
|
+
);
|
|
3338
|
+
const row = rows[0];
|
|
3339
|
+
if (!row) {
|
|
3340
|
+
throw new Error("Snippet not found");
|
|
3341
|
+
}
|
|
3342
|
+
return mapSnippetSqlRow(row);
|
|
3343
|
+
}
|
|
2970
3344
|
/**
|
|
2971
3345
|
* Lists all saved requests in a collection.
|
|
2972
3346
|
*
|
|
@@ -3529,6 +3903,7 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
3529
3903
|
role,
|
|
3530
3904
|
collection_access,
|
|
3531
3905
|
environment_access,
|
|
3906
|
+
snippet_access,
|
|
3532
3907
|
llm_access,
|
|
3533
3908
|
llm_models,
|
|
3534
3909
|
llm_monthly_token_limit,
|
|
@@ -3536,13 +3911,14 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
3536
3911
|
updated_at,
|
|
3537
3912
|
created_by_user_id,
|
|
3538
3913
|
updated_by_user_id
|
|
3539
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3914
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3540
3915
|
[
|
|
3541
3916
|
id,
|
|
3542
3917
|
trimmedName,
|
|
3543
3918
|
input.role,
|
|
3544
3919
|
serializeAccessList(input.collectionAccess),
|
|
3545
3920
|
serializeAccessList(input.environmentAccess),
|
|
3921
|
+
serializeAccessList(input.snippetAccess),
|
|
3546
3922
|
0,
|
|
3547
3923
|
serializeAccessList([]),
|
|
3548
3924
|
null,
|
|
@@ -3671,6 +4047,20 @@ CREATE TABLE IF NOT EXISTS environments (
|
|
|
3671
4047
|
updated_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL
|
|
3672
4048
|
);
|
|
3673
4049
|
`.trim();
|
|
4050
|
+
var SNIPPETS_MIGRATION_SQL2 = `
|
|
4051
|
+
CREATE TABLE IF NOT EXISTS snippets (
|
|
4052
|
+
id TEXT PRIMARY KEY,
|
|
4053
|
+
name TEXT NOT NULL,
|
|
4054
|
+
code TEXT NOT NULL DEFAULT '',
|
|
4055
|
+
scope TEXT NOT NULL DEFAULT 'any' CHECK (scope IN ('pre-request', 'post-request', 'any')),
|
|
4056
|
+
sort_order INT NOT NULL DEFAULT 0,
|
|
4057
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
4058
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
4059
|
+
created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
4060
|
+
updated_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
4061
|
+
deletion_locked BOOLEAN NOT NULL DEFAULT FALSE
|
|
4062
|
+
);
|
|
4063
|
+
`.trim();
|
|
3674
4064
|
var FOLDERS_MIGRATION_SQL2 = `
|
|
3675
4065
|
CREATE TABLE IF NOT EXISTS folders (
|
|
3676
4066
|
id TEXT PRIMARY KEY,
|
|
@@ -3716,6 +4106,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|
|
3716
4106
|
role TEXT NOT NULL CHECK (role IN ('admin', 'user')),
|
|
3717
4107
|
collection_access TEXT NOT NULL DEFAULT '[]',
|
|
3718
4108
|
environment_access TEXT NOT NULL DEFAULT '[]',
|
|
4109
|
+
snippet_access TEXT NOT NULL DEFAULT '[]',
|
|
3719
4110
|
created_at TIMESTAMPTZ NOT NULL,
|
|
3720
4111
|
updated_at TIMESTAMPTZ NOT NULL,
|
|
3721
4112
|
created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
@@ -3827,11 +4218,23 @@ var ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL2 = `
|
|
|
3827
4218
|
ALTER TABLE environments
|
|
3828
4219
|
ADD COLUMN IF NOT EXISTS deletion_locked BOOLEAN NOT NULL DEFAULT FALSE;
|
|
3829
4220
|
`.trim();
|
|
4221
|
+
var USERS_SNIPPET_ACCESS_MIGRATION_SQL2 = `
|
|
4222
|
+
ALTER TABLE users
|
|
4223
|
+
ADD COLUMN IF NOT EXISTS snippet_access TEXT NOT NULL DEFAULT '[]';
|
|
4224
|
+
`.trim();
|
|
4225
|
+
var USERS_SNIPPET_ACCESS_BACKFILL_SQL2 = `
|
|
4226
|
+
UPDATE users
|
|
4227
|
+
SET snippet_access = '["*"]'
|
|
4228
|
+
WHERE role = 'user'
|
|
4229
|
+
AND snippet_access = '[]'
|
|
4230
|
+
AND collection_access LIKE '%"*"%';
|
|
4231
|
+
`.trim();
|
|
3830
4232
|
var POSTGRES_MIGRATIONS = [
|
|
3831
4233
|
USERS_MIGRATION_SQL2,
|
|
3832
4234
|
API_TOKENS_MIGRATION_SQL2,
|
|
3833
4235
|
COLLECTIONS_MIGRATION_SQL2,
|
|
3834
4236
|
ENVIRONMENTS_MIGRATION_SQL2,
|
|
4237
|
+
SNIPPETS_MIGRATION_SQL2,
|
|
3835
4238
|
FOLDERS_MIGRATION_SQL2,
|
|
3836
4239
|
REQUESTS_MIGRATION_SQL2,
|
|
3837
4240
|
AUDIT_LOG_MIGRATION_SQL2,
|
|
@@ -3849,7 +4252,9 @@ var POSTGRES_MIGRATIONS = [
|
|
|
3849
4252
|
LLM_USAGE_MIGRATION_SQL2,
|
|
3850
4253
|
LLM_USAGE_LOG_MIGRATION_SQL2,
|
|
3851
4254
|
COLLECTIONS_DELETION_LOCKED_MIGRATION_SQL2,
|
|
3852
|
-
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL2
|
|
4255
|
+
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL2,
|
|
4256
|
+
USERS_SNIPPET_ACCESS_MIGRATION_SQL2,
|
|
4257
|
+
USERS_SNIPPET_ACCESS_BACKFILL_SQL2
|
|
3853
4258
|
];
|
|
3854
4259
|
|
|
3855
4260
|
// src/db/postgres/schemas.ts
|
|
@@ -3873,6 +4278,7 @@ var postgresConfigSchema = z4.object({
|
|
|
3873
4278
|
var { Pool } = pg;
|
|
3874
4279
|
var COLLECTION_SELECT2 = `SELECT ${COLLECTION_SELECT_COLUMNS} FROM collections`;
|
|
3875
4280
|
var ENVIRONMENT_SELECT2 = `SELECT ${ENVIRONMENT_SELECT_COLUMNS} FROM environments`;
|
|
4281
|
+
var SNIPPET_SELECT2 = `SELECT ${SNIPPET_SELECT_COLUMNS} FROM snippets`;
|
|
3876
4282
|
var USER_SELECT2 = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
|
|
3877
4283
|
var API_TOKEN_SELECT2 = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
|
|
3878
4284
|
var FOLDER_SELECT2 = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
|
|
@@ -4013,6 +4419,7 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4013
4419
|
role,
|
|
4014
4420
|
collection_access,
|
|
4015
4421
|
environment_access,
|
|
4422
|
+
snippet_access,
|
|
4016
4423
|
llm_access,
|
|
4017
4424
|
llm_models,
|
|
4018
4425
|
llm_monthly_token_limit,
|
|
@@ -4020,7 +4427,7 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4020
4427
|
updated_at,
|
|
4021
4428
|
created_by_user_id,
|
|
4022
4429
|
updated_by_user_id
|
|
4023
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
4430
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
4024
4431
|
RETURNING ${USER_SELECT_COLUMNS}`,
|
|
4025
4432
|
[
|
|
4026
4433
|
id,
|
|
@@ -4028,6 +4435,7 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4028
4435
|
input.role,
|
|
4029
4436
|
serializeAccessList(input.collectionAccess),
|
|
4030
4437
|
serializeAccessList(input.environmentAccess),
|
|
4438
|
+
serializeAccessList(input.snippetAccess),
|
|
4031
4439
|
input.llmAccess ?? false,
|
|
4032
4440
|
serializeAccessList(input.llmModels ?? []),
|
|
4033
4441
|
input.llmMonthlyTokenLimit ?? null,
|
|
@@ -4092,6 +4500,7 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4092
4500
|
const role = input.role ?? existing.role;
|
|
4093
4501
|
const collectionAccess = input.collectionAccess ?? existing.collectionAccess;
|
|
4094
4502
|
const environmentAccess = input.environmentAccess ?? existing.environmentAccess;
|
|
4503
|
+
const snippetAccess = input.snippetAccess ?? existing.snippetAccess;
|
|
4095
4504
|
const llmAccess = input.llmAccess ?? existing.llmAccess;
|
|
4096
4505
|
const llmModels = input.llmModels ?? existing.llmModels;
|
|
4097
4506
|
const llmMonthlyTokenLimit = input.llmMonthlyTokenLimit !== void 0 ? input.llmMonthlyTokenLimit : existing.llmMonthlyTokenLimit;
|
|
@@ -4102,17 +4511,19 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4102
4511
|
role = $2,
|
|
4103
4512
|
collection_access = $3,
|
|
4104
4513
|
environment_access = $4,
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4514
|
+
snippet_access = $5,
|
|
4515
|
+
llm_access = $6,
|
|
4516
|
+
llm_models = $7,
|
|
4517
|
+
llm_monthly_token_limit = $8,
|
|
4518
|
+
updated_at = $9,
|
|
4519
|
+
updated_by_user_id = $10
|
|
4520
|
+
WHERE id = $11`,
|
|
4111
4521
|
[
|
|
4112
4522
|
name,
|
|
4113
4523
|
role,
|
|
4114
4524
|
serializeAccessList(collectionAccess),
|
|
4115
4525
|
serializeAccessList(environmentAccess),
|
|
4526
|
+
serializeAccessList(snippetAccess),
|
|
4116
4527
|
llmAccess,
|
|
4117
4528
|
serializeAccessList(llmModels),
|
|
4118
4529
|
llmMonthlyTokenLimit,
|
|
@@ -4174,7 +4585,8 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4174
4585
|
name: BOOTSTRAP_USER_NAME,
|
|
4175
4586
|
role: "user",
|
|
4176
4587
|
collectionAccess: ["*"],
|
|
4177
|
-
environmentAccess: ["*"]
|
|
4588
|
+
environmentAccess: ["*"],
|
|
4589
|
+
snippetAccess: ["*"]
|
|
4178
4590
|
},
|
|
4179
4591
|
systemUserId
|
|
4180
4592
|
);
|
|
@@ -4571,39 +4983,163 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
4571
4983
|
return mapEnvironmentSqlRow(row);
|
|
4572
4984
|
}
|
|
4573
4985
|
/**
|
|
4574
|
-
* Lists all
|
|
4575
|
-
*
|
|
4576
|
-
* @param collectionId - Collection to query.
|
|
4986
|
+
* Lists all snippets ordered by sort order then name.
|
|
4577
4987
|
*/
|
|
4578
|
-
async
|
|
4988
|
+
async listSnippets() {
|
|
4579
4989
|
const result = await this.query(
|
|
4580
|
-
`${
|
|
4581
|
-
[collectionId]
|
|
4990
|
+
`${SNIPPET_SELECT2} ORDER BY sort_order ASC, name ASC`
|
|
4582
4991
|
);
|
|
4583
|
-
return result.rows.map(
|
|
4584
|
-
}
|
|
4585
|
-
/**
|
|
4586
|
-
* Finds a saved request by id.
|
|
4587
|
-
*
|
|
4588
|
-
* @param id - Request identifier to look up.
|
|
4589
|
-
*/
|
|
4590
|
-
async findRequestById(id) {
|
|
4591
|
-
const result = await this.query(`${REQUEST_SELECT2} WHERE id = $1 LIMIT 1`, [id]);
|
|
4592
|
-
const row = result.rows[0];
|
|
4593
|
-
return row ? mapRequestSqlRow(row) : null;
|
|
4992
|
+
return result.rows.map(mapSnippetSqlRow);
|
|
4594
4993
|
}
|
|
4595
4994
|
/**
|
|
4596
|
-
*
|
|
4995
|
+
* Creates a new snippet with the given fields.
|
|
4597
4996
|
*
|
|
4598
|
-
* @param
|
|
4599
|
-
* @param
|
|
4997
|
+
* @param name - Display name for the snippet.
|
|
4998
|
+
* @param code - JavaScript source for the snippet.
|
|
4999
|
+
* @param scope - Execution scope for the snippet.
|
|
5000
|
+
* @param actingUserId - User performing the create action.
|
|
4600
5001
|
*/
|
|
4601
|
-
async
|
|
4602
|
-
const trimmedName = trimRequiredName(
|
|
4603
|
-
const
|
|
4604
|
-
const
|
|
4605
|
-
const
|
|
4606
|
-
|
|
5002
|
+
async createSnippet(name, code, scope, actingUserId) {
|
|
5003
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
5004
|
+
const id = randomUUID4();
|
|
5005
|
+
const now = /* @__PURE__ */ new Date();
|
|
5006
|
+
const maxResult = await this.query(
|
|
5007
|
+
"SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM snippets"
|
|
5008
|
+
);
|
|
5009
|
+
const maxOrder = maxResult.rows[0]?.max_order ?? -1;
|
|
5010
|
+
const result = await this.query(
|
|
5011
|
+
`INSERT INTO snippets (
|
|
5012
|
+
id,
|
|
5013
|
+
name,
|
|
5014
|
+
code,
|
|
5015
|
+
scope,
|
|
5016
|
+
sort_order,
|
|
5017
|
+
created_at,
|
|
5018
|
+
updated_at,
|
|
5019
|
+
created_by_user_id,
|
|
5020
|
+
updated_by_user_id
|
|
5021
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
5022
|
+
RETURNING ${SNIPPET_SELECT_COLUMNS}`,
|
|
5023
|
+
[id, trimmedName, code, scope, maxOrder + 1, now, now, actingUserId, actingUserId]
|
|
5024
|
+
);
|
|
5025
|
+
const row = result.rows[0];
|
|
5026
|
+
if (!row) {
|
|
5027
|
+
throw new Error("Snippet not found after insert");
|
|
5028
|
+
}
|
|
5029
|
+
await this.recordAuditEntry(actingUserId, "create", "snippet", id);
|
|
5030
|
+
return mapSnippetSqlRow(row);
|
|
5031
|
+
}
|
|
5032
|
+
/**
|
|
5033
|
+
* Updates a snippet's name, code, and scope. Sort order is left unchanged.
|
|
5034
|
+
*
|
|
5035
|
+
* @param actingUserId - User performing the update action.
|
|
5036
|
+
*/
|
|
5037
|
+
async updateSnippet(id, name, code, scope, actingUserId) {
|
|
5038
|
+
const trimmedName = trimRequiredName(name, "Snippet name");
|
|
5039
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
5040
|
+
const result = await this.query(
|
|
5041
|
+
`UPDATE snippets
|
|
5042
|
+
SET name = $1,
|
|
5043
|
+
code = $2,
|
|
5044
|
+
scope = $3,
|
|
5045
|
+
updated_at = $4,
|
|
5046
|
+
updated_by_user_id = $5
|
|
5047
|
+
WHERE id = $6`,
|
|
5048
|
+
[trimmedName, code, scope, updatedAt, actingUserId, id]
|
|
5049
|
+
);
|
|
5050
|
+
if ((result.rowCount ?? 0) === 0) {
|
|
5051
|
+
throw new Error("Snippet not found");
|
|
5052
|
+
}
|
|
5053
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
5054
|
+
const selectResult = await this.query(`${SNIPPET_SELECT2} WHERE id = $1`, [id]);
|
|
5055
|
+
const row = selectResult.rows[0];
|
|
5056
|
+
if (!row) {
|
|
5057
|
+
throw new Error("Snippet not found");
|
|
5058
|
+
}
|
|
5059
|
+
return mapSnippetSqlRow(row);
|
|
5060
|
+
}
|
|
5061
|
+
/**
|
|
5062
|
+
* Deletes a snippet.
|
|
5063
|
+
*
|
|
5064
|
+
* @param id - Snippet ID to delete.
|
|
5065
|
+
* @param actingUserId - User performing the delete action.
|
|
5066
|
+
*/
|
|
5067
|
+
async deleteSnippet(id, actingUserId) {
|
|
5068
|
+
await this.recordAuditEntry(actingUserId, "delete", "snippet", id);
|
|
5069
|
+
await this.query("DELETE FROM snippets WHERE id = $1", [id]);
|
|
5070
|
+
}
|
|
5071
|
+
/**
|
|
5072
|
+
* Finds a snippet by stable identifier.
|
|
5073
|
+
*
|
|
5074
|
+
* @param id - Snippet ID to look up.
|
|
5075
|
+
*/
|
|
5076
|
+
async findSnippetById(id) {
|
|
5077
|
+
const result = await this.query(`${SNIPPET_SELECT2} WHERE id = $1`, [id]);
|
|
5078
|
+
const row = result.rows[0];
|
|
5079
|
+
return row ? mapSnippetSqlRow(row) : null;
|
|
5080
|
+
}
|
|
5081
|
+
/**
|
|
5082
|
+
* Updates whether non-admin users may delete a snippet.
|
|
5083
|
+
*
|
|
5084
|
+
* @param id - Snippet ID to update.
|
|
5085
|
+
* @param deletionLocked - When true, user-role tokens cannot delete the snippet.
|
|
5086
|
+
* @param actingUserId - Admin user performing the update.
|
|
5087
|
+
*/
|
|
5088
|
+
async setSnippetDeletionLocked(id, deletionLocked, actingUserId) {
|
|
5089
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
5090
|
+
const result = await this.query(
|
|
5091
|
+
`UPDATE snippets
|
|
5092
|
+
SET deletion_locked = $1,
|
|
5093
|
+
updated_at = $2,
|
|
5094
|
+
updated_by_user_id = $3
|
|
5095
|
+
WHERE id = $4`,
|
|
5096
|
+
[deletionLocked, updatedAt, actingUserId, id]
|
|
5097
|
+
);
|
|
5098
|
+
if ((result.rowCount ?? 0) === 0) {
|
|
5099
|
+
throw new Error("Snippet not found");
|
|
5100
|
+
}
|
|
5101
|
+
await this.recordAuditEntry(actingUserId, "update", "snippet", id);
|
|
5102
|
+
const selectResult = await this.query(`${SNIPPET_SELECT2} WHERE id = $1`, [id]);
|
|
5103
|
+
const row = selectResult.rows[0];
|
|
5104
|
+
if (!row) {
|
|
5105
|
+
throw new Error("Snippet not found");
|
|
5106
|
+
}
|
|
5107
|
+
return mapSnippetSqlRow(row);
|
|
5108
|
+
}
|
|
5109
|
+
/**
|
|
5110
|
+
* Lists all saved requests in a collection.
|
|
5111
|
+
*
|
|
5112
|
+
* @param collectionId - Collection to query.
|
|
5113
|
+
*/
|
|
5114
|
+
async listRequests(collectionId) {
|
|
5115
|
+
const result = await this.query(
|
|
5116
|
+
`${REQUEST_SELECT2} WHERE collection_id = $1 ORDER BY sort_order ASC, name ASC`,
|
|
5117
|
+
[collectionId]
|
|
5118
|
+
);
|
|
5119
|
+
return result.rows.map(mapRequestSqlRow);
|
|
5120
|
+
}
|
|
5121
|
+
/**
|
|
5122
|
+
* Finds a saved request by id.
|
|
5123
|
+
*
|
|
5124
|
+
* @param id - Request identifier to look up.
|
|
5125
|
+
*/
|
|
5126
|
+
async findRequestById(id) {
|
|
5127
|
+
const result = await this.query(`${REQUEST_SELECT2} WHERE id = $1 LIMIT 1`, [id]);
|
|
5128
|
+
const row = result.rows[0];
|
|
5129
|
+
return row ? mapRequestSqlRow(row) : null;
|
|
5130
|
+
}
|
|
5131
|
+
/**
|
|
5132
|
+
* Inserts a new request or updates an existing one.
|
|
5133
|
+
*
|
|
5134
|
+
* @param input - Request fields to persist.
|
|
5135
|
+
* @param actingUserId - User performing the save action.
|
|
5136
|
+
*/
|
|
5137
|
+
async saveRequest(input, actingUserId) {
|
|
5138
|
+
const trimmedName = trimRequiredName(input.name, "Request name");
|
|
5139
|
+
const headers = JSON.stringify(input.headers);
|
|
5140
|
+
const params = JSON.stringify(input.params);
|
|
5141
|
+
const auth = JSON.stringify(input.auth);
|
|
5142
|
+
const folderId = input.folderId ?? null;
|
|
4607
5143
|
const now = /* @__PURE__ */ new Date();
|
|
4608
5144
|
if (folderId != null) {
|
|
4609
5145
|
const folderResult = await this.query(
|
|
@@ -5127,6 +5663,7 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
5127
5663
|
role,
|
|
5128
5664
|
collection_access,
|
|
5129
5665
|
environment_access,
|
|
5666
|
+
snippet_access,
|
|
5130
5667
|
llm_access,
|
|
5131
5668
|
llm_models,
|
|
5132
5669
|
llm_monthly_token_limit,
|
|
@@ -5134,13 +5671,14 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
5134
5671
|
updated_at,
|
|
5135
5672
|
created_by_user_id,
|
|
5136
5673
|
updated_by_user_id
|
|
5137
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
|
5674
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
|
5138
5675
|
[
|
|
5139
5676
|
id,
|
|
5140
5677
|
SYSTEM_USER_NAME,
|
|
5141
5678
|
input.role,
|
|
5142
5679
|
serializeAccessList(input.collectionAccess),
|
|
5143
5680
|
serializeAccessList(input.environmentAccess),
|
|
5681
|
+
serializeAccessList(input.snippetAccess),
|
|
5144
5682
|
false,
|
|
5145
5683
|
serializeAccessList([]),
|
|
5146
5684
|
null,
|
|
@@ -5440,21 +5978,26 @@ function normalizeLlmForRole(role, llmAccess, llmModels) {
|
|
|
5440
5978
|
llmModels
|
|
5441
5979
|
};
|
|
5442
5980
|
}
|
|
5443
|
-
function normalizeAccessForRole(role, collectionAccess, environmentAccess) {
|
|
5981
|
+
function normalizeAccessForRole(role, collectionAccess, environmentAccess, snippetAccess) {
|
|
5444
5982
|
if (role === "admin") {
|
|
5445
|
-
if (collectionAccess.length > 0 || environmentAccess.length > 0) {
|
|
5446
|
-
throw new ValidationError(
|
|
5983
|
+
if (collectionAccess.length > 0 || environmentAccess.length > 0 || snippetAccess.length > 0) {
|
|
5984
|
+
throw new ValidationError(
|
|
5985
|
+
"Admin users cannot have collection, environment, or snippet access."
|
|
5986
|
+
);
|
|
5447
5987
|
}
|
|
5448
5988
|
return {
|
|
5449
5989
|
collectionAccess: [],
|
|
5450
|
-
environmentAccess: []
|
|
5990
|
+
environmentAccess: [],
|
|
5991
|
+
snippetAccess: []
|
|
5451
5992
|
};
|
|
5452
5993
|
}
|
|
5453
5994
|
validateAccessList(collectionAccess);
|
|
5454
5995
|
validateAccessList(environmentAccess);
|
|
5996
|
+
validateAccessList(snippetAccess);
|
|
5455
5997
|
return {
|
|
5456
5998
|
collectionAccess,
|
|
5457
|
-
environmentAccess
|
|
5999
|
+
environmentAccess,
|
|
6000
|
+
snippetAccess
|
|
5458
6001
|
};
|
|
5459
6002
|
}
|
|
5460
6003
|
function findUnknownAccessIds(access, knownIds) {
|
|
@@ -5480,6 +6023,9 @@ function validateSubmittedAccessLists(submitted, catalogs) {
|
|
|
5480
6023
|
"environment"
|
|
5481
6024
|
);
|
|
5482
6025
|
}
|
|
6026
|
+
if (submitted.snippetAccess !== void 0) {
|
|
6027
|
+
validateKnownAccessIds(submitted.snippetAccess, catalogs.knownSnippetIds, "snippet");
|
|
6028
|
+
}
|
|
5483
6029
|
}
|
|
5484
6030
|
if (submitted.llmModels !== void 0 && catalogs.knownLlmModelIds !== null) {
|
|
5485
6031
|
validateKnownAccessIds(submitted.llmModels, catalogs.knownLlmModelIds, "LLM model");
|
|
@@ -5493,6 +6039,9 @@ function buildAccessListWarnings(stored, catalogs) {
|
|
|
5493
6039
|
for (const id of findUnknownAccessIds(stored.environmentAccess, catalogs.knownEnvironmentIds)) {
|
|
5494
6040
|
warnings.push(`Unknown environment id "${id}".`);
|
|
5495
6041
|
}
|
|
6042
|
+
for (const id of findUnknownAccessIds(stored.snippetAccess, catalogs.knownSnippetIds)) {
|
|
6043
|
+
warnings.push(`Unknown snippet id "${id}".`);
|
|
6044
|
+
}
|
|
5496
6045
|
if (catalogs.knownLlmModelIds !== null) {
|
|
5497
6046
|
for (const id of findUnknownAccessIds(stored.llmModels, catalogs.knownLlmModelIds)) {
|
|
5498
6047
|
warnings.push(`Unknown LLM model id "${id}".`);
|
|
@@ -5500,10 +6049,11 @@ function buildAccessListWarnings(stored, catalogs) {
|
|
|
5500
6049
|
}
|
|
5501
6050
|
return warnings;
|
|
5502
6051
|
}
|
|
5503
|
-
function buildAccessCatalogIds(collections, environments, llmModelIds) {
|
|
6052
|
+
function buildAccessCatalogIds(collections, environments, snippets, llmModelIds) {
|
|
5504
6053
|
return {
|
|
5505
6054
|
knownCollectionIds: new Set(collections.map((collection) => collection.id)),
|
|
5506
6055
|
knownEnvironmentIds: new Set(environments.map((environment) => environment.id)),
|
|
6056
|
+
knownSnippetIds: new Set(snippets.map((snippet) => snippet.id)),
|
|
5507
6057
|
knownLlmModelIds: llmModelIds === null ? null : new Set(llmModelIds)
|
|
5508
6058
|
};
|
|
5509
6059
|
}
|
|
@@ -5511,7 +6061,8 @@ function buildAdminUserUpdateInput(existing, body) {
|
|
|
5511
6061
|
const role = body.role ?? existing.role;
|
|
5512
6062
|
const collectionAccess = role === "admin" ? [] : body.collectionAccess ?? existing.collectionAccess;
|
|
5513
6063
|
const environmentAccess = role === "admin" ? [] : body.environmentAccess ?? existing.environmentAccess;
|
|
5514
|
-
const
|
|
6064
|
+
const snippetAccess = role === "admin" ? [] : body.snippetAccess ?? existing.snippetAccess;
|
|
6065
|
+
const access = normalizeAccessForRole(role, collectionAccess, environmentAccess, snippetAccess);
|
|
5515
6066
|
const llmAccess = role === "admin" ? false : body.llmAccess ?? existing.llmAccess;
|
|
5516
6067
|
const llmModels = role === "admin" ? [] : body.llmModels ?? existing.llmModels;
|
|
5517
6068
|
const llm = normalizeLlmForRole(role, llmAccess, llmModels);
|
|
@@ -5520,6 +6071,7 @@ function buildAdminUserUpdateInput(existing, body) {
|
|
|
5520
6071
|
role: body.role,
|
|
5521
6072
|
collectionAccess: access.collectionAccess,
|
|
5522
6073
|
environmentAccess: access.environmentAccess,
|
|
6074
|
+
snippetAccess: access.snippetAccess,
|
|
5523
6075
|
llmAccess: llm.llmAccess,
|
|
5524
6076
|
llmModels: llm.llmModels,
|
|
5525
6077
|
llmMonthlyTokenLimit: body.llmMonthlyTokenLimit
|
|
@@ -5528,7 +6080,13 @@ function buildAdminUserUpdateInput(existing, body) {
|
|
|
5528
6080
|
function buildAdminUserCreateInput(body) {
|
|
5529
6081
|
const collectionAccess = body.collectionAccess ?? [];
|
|
5530
6082
|
const environmentAccess = body.environmentAccess ?? [];
|
|
5531
|
-
const
|
|
6083
|
+
const snippetAccess = body.snippetAccess ?? [];
|
|
6084
|
+
const access = normalizeAccessForRole(
|
|
6085
|
+
body.role,
|
|
6086
|
+
collectionAccess,
|
|
6087
|
+
environmentAccess,
|
|
6088
|
+
snippetAccess
|
|
6089
|
+
);
|
|
5532
6090
|
const llmAccess = body.role === "admin" ? false : body.llmAccess ?? false;
|
|
5533
6091
|
const llmModels = body.role === "admin" ? [] : body.llmModels ?? [];
|
|
5534
6092
|
const llm = normalizeLlmForRole(body.role, llmAccess, llmModels);
|
|
@@ -5537,6 +6095,7 @@ function buildAdminUserCreateInput(body) {
|
|
|
5537
6095
|
role: body.role,
|
|
5538
6096
|
collectionAccess: access.collectionAccess ?? [],
|
|
5539
6097
|
environmentAccess: access.environmentAccess ?? [],
|
|
6098
|
+
snippetAccess: access.snippetAccess ?? [],
|
|
5540
6099
|
llmAccess: llm.llmAccess ?? false,
|
|
5541
6100
|
llmModels: llm.llmModels ?? [],
|
|
5542
6101
|
llmMonthlyTokenLimit: body.llmMonthlyTokenLimit ?? null
|
|
@@ -5635,6 +6194,7 @@ function printUser(user, usage) {
|
|
|
5635
6194
|
console.log(` role: ${user.role}`);
|
|
5636
6195
|
console.log(` collection access: ${formatAccessList(user.collectionAccess)}`);
|
|
5637
6196
|
console.log(` environment access: ${formatAccessList(user.environmentAccess)}`);
|
|
6197
|
+
console.log(` snippet access: ${formatAccessList(user.snippetAccess)}`);
|
|
5638
6198
|
console.log(` llm access: ${user.llmAccess ? "enabled" : "disabled"}`);
|
|
5639
6199
|
console.log(` llm models: ${formatAccessList(user.llmModels)}`);
|
|
5640
6200
|
console.log(
|
|
@@ -5654,13 +6214,15 @@ function printCreatedApiToken(user, record, secret) {
|
|
|
5654
6214
|
console.log(secret);
|
|
5655
6215
|
}
|
|
5656
6216
|
async function loadAccessCatalogs(db, llm) {
|
|
5657
|
-
const [collections, environments] = await Promise.all([
|
|
6217
|
+
const [collections, environments, snippets] = await Promise.all([
|
|
5658
6218
|
db.listCollections(),
|
|
5659
|
-
db.listEnvironments()
|
|
6219
|
+
db.listEnvironments(),
|
|
6220
|
+
db.listSnippets()
|
|
5660
6221
|
]);
|
|
5661
6222
|
return buildAccessCatalogIds(
|
|
5662
6223
|
collections,
|
|
5663
6224
|
environments,
|
|
6225
|
+
snippets,
|
|
5664
6226
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
5665
6227
|
);
|
|
5666
6228
|
}
|
|
@@ -5686,7 +6248,12 @@ async function userCreateCommand(options) {
|
|
|
5686
6248
|
const config = loadServerConfig(options.config);
|
|
5687
6249
|
const db = createDatabase(config.db);
|
|
5688
6250
|
const access = mapValidationError(
|
|
5689
|
-
() => normalizeAccessForRole(
|
|
6251
|
+
() => normalizeAccessForRole(
|
|
6252
|
+
options.role,
|
|
6253
|
+
options.collectionAccess,
|
|
6254
|
+
options.environmentAccess,
|
|
6255
|
+
options.snippetAccess
|
|
6256
|
+
)
|
|
5690
6257
|
);
|
|
5691
6258
|
await db.connect();
|
|
5692
6259
|
const actingUserId = await requireSystemUserId(db);
|
|
@@ -5700,6 +6267,7 @@ async function userCreateCommand(options) {
|
|
|
5700
6267
|
role: options.role,
|
|
5701
6268
|
collectionAccess: access.collectionAccess,
|
|
5702
6269
|
environmentAccess: access.environmentAccess,
|
|
6270
|
+
snippetAccess: access.snippetAccess,
|
|
5703
6271
|
llmModels
|
|
5704
6272
|
},
|
|
5705
6273
|
catalogs
|
|
@@ -5710,6 +6278,7 @@ async function userCreateCommand(options) {
|
|
|
5710
6278
|
role: options.role,
|
|
5711
6279
|
collectionAccess: access.collectionAccess ?? [],
|
|
5712
6280
|
environmentAccess: access.environmentAccess ?? [],
|
|
6281
|
+
snippetAccess: access.snippetAccess ?? [],
|
|
5713
6282
|
llmAccess: llm.llmAccess,
|
|
5714
6283
|
llmModels: llm.llmModels,
|
|
5715
6284
|
llmMonthlyTokenLimit: options.llmMonthlyTokens ?? null
|
|
@@ -5747,6 +6316,7 @@ async function userListCommand(options) {
|
|
|
5747
6316
|
{
|
|
5748
6317
|
collectionAccess: user.collectionAccess,
|
|
5749
6318
|
environmentAccess: user.environmentAccess,
|
|
6319
|
+
snippetAccess: user.snippetAccess,
|
|
5750
6320
|
llmModels: user.llmModels
|
|
5751
6321
|
},
|
|
5752
6322
|
catalogs
|
|
@@ -5776,6 +6346,7 @@ async function userShowCommand(options) {
|
|
|
5776
6346
|
{
|
|
5777
6347
|
collectionAccess: user.collectionAccess,
|
|
5778
6348
|
environmentAccess: user.environmentAccess,
|
|
6349
|
+
snippetAccess: user.snippetAccess,
|
|
5779
6350
|
llmModels: user.llmModels
|
|
5780
6351
|
},
|
|
5781
6352
|
catalogs
|
|
@@ -5797,8 +6368,9 @@ async function userUpdateCommand(options) {
|
|
|
5797
6368
|
const role = options.role ?? existing.role;
|
|
5798
6369
|
const collectionAccess = options.collectionAccess ?? (options.role === "admin" ? [] : existing.collectionAccess);
|
|
5799
6370
|
const environmentAccess = options.environmentAccess ?? (options.role === "admin" ? [] : existing.environmentAccess);
|
|
6371
|
+
const snippetAccess = options.snippetAccess ?? (options.role === "admin" ? [] : existing.snippetAccess);
|
|
5800
6372
|
const access = mapValidationError(
|
|
5801
|
-
() => normalizeAccessForRole(role, collectionAccess, environmentAccess)
|
|
6373
|
+
() => normalizeAccessForRole(role, collectionAccess, environmentAccess, snippetAccess)
|
|
5802
6374
|
);
|
|
5803
6375
|
const llmAccess = role === "admin" ? false : options.llmAccess ?? existing.llmAccess;
|
|
5804
6376
|
const llmModels = role === "admin" ? [] : options.llmModels ?? existing.llmModels;
|
|
@@ -5809,6 +6381,7 @@ async function userUpdateCommand(options) {
|
|
|
5809
6381
|
role,
|
|
5810
6382
|
collectionAccess: options.collectionAccess,
|
|
5811
6383
|
environmentAccess: options.environmentAccess,
|
|
6384
|
+
snippetAccess: options.snippetAccess,
|
|
5812
6385
|
llmModels: options.llmModels
|
|
5813
6386
|
},
|
|
5814
6387
|
catalogs
|
|
@@ -5818,6 +6391,7 @@ async function userUpdateCommand(options) {
|
|
|
5818
6391
|
role: options.role,
|
|
5819
6392
|
collectionAccess: access.collectionAccess,
|
|
5820
6393
|
environmentAccess: access.environmentAccess,
|
|
6394
|
+
snippetAccess: access.snippetAccess,
|
|
5821
6395
|
llmAccess: llm.llmAccess,
|
|
5822
6396
|
llmModels: llm.llmModels,
|
|
5823
6397
|
llmMonthlyTokenLimit: options.llmMonthlyTokens !== void 0 ? options.llmMonthlyTokens : void 0
|
|
@@ -5901,6 +6475,11 @@ function registerUserCommand(program, handlers = {}) {
|
|
|
5901
6475
|
"Environment id or * (repeatable)",
|
|
5902
6476
|
parseAccessFlag,
|
|
5903
6477
|
[]
|
|
6478
|
+
).option(
|
|
6479
|
+
"--snippet-access <id>",
|
|
6480
|
+
"Snippet id or * (repeatable)",
|
|
6481
|
+
parseAccessFlag,
|
|
6482
|
+
[]
|
|
5904
6483
|
).option("--llm-access", "Enable hub-proxied LLM access for the user").option("--llm-model <id>", "LLM model id or * (repeatable)", parseAccessFlag, []).option("--llm-monthly-tokens <count>", "Monthly LLM token limit", parseMonthlyTokenLimit).action(
|
|
5905
6484
|
/**
|
|
5906
6485
|
* Runs the user create subcommand after merging global CLI options.
|
|
@@ -5935,6 +6514,11 @@ function registerUserCommand(program, handlers = {}) {
|
|
|
5935
6514
|
"Replacement environment id or * (repeatable)",
|
|
5936
6515
|
parseAccessFlag,
|
|
5937
6516
|
[]
|
|
6517
|
+
).option(
|
|
6518
|
+
"--snippet-access <id>",
|
|
6519
|
+
"Replacement snippet id or * (repeatable)",
|
|
6520
|
+
parseAccessFlag,
|
|
6521
|
+
[]
|
|
5938
6522
|
).option("--llm-access", "Enable hub-proxied LLM access for the user").option("--no-llm-access", "Disable hub-proxied LLM access for the user").option(
|
|
5939
6523
|
"--llm-model <id>",
|
|
5940
6524
|
"Replacement LLM model id or * (repeatable)",
|
|
@@ -5950,6 +6534,7 @@ function registerUserCommand(program, handlers = {}) {
|
|
|
5950
6534
|
...merged,
|
|
5951
6535
|
collectionAccess: (options.collectionAccess ?? []).length > 0 ? options.collectionAccess : void 0,
|
|
5952
6536
|
environmentAccess: (options.environmentAccess ?? []).length > 0 ? options.environmentAccess : void 0,
|
|
6537
|
+
snippetAccess: (options.snippetAccess ?? []).length > 0 ? options.snippetAccess : void 0,
|
|
5953
6538
|
llmModels: (() => {
|
|
5954
6539
|
const llmModels = readLlmModelsOption(options);
|
|
5955
6540
|
return llmModels.length > 0 ? llmModels : void 0;
|
|
@@ -6067,6 +6652,9 @@ function canListCollections(user) {
|
|
|
6067
6652
|
function canListEnvironments(user) {
|
|
6068
6653
|
return canUseDataApi(user) || canUseManagementApi(user);
|
|
6069
6654
|
}
|
|
6655
|
+
function canListSnippets(user) {
|
|
6656
|
+
return canUseDataApi(user) || canUseManagementApi(user);
|
|
6657
|
+
}
|
|
6070
6658
|
function hasWildcardAccess(access) {
|
|
6071
6659
|
return access.includes("*");
|
|
6072
6660
|
}
|
|
@@ -6088,6 +6676,15 @@ function canAccessEnvironment(user, environmentId) {
|
|
|
6088
6676
|
}
|
|
6089
6677
|
return user.environmentAccess.includes(environmentId);
|
|
6090
6678
|
}
|
|
6679
|
+
function canAccessSnippet(user, snippetId) {
|
|
6680
|
+
if (user.role === "admin") {
|
|
6681
|
+
return false;
|
|
6682
|
+
}
|
|
6683
|
+
if (hasWildcardAccess(user.snippetAccess)) {
|
|
6684
|
+
return true;
|
|
6685
|
+
}
|
|
6686
|
+
return user.snippetAccess.includes(snippetId);
|
|
6687
|
+
}
|
|
6091
6688
|
function canDeleteCollection(user, collection) {
|
|
6092
6689
|
return canUseDataApi(user) && canAccessCollection(user, collection.id) && collection.createdByUserId === user.id && !collection.deletionLocked;
|
|
6093
6690
|
}
|
|
@@ -6100,6 +6697,9 @@ function canCreateCollection(user) {
|
|
|
6100
6697
|
function canCreateEnvironment(user) {
|
|
6101
6698
|
return user.role === "user" && hasWildcardAccess(user.environmentAccess);
|
|
6102
6699
|
}
|
|
6700
|
+
function canCreateSnippet(user) {
|
|
6701
|
+
return user.role === "user" && hasWildcardAccess(user.snippetAccess);
|
|
6702
|
+
}
|
|
6103
6703
|
function filterAccessibleCollections(user, collections) {
|
|
6104
6704
|
if (user.role === "admin" || hasWildcardAccess(user.collectionAccess)) {
|
|
6105
6705
|
return collections;
|
|
@@ -6114,6 +6714,13 @@ function filterAccessibleEnvironments(user, environments) {
|
|
|
6114
6714
|
const allowed = new Set(user.environmentAccess);
|
|
6115
6715
|
return environments.filter((environment) => allowed.has(environment.id));
|
|
6116
6716
|
}
|
|
6717
|
+
function filterAccessibleSnippets(user, snippets) {
|
|
6718
|
+
if (user.role === "admin" || hasWildcardAccess(user.snippetAccess)) {
|
|
6719
|
+
return snippets;
|
|
6720
|
+
}
|
|
6721
|
+
const allowed = new Set(user.snippetAccess);
|
|
6722
|
+
return snippets.filter((snippet) => allowed.has(snippet.id));
|
|
6723
|
+
}
|
|
6117
6724
|
function canUseLlm(user) {
|
|
6118
6725
|
return user.role !== "admin" && user.llmAccess;
|
|
6119
6726
|
}
|
|
@@ -6258,7 +6865,7 @@ function denyUnlessAllowed(reply, allowed) {
|
|
|
6258
6865
|
}
|
|
6259
6866
|
|
|
6260
6867
|
// src/server/routes/schemas/admin.ts
|
|
6261
|
-
import { z as
|
|
6868
|
+
import { z as z9 } from "zod/v4";
|
|
6262
6869
|
|
|
6263
6870
|
// src/server/routes/schemas/auth.ts
|
|
6264
6871
|
import { z as z6 } from "zod/v4";
|
|
@@ -6325,247 +6932,154 @@ var llmUsageSummaryResponseSchema = z7.object({
|
|
|
6325
6932
|
limit: z7.number().int().positive().nullable()
|
|
6326
6933
|
});
|
|
6327
6934
|
|
|
6328
|
-
// src/server/routes/schemas/
|
|
6329
|
-
|
|
6935
|
+
// src/server/routes/schemas/entities.ts
|
|
6936
|
+
import { z as z8 } from "zod/v4";
|
|
6937
|
+
var collectionRecordSchema = z8.object({
|
|
6330
6938
|
id: z8.string(),
|
|
6331
6939
|
name: z8.string(),
|
|
6940
|
+
variables: z8.array(variableSchema),
|
|
6941
|
+
headers: z8.array(keyValueSchema),
|
|
6942
|
+
auth: authConfigSchema,
|
|
6943
|
+
preRequestScript: z8.string(),
|
|
6944
|
+
postRequestScript: z8.string(),
|
|
6945
|
+
createdAt: timestampSchema,
|
|
6946
|
+
updatedAt: timestampSchema,
|
|
6947
|
+
createdByUserId: z8.string().nullable(),
|
|
6948
|
+
updatedByUserId: z8.string().nullable(),
|
|
6332
6949
|
deletionLocked: z8.boolean()
|
|
6333
6950
|
});
|
|
6334
|
-
var
|
|
6951
|
+
var environmentRecordSchema = z8.object({
|
|
6335
6952
|
id: z8.string(),
|
|
6336
6953
|
name: z8.string(),
|
|
6954
|
+
variables: z8.array(variableSchema),
|
|
6955
|
+
createdAt: timestampSchema,
|
|
6956
|
+
updatedAt: timestampSchema,
|
|
6957
|
+
createdByUserId: z8.string().nullable(),
|
|
6958
|
+
updatedByUserId: z8.string().nullable(),
|
|
6337
6959
|
deletionLocked: z8.boolean()
|
|
6338
6960
|
});
|
|
6339
|
-
var
|
|
6340
|
-
|
|
6341
|
-
});
|
|
6342
|
-
var updateAdminEnvironmentBodySchema = z8.object({
|
|
6343
|
-
deletionLocked: z8.boolean()
|
|
6344
|
-
});
|
|
6345
|
-
var listAdminCollectionsResponseSchema = z8.object({
|
|
6346
|
-
collections: z8.array(adminResourceOptionSchema)
|
|
6347
|
-
});
|
|
6348
|
-
var listAdminEnvironmentsResponseSchema = z8.object({
|
|
6349
|
-
environments: z8.array(adminResourceOptionSchema)
|
|
6350
|
-
});
|
|
6351
|
-
var listAdminLlmModelsResponseSchema = listLlmModelsResponseSchema;
|
|
6352
|
-
var hubUserRecordSchema = z8.object({
|
|
6961
|
+
var snippetScopeSchema = z8.enum(["pre-request", "post-request", "any"]);
|
|
6962
|
+
var snippetRecordSchema = z8.object({
|
|
6353
6963
|
id: z8.string(),
|
|
6354
6964
|
name: z8.string(),
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
llmAccess: z8.boolean(),
|
|
6359
|
-
llmModels: z8.array(z8.string()),
|
|
6360
|
-
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable(),
|
|
6965
|
+
code: z8.string(),
|
|
6966
|
+
scope: snippetScopeSchema,
|
|
6967
|
+
sortOrder: z8.number().int(),
|
|
6361
6968
|
createdAt: timestampSchema,
|
|
6362
|
-
updatedAt: timestampSchema
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
});
|
|
6367
|
-
var listAdminUsersResponseSchema = z8.object({
|
|
6368
|
-
users: z8.array(adminUserListEntrySchema)
|
|
6369
|
-
});
|
|
6370
|
-
var updateAdminUserBodySchema = z8.object({
|
|
6371
|
-
name: z8.string().trim().min(1).optional(),
|
|
6372
|
-
role: userRoleSchema.optional(),
|
|
6373
|
-
collectionAccess: z8.array(z8.string()).optional(),
|
|
6374
|
-
environmentAccess: z8.array(z8.string()).optional(),
|
|
6375
|
-
llmAccess: z8.boolean().optional(),
|
|
6376
|
-
llmModels: z8.array(z8.string()).optional(),
|
|
6377
|
-
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
6378
|
-
});
|
|
6379
|
-
var createAdminUserBodySchema = z8.object({
|
|
6380
|
-
name: z8.string().trim().min(1),
|
|
6381
|
-
role: userRoleSchema,
|
|
6382
|
-
collectionAccess: z8.array(z8.string()).optional(),
|
|
6383
|
-
environmentAccess: z8.array(z8.string()).optional(),
|
|
6384
|
-
llmAccess: z8.boolean().optional(),
|
|
6385
|
-
llmModels: z8.array(z8.string()).optional(),
|
|
6386
|
-
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
6969
|
+
updatedAt: timestampSchema,
|
|
6970
|
+
createdByUserId: z8.string().nullable(),
|
|
6971
|
+
updatedByUserId: z8.string().nullable(),
|
|
6972
|
+
deletionLocked: z8.boolean()
|
|
6387
6973
|
});
|
|
6388
|
-
var
|
|
6974
|
+
var folderRecordSchema = z8.object({
|
|
6389
6975
|
id: z8.string(),
|
|
6390
|
-
|
|
6976
|
+
collectionId: z8.string(),
|
|
6391
6977
|
name: z8.string(),
|
|
6392
|
-
|
|
6393
|
-
createdAt: timestampSchema,
|
|
6394
|
-
lastUsedAt: timestampSchema.nullable(),
|
|
6395
|
-
revokedAt: timestampSchema.nullable()
|
|
6396
|
-
});
|
|
6397
|
-
var createAdminUserResponseSchema = z8.object({
|
|
6398
|
-
user: hubUserRecordSchema,
|
|
6399
|
-
token: hubApiTokenRecordSchema,
|
|
6400
|
-
secret: z8.string()
|
|
6401
|
-
});
|
|
6402
|
-
var createAdminTokenBodySchema = z8.object({
|
|
6403
|
-
name: z8.string().trim().min(1)
|
|
6404
|
-
});
|
|
6405
|
-
var createdApiTokenResponseSchema = z8.object({
|
|
6406
|
-
token: hubApiTokenRecordSchema,
|
|
6407
|
-
secret: z8.string()
|
|
6408
|
-
});
|
|
6409
|
-
var listAdminTokensResponseSchema = z8.object({
|
|
6410
|
-
tokens: z8.array(hubApiTokenRecordSchema)
|
|
6411
|
-
});
|
|
6412
|
-
var reloadConfigSectionResultSchema = z8.object({
|
|
6413
|
-
section: z8.enum(["db", "redis", "llm", "plugins", "server"]),
|
|
6414
|
-
status: z8.enum(["reloaded", "unchanged", "failed", "restart-required"]),
|
|
6415
|
-
error: z8.string().optional()
|
|
6416
|
-
});
|
|
6417
|
-
var reloadConfigResponseSchema = z8.object({
|
|
6418
|
-
sections: z8.array(reloadConfigSectionResultSchema),
|
|
6419
|
-
fatalError: z8.string().optional()
|
|
6420
|
-
});
|
|
6421
|
-
function serializeHubUser(user) {
|
|
6422
|
-
return {
|
|
6423
|
-
id: user.id,
|
|
6424
|
-
name: user.name,
|
|
6425
|
-
role: user.role,
|
|
6426
|
-
collectionAccess: user.collectionAccess,
|
|
6427
|
-
environmentAccess: user.environmentAccess,
|
|
6428
|
-
llmAccess: user.llmAccess,
|
|
6429
|
-
llmModels: user.llmModels,
|
|
6430
|
-
llmMonthlyTokenLimit: user.llmMonthlyTokenLimit,
|
|
6431
|
-
createdAt: user.createdAt.toISOString(),
|
|
6432
|
-
updatedAt: user.updatedAt.toISOString()
|
|
6433
|
-
};
|
|
6434
|
-
}
|
|
6435
|
-
function serializeApiToken(token) {
|
|
6436
|
-
return {
|
|
6437
|
-
id: token.id,
|
|
6438
|
-
userId: token.userId,
|
|
6439
|
-
name: token.name,
|
|
6440
|
-
tokenPrefix: token.tokenPrefix,
|
|
6441
|
-
createdAt: token.createdAt.toISOString(),
|
|
6442
|
-
lastUsedAt: token.lastUsedAt ? token.lastUsedAt.toISOString() : null,
|
|
6443
|
-
revokedAt: token.revokedAt ? token.revokedAt.toISOString() : null
|
|
6444
|
-
};
|
|
6445
|
-
}
|
|
6446
|
-
|
|
6447
|
-
// src/server/routes/schemas/entities.ts
|
|
6448
|
-
import { z as z9 } from "zod/v4";
|
|
6449
|
-
var collectionRecordSchema = z9.object({
|
|
6450
|
-
id: z9.string(),
|
|
6451
|
-
name: z9.string(),
|
|
6452
|
-
variables: z9.array(variableSchema),
|
|
6453
|
-
headers: z9.array(keyValueSchema),
|
|
6454
|
-
auth: authConfigSchema,
|
|
6455
|
-
preRequestScript: z9.string(),
|
|
6456
|
-
postRequestScript: z9.string(),
|
|
6457
|
-
createdAt: timestampSchema,
|
|
6458
|
-
updatedAt: timestampSchema,
|
|
6459
|
-
createdByUserId: z9.string().nullable(),
|
|
6460
|
-
updatedByUserId: z9.string().nullable(),
|
|
6461
|
-
deletionLocked: z9.boolean()
|
|
6462
|
-
});
|
|
6463
|
-
var environmentRecordSchema = z9.object({
|
|
6464
|
-
id: z9.string(),
|
|
6465
|
-
name: z9.string(),
|
|
6466
|
-
variables: z9.array(variableSchema),
|
|
6467
|
-
createdAt: timestampSchema,
|
|
6468
|
-
updatedAt: timestampSchema,
|
|
6469
|
-
createdByUserId: z9.string().nullable(),
|
|
6470
|
-
updatedByUserId: z9.string().nullable(),
|
|
6471
|
-
deletionLocked: z9.boolean()
|
|
6472
|
-
});
|
|
6473
|
-
var folderRecordSchema = z9.object({
|
|
6474
|
-
id: z9.string(),
|
|
6475
|
-
collectionId: z9.string(),
|
|
6476
|
-
name: z9.string(),
|
|
6477
|
-
sortOrder: z9.number().int(),
|
|
6978
|
+
sortOrder: z8.number().int(),
|
|
6478
6979
|
createdAt: timestampSchema,
|
|
6479
6980
|
updatedAt: timestampSchema,
|
|
6480
|
-
createdByUserId:
|
|
6481
|
-
updatedByUserId:
|
|
6981
|
+
createdByUserId: z8.string().nullable(),
|
|
6982
|
+
updatedByUserId: z8.string().nullable()
|
|
6482
6983
|
});
|
|
6483
|
-
var savedRequestRecordSchema =
|
|
6484
|
-
id:
|
|
6485
|
-
collectionId:
|
|
6486
|
-
name:
|
|
6984
|
+
var savedRequestRecordSchema = z8.object({
|
|
6985
|
+
id: z8.string(),
|
|
6986
|
+
collectionId: z8.string(),
|
|
6987
|
+
name: z8.string(),
|
|
6487
6988
|
method: httpMethodSchema,
|
|
6488
|
-
url:
|
|
6489
|
-
headers:
|
|
6490
|
-
params:
|
|
6989
|
+
url: z8.string(),
|
|
6990
|
+
headers: z8.array(keyValueSchema),
|
|
6991
|
+
params: z8.array(keyValueSchema),
|
|
6491
6992
|
auth: authConfigSchema,
|
|
6492
|
-
body:
|
|
6993
|
+
body: z8.string(),
|
|
6493
6994
|
bodyType: bodyTypeSchema,
|
|
6494
|
-
preRequestScript:
|
|
6495
|
-
postRequestScript:
|
|
6496
|
-
comment:
|
|
6497
|
-
folderId:
|
|
6498
|
-
sortOrder:
|
|
6995
|
+
preRequestScript: z8.string(),
|
|
6996
|
+
postRequestScript: z8.string(),
|
|
6997
|
+
comment: z8.string(),
|
|
6998
|
+
folderId: z8.string().nullable(),
|
|
6999
|
+
sortOrder: z8.number().int(),
|
|
6499
7000
|
createdAt: timestampSchema,
|
|
6500
7001
|
updatedAt: timestampSchema,
|
|
6501
|
-
createdByUserId:
|
|
6502
|
-
updatedByUserId:
|
|
7002
|
+
createdByUserId: z8.string().nullable(),
|
|
7003
|
+
updatedByUserId: z8.string().nullable()
|
|
6503
7004
|
});
|
|
6504
|
-
var createCollectionBodySchema =
|
|
6505
|
-
name:
|
|
7005
|
+
var createCollectionBodySchema = z8.object({
|
|
7006
|
+
name: z8.string().trim().min(1)
|
|
6506
7007
|
});
|
|
6507
|
-
var updateCollectionBodySchema =
|
|
6508
|
-
name:
|
|
6509
|
-
variables:
|
|
6510
|
-
headers:
|
|
6511
|
-
preRequestScript:
|
|
6512
|
-
postRequestScript:
|
|
7008
|
+
var updateCollectionBodySchema = z8.object({
|
|
7009
|
+
name: z8.string().trim().min(1),
|
|
7010
|
+
variables: z8.array(variableSchema),
|
|
7011
|
+
headers: z8.array(keyValueSchema),
|
|
7012
|
+
preRequestScript: z8.string(),
|
|
7013
|
+
postRequestScript: z8.string(),
|
|
6513
7014
|
auth: authConfigSchema
|
|
6514
7015
|
});
|
|
6515
|
-
var createEnvironmentBodySchema =
|
|
6516
|
-
name:
|
|
7016
|
+
var createEnvironmentBodySchema = z8.object({
|
|
7017
|
+
name: z8.string().trim().min(1)
|
|
6517
7018
|
});
|
|
6518
|
-
var updateEnvironmentBodySchema =
|
|
6519
|
-
name:
|
|
6520
|
-
variables:
|
|
7019
|
+
var updateEnvironmentBodySchema = z8.object({
|
|
7020
|
+
name: z8.string().trim().min(1),
|
|
7021
|
+
variables: z8.array(variableSchema)
|
|
6521
7022
|
});
|
|
6522
|
-
var
|
|
6523
|
-
name:
|
|
7023
|
+
var createSnippetBodySchema = z8.object({
|
|
7024
|
+
name: z8.string().trim().min(1),
|
|
7025
|
+
code: z8.string().default(""),
|
|
7026
|
+
scope: snippetScopeSchema.default("any")
|
|
6524
7027
|
});
|
|
6525
|
-
var
|
|
6526
|
-
name:
|
|
7028
|
+
var updateSnippetBodySchema = z8.object({
|
|
7029
|
+
name: z8.string().trim().min(1),
|
|
7030
|
+
code: z8.string(),
|
|
7031
|
+
scope: snippetScopeSchema
|
|
6527
7032
|
});
|
|
6528
|
-
var
|
|
6529
|
-
|
|
7033
|
+
var createFolderBodySchema = z8.object({
|
|
7034
|
+
name: z8.string().trim().min(1)
|
|
6530
7035
|
});
|
|
6531
|
-
var
|
|
6532
|
-
name:
|
|
7036
|
+
var renameFolderBodySchema = z8.object({
|
|
7037
|
+
name: z8.string().trim().min(1)
|
|
7038
|
+
});
|
|
7039
|
+
var reorderFoldersBodySchema = z8.object({
|
|
7040
|
+
orderedFolderIds: z8.array(z8.string().trim().min(1))
|
|
7041
|
+
});
|
|
7042
|
+
var saveRequestBodySchema = z8.object({
|
|
7043
|
+
name: z8.string().trim().min(1),
|
|
6533
7044
|
method: httpMethodSchema,
|
|
6534
|
-
url:
|
|
6535
|
-
headers:
|
|
6536
|
-
params:
|
|
7045
|
+
url: z8.string(),
|
|
7046
|
+
headers: z8.array(keyValueSchema),
|
|
7047
|
+
params: z8.array(keyValueSchema),
|
|
6537
7048
|
auth: authConfigSchema,
|
|
6538
|
-
body:
|
|
7049
|
+
body: z8.string(),
|
|
6539
7050
|
bodyType: bodyTypeSchema,
|
|
6540
|
-
preRequestScript:
|
|
6541
|
-
postRequestScript:
|
|
6542
|
-
comment:
|
|
6543
|
-
folderId:
|
|
7051
|
+
preRequestScript: z8.string(),
|
|
7052
|
+
postRequestScript: z8.string(),
|
|
7053
|
+
comment: z8.string(),
|
|
7054
|
+
folderId: z8.string().nullable().optional()
|
|
6544
7055
|
});
|
|
6545
7056
|
var updateSaveRequestBodySchema = saveRequestBodySchema.extend({
|
|
6546
|
-
collectionId:
|
|
7057
|
+
collectionId: z8.string().trim().min(1)
|
|
6547
7058
|
});
|
|
6548
|
-
var reorderRequestsBodySchema =
|
|
6549
|
-
folderId:
|
|
6550
|
-
orderedRequestIds:
|
|
7059
|
+
var reorderRequestsBodySchema = z8.object({
|
|
7060
|
+
folderId: z8.string().nullable(),
|
|
7061
|
+
orderedRequestIds: z8.array(z8.string().trim().min(1))
|
|
6551
7062
|
});
|
|
6552
|
-
var moveRequestBodySchema =
|
|
6553
|
-
folderId:
|
|
6554
|
-
index:
|
|
7063
|
+
var moveRequestBodySchema = z8.object({
|
|
7064
|
+
folderId: z8.string().nullable(),
|
|
7065
|
+
index: z8.number().int().min(0)
|
|
6555
7066
|
});
|
|
6556
|
-
var listCollectionsResponseSchema =
|
|
6557
|
-
collections:
|
|
7067
|
+
var listCollectionsResponseSchema = z8.object({
|
|
7068
|
+
collections: z8.array(collectionRecordSchema)
|
|
6558
7069
|
});
|
|
6559
|
-
var listEnvironmentsResponseSchema =
|
|
6560
|
-
environments:
|
|
7070
|
+
var listEnvironmentsResponseSchema = z8.object({
|
|
7071
|
+
environments: z8.array(environmentRecordSchema)
|
|
6561
7072
|
});
|
|
6562
|
-
var
|
|
6563
|
-
|
|
7073
|
+
var listSnippetsResponseSchema = z8.object({
|
|
7074
|
+
snippets: z8.array(snippetRecordSchema)
|
|
6564
7075
|
});
|
|
6565
|
-
var
|
|
6566
|
-
|
|
7076
|
+
var listFoldersResponseSchema = z8.object({
|
|
7077
|
+
folders: z8.array(folderRecordSchema)
|
|
6567
7078
|
});
|
|
6568
|
-
var
|
|
7079
|
+
var listRequestsResponseSchema = z8.object({
|
|
7080
|
+
requests: z8.array(savedRequestRecordSchema)
|
|
7081
|
+
});
|
|
7082
|
+
var emptyResponseSchema = z8.null();
|
|
6569
7083
|
function serializeCollection(record) {
|
|
6570
7084
|
return {
|
|
6571
7085
|
...record,
|
|
@@ -6580,6 +7094,13 @@ function serializeEnvironment(record) {
|
|
|
6580
7094
|
updatedAt: record.updatedAt.toISOString()
|
|
6581
7095
|
};
|
|
6582
7096
|
}
|
|
7097
|
+
function serializeSnippet(record) {
|
|
7098
|
+
return {
|
|
7099
|
+
...record,
|
|
7100
|
+
createdAt: record.createdAt.toISOString(),
|
|
7101
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7102
|
+
};
|
|
7103
|
+
}
|
|
6583
7104
|
function serializeFolder(record) {
|
|
6584
7105
|
return {
|
|
6585
7106
|
...record,
|
|
@@ -6595,18 +7116,167 @@ function serializeSavedRequest(record) {
|
|
|
6595
7116
|
};
|
|
6596
7117
|
}
|
|
6597
7118
|
|
|
6598
|
-
// src/server/routes/admin.ts
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
}
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
7119
|
+
// src/server/routes/schemas/admin.ts
|
|
7120
|
+
var adminResourceOptionSchema = z9.object({
|
|
7121
|
+
id: z9.string(),
|
|
7122
|
+
name: z9.string(),
|
|
7123
|
+
deletionLocked: z9.boolean()
|
|
7124
|
+
});
|
|
7125
|
+
var adminEntityConfigSchema = z9.object({
|
|
7126
|
+
id: z9.string(),
|
|
7127
|
+
name: z9.string(),
|
|
7128
|
+
deletionLocked: z9.boolean()
|
|
7129
|
+
});
|
|
7130
|
+
var updateAdminCollectionBodySchema = z9.object({
|
|
7131
|
+
deletionLocked: z9.boolean()
|
|
7132
|
+
});
|
|
7133
|
+
var updateAdminEnvironmentBodySchema = z9.object({
|
|
7134
|
+
deletionLocked: z9.boolean()
|
|
7135
|
+
});
|
|
7136
|
+
var updateAdminSnippetBodySchema = z9.object({
|
|
7137
|
+
name: z9.string().trim().min(1).optional(),
|
|
7138
|
+
code: z9.string().optional(),
|
|
7139
|
+
scope: snippetScopeSchema.optional(),
|
|
7140
|
+
deletionLocked: z9.boolean().optional()
|
|
7141
|
+
}).superRefine((body, ctx) => {
|
|
7142
|
+
const hasContentFields = body.name !== void 0 || body.code !== void 0 || body.scope !== void 0;
|
|
7143
|
+
const hasFullContent = body.name !== void 0 && body.code !== void 0 && body.scope !== void 0;
|
|
7144
|
+
if (hasContentFields && !hasFullContent) {
|
|
7145
|
+
ctx.addIssue({
|
|
7146
|
+
code: "custom",
|
|
7147
|
+
message: "name, code, and scope must be provided together"
|
|
7148
|
+
});
|
|
6607
7149
|
}
|
|
6608
|
-
|
|
6609
|
-
|
|
7150
|
+
if (!hasContentFields && body.deletionLocked === void 0) {
|
|
7151
|
+
ctx.addIssue({
|
|
7152
|
+
code: "custom",
|
|
7153
|
+
message: "Provide snippet content (name, code, scope) and/or deletionLocked"
|
|
7154
|
+
});
|
|
7155
|
+
}
|
|
7156
|
+
});
|
|
7157
|
+
var createAdminSnippetBodySchema = createSnippetBodySchema;
|
|
7158
|
+
var listAdminCollectionsResponseSchema = z9.object({
|
|
7159
|
+
collections: z9.array(adminResourceOptionSchema)
|
|
7160
|
+
});
|
|
7161
|
+
var listAdminEnvironmentsResponseSchema = z9.object({
|
|
7162
|
+
environments: z9.array(adminResourceOptionSchema)
|
|
7163
|
+
});
|
|
7164
|
+
var listAdminSnippetsResponseSchema = z9.object({
|
|
7165
|
+
snippets: z9.array(snippetRecordSchema)
|
|
7166
|
+
});
|
|
7167
|
+
var adminSnippetRecordSchema = snippetRecordSchema;
|
|
7168
|
+
var listAdminLlmModelsResponseSchema = listLlmModelsResponseSchema;
|
|
7169
|
+
var hubUserRecordSchema = z9.object({
|
|
7170
|
+
id: z9.string(),
|
|
7171
|
+
name: z9.string(),
|
|
7172
|
+
role: userRoleSchema,
|
|
7173
|
+
collectionAccess: z9.array(z9.string()),
|
|
7174
|
+
environmentAccess: z9.array(z9.string()),
|
|
7175
|
+
snippetAccess: z9.array(z9.string()),
|
|
7176
|
+
llmAccess: z9.boolean(),
|
|
7177
|
+
llmModels: z9.array(z9.string()),
|
|
7178
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable(),
|
|
7179
|
+
createdAt: timestampSchema,
|
|
7180
|
+
updatedAt: timestampSchema
|
|
7181
|
+
});
|
|
7182
|
+
var adminUserListEntrySchema = hubUserRecordSchema.extend({
|
|
7183
|
+
warnings: z9.array(z9.string())
|
|
7184
|
+
});
|
|
7185
|
+
var listAdminUsersResponseSchema = z9.object({
|
|
7186
|
+
users: z9.array(adminUserListEntrySchema)
|
|
7187
|
+
});
|
|
7188
|
+
var updateAdminUserBodySchema = z9.object({
|
|
7189
|
+
name: z9.string().trim().min(1).optional(),
|
|
7190
|
+
role: userRoleSchema.optional(),
|
|
7191
|
+
collectionAccess: z9.array(z9.string()).optional(),
|
|
7192
|
+
environmentAccess: z9.array(z9.string()).optional(),
|
|
7193
|
+
snippetAccess: z9.array(z9.string()).optional(),
|
|
7194
|
+
llmAccess: z9.boolean().optional(),
|
|
7195
|
+
llmModels: z9.array(z9.string()).optional(),
|
|
7196
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable().optional()
|
|
7197
|
+
});
|
|
7198
|
+
var createAdminUserBodySchema = z9.object({
|
|
7199
|
+
name: z9.string().trim().min(1),
|
|
7200
|
+
role: userRoleSchema,
|
|
7201
|
+
collectionAccess: z9.array(z9.string()).optional(),
|
|
7202
|
+
environmentAccess: z9.array(z9.string()).optional(),
|
|
7203
|
+
snippetAccess: z9.array(z9.string()).optional(),
|
|
7204
|
+
llmAccess: z9.boolean().optional(),
|
|
7205
|
+
llmModels: z9.array(z9.string()).optional(),
|
|
7206
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable().optional()
|
|
7207
|
+
});
|
|
7208
|
+
var hubApiTokenRecordSchema = z9.object({
|
|
7209
|
+
id: z9.string(),
|
|
7210
|
+
userId: z9.string(),
|
|
7211
|
+
name: z9.string(),
|
|
7212
|
+
tokenPrefix: z9.string(),
|
|
7213
|
+
createdAt: timestampSchema,
|
|
7214
|
+
lastUsedAt: timestampSchema.nullable(),
|
|
7215
|
+
revokedAt: timestampSchema.nullable()
|
|
7216
|
+
});
|
|
7217
|
+
var createAdminUserResponseSchema = z9.object({
|
|
7218
|
+
user: hubUserRecordSchema,
|
|
7219
|
+
token: hubApiTokenRecordSchema,
|
|
7220
|
+
secret: z9.string()
|
|
7221
|
+
});
|
|
7222
|
+
var createAdminTokenBodySchema = z9.object({
|
|
7223
|
+
name: z9.string().trim().min(1)
|
|
7224
|
+
});
|
|
7225
|
+
var createdApiTokenResponseSchema = z9.object({
|
|
7226
|
+
token: hubApiTokenRecordSchema,
|
|
7227
|
+
secret: z9.string()
|
|
7228
|
+
});
|
|
7229
|
+
var listAdminTokensResponseSchema = z9.object({
|
|
7230
|
+
tokens: z9.array(hubApiTokenRecordSchema)
|
|
7231
|
+
});
|
|
7232
|
+
var reloadConfigSectionResultSchema = z9.object({
|
|
7233
|
+
section: z9.enum(["db", "redis", "llm", "plugins", "server"]),
|
|
7234
|
+
status: z9.enum(["reloaded", "unchanged", "failed", "restart-required"]),
|
|
7235
|
+
error: z9.string().optional()
|
|
7236
|
+
});
|
|
7237
|
+
var reloadConfigResponseSchema = z9.object({
|
|
7238
|
+
sections: z9.array(reloadConfigSectionResultSchema),
|
|
7239
|
+
fatalError: z9.string().optional()
|
|
7240
|
+
});
|
|
7241
|
+
function serializeHubUser(user) {
|
|
7242
|
+
return {
|
|
7243
|
+
id: user.id,
|
|
7244
|
+
name: user.name,
|
|
7245
|
+
role: user.role,
|
|
7246
|
+
collectionAccess: user.collectionAccess,
|
|
7247
|
+
environmentAccess: user.environmentAccess,
|
|
7248
|
+
snippetAccess: user.snippetAccess,
|
|
7249
|
+
llmAccess: user.llmAccess,
|
|
7250
|
+
llmModels: user.llmModels,
|
|
7251
|
+
llmMonthlyTokenLimit: user.llmMonthlyTokenLimit,
|
|
7252
|
+
createdAt: user.createdAt.toISOString(),
|
|
7253
|
+
updatedAt: user.updatedAt.toISOString()
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7256
|
+
function serializeApiToken(token) {
|
|
7257
|
+
return {
|
|
7258
|
+
id: token.id,
|
|
7259
|
+
userId: token.userId,
|
|
7260
|
+
name: token.name,
|
|
7261
|
+
tokenPrefix: token.tokenPrefix,
|
|
7262
|
+
createdAt: token.createdAt.toISOString(),
|
|
7263
|
+
lastUsedAt: token.lastUsedAt ? token.lastUsedAt.toISOString() : null,
|
|
7264
|
+
revokedAt: token.revokedAt ? token.revokedAt.toISOString() : null
|
|
7265
|
+
};
|
|
7266
|
+
}
|
|
7267
|
+
|
|
7268
|
+
// src/server/routes/admin.ts
|
|
7269
|
+
function sendLlmUnavailable(reply) {
|
|
7270
|
+
return reply.code(503).send({
|
|
7271
|
+
error: "LLM support is not configured on this Team Hub."
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
function denySystemUserTarget(reply, existing, systemUserId) {
|
|
7275
|
+
if (!isSystemUser(existing, systemUserId)) {
|
|
7276
|
+
return false;
|
|
7277
|
+
}
|
|
7278
|
+
void reply.code(403).send(errorResponseSchema.parse({ error: "Forbidden" }));
|
|
7279
|
+
return true;
|
|
6610
7280
|
}
|
|
6611
7281
|
function denySelfUserTarget(reply, targetUserId, actorUserId) {
|
|
6612
7282
|
if (targetUserId !== actorUserId) {
|
|
@@ -6648,14 +7318,16 @@ async function registerAdminRoutes(app, options) {
|
|
|
6648
7318
|
}
|
|
6649
7319
|
const systemUserId = db.getSystemUserId();
|
|
6650
7320
|
const llm = getLlm();
|
|
6651
|
-
const [users, collections, environments] = await Promise.all([
|
|
7321
|
+
const [users, collections, environments, snippets] = await Promise.all([
|
|
6652
7322
|
db.listUsers(),
|
|
6653
7323
|
db.listCollections(),
|
|
6654
|
-
db.listEnvironments()
|
|
7324
|
+
db.listEnvironments(),
|
|
7325
|
+
db.listSnippets()
|
|
6655
7326
|
]);
|
|
6656
7327
|
const catalogs = buildAccessCatalogIds(
|
|
6657
7328
|
collections,
|
|
6658
7329
|
environments,
|
|
7330
|
+
snippets,
|
|
6659
7331
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
6660
7332
|
);
|
|
6661
7333
|
return reply.send({
|
|
@@ -6665,6 +7337,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
6665
7337
|
{
|
|
6666
7338
|
collectionAccess: record.collectionAccess,
|
|
6667
7339
|
environmentAccess: record.environmentAccess,
|
|
7340
|
+
snippetAccess: record.snippetAccess,
|
|
6668
7341
|
llmModels: record.llmModels
|
|
6669
7342
|
},
|
|
6670
7343
|
catalogs
|
|
@@ -6701,13 +7374,15 @@ async function registerAdminRoutes(app, options) {
|
|
|
6701
7374
|
}
|
|
6702
7375
|
const input = buildAdminUserCreateInput(request.body);
|
|
6703
7376
|
const llm = getLlm();
|
|
6704
|
-
const [collections, environments] = await Promise.all([
|
|
7377
|
+
const [collections, environments, snippets] = await Promise.all([
|
|
6705
7378
|
db.listCollections(),
|
|
6706
|
-
db.listEnvironments()
|
|
7379
|
+
db.listEnvironments(),
|
|
7380
|
+
db.listSnippets()
|
|
6707
7381
|
]);
|
|
6708
7382
|
const catalogs = buildAccessCatalogIds(
|
|
6709
7383
|
collections,
|
|
6710
7384
|
environments,
|
|
7385
|
+
snippets,
|
|
6711
7386
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
6712
7387
|
);
|
|
6713
7388
|
validateSubmittedAccessLists(
|
|
@@ -6715,6 +7390,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
6715
7390
|
role: request.body.role,
|
|
6716
7391
|
collectionAccess: request.body.collectionAccess,
|
|
6717
7392
|
environmentAccess: request.body.environmentAccess,
|
|
7393
|
+
snippetAccess: request.body.snippetAccess,
|
|
6718
7394
|
llmModels: request.body.llmModels
|
|
6719
7395
|
},
|
|
6720
7396
|
catalogs
|
|
@@ -6877,6 +7553,70 @@ async function registerAdminRoutes(app, options) {
|
|
|
6877
7553
|
}
|
|
6878
7554
|
}
|
|
6879
7555
|
});
|
|
7556
|
+
routes.route({
|
|
7557
|
+
method: "GET",
|
|
7558
|
+
url: "/admin/snippets",
|
|
7559
|
+
schema: {
|
|
7560
|
+
response: {
|
|
7561
|
+
200: listAdminSnippetsResponseSchema,
|
|
7562
|
+
403: errorResponseSchema
|
|
7563
|
+
}
|
|
7564
|
+
},
|
|
7565
|
+
/**
|
|
7566
|
+
* Lists all snippets for operator user management.
|
|
7567
|
+
*/
|
|
7568
|
+
handler: async (request, reply) => {
|
|
7569
|
+
try {
|
|
7570
|
+
const user = requireAuthenticatedUser(request);
|
|
7571
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7572
|
+
return;
|
|
7573
|
+
}
|
|
7574
|
+
const snippets = await db.listSnippets();
|
|
7575
|
+
return reply.send({
|
|
7576
|
+
snippets: snippets.map((snippet) => serializeSnippet(snippet))
|
|
7577
|
+
});
|
|
7578
|
+
} catch (error) {
|
|
7579
|
+
if (handleDbError(reply, error)) {
|
|
7580
|
+
return;
|
|
7581
|
+
}
|
|
7582
|
+
throw error;
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
});
|
|
7586
|
+
routes.route({
|
|
7587
|
+
method: "POST",
|
|
7588
|
+
url: "/admin/snippets",
|
|
7589
|
+
schema: {
|
|
7590
|
+
body: createAdminSnippetBodySchema,
|
|
7591
|
+
response: {
|
|
7592
|
+
201: adminSnippetRecordSchema,
|
|
7593
|
+
403: errorResponseSchema
|
|
7594
|
+
}
|
|
7595
|
+
},
|
|
7596
|
+
/**
|
|
7597
|
+
* Creates a snippet through the management API.
|
|
7598
|
+
*/
|
|
7599
|
+
handler: async (request, reply) => {
|
|
7600
|
+
try {
|
|
7601
|
+
const user = requireAuthenticatedUser(request);
|
|
7602
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
const snippet = await db.createSnippet(
|
|
7606
|
+
request.body.name,
|
|
7607
|
+
request.body.code,
|
|
7608
|
+
request.body.scope,
|
|
7609
|
+
user.id
|
|
7610
|
+
);
|
|
7611
|
+
return reply.code(201).send(serializeSnippet(snippet));
|
|
7612
|
+
} catch (error) {
|
|
7613
|
+
if (handleDbError(reply, error)) {
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
throw error;
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7619
|
+
});
|
|
6880
7620
|
routes.route({
|
|
6881
7621
|
method: "DELETE",
|
|
6882
7622
|
url: "/admin/collections/:id",
|
|
@@ -6947,6 +7687,84 @@ async function registerAdminRoutes(app, options) {
|
|
|
6947
7687
|
}
|
|
6948
7688
|
}
|
|
6949
7689
|
});
|
|
7690
|
+
routes.route({
|
|
7691
|
+
method: "DELETE",
|
|
7692
|
+
url: "/admin/snippets/:id",
|
|
7693
|
+
schema: {
|
|
7694
|
+
params: idParamSchema,
|
|
7695
|
+
response: {
|
|
7696
|
+
204: emptyResponseSchema,
|
|
7697
|
+
403: errorResponseSchema,
|
|
7698
|
+
404: errorResponseSchema
|
|
7699
|
+
}
|
|
7700
|
+
},
|
|
7701
|
+
/**
|
|
7702
|
+
* Deletes a snippet regardless of deletion lock state.
|
|
7703
|
+
*/
|
|
7704
|
+
handler: async (request, reply) => {
|
|
7705
|
+
try {
|
|
7706
|
+
const user = requireAuthenticatedUser(request);
|
|
7707
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7708
|
+
return;
|
|
7709
|
+
}
|
|
7710
|
+
const snippet = await db.findSnippetById(request.params.id);
|
|
7711
|
+
if (!snippet) {
|
|
7712
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
7713
|
+
return;
|
|
7714
|
+
}
|
|
7715
|
+
await db.deleteSnippet(request.params.id, user.id);
|
|
7716
|
+
return reply.code(204).send(null);
|
|
7717
|
+
} catch (error) {
|
|
7718
|
+
if (handleDbError(reply, error)) {
|
|
7719
|
+
return;
|
|
7720
|
+
}
|
|
7721
|
+
throw error;
|
|
7722
|
+
}
|
|
7723
|
+
}
|
|
7724
|
+
});
|
|
7725
|
+
routes.route({
|
|
7726
|
+
method: "PUT",
|
|
7727
|
+
url: "/admin/snippets/:id",
|
|
7728
|
+
schema: {
|
|
7729
|
+
params: idParamSchema,
|
|
7730
|
+
body: updateAdminSnippetBodySchema,
|
|
7731
|
+
response: {
|
|
7732
|
+
200: adminSnippetRecordSchema,
|
|
7733
|
+
403: errorResponseSchema,
|
|
7734
|
+
404: errorResponseSchema
|
|
7735
|
+
}
|
|
7736
|
+
},
|
|
7737
|
+
/**
|
|
7738
|
+
* Updates snippet content and/or admin configuration.
|
|
7739
|
+
*/
|
|
7740
|
+
handler: async (request, reply) => {
|
|
7741
|
+
try {
|
|
7742
|
+
const user = requireAuthenticatedUser(request);
|
|
7743
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7744
|
+
return;
|
|
7745
|
+
}
|
|
7746
|
+
const existing = await db.findSnippetById(request.params.id);
|
|
7747
|
+
if (!existing) {
|
|
7748
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
7749
|
+
return;
|
|
7750
|
+
}
|
|
7751
|
+
let snippet = existing;
|
|
7752
|
+
const { name, code, scope, deletionLocked } = request.body;
|
|
7753
|
+
if (name !== void 0 && code !== void 0 && scope !== void 0) {
|
|
7754
|
+
snippet = await db.updateSnippet(request.params.id, name, code, scope, user.id);
|
|
7755
|
+
}
|
|
7756
|
+
if (deletionLocked !== void 0) {
|
|
7757
|
+
snippet = await db.setSnippetDeletionLocked(request.params.id, deletionLocked, user.id);
|
|
7758
|
+
}
|
|
7759
|
+
return reply.send(serializeSnippet(snippet));
|
|
7760
|
+
} catch (error) {
|
|
7761
|
+
if (handleDbError(reply, error)) {
|
|
7762
|
+
return;
|
|
7763
|
+
}
|
|
7764
|
+
throw error;
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
});
|
|
6950
7768
|
routes.route({
|
|
6951
7769
|
method: "PUT",
|
|
6952
7770
|
url: "/admin/collections/:id",
|
|
@@ -7127,13 +7945,15 @@ async function registerAdminRoutes(app, options) {
|
|
|
7127
7945
|
const input = buildAdminUserUpdateInput(existing, request.body);
|
|
7128
7946
|
const role = request.body.role ?? existing.role;
|
|
7129
7947
|
const llm = getLlm();
|
|
7130
|
-
const [collections, environments] = await Promise.all([
|
|
7948
|
+
const [collections, environments, snippets] = await Promise.all([
|
|
7131
7949
|
db.listCollections(),
|
|
7132
|
-
db.listEnvironments()
|
|
7950
|
+
db.listEnvironments(),
|
|
7951
|
+
db.listSnippets()
|
|
7133
7952
|
]);
|
|
7134
7953
|
const catalogs = buildAccessCatalogIds(
|
|
7135
7954
|
collections,
|
|
7136
7955
|
environments,
|
|
7956
|
+
snippets,
|
|
7137
7957
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
7138
7958
|
);
|
|
7139
7959
|
validateSubmittedAccessLists(
|
|
@@ -7141,6 +7961,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
7141
7961
|
role,
|
|
7142
7962
|
collectionAccess: request.body.collectionAccess,
|
|
7143
7963
|
environmentAccess: request.body.environmentAccess,
|
|
7964
|
+
snippetAccess: request.body.snippetAccess,
|
|
7144
7965
|
llmModels: request.body.llmModels
|
|
7145
7966
|
},
|
|
7146
7967
|
catalogs
|
|
@@ -7677,6 +8498,150 @@ async function registerEnvironmentRoutes(app, db) {
|
|
|
7677
8498
|
});
|
|
7678
8499
|
}
|
|
7679
8500
|
|
|
8501
|
+
// src/server/routes/snippets.ts
|
|
8502
|
+
async function registerSnippetRoutes(app, db) {
|
|
8503
|
+
const routes = app.withTypeProvider();
|
|
8504
|
+
routes.route({
|
|
8505
|
+
method: "GET",
|
|
8506
|
+
url: "/snippets",
|
|
8507
|
+
schema: {
|
|
8508
|
+
response: {
|
|
8509
|
+
200: listSnippetsResponseSchema
|
|
8510
|
+
}
|
|
8511
|
+
},
|
|
8512
|
+
/**
|
|
8513
|
+
* Lists all snippets ordered by sort order then name.
|
|
8514
|
+
*/
|
|
8515
|
+
handler: async (request, reply) => {
|
|
8516
|
+
try {
|
|
8517
|
+
const user = requireAuthenticatedUser(request);
|
|
8518
|
+
if (denyUnlessAllowed(reply, canListSnippets(user))) {
|
|
8519
|
+
return;
|
|
8520
|
+
}
|
|
8521
|
+
const snippets = await db.listSnippets();
|
|
8522
|
+
return reply.send({
|
|
8523
|
+
snippets: filterAccessibleSnippets(user, snippets).map(
|
|
8524
|
+
(snippet) => serializeSnippet(snippet)
|
|
8525
|
+
)
|
|
8526
|
+
});
|
|
8527
|
+
} catch (error) {
|
|
8528
|
+
if (handleDbError(reply, error)) {
|
|
8529
|
+
return;
|
|
8530
|
+
}
|
|
8531
|
+
throw error;
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
});
|
|
8535
|
+
routes.route({
|
|
8536
|
+
method: "POST",
|
|
8537
|
+
url: "/snippets",
|
|
8538
|
+
schema: {
|
|
8539
|
+
body: createSnippetBodySchema,
|
|
8540
|
+
response: {
|
|
8541
|
+
200: snippetRecordSchema,
|
|
8542
|
+
400: errorResponseSchema
|
|
8543
|
+
}
|
|
8544
|
+
},
|
|
8545
|
+
/**
|
|
8546
|
+
* Creates a new snippet with the given display name and content.
|
|
8547
|
+
*/
|
|
8548
|
+
handler: async (request, reply) => {
|
|
8549
|
+
try {
|
|
8550
|
+
const user = requireAuthenticatedUser(request);
|
|
8551
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canCreateSnippet(user))) {
|
|
8552
|
+
return;
|
|
8553
|
+
}
|
|
8554
|
+
const snippet = await db.createSnippet(
|
|
8555
|
+
request.body.name,
|
|
8556
|
+
request.body.code,
|
|
8557
|
+
request.body.scope,
|
|
8558
|
+
user.id
|
|
8559
|
+
);
|
|
8560
|
+
return reply.send(serializeSnippet(snippet));
|
|
8561
|
+
} catch (error) {
|
|
8562
|
+
if (handleDbError(reply, error)) {
|
|
8563
|
+
return;
|
|
8564
|
+
}
|
|
8565
|
+
throw error;
|
|
8566
|
+
}
|
|
8567
|
+
}
|
|
8568
|
+
});
|
|
8569
|
+
routes.route({
|
|
8570
|
+
method: "PUT",
|
|
8571
|
+
url: "/snippets/:id",
|
|
8572
|
+
schema: {
|
|
8573
|
+
params: idParamSchema,
|
|
8574
|
+
body: updateSnippetBodySchema,
|
|
8575
|
+
response: {
|
|
8576
|
+
200: snippetRecordSchema,
|
|
8577
|
+
400: errorResponseSchema,
|
|
8578
|
+
404: errorResponseSchema
|
|
8579
|
+
}
|
|
8580
|
+
},
|
|
8581
|
+
/**
|
|
8582
|
+
* Updates a snippet's name, code, and scope. Sort order is preserved.
|
|
8583
|
+
*/
|
|
8584
|
+
handler: async (request, reply) => {
|
|
8585
|
+
try {
|
|
8586
|
+
const user = requireAuthenticatedUser(request);
|
|
8587
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canAccessSnippet(user, request.params.id))) {
|
|
8588
|
+
return;
|
|
8589
|
+
}
|
|
8590
|
+
const snippet = await db.updateSnippet(
|
|
8591
|
+
request.params.id,
|
|
8592
|
+
request.body.name,
|
|
8593
|
+
request.body.code,
|
|
8594
|
+
request.body.scope,
|
|
8595
|
+
user.id
|
|
8596
|
+
);
|
|
8597
|
+
return reply.send(serializeSnippet(snippet));
|
|
8598
|
+
} catch (error) {
|
|
8599
|
+
if (handleDbError(reply, error)) {
|
|
8600
|
+
return;
|
|
8601
|
+
}
|
|
8602
|
+
throw error;
|
|
8603
|
+
}
|
|
8604
|
+
}
|
|
8605
|
+
});
|
|
8606
|
+
routes.route({
|
|
8607
|
+
method: "DELETE",
|
|
8608
|
+
url: "/snippets/:id",
|
|
8609
|
+
schema: {
|
|
8610
|
+
params: idParamSchema,
|
|
8611
|
+
response: {
|
|
8612
|
+
204: emptyResponseSchema,
|
|
8613
|
+
404: errorResponseSchema
|
|
8614
|
+
}
|
|
8615
|
+
},
|
|
8616
|
+
/**
|
|
8617
|
+
* Deletes a snippet by id.
|
|
8618
|
+
*/
|
|
8619
|
+
handler: async (request, reply) => {
|
|
8620
|
+
try {
|
|
8621
|
+
const user = requireAuthenticatedUser(request);
|
|
8622
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canAccessSnippet(user, request.params.id))) {
|
|
8623
|
+
return;
|
|
8624
|
+
}
|
|
8625
|
+
const snippet = await db.findSnippetById(request.params.id);
|
|
8626
|
+
if (!snippet) {
|
|
8627
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
8628
|
+
return;
|
|
8629
|
+
}
|
|
8630
|
+
if (snippet.deletionLocked) {
|
|
8631
|
+
throw new DeletionLockedError("snippet");
|
|
8632
|
+
}
|
|
8633
|
+
await db.deleteSnippet(request.params.id, user.id);
|
|
8634
|
+
return reply.code(204).send(null);
|
|
8635
|
+
} catch (error) {
|
|
8636
|
+
if (handleDbError(reply, error)) {
|
|
8637
|
+
return;
|
|
8638
|
+
}
|
|
8639
|
+
throw error;
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
});
|
|
8643
|
+
}
|
|
8644
|
+
|
|
7680
8645
|
// src/server/routes/folders.ts
|
|
7681
8646
|
async function registerFolderRoutes(app, db) {
|
|
7682
8647
|
const routes = app.withTypeProvider();
|
|
@@ -8778,6 +9743,7 @@ async function registerProtectedRoutes(app, options) {
|
|
|
8778
9743
|
});
|
|
8779
9744
|
await registerCollectionRoutes(app, options.db);
|
|
8780
9745
|
await registerEnvironmentRoutes(app, options.db);
|
|
9746
|
+
await registerSnippetRoutes(app, options.db);
|
|
8781
9747
|
await registerFolderRoutes(app, options.db);
|
|
8782
9748
|
await registerRequestRoutes(app, options.db);
|
|
8783
9749
|
await registerLlmRoutes(app, { db: options.db, getLlm: options.getLlm });
|