@harborclient/team-hub 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +975 -63
- 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
|
}
|
|
@@ -6342,12 +6949,18 @@ var updateAdminCollectionBodySchema = z8.object({
|
|
|
6342
6949
|
var updateAdminEnvironmentBodySchema = z8.object({
|
|
6343
6950
|
deletionLocked: z8.boolean()
|
|
6344
6951
|
});
|
|
6952
|
+
var updateAdminSnippetBodySchema = z8.object({
|
|
6953
|
+
deletionLocked: z8.boolean()
|
|
6954
|
+
});
|
|
6345
6955
|
var listAdminCollectionsResponseSchema = z8.object({
|
|
6346
6956
|
collections: z8.array(adminResourceOptionSchema)
|
|
6347
6957
|
});
|
|
6348
6958
|
var listAdminEnvironmentsResponseSchema = z8.object({
|
|
6349
6959
|
environments: z8.array(adminResourceOptionSchema)
|
|
6350
6960
|
});
|
|
6961
|
+
var listAdminSnippetsResponseSchema = z8.object({
|
|
6962
|
+
snippets: z8.array(adminResourceOptionSchema)
|
|
6963
|
+
});
|
|
6351
6964
|
var listAdminLlmModelsResponseSchema = listLlmModelsResponseSchema;
|
|
6352
6965
|
var hubUserRecordSchema = z8.object({
|
|
6353
6966
|
id: z8.string(),
|
|
@@ -6355,6 +6968,7 @@ var hubUserRecordSchema = z8.object({
|
|
|
6355
6968
|
role: userRoleSchema,
|
|
6356
6969
|
collectionAccess: z8.array(z8.string()),
|
|
6357
6970
|
environmentAccess: z8.array(z8.string()),
|
|
6971
|
+
snippetAccess: z8.array(z8.string()),
|
|
6358
6972
|
llmAccess: z8.boolean(),
|
|
6359
6973
|
llmModels: z8.array(z8.string()),
|
|
6360
6974
|
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable(),
|
|
@@ -6372,6 +6986,7 @@ var updateAdminUserBodySchema = z8.object({
|
|
|
6372
6986
|
role: userRoleSchema.optional(),
|
|
6373
6987
|
collectionAccess: z8.array(z8.string()).optional(),
|
|
6374
6988
|
environmentAccess: z8.array(z8.string()).optional(),
|
|
6989
|
+
snippetAccess: z8.array(z8.string()).optional(),
|
|
6375
6990
|
llmAccess: z8.boolean().optional(),
|
|
6376
6991
|
llmModels: z8.array(z8.string()).optional(),
|
|
6377
6992
|
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
@@ -6381,6 +6996,7 @@ var createAdminUserBodySchema = z8.object({
|
|
|
6381
6996
|
role: userRoleSchema,
|
|
6382
6997
|
collectionAccess: z8.array(z8.string()).optional(),
|
|
6383
6998
|
environmentAccess: z8.array(z8.string()).optional(),
|
|
6999
|
+
snippetAccess: z8.array(z8.string()).optional(),
|
|
6384
7000
|
llmAccess: z8.boolean().optional(),
|
|
6385
7001
|
llmModels: z8.array(z8.string()).optional(),
|
|
6386
7002
|
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
@@ -6425,6 +7041,7 @@ function serializeHubUser(user) {
|
|
|
6425
7041
|
role: user.role,
|
|
6426
7042
|
collectionAccess: user.collectionAccess,
|
|
6427
7043
|
environmentAccess: user.environmentAccess,
|
|
7044
|
+
snippetAccess: user.snippetAccess,
|
|
6428
7045
|
llmAccess: user.llmAccess,
|
|
6429
7046
|
llmModels: user.llmModels,
|
|
6430
7047
|
llmMonthlyTokenLimit: user.llmMonthlyTokenLimit,
|
|
@@ -6470,6 +7087,19 @@ var environmentRecordSchema = z9.object({
|
|
|
6470
7087
|
updatedByUserId: z9.string().nullable(),
|
|
6471
7088
|
deletionLocked: z9.boolean()
|
|
6472
7089
|
});
|
|
7090
|
+
var snippetScopeSchema = z9.enum(["pre-request", "post-request", "any"]);
|
|
7091
|
+
var snippetRecordSchema = z9.object({
|
|
7092
|
+
id: z9.string(),
|
|
7093
|
+
name: z9.string(),
|
|
7094
|
+
code: z9.string(),
|
|
7095
|
+
scope: snippetScopeSchema,
|
|
7096
|
+
sortOrder: z9.number().int(),
|
|
7097
|
+
createdAt: timestampSchema,
|
|
7098
|
+
updatedAt: timestampSchema,
|
|
7099
|
+
createdByUserId: z9.string().nullable(),
|
|
7100
|
+
updatedByUserId: z9.string().nullable(),
|
|
7101
|
+
deletionLocked: z9.boolean()
|
|
7102
|
+
});
|
|
6473
7103
|
var folderRecordSchema = z9.object({
|
|
6474
7104
|
id: z9.string(),
|
|
6475
7105
|
collectionId: z9.string(),
|
|
@@ -6519,6 +7149,16 @@ var updateEnvironmentBodySchema = z9.object({
|
|
|
6519
7149
|
name: z9.string().trim().min(1),
|
|
6520
7150
|
variables: z9.array(variableSchema)
|
|
6521
7151
|
});
|
|
7152
|
+
var createSnippetBodySchema = z9.object({
|
|
7153
|
+
name: z9.string().trim().min(1),
|
|
7154
|
+
code: z9.string().default(""),
|
|
7155
|
+
scope: snippetScopeSchema.default("any")
|
|
7156
|
+
});
|
|
7157
|
+
var updateSnippetBodySchema = z9.object({
|
|
7158
|
+
name: z9.string().trim().min(1),
|
|
7159
|
+
code: z9.string(),
|
|
7160
|
+
scope: snippetScopeSchema
|
|
7161
|
+
});
|
|
6522
7162
|
var createFolderBodySchema = z9.object({
|
|
6523
7163
|
name: z9.string().trim().min(1)
|
|
6524
7164
|
});
|
|
@@ -6559,6 +7199,9 @@ var listCollectionsResponseSchema = z9.object({
|
|
|
6559
7199
|
var listEnvironmentsResponseSchema = z9.object({
|
|
6560
7200
|
environments: z9.array(environmentRecordSchema)
|
|
6561
7201
|
});
|
|
7202
|
+
var listSnippetsResponseSchema = z9.object({
|
|
7203
|
+
snippets: z9.array(snippetRecordSchema)
|
|
7204
|
+
});
|
|
6562
7205
|
var listFoldersResponseSchema = z9.object({
|
|
6563
7206
|
folders: z9.array(folderRecordSchema)
|
|
6564
7207
|
});
|
|
@@ -6580,6 +7223,13 @@ function serializeEnvironment(record) {
|
|
|
6580
7223
|
updatedAt: record.updatedAt.toISOString()
|
|
6581
7224
|
};
|
|
6582
7225
|
}
|
|
7226
|
+
function serializeSnippet(record) {
|
|
7227
|
+
return {
|
|
7228
|
+
...record,
|
|
7229
|
+
createdAt: record.createdAt.toISOString(),
|
|
7230
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7231
|
+
};
|
|
7232
|
+
}
|
|
6583
7233
|
function serializeFolder(record) {
|
|
6584
7234
|
return {
|
|
6585
7235
|
...record,
|
|
@@ -6648,14 +7298,16 @@ async function registerAdminRoutes(app, options) {
|
|
|
6648
7298
|
}
|
|
6649
7299
|
const systemUserId = db.getSystemUserId();
|
|
6650
7300
|
const llm = getLlm();
|
|
6651
|
-
const [users, collections, environments] = await Promise.all([
|
|
7301
|
+
const [users, collections, environments, snippets] = await Promise.all([
|
|
6652
7302
|
db.listUsers(),
|
|
6653
7303
|
db.listCollections(),
|
|
6654
|
-
db.listEnvironments()
|
|
7304
|
+
db.listEnvironments(),
|
|
7305
|
+
db.listSnippets()
|
|
6655
7306
|
]);
|
|
6656
7307
|
const catalogs = buildAccessCatalogIds(
|
|
6657
7308
|
collections,
|
|
6658
7309
|
environments,
|
|
7310
|
+
snippets,
|
|
6659
7311
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
6660
7312
|
);
|
|
6661
7313
|
return reply.send({
|
|
@@ -6665,6 +7317,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
6665
7317
|
{
|
|
6666
7318
|
collectionAccess: record.collectionAccess,
|
|
6667
7319
|
environmentAccess: record.environmentAccess,
|
|
7320
|
+
snippetAccess: record.snippetAccess,
|
|
6668
7321
|
llmModels: record.llmModels
|
|
6669
7322
|
},
|
|
6670
7323
|
catalogs
|
|
@@ -6701,13 +7354,15 @@ async function registerAdminRoutes(app, options) {
|
|
|
6701
7354
|
}
|
|
6702
7355
|
const input = buildAdminUserCreateInput(request.body);
|
|
6703
7356
|
const llm = getLlm();
|
|
6704
|
-
const [collections, environments] = await Promise.all([
|
|
7357
|
+
const [collections, environments, snippets] = await Promise.all([
|
|
6705
7358
|
db.listCollections(),
|
|
6706
|
-
db.listEnvironments()
|
|
7359
|
+
db.listEnvironments(),
|
|
7360
|
+
db.listSnippets()
|
|
6707
7361
|
]);
|
|
6708
7362
|
const catalogs = buildAccessCatalogIds(
|
|
6709
7363
|
collections,
|
|
6710
7364
|
environments,
|
|
7365
|
+
snippets,
|
|
6711
7366
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
6712
7367
|
);
|
|
6713
7368
|
validateSubmittedAccessLists(
|
|
@@ -6715,6 +7370,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
6715
7370
|
role: request.body.role,
|
|
6716
7371
|
collectionAccess: request.body.collectionAccess,
|
|
6717
7372
|
environmentAccess: request.body.environmentAccess,
|
|
7373
|
+
snippetAccess: request.body.snippetAccess,
|
|
6718
7374
|
llmModels: request.body.llmModels
|
|
6719
7375
|
},
|
|
6720
7376
|
catalogs
|
|
@@ -6877,6 +7533,40 @@ async function registerAdminRoutes(app, options) {
|
|
|
6877
7533
|
}
|
|
6878
7534
|
}
|
|
6879
7535
|
});
|
|
7536
|
+
routes.route({
|
|
7537
|
+
method: "GET",
|
|
7538
|
+
url: "/admin/snippets",
|
|
7539
|
+
schema: {
|
|
7540
|
+
response: {
|
|
7541
|
+
200: listAdminSnippetsResponseSchema,
|
|
7542
|
+
403: errorResponseSchema
|
|
7543
|
+
}
|
|
7544
|
+
},
|
|
7545
|
+
/**
|
|
7546
|
+
* Lists all snippets for operator user management.
|
|
7547
|
+
*/
|
|
7548
|
+
handler: async (request, reply) => {
|
|
7549
|
+
try {
|
|
7550
|
+
const user = requireAuthenticatedUser(request);
|
|
7551
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7552
|
+
return;
|
|
7553
|
+
}
|
|
7554
|
+
const snippets = await db.listSnippets();
|
|
7555
|
+
return reply.send({
|
|
7556
|
+
snippets: snippets.map((snippet) => ({
|
|
7557
|
+
id: snippet.id,
|
|
7558
|
+
name: snippet.name,
|
|
7559
|
+
deletionLocked: snippet.deletionLocked
|
|
7560
|
+
}))
|
|
7561
|
+
});
|
|
7562
|
+
} catch (error) {
|
|
7563
|
+
if (handleDbError(reply, error)) {
|
|
7564
|
+
return;
|
|
7565
|
+
}
|
|
7566
|
+
throw error;
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7569
|
+
});
|
|
6880
7570
|
routes.route({
|
|
6881
7571
|
method: "DELETE",
|
|
6882
7572
|
url: "/admin/collections/:id",
|
|
@@ -6947,6 +7637,80 @@ async function registerAdminRoutes(app, options) {
|
|
|
6947
7637
|
}
|
|
6948
7638
|
}
|
|
6949
7639
|
});
|
|
7640
|
+
routes.route({
|
|
7641
|
+
method: "DELETE",
|
|
7642
|
+
url: "/admin/snippets/:id",
|
|
7643
|
+
schema: {
|
|
7644
|
+
params: idParamSchema,
|
|
7645
|
+
response: {
|
|
7646
|
+
204: emptyResponseSchema,
|
|
7647
|
+
403: errorResponseSchema,
|
|
7648
|
+
404: errorResponseSchema
|
|
7649
|
+
}
|
|
7650
|
+
},
|
|
7651
|
+
/**
|
|
7652
|
+
* Deletes a snippet regardless of deletion lock state.
|
|
7653
|
+
*/
|
|
7654
|
+
handler: async (request, reply) => {
|
|
7655
|
+
try {
|
|
7656
|
+
const user = requireAuthenticatedUser(request);
|
|
7657
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7658
|
+
return;
|
|
7659
|
+
}
|
|
7660
|
+
const snippet = await db.findSnippetById(request.params.id);
|
|
7661
|
+
if (!snippet) {
|
|
7662
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
7665
|
+
await db.deleteSnippet(request.params.id, user.id);
|
|
7666
|
+
return reply.code(204).send(null);
|
|
7667
|
+
} catch (error) {
|
|
7668
|
+
if (handleDbError(reply, error)) {
|
|
7669
|
+
return;
|
|
7670
|
+
}
|
|
7671
|
+
throw error;
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
});
|
|
7675
|
+
routes.route({
|
|
7676
|
+
method: "PUT",
|
|
7677
|
+
url: "/admin/snippets/:id",
|
|
7678
|
+
schema: {
|
|
7679
|
+
params: idParamSchema,
|
|
7680
|
+
body: updateAdminSnippetBodySchema,
|
|
7681
|
+
response: {
|
|
7682
|
+
200: adminEntityConfigSchema,
|
|
7683
|
+
403: errorResponseSchema,
|
|
7684
|
+
404: errorResponseSchema
|
|
7685
|
+
}
|
|
7686
|
+
},
|
|
7687
|
+
/**
|
|
7688
|
+
* Updates admin configuration for a snippet.
|
|
7689
|
+
*/
|
|
7690
|
+
handler: async (request, reply) => {
|
|
7691
|
+
try {
|
|
7692
|
+
const user = requireAuthenticatedUser(request);
|
|
7693
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
const snippet = await db.setSnippetDeletionLocked(
|
|
7697
|
+
request.params.id,
|
|
7698
|
+
request.body.deletionLocked,
|
|
7699
|
+
user.id
|
|
7700
|
+
);
|
|
7701
|
+
return reply.send({
|
|
7702
|
+
id: snippet.id,
|
|
7703
|
+
name: snippet.name,
|
|
7704
|
+
deletionLocked: snippet.deletionLocked
|
|
7705
|
+
});
|
|
7706
|
+
} catch (error) {
|
|
7707
|
+
if (handleDbError(reply, error)) {
|
|
7708
|
+
return;
|
|
7709
|
+
}
|
|
7710
|
+
throw error;
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7713
|
+
});
|
|
6950
7714
|
routes.route({
|
|
6951
7715
|
method: "PUT",
|
|
6952
7716
|
url: "/admin/collections/:id",
|
|
@@ -7127,13 +7891,15 @@ async function registerAdminRoutes(app, options) {
|
|
|
7127
7891
|
const input = buildAdminUserUpdateInput(existing, request.body);
|
|
7128
7892
|
const role = request.body.role ?? existing.role;
|
|
7129
7893
|
const llm = getLlm();
|
|
7130
|
-
const [collections, environments] = await Promise.all([
|
|
7894
|
+
const [collections, environments, snippets] = await Promise.all([
|
|
7131
7895
|
db.listCollections(),
|
|
7132
|
-
db.listEnvironments()
|
|
7896
|
+
db.listEnvironments(),
|
|
7897
|
+
db.listSnippets()
|
|
7133
7898
|
]);
|
|
7134
7899
|
const catalogs = buildAccessCatalogIds(
|
|
7135
7900
|
collections,
|
|
7136
7901
|
environments,
|
|
7902
|
+
snippets,
|
|
7137
7903
|
llm ? listHubOfferedModels(llm).map((model) => model.id) : null
|
|
7138
7904
|
);
|
|
7139
7905
|
validateSubmittedAccessLists(
|
|
@@ -7141,6 +7907,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
7141
7907
|
role,
|
|
7142
7908
|
collectionAccess: request.body.collectionAccess,
|
|
7143
7909
|
environmentAccess: request.body.environmentAccess,
|
|
7910
|
+
snippetAccess: request.body.snippetAccess,
|
|
7144
7911
|
llmModels: request.body.llmModels
|
|
7145
7912
|
},
|
|
7146
7913
|
catalogs
|
|
@@ -7677,6 +8444,150 @@ async function registerEnvironmentRoutes(app, db) {
|
|
|
7677
8444
|
});
|
|
7678
8445
|
}
|
|
7679
8446
|
|
|
8447
|
+
// src/server/routes/snippets.ts
|
|
8448
|
+
async function registerSnippetRoutes(app, db) {
|
|
8449
|
+
const routes = app.withTypeProvider();
|
|
8450
|
+
routes.route({
|
|
8451
|
+
method: "GET",
|
|
8452
|
+
url: "/snippets",
|
|
8453
|
+
schema: {
|
|
8454
|
+
response: {
|
|
8455
|
+
200: listSnippetsResponseSchema
|
|
8456
|
+
}
|
|
8457
|
+
},
|
|
8458
|
+
/**
|
|
8459
|
+
* Lists all snippets ordered by sort order then name.
|
|
8460
|
+
*/
|
|
8461
|
+
handler: async (request, reply) => {
|
|
8462
|
+
try {
|
|
8463
|
+
const user = requireAuthenticatedUser(request);
|
|
8464
|
+
if (denyUnlessAllowed(reply, canListSnippets(user))) {
|
|
8465
|
+
return;
|
|
8466
|
+
}
|
|
8467
|
+
const snippets = await db.listSnippets();
|
|
8468
|
+
return reply.send({
|
|
8469
|
+
snippets: filterAccessibleSnippets(user, snippets).map(
|
|
8470
|
+
(snippet) => serializeSnippet(snippet)
|
|
8471
|
+
)
|
|
8472
|
+
});
|
|
8473
|
+
} catch (error) {
|
|
8474
|
+
if (handleDbError(reply, error)) {
|
|
8475
|
+
return;
|
|
8476
|
+
}
|
|
8477
|
+
throw error;
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
});
|
|
8481
|
+
routes.route({
|
|
8482
|
+
method: "POST",
|
|
8483
|
+
url: "/snippets",
|
|
8484
|
+
schema: {
|
|
8485
|
+
body: createSnippetBodySchema,
|
|
8486
|
+
response: {
|
|
8487
|
+
200: snippetRecordSchema,
|
|
8488
|
+
400: errorResponseSchema
|
|
8489
|
+
}
|
|
8490
|
+
},
|
|
8491
|
+
/**
|
|
8492
|
+
* Creates a new snippet with the given display name and content.
|
|
8493
|
+
*/
|
|
8494
|
+
handler: async (request, reply) => {
|
|
8495
|
+
try {
|
|
8496
|
+
const user = requireAuthenticatedUser(request);
|
|
8497
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canCreateSnippet(user))) {
|
|
8498
|
+
return;
|
|
8499
|
+
}
|
|
8500
|
+
const snippet = await db.createSnippet(
|
|
8501
|
+
request.body.name,
|
|
8502
|
+
request.body.code,
|
|
8503
|
+
request.body.scope,
|
|
8504
|
+
user.id
|
|
8505
|
+
);
|
|
8506
|
+
return reply.send(serializeSnippet(snippet));
|
|
8507
|
+
} catch (error) {
|
|
8508
|
+
if (handleDbError(reply, error)) {
|
|
8509
|
+
return;
|
|
8510
|
+
}
|
|
8511
|
+
throw error;
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8514
|
+
});
|
|
8515
|
+
routes.route({
|
|
8516
|
+
method: "PUT",
|
|
8517
|
+
url: "/snippets/:id",
|
|
8518
|
+
schema: {
|
|
8519
|
+
params: idParamSchema,
|
|
8520
|
+
body: updateSnippetBodySchema,
|
|
8521
|
+
response: {
|
|
8522
|
+
200: snippetRecordSchema,
|
|
8523
|
+
400: errorResponseSchema,
|
|
8524
|
+
404: errorResponseSchema
|
|
8525
|
+
}
|
|
8526
|
+
},
|
|
8527
|
+
/**
|
|
8528
|
+
* Updates a snippet's name, code, and scope. Sort order is preserved.
|
|
8529
|
+
*/
|
|
8530
|
+
handler: async (request, reply) => {
|
|
8531
|
+
try {
|
|
8532
|
+
const user = requireAuthenticatedUser(request);
|
|
8533
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canAccessSnippet(user, request.params.id))) {
|
|
8534
|
+
return;
|
|
8535
|
+
}
|
|
8536
|
+
const snippet = await db.updateSnippet(
|
|
8537
|
+
request.params.id,
|
|
8538
|
+
request.body.name,
|
|
8539
|
+
request.body.code,
|
|
8540
|
+
request.body.scope,
|
|
8541
|
+
user.id
|
|
8542
|
+
);
|
|
8543
|
+
return reply.send(serializeSnippet(snippet));
|
|
8544
|
+
} catch (error) {
|
|
8545
|
+
if (handleDbError(reply, error)) {
|
|
8546
|
+
return;
|
|
8547
|
+
}
|
|
8548
|
+
throw error;
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
});
|
|
8552
|
+
routes.route({
|
|
8553
|
+
method: "DELETE",
|
|
8554
|
+
url: "/snippets/:id",
|
|
8555
|
+
schema: {
|
|
8556
|
+
params: idParamSchema,
|
|
8557
|
+
response: {
|
|
8558
|
+
204: emptyResponseSchema,
|
|
8559
|
+
404: errorResponseSchema
|
|
8560
|
+
}
|
|
8561
|
+
},
|
|
8562
|
+
/**
|
|
8563
|
+
* Deletes a snippet by id.
|
|
8564
|
+
*/
|
|
8565
|
+
handler: async (request, reply) => {
|
|
8566
|
+
try {
|
|
8567
|
+
const user = requireAuthenticatedUser(request);
|
|
8568
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user) && canAccessSnippet(user, request.params.id))) {
|
|
8569
|
+
return;
|
|
8570
|
+
}
|
|
8571
|
+
const snippet = await db.findSnippetById(request.params.id);
|
|
8572
|
+
if (!snippet) {
|
|
8573
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
8574
|
+
return;
|
|
8575
|
+
}
|
|
8576
|
+
if (snippet.deletionLocked) {
|
|
8577
|
+
throw new DeletionLockedError("snippet");
|
|
8578
|
+
}
|
|
8579
|
+
await db.deleteSnippet(request.params.id, user.id);
|
|
8580
|
+
return reply.code(204).send(null);
|
|
8581
|
+
} catch (error) {
|
|
8582
|
+
if (handleDbError(reply, error)) {
|
|
8583
|
+
return;
|
|
8584
|
+
}
|
|
8585
|
+
throw error;
|
|
8586
|
+
}
|
|
8587
|
+
}
|
|
8588
|
+
});
|
|
8589
|
+
}
|
|
8590
|
+
|
|
7680
8591
|
// src/server/routes/folders.ts
|
|
7681
8592
|
async function registerFolderRoutes(app, db) {
|
|
7682
8593
|
const routes = app.withTypeProvider();
|
|
@@ -8778,6 +9689,7 @@ async function registerProtectedRoutes(app, options) {
|
|
|
8778
9689
|
});
|
|
8779
9690
|
await registerCollectionRoutes(app, options.db);
|
|
8780
9691
|
await registerEnvironmentRoutes(app, options.db);
|
|
9692
|
+
await registerSnippetRoutes(app, options.db);
|
|
8781
9693
|
await registerFolderRoutes(app, options.db);
|
|
8782
9694
|
await registerRequestRoutes(app, options.db);
|
|
8783
9695
|
await registerLlmRoutes(app, { db: options.db, getLlm: options.getLlm });
|