@harborclient/team-hub 0.4.0 → 0.4.2
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 +928 -267
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -364,6 +364,7 @@ var REQUESTS_COLLECTION = "requests";
|
|
|
364
364
|
var AUDIT_LOG_COLLECTION = "auditLog";
|
|
365
365
|
var LLM_USAGE_COLLECTION = "llmUsage";
|
|
366
366
|
var LLM_USAGE_LOG_COLLECTION = "llmUsageLog";
|
|
367
|
+
var RUN_RESULTS_COLLECTION = "runResults";
|
|
367
368
|
var WRITE_BATCH_LIMIT = 500;
|
|
368
369
|
|
|
369
370
|
// src/db/systemUsers.ts
|
|
@@ -400,7 +401,7 @@ var firestoreConfigSchema = z2.object({
|
|
|
400
401
|
|
|
401
402
|
// src/db/firestore/utils.ts
|
|
402
403
|
function parseAuditEntityType(value) {
|
|
403
|
-
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request") {
|
|
404
|
+
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "run_result") {
|
|
404
405
|
return value;
|
|
405
406
|
}
|
|
406
407
|
throw new Error(`Invalid audit entity type: ${value}`);
|
|
@@ -544,6 +545,19 @@ function mapFirestoreRequest(id, data) {
|
|
|
544
545
|
updatedByUserId: data.updatedByUserId ?? null
|
|
545
546
|
};
|
|
546
547
|
}
|
|
548
|
+
function mapFirestoreRunResult(id, data) {
|
|
549
|
+
return {
|
|
550
|
+
id,
|
|
551
|
+
kind: data.kind,
|
|
552
|
+
label: data.label,
|
|
553
|
+
collectionName: data.collectionName,
|
|
554
|
+
requestName: data.requestName,
|
|
555
|
+
summary: data.summary,
|
|
556
|
+
payload: data.payload,
|
|
557
|
+
createdAt: data.createdAt,
|
|
558
|
+
createdByUserId: data.createdByUserId ?? null
|
|
559
|
+
};
|
|
560
|
+
}
|
|
547
561
|
function mapFirestoreAuditLog(id, data) {
|
|
548
562
|
return {
|
|
549
563
|
id,
|
|
@@ -557,6 +571,53 @@ function mapFirestoreAuditLog(id, data) {
|
|
|
557
571
|
};
|
|
558
572
|
}
|
|
559
573
|
|
|
574
|
+
// src/db/runResultPayload.ts
|
|
575
|
+
function isRecord(value) {
|
|
576
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
577
|
+
}
|
|
578
|
+
function summarizeResultRows(results) {
|
|
579
|
+
let passed = 0;
|
|
580
|
+
let failed = 0;
|
|
581
|
+
let skipped = 0;
|
|
582
|
+
for (const row of results) {
|
|
583
|
+
if (!isRecord(row)) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
const status = row.status;
|
|
587
|
+
if (status === "passed") {
|
|
588
|
+
passed += 1;
|
|
589
|
+
} else if (status === "failed") {
|
|
590
|
+
failed += 1;
|
|
591
|
+
} else if (status === "skipped") {
|
|
592
|
+
skipped += 1;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return { passed, failed, skipped };
|
|
596
|
+
}
|
|
597
|
+
function buildDefaultRunResultLabel(metadata) {
|
|
598
|
+
const target = metadata.requestName ?? metadata.collectionName ?? "Run";
|
|
599
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
600
|
+
return `${target} \u2014 ${timestamp}`;
|
|
601
|
+
}
|
|
602
|
+
function parseRunResultPayload(payload) {
|
|
603
|
+
const kind = payload.harborclientExport;
|
|
604
|
+
if (kind !== "collection-run-results" && kind !== "request-run-results") {
|
|
605
|
+
throw new Error("Invalid run result payload: harborclientExport is required");
|
|
606
|
+
}
|
|
607
|
+
const results = payload.results;
|
|
608
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
609
|
+
throw new Error("Invalid run result payload: results are required");
|
|
610
|
+
}
|
|
611
|
+
const collection = isRecord(payload.collection) ? payload.collection : void 0;
|
|
612
|
+
const request = isRecord(payload.request) ? payload.request : void 0;
|
|
613
|
+
return {
|
|
614
|
+
kind,
|
|
615
|
+
collectionName: typeof collection?.name === "string" ? collection.name : null,
|
|
616
|
+
requestName: typeof request?.name === "string" ? request.name : null,
|
|
617
|
+
summary: summarizeResultRows(results)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
560
621
|
// src/db/trimRequiredName.ts
|
|
561
622
|
function trimRequiredName(name, label) {
|
|
562
623
|
const trimmed = name.trim();
|
|
@@ -1822,6 +1883,78 @@ var FirestoreDatabase = class _FirestoreDatabase {
|
|
|
1822
1883
|
}
|
|
1823
1884
|
return usage;
|
|
1824
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Lists run results saved by the given user, newest first.
|
|
1888
|
+
*
|
|
1889
|
+
* @param userId - Owning user identifier.
|
|
1890
|
+
*/
|
|
1891
|
+
async listRunResultsForUser(userId) {
|
|
1892
|
+
const snapshot = await this.requireClient().collection(RUN_RESULTS_COLLECTION).where("createdByUserId", "==", userId).orderBy("createdAt", "desc").get();
|
|
1893
|
+
return snapshot.docs.map(
|
|
1894
|
+
(doc) => mapFirestoreRunResult(doc.id, doc.data())
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Lists all run results for admin inspection, newest first.
|
|
1899
|
+
*/
|
|
1900
|
+
async listAllRunResults() {
|
|
1901
|
+
const snapshot = await this.requireClient().collection(RUN_RESULTS_COLLECTION).orderBy("createdAt", "desc").get();
|
|
1902
|
+
return snapshot.docs.map(
|
|
1903
|
+
(doc) => mapFirestoreRunResult(doc.id, doc.data())
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Creates a standalone run result snapshot.
|
|
1908
|
+
*
|
|
1909
|
+
* @param input - HarborClient export payload and optional label.
|
|
1910
|
+
* @param actingUserId - User performing the create action.
|
|
1911
|
+
*/
|
|
1912
|
+
async createRunResult(input, actingUserId) {
|
|
1913
|
+
const metadata = parseRunResultPayload(input.payload);
|
|
1914
|
+
const label = input.label?.trim() || buildDefaultRunResultLabel(metadata);
|
|
1915
|
+
const id = randomUUID2();
|
|
1916
|
+
const now = /* @__PURE__ */ new Date();
|
|
1917
|
+
const data = {
|
|
1918
|
+
kind: metadata.kind,
|
|
1919
|
+
label,
|
|
1920
|
+
collectionName: metadata.collectionName,
|
|
1921
|
+
requestName: metadata.requestName,
|
|
1922
|
+
summary: metadata.summary,
|
|
1923
|
+
payload: input.payload,
|
|
1924
|
+
createdAt: now,
|
|
1925
|
+
createdByUserId: actingUserId
|
|
1926
|
+
};
|
|
1927
|
+
await this.requireClient().collection(RUN_RESULTS_COLLECTION).doc(id).set(data);
|
|
1928
|
+
await this.recordAuditEntry(actingUserId, "create", "run_result", id);
|
|
1929
|
+
return mapFirestoreRunResult(id, data);
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Finds a run result by stable identifier.
|
|
1933
|
+
*
|
|
1934
|
+
* @param id - Run result ID to look up.
|
|
1935
|
+
*/
|
|
1936
|
+
async findRunResultById(id) {
|
|
1937
|
+
const snapshot = await this.requireClient().collection(RUN_RESULTS_COLLECTION).doc(id).get();
|
|
1938
|
+
if (!snapshot.exists) {
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
return mapFirestoreRunResult(id, snapshot.data());
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Deletes a run result.
|
|
1945
|
+
*
|
|
1946
|
+
* @param id - Run result ID to delete.
|
|
1947
|
+
* @param actingUserId - User performing the delete action.
|
|
1948
|
+
*/
|
|
1949
|
+
async deleteRunResult(id, actingUserId) {
|
|
1950
|
+
const docRef = this.requireClient().collection(RUN_RESULTS_COLLECTION).doc(id);
|
|
1951
|
+
const snapshot = await docRef.get();
|
|
1952
|
+
if (!snapshot.exists) {
|
|
1953
|
+
throw new Error("Run result not found");
|
|
1954
|
+
}
|
|
1955
|
+
await this.recordAuditEntry(actingUserId, "delete", "run_result", id);
|
|
1956
|
+
await docRef.delete();
|
|
1957
|
+
}
|
|
1825
1958
|
/**
|
|
1826
1959
|
* Inserts a per-request LLM usage log entry.
|
|
1827
1960
|
*
|
|
@@ -1976,7 +2109,7 @@ function parseAuditAction(value) {
|
|
|
1976
2109
|
throw new Error(`Invalid audit action: ${value}`);
|
|
1977
2110
|
}
|
|
1978
2111
|
function parseAuditEntityType2(value) {
|
|
1979
|
-
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request") {
|
|
2112
|
+
if (value === "user" || value === "api_token" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "run_result") {
|
|
1980
2113
|
return value;
|
|
1981
2114
|
}
|
|
1982
2115
|
throw new Error(`Invalid audit entity type: ${value}`);
|
|
@@ -2073,6 +2206,23 @@ function mapSnippetSqlRow(row) {
|
|
|
2073
2206
|
deletionLocked: Boolean(row.deletion_locked)
|
|
2074
2207
|
};
|
|
2075
2208
|
}
|
|
2209
|
+
function mapRunResultSqlRow(row) {
|
|
2210
|
+
return {
|
|
2211
|
+
id: row.id,
|
|
2212
|
+
kind: row.kind,
|
|
2213
|
+
label: row.label,
|
|
2214
|
+
collectionName: row.collection_name,
|
|
2215
|
+
requestName: row.request_name,
|
|
2216
|
+
summary: {
|
|
2217
|
+
passed: row.summary_passed,
|
|
2218
|
+
failed: row.summary_failed,
|
|
2219
|
+
skipped: row.summary_skipped
|
|
2220
|
+
},
|
|
2221
|
+
payload: parseJson(row.payload, {}),
|
|
2222
|
+
createdAt: row.created_at,
|
|
2223
|
+
createdByUserId: row.created_by_user_id ?? null
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2076
2226
|
function mapFolderSqlRow(row) {
|
|
2077
2227
|
return {
|
|
2078
2228
|
id: row.id,
|
|
@@ -2349,6 +2499,22 @@ WHERE role = 'user'
|
|
|
2349
2499
|
AND snippet_access = '[]'
|
|
2350
2500
|
AND collection_access LIKE '%"*"%';
|
|
2351
2501
|
`.trim();
|
|
2502
|
+
var RUN_RESULTS_MIGRATION_SQL = `
|
|
2503
|
+
CREATE TABLE IF NOT EXISTS run_results (
|
|
2504
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
2505
|
+
kind VARCHAR(64) NOT NULL,
|
|
2506
|
+
label VARCHAR(512) NOT NULL,
|
|
2507
|
+
collection_name VARCHAR(512),
|
|
2508
|
+
request_name VARCHAR(512),
|
|
2509
|
+
summary_passed INT NOT NULL DEFAULT 0,
|
|
2510
|
+
summary_failed INT NOT NULL DEFAULT 0,
|
|
2511
|
+
summary_skipped INT NOT NULL DEFAULT 0,
|
|
2512
|
+
payload LONGTEXT NOT NULL,
|
|
2513
|
+
created_at DATETIME NOT NULL,
|
|
2514
|
+
created_by_user_id VARCHAR(36),
|
|
2515
|
+
INDEX run_results_created_idx (created_at)
|
|
2516
|
+
)
|
|
2517
|
+
`.trim();
|
|
2352
2518
|
var MYSQL_MIGRATIONS = [
|
|
2353
2519
|
USERS_MIGRATION_SQL,
|
|
2354
2520
|
API_TOKENS_MIGRATION_SQL,
|
|
@@ -2374,7 +2540,8 @@ var MYSQL_MIGRATIONS = [
|
|
|
2374
2540
|
COLLECTIONS_DELETION_LOCKED_MIGRATION_SQL,
|
|
2375
2541
|
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL,
|
|
2376
2542
|
USERS_SNIPPET_ACCESS_MIGRATION_SQL,
|
|
2377
|
-
USERS_SNIPPET_ACCESS_BACKFILL_SQL
|
|
2543
|
+
USERS_SNIPPET_ACCESS_BACKFILL_SQL,
|
|
2544
|
+
RUN_RESULTS_MIGRATION_SQL
|
|
2378
2545
|
];
|
|
2379
2546
|
|
|
2380
2547
|
// src/db/mysql/schemas.ts
|
|
@@ -2478,6 +2645,8 @@ var LLM_USAGE_SELECT_COLUMNS = `id, user_id, period, prompt_tokens, completion_t
|
|
|
2478
2645
|
var COLLECTION_SELECT = `SELECT ${COLLECTION_SELECT_COLUMNS} FROM collections`;
|
|
2479
2646
|
var ENVIRONMENT_SELECT = `SELECT ${ENVIRONMENT_SELECT_COLUMNS} FROM environments`;
|
|
2480
2647
|
var SNIPPET_SELECT = `SELECT ${SNIPPET_SELECT_COLUMNS} FROM snippets`;
|
|
2648
|
+
var RUN_RESULT_SELECT_COLUMNS = "id, kind, label, collection_name, request_name, summary_passed, summary_failed, summary_skipped, payload, created_at, created_by_user_id";
|
|
2649
|
+
var RUN_RESULT_SELECT = `SELECT ${RUN_RESULT_SELECT_COLUMNS} FROM run_results`;
|
|
2481
2650
|
var USER_SELECT = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
|
|
2482
2651
|
var API_TOKEN_SELECT = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
|
|
2483
2652
|
var FOLDER_SELECT = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
|
|
@@ -3883,6 +4052,93 @@ var MysqlDatabase = class _MysqlDatabase {
|
|
|
3883
4052
|
);
|
|
3884
4053
|
return rows.map(mapLlmUsageLogSqlRow);
|
|
3885
4054
|
}
|
|
4055
|
+
/**
|
|
4056
|
+
* Lists run results saved by the given user, newest first.
|
|
4057
|
+
*/
|
|
4058
|
+
async listRunResultsForUser(userId) {
|
|
4059
|
+
const rows = await this.queryRows(
|
|
4060
|
+
`${RUN_RESULT_SELECT} WHERE created_by_user_id = ? ORDER BY created_at DESC`,
|
|
4061
|
+
[userId]
|
|
4062
|
+
);
|
|
4063
|
+
return rows.map(mapRunResultSqlRow);
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* Lists all run results for admin inspection, newest first.
|
|
4067
|
+
*/
|
|
4068
|
+
async listAllRunResults() {
|
|
4069
|
+
const rows = await this.queryRows(
|
|
4070
|
+
`${RUN_RESULT_SELECT} ORDER BY created_at DESC`
|
|
4071
|
+
);
|
|
4072
|
+
return rows.map(mapRunResultSqlRow);
|
|
4073
|
+
}
|
|
4074
|
+
/**
|
|
4075
|
+
* Creates a standalone run result snapshot.
|
|
4076
|
+
*/
|
|
4077
|
+
async createRunResult(input, actingUserId) {
|
|
4078
|
+
const metadata = parseRunResultPayload(input.payload);
|
|
4079
|
+
const label = input.label?.trim() || buildDefaultRunResultLabel(metadata);
|
|
4080
|
+
const id = randomUUID3();
|
|
4081
|
+
const now = /* @__PURE__ */ new Date();
|
|
4082
|
+
await this.executeStatement(
|
|
4083
|
+
`INSERT INTO run_results (
|
|
4084
|
+
id,
|
|
4085
|
+
kind,
|
|
4086
|
+
label,
|
|
4087
|
+
collection_name,
|
|
4088
|
+
request_name,
|
|
4089
|
+
summary_passed,
|
|
4090
|
+
summary_failed,
|
|
4091
|
+
summary_skipped,
|
|
4092
|
+
payload,
|
|
4093
|
+
created_at,
|
|
4094
|
+
created_by_user_id
|
|
4095
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4096
|
+
[
|
|
4097
|
+
id,
|
|
4098
|
+
metadata.kind,
|
|
4099
|
+
label,
|
|
4100
|
+
metadata.collectionName,
|
|
4101
|
+
metadata.requestName,
|
|
4102
|
+
metadata.summary.passed,
|
|
4103
|
+
metadata.summary.failed,
|
|
4104
|
+
metadata.summary.skipped,
|
|
4105
|
+
JSON.stringify(input.payload),
|
|
4106
|
+
now,
|
|
4107
|
+
actingUserId
|
|
4108
|
+
]
|
|
4109
|
+
);
|
|
4110
|
+
const rows = await this.queryRows(
|
|
4111
|
+
`${RUN_RESULT_SELECT} WHERE id = ?`,
|
|
4112
|
+
[id]
|
|
4113
|
+
);
|
|
4114
|
+
const row = rows[0];
|
|
4115
|
+
if (!row) {
|
|
4116
|
+
throw new Error("Run result not found after insert");
|
|
4117
|
+
}
|
|
4118
|
+
await this.recordAuditEntry(actingUserId, "create", "run_result", id);
|
|
4119
|
+
return mapRunResultSqlRow(row);
|
|
4120
|
+
}
|
|
4121
|
+
/**
|
|
4122
|
+
* Finds a run result by id.
|
|
4123
|
+
*/
|
|
4124
|
+
async findRunResultById(id) {
|
|
4125
|
+
const rows = await this.queryRows(
|
|
4126
|
+
`${RUN_RESULT_SELECT} WHERE id = ?`,
|
|
4127
|
+
[id]
|
|
4128
|
+
);
|
|
4129
|
+
const row = rows[0];
|
|
4130
|
+
return row ? mapRunResultSqlRow(row) : null;
|
|
4131
|
+
}
|
|
4132
|
+
/**
|
|
4133
|
+
* Deletes a run result by id.
|
|
4134
|
+
*/
|
|
4135
|
+
async deleteRunResult(id, actingUserId) {
|
|
4136
|
+
const result = await this.executeStatement("DELETE FROM run_results WHERE id = ?", [id]);
|
|
4137
|
+
if (result.affectedRows === 0) {
|
|
4138
|
+
throw new Error("Run result not found");
|
|
4139
|
+
}
|
|
4140
|
+
await this.recordAuditEntry(actingUserId, "delete", "run_result", id);
|
|
4141
|
+
}
|
|
3886
4142
|
/**
|
|
3887
4143
|
* Ensures the internal system user exists and caches its identifier.
|
|
3888
4144
|
*/
|
|
@@ -4229,6 +4485,22 @@ WHERE role = 'user'
|
|
|
4229
4485
|
AND snippet_access = '[]'
|
|
4230
4486
|
AND collection_access LIKE '%"*"%';
|
|
4231
4487
|
`.trim();
|
|
4488
|
+
var RUN_RESULTS_MIGRATION_SQL2 = `
|
|
4489
|
+
CREATE TABLE IF NOT EXISTS run_results (
|
|
4490
|
+
id TEXT PRIMARY KEY,
|
|
4491
|
+
kind TEXT NOT NULL CHECK (kind IN ('collection-run-results', 'request-run-results')),
|
|
4492
|
+
label TEXT NOT NULL,
|
|
4493
|
+
collection_name TEXT,
|
|
4494
|
+
request_name TEXT,
|
|
4495
|
+
summary_passed INT NOT NULL DEFAULT 0,
|
|
4496
|
+
summary_failed INT NOT NULL DEFAULT 0,
|
|
4497
|
+
summary_skipped INT NOT NULL DEFAULT 0,
|
|
4498
|
+
payload TEXT NOT NULL,
|
|
4499
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
4500
|
+
created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL
|
|
4501
|
+
);
|
|
4502
|
+
CREATE INDEX IF NOT EXISTS run_results_created_idx ON run_results (created_at DESC);
|
|
4503
|
+
`.trim();
|
|
4232
4504
|
var POSTGRES_MIGRATIONS = [
|
|
4233
4505
|
USERS_MIGRATION_SQL2,
|
|
4234
4506
|
API_TOKENS_MIGRATION_SQL2,
|
|
@@ -4254,7 +4526,8 @@ var POSTGRES_MIGRATIONS = [
|
|
|
4254
4526
|
COLLECTIONS_DELETION_LOCKED_MIGRATION_SQL2,
|
|
4255
4527
|
ENVIRONMENTS_DELETION_LOCKED_MIGRATION_SQL2,
|
|
4256
4528
|
USERS_SNIPPET_ACCESS_MIGRATION_SQL2,
|
|
4257
|
-
USERS_SNIPPET_ACCESS_BACKFILL_SQL2
|
|
4529
|
+
USERS_SNIPPET_ACCESS_BACKFILL_SQL2,
|
|
4530
|
+
RUN_RESULTS_MIGRATION_SQL2
|
|
4258
4531
|
];
|
|
4259
4532
|
|
|
4260
4533
|
// src/db/postgres/schemas.ts
|
|
@@ -4279,6 +4552,8 @@ var { Pool } = pg;
|
|
|
4279
4552
|
var COLLECTION_SELECT2 = `SELECT ${COLLECTION_SELECT_COLUMNS} FROM collections`;
|
|
4280
4553
|
var ENVIRONMENT_SELECT2 = `SELECT ${ENVIRONMENT_SELECT_COLUMNS} FROM environments`;
|
|
4281
4554
|
var SNIPPET_SELECT2 = `SELECT ${SNIPPET_SELECT_COLUMNS} FROM snippets`;
|
|
4555
|
+
var RUN_RESULT_SELECT_COLUMNS2 = "id, kind, label, collection_name, request_name, summary_passed, summary_failed, summary_skipped, payload, created_at, created_by_user_id";
|
|
4556
|
+
var RUN_RESULT_SELECT2 = `SELECT ${RUN_RESULT_SELECT_COLUMNS2} FROM run_results`;
|
|
4282
4557
|
var USER_SELECT2 = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
|
|
4283
4558
|
var API_TOKEN_SELECT2 = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
|
|
4284
4559
|
var FOLDER_SELECT2 = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
|
|
@@ -5629,6 +5904,87 @@ var PostgresDatabase = class _PostgresDatabase {
|
|
|
5629
5904
|
);
|
|
5630
5905
|
return result.rows.map(mapLlmUsageLogSqlRow);
|
|
5631
5906
|
}
|
|
5907
|
+
/**
|
|
5908
|
+
* Lists run results saved by the given user, newest first.
|
|
5909
|
+
*/
|
|
5910
|
+
async listRunResultsForUser(userId) {
|
|
5911
|
+
const result = await this.query(
|
|
5912
|
+
`${RUN_RESULT_SELECT2} WHERE created_by_user_id = $1 ORDER BY created_at DESC`,
|
|
5913
|
+
[userId]
|
|
5914
|
+
);
|
|
5915
|
+
return result.rows.map(mapRunResultSqlRow);
|
|
5916
|
+
}
|
|
5917
|
+
/**
|
|
5918
|
+
* Lists all run results for admin inspection, newest first.
|
|
5919
|
+
*/
|
|
5920
|
+
async listAllRunResults() {
|
|
5921
|
+
const result = await this.query(
|
|
5922
|
+
`${RUN_RESULT_SELECT2} ORDER BY created_at DESC`
|
|
5923
|
+
);
|
|
5924
|
+
return result.rows.map(mapRunResultSqlRow);
|
|
5925
|
+
}
|
|
5926
|
+
/**
|
|
5927
|
+
* Creates a standalone run result snapshot.
|
|
5928
|
+
*/
|
|
5929
|
+
async createRunResult(input, actingUserId) {
|
|
5930
|
+
const metadata = parseRunResultPayload(input.payload);
|
|
5931
|
+
const label = input.label?.trim() || buildDefaultRunResultLabel(metadata);
|
|
5932
|
+
const id = randomUUID4();
|
|
5933
|
+
const now = /* @__PURE__ */ new Date();
|
|
5934
|
+
const result = await this.query(
|
|
5935
|
+
`INSERT INTO run_results (
|
|
5936
|
+
id,
|
|
5937
|
+
kind,
|
|
5938
|
+
label,
|
|
5939
|
+
collection_name,
|
|
5940
|
+
request_name,
|
|
5941
|
+
summary_passed,
|
|
5942
|
+
summary_failed,
|
|
5943
|
+
summary_skipped,
|
|
5944
|
+
payload,
|
|
5945
|
+
created_at,
|
|
5946
|
+
created_by_user_id
|
|
5947
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
5948
|
+
RETURNING ${RUN_RESULT_SELECT_COLUMNS2}`,
|
|
5949
|
+
[
|
|
5950
|
+
id,
|
|
5951
|
+
metadata.kind,
|
|
5952
|
+
label,
|
|
5953
|
+
metadata.collectionName,
|
|
5954
|
+
metadata.requestName,
|
|
5955
|
+
metadata.summary.passed,
|
|
5956
|
+
metadata.summary.failed,
|
|
5957
|
+
metadata.summary.skipped,
|
|
5958
|
+
JSON.stringify(input.payload),
|
|
5959
|
+
now,
|
|
5960
|
+
actingUserId
|
|
5961
|
+
]
|
|
5962
|
+
);
|
|
5963
|
+
const row = result.rows[0];
|
|
5964
|
+
if (!row) {
|
|
5965
|
+
throw new Error("Run result not found after insert");
|
|
5966
|
+
}
|
|
5967
|
+
await this.recordAuditEntry(actingUserId, "create", "run_result", id);
|
|
5968
|
+
return mapRunResultSqlRow(row);
|
|
5969
|
+
}
|
|
5970
|
+
/**
|
|
5971
|
+
* Finds a run result by id.
|
|
5972
|
+
*/
|
|
5973
|
+
async findRunResultById(id) {
|
|
5974
|
+
const result = await this.query(`${RUN_RESULT_SELECT2} WHERE id = $1`, [id]);
|
|
5975
|
+
const row = result.rows[0];
|
|
5976
|
+
return row ? mapRunResultSqlRow(row) : null;
|
|
5977
|
+
}
|
|
5978
|
+
/**
|
|
5979
|
+
* Deletes a run result by id.
|
|
5980
|
+
*/
|
|
5981
|
+
async deleteRunResult(id, actingUserId) {
|
|
5982
|
+
const result = await this.query("DELETE FROM run_results WHERE id = $1", [id]);
|
|
5983
|
+
if ((result.rowCount ?? 0) === 0) {
|
|
5984
|
+
throw new Error("Run result not found");
|
|
5985
|
+
}
|
|
5986
|
+
await this.recordAuditEntry(actingUserId, "delete", "run_result", id);
|
|
5987
|
+
}
|
|
5632
5988
|
/**
|
|
5633
5989
|
* Returns the active pool or throws when connect has not been called.
|
|
5634
5990
|
*
|
|
@@ -6655,6 +7011,9 @@ function canListEnvironments(user) {
|
|
|
6655
7011
|
function canListSnippets(user) {
|
|
6656
7012
|
return canUseDataApi(user) || canUseManagementApi(user);
|
|
6657
7013
|
}
|
|
7014
|
+
function canListRunResults(user) {
|
|
7015
|
+
return canUseDataApi(user) || canUseManagementApi(user);
|
|
7016
|
+
}
|
|
6658
7017
|
function hasWildcardAccess(access) {
|
|
6659
7018
|
return access.includes("*");
|
|
6660
7019
|
}
|
|
@@ -6691,6 +7050,9 @@ function canDeleteCollection(user, collection) {
|
|
|
6691
7050
|
function canDeleteRequest(user, request) {
|
|
6692
7051
|
return canUseDataApi(user) && canAccessCollection(user, request.collectionId) && request.createdByUserId === user.id;
|
|
6693
7052
|
}
|
|
7053
|
+
function canDeleteRunResult(user, runResult) {
|
|
7054
|
+
return canUseDataApi(user) && runResult.createdByUserId === user.id;
|
|
7055
|
+
}
|
|
6694
7056
|
function canCreateCollection(user) {
|
|
6695
7057
|
return user.role === "user" && hasWildcardAccess(user.collectionAccess);
|
|
6696
7058
|
}
|
|
@@ -6865,7 +7227,7 @@ function denyUnlessAllowed(reply, allowed) {
|
|
|
6865
7227
|
}
|
|
6866
7228
|
|
|
6867
7229
|
// src/server/routes/schemas/admin.ts
|
|
6868
|
-
import { z as
|
|
7230
|
+
import { z as z9 } from "zod/v4";
|
|
6869
7231
|
|
|
6870
7232
|
// src/server/routes/schemas/auth.ts
|
|
6871
7233
|
import { z as z6 } from "zod/v4";
|
|
@@ -6932,316 +7294,381 @@ var llmUsageSummaryResponseSchema = z7.object({
|
|
|
6932
7294
|
limit: z7.number().int().positive().nullable()
|
|
6933
7295
|
});
|
|
6934
7296
|
|
|
6935
|
-
// src/server/routes/schemas/
|
|
6936
|
-
|
|
7297
|
+
// src/server/routes/schemas/entities.ts
|
|
7298
|
+
import { z as z8 } from "zod/v4";
|
|
7299
|
+
var collectionRecordSchema = z8.object({
|
|
6937
7300
|
id: z8.string(),
|
|
6938
7301
|
name: z8.string(),
|
|
7302
|
+
variables: z8.array(variableSchema),
|
|
7303
|
+
headers: z8.array(keyValueSchema),
|
|
7304
|
+
auth: authConfigSchema,
|
|
7305
|
+
preRequestScript: z8.string(),
|
|
7306
|
+
postRequestScript: z8.string(),
|
|
7307
|
+
createdAt: timestampSchema,
|
|
7308
|
+
updatedAt: timestampSchema,
|
|
7309
|
+
createdByUserId: z8.string().nullable(),
|
|
7310
|
+
updatedByUserId: z8.string().nullable(),
|
|
6939
7311
|
deletionLocked: z8.boolean()
|
|
6940
7312
|
});
|
|
6941
|
-
var
|
|
7313
|
+
var environmentRecordSchema = z8.object({
|
|
6942
7314
|
id: z8.string(),
|
|
6943
7315
|
name: z8.string(),
|
|
7316
|
+
variables: z8.array(variableSchema),
|
|
7317
|
+
createdAt: timestampSchema,
|
|
7318
|
+
updatedAt: timestampSchema,
|
|
7319
|
+
createdByUserId: z8.string().nullable(),
|
|
7320
|
+
updatedByUserId: z8.string().nullable(),
|
|
6944
7321
|
deletionLocked: z8.boolean()
|
|
6945
7322
|
});
|
|
6946
|
-
var
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
7323
|
+
var snippetScopeSchema = z8.enum(["pre-request", "post-request", "any"]);
|
|
7324
|
+
var snippetRecordSchema = z8.object({
|
|
7325
|
+
id: z8.string(),
|
|
7326
|
+
name: z8.string(),
|
|
7327
|
+
code: z8.string(),
|
|
7328
|
+
scope: snippetScopeSchema,
|
|
7329
|
+
sortOrder: z8.number().int(),
|
|
7330
|
+
createdAt: timestampSchema,
|
|
7331
|
+
updatedAt: timestampSchema,
|
|
7332
|
+
createdByUserId: z8.string().nullable(),
|
|
7333
|
+
updatedByUserId: z8.string().nullable(),
|
|
6953
7334
|
deletionLocked: z8.boolean()
|
|
6954
7335
|
});
|
|
6955
|
-
var
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
7336
|
+
var folderRecordSchema = z8.object({
|
|
7337
|
+
id: z8.string(),
|
|
7338
|
+
collectionId: z8.string(),
|
|
7339
|
+
name: z8.string(),
|
|
7340
|
+
sortOrder: z8.number().int(),
|
|
7341
|
+
createdAt: timestampSchema,
|
|
7342
|
+
updatedAt: timestampSchema,
|
|
7343
|
+
createdByUserId: z8.string().nullable(),
|
|
7344
|
+
updatedByUserId: z8.string().nullable()
|
|
6963
7345
|
});
|
|
6964
|
-
var
|
|
6965
|
-
var hubUserRecordSchema = z8.object({
|
|
7346
|
+
var savedRequestRecordSchema = z8.object({
|
|
6966
7347
|
id: z8.string(),
|
|
7348
|
+
collectionId: z8.string(),
|
|
6967
7349
|
name: z8.string(),
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
7350
|
+
method: httpMethodSchema,
|
|
7351
|
+
url: z8.string(),
|
|
7352
|
+
headers: z8.array(keyValueSchema),
|
|
7353
|
+
params: z8.array(keyValueSchema),
|
|
7354
|
+
auth: authConfigSchema,
|
|
7355
|
+
body: z8.string(),
|
|
7356
|
+
bodyType: bodyTypeSchema,
|
|
7357
|
+
preRequestScript: z8.string(),
|
|
7358
|
+
postRequestScript: z8.string(),
|
|
7359
|
+
comment: z8.string(),
|
|
7360
|
+
folderId: z8.string().nullable(),
|
|
7361
|
+
sortOrder: z8.number().int(),
|
|
6975
7362
|
createdAt: timestampSchema,
|
|
6976
|
-
updatedAt: timestampSchema
|
|
7363
|
+
updatedAt: timestampSchema,
|
|
7364
|
+
createdByUserId: z8.string().nullable(),
|
|
7365
|
+
updatedByUserId: z8.string().nullable()
|
|
6977
7366
|
});
|
|
6978
|
-
var
|
|
6979
|
-
|
|
7367
|
+
var createCollectionBodySchema = z8.object({
|
|
7368
|
+
name: z8.string().trim().min(1)
|
|
6980
7369
|
});
|
|
6981
|
-
var
|
|
6982
|
-
|
|
7370
|
+
var updateCollectionBodySchema = z8.object({
|
|
7371
|
+
name: z8.string().trim().min(1),
|
|
7372
|
+
variables: z8.array(variableSchema),
|
|
7373
|
+
headers: z8.array(keyValueSchema),
|
|
7374
|
+
preRequestScript: z8.string(),
|
|
7375
|
+
postRequestScript: z8.string(),
|
|
7376
|
+
auth: authConfigSchema
|
|
6983
7377
|
});
|
|
6984
|
-
var
|
|
6985
|
-
name: z8.string().trim().min(1)
|
|
6986
|
-
role: userRoleSchema.optional(),
|
|
6987
|
-
collectionAccess: z8.array(z8.string()).optional(),
|
|
6988
|
-
environmentAccess: z8.array(z8.string()).optional(),
|
|
6989
|
-
snippetAccess: z8.array(z8.string()).optional(),
|
|
6990
|
-
llmAccess: z8.boolean().optional(),
|
|
6991
|
-
llmModels: z8.array(z8.string()).optional(),
|
|
6992
|
-
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
7378
|
+
var createEnvironmentBodySchema = z8.object({
|
|
7379
|
+
name: z8.string().trim().min(1)
|
|
6993
7380
|
});
|
|
6994
|
-
var
|
|
7381
|
+
var updateEnvironmentBodySchema = z8.object({
|
|
6995
7382
|
name: z8.string().trim().min(1),
|
|
6996
|
-
|
|
6997
|
-
collectionAccess: z8.array(z8.string()).optional(),
|
|
6998
|
-
environmentAccess: z8.array(z8.string()).optional(),
|
|
6999
|
-
snippetAccess: z8.array(z8.string()).optional(),
|
|
7000
|
-
llmAccess: z8.boolean().optional(),
|
|
7001
|
-
llmModels: z8.array(z8.string()).optional(),
|
|
7002
|
-
llmMonthlyTokenLimit: z8.number().int().nonnegative().nullable().optional()
|
|
7383
|
+
variables: z8.array(variableSchema)
|
|
7003
7384
|
});
|
|
7004
|
-
var
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
tokenPrefix: z8.string(),
|
|
7009
|
-
createdAt: timestampSchema,
|
|
7010
|
-
lastUsedAt: timestampSchema.nullable(),
|
|
7011
|
-
revokedAt: timestampSchema.nullable()
|
|
7385
|
+
var createSnippetBodySchema = z8.object({
|
|
7386
|
+
name: z8.string().trim().min(1),
|
|
7387
|
+
code: z8.string().default(""),
|
|
7388
|
+
scope: snippetScopeSchema.default("any")
|
|
7012
7389
|
});
|
|
7013
|
-
var
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7390
|
+
var updateSnippetBodySchema = z8.object({
|
|
7391
|
+
name: z8.string().trim().min(1),
|
|
7392
|
+
code: z8.string(),
|
|
7393
|
+
scope: snippetScopeSchema
|
|
7017
7394
|
});
|
|
7018
|
-
var
|
|
7395
|
+
var createFolderBodySchema = z8.object({
|
|
7019
7396
|
name: z8.string().trim().min(1)
|
|
7020
7397
|
});
|
|
7021
|
-
var
|
|
7022
|
-
|
|
7023
|
-
|
|
7398
|
+
var renameFolderBodySchema = z8.object({
|
|
7399
|
+
name: z8.string().trim().min(1)
|
|
7400
|
+
});
|
|
7401
|
+
var reorderFoldersBodySchema = z8.object({
|
|
7402
|
+
orderedFolderIds: z8.array(z8.string().trim().min(1))
|
|
7403
|
+
});
|
|
7404
|
+
var saveRequestBodySchema = z8.object({
|
|
7405
|
+
name: z8.string().trim().min(1),
|
|
7406
|
+
method: httpMethodSchema,
|
|
7407
|
+
url: z8.string(),
|
|
7408
|
+
headers: z8.array(keyValueSchema),
|
|
7409
|
+
params: z8.array(keyValueSchema),
|
|
7410
|
+
auth: authConfigSchema,
|
|
7411
|
+
body: z8.string(),
|
|
7412
|
+
bodyType: bodyTypeSchema,
|
|
7413
|
+
preRequestScript: z8.string(),
|
|
7414
|
+
postRequestScript: z8.string(),
|
|
7415
|
+
comment: z8.string(),
|
|
7416
|
+
folderId: z8.string().nullable().optional()
|
|
7417
|
+
});
|
|
7418
|
+
var updateSaveRequestBodySchema = saveRequestBodySchema.extend({
|
|
7419
|
+
collectionId: z8.string().trim().min(1)
|
|
7024
7420
|
});
|
|
7025
|
-
var
|
|
7026
|
-
|
|
7421
|
+
var reorderRequestsBodySchema = z8.object({
|
|
7422
|
+
folderId: z8.string().nullable(),
|
|
7423
|
+
orderedRequestIds: z8.array(z8.string().trim().min(1))
|
|
7027
7424
|
});
|
|
7028
|
-
var
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
error: z8.string().optional()
|
|
7425
|
+
var moveRequestBodySchema = z8.object({
|
|
7426
|
+
folderId: z8.string().nullable(),
|
|
7427
|
+
index: z8.number().int().min(0)
|
|
7032
7428
|
});
|
|
7033
|
-
var
|
|
7034
|
-
|
|
7035
|
-
fatalError: z8.string().optional()
|
|
7429
|
+
var listCollectionsResponseSchema = z8.object({
|
|
7430
|
+
collections: z8.array(collectionRecordSchema)
|
|
7036
7431
|
});
|
|
7037
|
-
|
|
7432
|
+
var listEnvironmentsResponseSchema = z8.object({
|
|
7433
|
+
environments: z8.array(environmentRecordSchema)
|
|
7434
|
+
});
|
|
7435
|
+
var listSnippetsResponseSchema = z8.object({
|
|
7436
|
+
snippets: z8.array(snippetRecordSchema)
|
|
7437
|
+
});
|
|
7438
|
+
var listFoldersResponseSchema = z8.object({
|
|
7439
|
+
folders: z8.array(folderRecordSchema)
|
|
7440
|
+
});
|
|
7441
|
+
var listRequestsResponseSchema = z8.object({
|
|
7442
|
+
requests: z8.array(savedRequestRecordSchema)
|
|
7443
|
+
});
|
|
7444
|
+
var emptyResponseSchema = z8.null();
|
|
7445
|
+
function serializeCollection(record) {
|
|
7038
7446
|
return {
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
collectionAccess: user.collectionAccess,
|
|
7043
|
-
environmentAccess: user.environmentAccess,
|
|
7044
|
-
snippetAccess: user.snippetAccess,
|
|
7045
|
-
llmAccess: user.llmAccess,
|
|
7046
|
-
llmModels: user.llmModels,
|
|
7047
|
-
llmMonthlyTokenLimit: user.llmMonthlyTokenLimit,
|
|
7048
|
-
createdAt: user.createdAt.toISOString(),
|
|
7049
|
-
updatedAt: user.updatedAt.toISOString()
|
|
7447
|
+
...record,
|
|
7448
|
+
createdAt: record.createdAt.toISOString(),
|
|
7449
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7050
7450
|
};
|
|
7051
7451
|
}
|
|
7052
|
-
function
|
|
7452
|
+
function serializeEnvironment(record) {
|
|
7053
7453
|
return {
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
tokenPrefix: token.tokenPrefix,
|
|
7058
|
-
createdAt: token.createdAt.toISOString(),
|
|
7059
|
-
lastUsedAt: token.lastUsedAt ? token.lastUsedAt.toISOString() : null,
|
|
7060
|
-
revokedAt: token.revokedAt ? token.revokedAt.toISOString() : null
|
|
7454
|
+
...record,
|
|
7455
|
+
createdAt: record.createdAt.toISOString(),
|
|
7456
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7061
7457
|
};
|
|
7062
7458
|
}
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7459
|
+
function serializeSnippet(record) {
|
|
7460
|
+
return {
|
|
7461
|
+
...record,
|
|
7462
|
+
createdAt: record.createdAt.toISOString(),
|
|
7463
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7464
|
+
};
|
|
7465
|
+
}
|
|
7466
|
+
function serializeFolder(record) {
|
|
7467
|
+
return {
|
|
7468
|
+
...record,
|
|
7469
|
+
createdAt: record.createdAt.toISOString(),
|
|
7470
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7471
|
+
};
|
|
7472
|
+
}
|
|
7473
|
+
function serializeSavedRequest(record) {
|
|
7474
|
+
return {
|
|
7475
|
+
...record,
|
|
7476
|
+
createdAt: record.createdAt.toISOString(),
|
|
7477
|
+
updatedAt: record.updatedAt.toISOString()
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7480
|
+
var runResultRecordSchema = z8.object({
|
|
7481
|
+
id: z8.string(),
|
|
7482
|
+
kind: z8.enum(["collection-run-results", "request-run-results"]),
|
|
7483
|
+
label: z8.string(),
|
|
7484
|
+
collectionName: z8.string().nullable(),
|
|
7485
|
+
requestName: z8.string().nullable(),
|
|
7486
|
+
summary: z8.object({
|
|
7487
|
+
passed: z8.number().int().nonnegative(),
|
|
7488
|
+
failed: z8.number().int().nonnegative(),
|
|
7489
|
+
skipped: z8.number().int().nonnegative()
|
|
7490
|
+
}),
|
|
7491
|
+
createdAt: timestampSchema,
|
|
7492
|
+
createdByUserId: z8.string().nullable()
|
|
7493
|
+
});
|
|
7494
|
+
var runResultDetailSchema = runResultRecordSchema.extend({
|
|
7495
|
+
payload: z8.record(z8.string(), z8.unknown())
|
|
7496
|
+
});
|
|
7497
|
+
var createRunResultBodySchema = z8.object({
|
|
7498
|
+
label: z8.string().trim().min(1).optional(),
|
|
7499
|
+
payload: z8.record(z8.string(), z8.unknown())
|
|
7089
7500
|
});
|
|
7090
|
-
var
|
|
7091
|
-
|
|
7501
|
+
var listRunResultsResponseSchema = z8.object({
|
|
7502
|
+
runResults: z8.array(runResultRecordSchema)
|
|
7503
|
+
});
|
|
7504
|
+
function serializeRunResult(record) {
|
|
7505
|
+
return {
|
|
7506
|
+
id: record.id,
|
|
7507
|
+
kind: record.kind,
|
|
7508
|
+
label: record.label,
|
|
7509
|
+
collectionName: record.collectionName,
|
|
7510
|
+
requestName: record.requestName,
|
|
7511
|
+
summary: record.summary,
|
|
7512
|
+
createdAt: record.createdAt.toISOString(),
|
|
7513
|
+
createdByUserId: record.createdByUserId
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
function serializeRunResultDetail(record) {
|
|
7517
|
+
return {
|
|
7518
|
+
...serializeRunResult(record),
|
|
7519
|
+
payload: record.payload
|
|
7520
|
+
};
|
|
7521
|
+
}
|
|
7522
|
+
|
|
7523
|
+
// src/server/routes/schemas/admin.ts
|
|
7524
|
+
var adminResourceOptionSchema = z9.object({
|
|
7092
7525
|
id: z9.string(),
|
|
7093
7526
|
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
7527
|
deletionLocked: z9.boolean()
|
|
7102
7528
|
});
|
|
7103
|
-
var
|
|
7529
|
+
var adminEntityConfigSchema = z9.object({
|
|
7104
7530
|
id: z9.string(),
|
|
7105
|
-
collectionId: z9.string(),
|
|
7106
7531
|
name: z9.string(),
|
|
7107
|
-
|
|
7108
|
-
createdAt: timestampSchema,
|
|
7109
|
-
updatedAt: timestampSchema,
|
|
7110
|
-
createdByUserId: z9.string().nullable(),
|
|
7111
|
-
updatedByUserId: z9.string().nullable()
|
|
7532
|
+
deletionLocked: z9.boolean()
|
|
7112
7533
|
});
|
|
7113
|
-
var
|
|
7114
|
-
|
|
7115
|
-
collectionId: z9.string(),
|
|
7116
|
-
name: z9.string(),
|
|
7117
|
-
method: httpMethodSchema,
|
|
7118
|
-
url: z9.string(),
|
|
7119
|
-
headers: z9.array(keyValueSchema),
|
|
7120
|
-
params: z9.array(keyValueSchema),
|
|
7121
|
-
auth: authConfigSchema,
|
|
7122
|
-
body: z9.string(),
|
|
7123
|
-
bodyType: bodyTypeSchema,
|
|
7124
|
-
preRequestScript: z9.string(),
|
|
7125
|
-
postRequestScript: z9.string(),
|
|
7126
|
-
comment: z9.string(),
|
|
7127
|
-
folderId: z9.string().nullable(),
|
|
7128
|
-
sortOrder: z9.number().int(),
|
|
7129
|
-
createdAt: timestampSchema,
|
|
7130
|
-
updatedAt: timestampSchema,
|
|
7131
|
-
createdByUserId: z9.string().nullable(),
|
|
7132
|
-
updatedByUserId: z9.string().nullable()
|
|
7534
|
+
var updateAdminCollectionBodySchema = z9.object({
|
|
7535
|
+
deletionLocked: z9.boolean()
|
|
7133
7536
|
});
|
|
7134
|
-
var
|
|
7135
|
-
|
|
7537
|
+
var updateAdminEnvironmentBodySchema = z9.object({
|
|
7538
|
+
deletionLocked: z9.boolean()
|
|
7136
7539
|
});
|
|
7137
|
-
var
|
|
7138
|
-
name: z9.string().trim().min(1),
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7540
|
+
var updateAdminSnippetBodySchema = z9.object({
|
|
7541
|
+
name: z9.string().trim().min(1).optional(),
|
|
7542
|
+
code: z9.string().optional(),
|
|
7543
|
+
scope: snippetScopeSchema.optional(),
|
|
7544
|
+
deletionLocked: z9.boolean().optional()
|
|
7545
|
+
}).superRefine((body, ctx) => {
|
|
7546
|
+
const hasContentFields = body.name !== void 0 || body.code !== void 0 || body.scope !== void 0;
|
|
7547
|
+
const hasFullContent = body.name !== void 0 && body.code !== void 0 && body.scope !== void 0;
|
|
7548
|
+
if (hasContentFields && !hasFullContent) {
|
|
7549
|
+
ctx.addIssue({
|
|
7550
|
+
code: "custom",
|
|
7551
|
+
message: "name, code, and scope must be provided together"
|
|
7552
|
+
});
|
|
7553
|
+
}
|
|
7554
|
+
if (!hasContentFields && body.deletionLocked === void 0) {
|
|
7555
|
+
ctx.addIssue({
|
|
7556
|
+
code: "custom",
|
|
7557
|
+
message: "Provide snippet content (name, code, scope) and/or deletionLocked"
|
|
7558
|
+
});
|
|
7559
|
+
}
|
|
7144
7560
|
});
|
|
7145
|
-
var
|
|
7146
|
-
|
|
7561
|
+
var createAdminSnippetBodySchema = createSnippetBodySchema;
|
|
7562
|
+
var listAdminCollectionsResponseSchema = z9.object({
|
|
7563
|
+
collections: z9.array(adminResourceOptionSchema)
|
|
7147
7564
|
});
|
|
7148
|
-
var
|
|
7149
|
-
|
|
7150
|
-
variables: z9.array(variableSchema)
|
|
7565
|
+
var listAdminEnvironmentsResponseSchema = z9.object({
|
|
7566
|
+
environments: z9.array(adminResourceOptionSchema)
|
|
7151
7567
|
});
|
|
7152
|
-
var
|
|
7153
|
-
|
|
7154
|
-
code: z9.string().default(""),
|
|
7155
|
-
scope: snippetScopeSchema.default("any")
|
|
7568
|
+
var listAdminSnippetsResponseSchema = z9.object({
|
|
7569
|
+
snippets: z9.array(snippetRecordSchema)
|
|
7156
7570
|
});
|
|
7157
|
-
var
|
|
7158
|
-
|
|
7159
|
-
code: z9.string(),
|
|
7160
|
-
scope: snippetScopeSchema
|
|
7571
|
+
var listAdminRunResultsResponseSchema = z9.object({
|
|
7572
|
+
runResults: z9.array(runResultRecordSchema)
|
|
7161
7573
|
});
|
|
7162
|
-
var
|
|
7163
|
-
|
|
7574
|
+
var adminSnippetRecordSchema = snippetRecordSchema;
|
|
7575
|
+
var listAdminLlmModelsResponseSchema = listLlmModelsResponseSchema;
|
|
7576
|
+
var hubUserRecordSchema = z9.object({
|
|
7577
|
+
id: z9.string(),
|
|
7578
|
+
name: z9.string(),
|
|
7579
|
+
role: userRoleSchema,
|
|
7580
|
+
collectionAccess: z9.array(z9.string()),
|
|
7581
|
+
environmentAccess: z9.array(z9.string()),
|
|
7582
|
+
snippetAccess: z9.array(z9.string()),
|
|
7583
|
+
llmAccess: z9.boolean(),
|
|
7584
|
+
llmModels: z9.array(z9.string()),
|
|
7585
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable(),
|
|
7586
|
+
createdAt: timestampSchema,
|
|
7587
|
+
updatedAt: timestampSchema
|
|
7164
7588
|
});
|
|
7165
|
-
var
|
|
7166
|
-
|
|
7589
|
+
var adminUserListEntrySchema = hubUserRecordSchema.extend({
|
|
7590
|
+
warnings: z9.array(z9.string())
|
|
7167
7591
|
});
|
|
7168
|
-
var
|
|
7169
|
-
|
|
7592
|
+
var listAdminUsersResponseSchema = z9.object({
|
|
7593
|
+
users: z9.array(adminUserListEntrySchema)
|
|
7170
7594
|
});
|
|
7171
|
-
var
|
|
7172
|
-
name: z9.string().trim().min(1),
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
preRequestScript: z9.string(),
|
|
7181
|
-
postRequestScript: z9.string(),
|
|
7182
|
-
comment: z9.string(),
|
|
7183
|
-
folderId: z9.string().nullable().optional()
|
|
7595
|
+
var updateAdminUserBodySchema = z9.object({
|
|
7596
|
+
name: z9.string().trim().min(1).optional(),
|
|
7597
|
+
role: userRoleSchema.optional(),
|
|
7598
|
+
collectionAccess: z9.array(z9.string()).optional(),
|
|
7599
|
+
environmentAccess: z9.array(z9.string()).optional(),
|
|
7600
|
+
snippetAccess: z9.array(z9.string()).optional(),
|
|
7601
|
+
llmAccess: z9.boolean().optional(),
|
|
7602
|
+
llmModels: z9.array(z9.string()).optional(),
|
|
7603
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable().optional()
|
|
7184
7604
|
});
|
|
7185
|
-
var
|
|
7186
|
-
|
|
7605
|
+
var createAdminUserBodySchema = z9.object({
|
|
7606
|
+
name: z9.string().trim().min(1),
|
|
7607
|
+
role: userRoleSchema,
|
|
7608
|
+
collectionAccess: z9.array(z9.string()).optional(),
|
|
7609
|
+
environmentAccess: z9.array(z9.string()).optional(),
|
|
7610
|
+
snippetAccess: z9.array(z9.string()).optional(),
|
|
7611
|
+
llmAccess: z9.boolean().optional(),
|
|
7612
|
+
llmModels: z9.array(z9.string()).optional(),
|
|
7613
|
+
llmMonthlyTokenLimit: z9.number().int().nonnegative().nullable().optional()
|
|
7187
7614
|
});
|
|
7188
|
-
var
|
|
7189
|
-
|
|
7190
|
-
|
|
7615
|
+
var hubApiTokenRecordSchema = z9.object({
|
|
7616
|
+
id: z9.string(),
|
|
7617
|
+
userId: z9.string(),
|
|
7618
|
+
name: z9.string(),
|
|
7619
|
+
tokenPrefix: z9.string(),
|
|
7620
|
+
createdAt: timestampSchema,
|
|
7621
|
+
lastUsedAt: timestampSchema.nullable(),
|
|
7622
|
+
revokedAt: timestampSchema.nullable()
|
|
7191
7623
|
});
|
|
7192
|
-
var
|
|
7193
|
-
|
|
7194
|
-
|
|
7624
|
+
var createAdminUserResponseSchema = z9.object({
|
|
7625
|
+
user: hubUserRecordSchema,
|
|
7626
|
+
token: hubApiTokenRecordSchema,
|
|
7627
|
+
secret: z9.string()
|
|
7195
7628
|
});
|
|
7196
|
-
var
|
|
7197
|
-
|
|
7629
|
+
var createAdminTokenBodySchema = z9.object({
|
|
7630
|
+
name: z9.string().trim().min(1)
|
|
7198
7631
|
});
|
|
7199
|
-
var
|
|
7200
|
-
|
|
7632
|
+
var createdApiTokenResponseSchema = z9.object({
|
|
7633
|
+
token: hubApiTokenRecordSchema,
|
|
7634
|
+
secret: z9.string()
|
|
7201
7635
|
});
|
|
7202
|
-
var
|
|
7203
|
-
|
|
7636
|
+
var listAdminTokensResponseSchema = z9.object({
|
|
7637
|
+
tokens: z9.array(hubApiTokenRecordSchema)
|
|
7204
7638
|
});
|
|
7205
|
-
var
|
|
7206
|
-
|
|
7639
|
+
var reloadConfigSectionResultSchema = z9.object({
|
|
7640
|
+
section: z9.enum(["db", "redis", "llm", "plugins", "server"]),
|
|
7641
|
+
status: z9.enum(["reloaded", "unchanged", "failed", "restart-required"]),
|
|
7642
|
+
error: z9.string().optional()
|
|
7207
7643
|
});
|
|
7208
|
-
var
|
|
7209
|
-
|
|
7644
|
+
var reloadConfigResponseSchema = z9.object({
|
|
7645
|
+
sections: z9.array(reloadConfigSectionResultSchema),
|
|
7646
|
+
fatalError: z9.string().optional()
|
|
7210
7647
|
});
|
|
7211
|
-
|
|
7212
|
-
function serializeCollection(record) {
|
|
7213
|
-
return {
|
|
7214
|
-
...record,
|
|
7215
|
-
createdAt: record.createdAt.toISOString(),
|
|
7216
|
-
updatedAt: record.updatedAt.toISOString()
|
|
7217
|
-
};
|
|
7218
|
-
}
|
|
7219
|
-
function serializeEnvironment(record) {
|
|
7220
|
-
return {
|
|
7221
|
-
...record,
|
|
7222
|
-
createdAt: record.createdAt.toISOString(),
|
|
7223
|
-
updatedAt: record.updatedAt.toISOString()
|
|
7224
|
-
};
|
|
7225
|
-
}
|
|
7226
|
-
function serializeSnippet(record) {
|
|
7227
|
-
return {
|
|
7228
|
-
...record,
|
|
7229
|
-
createdAt: record.createdAt.toISOString(),
|
|
7230
|
-
updatedAt: record.updatedAt.toISOString()
|
|
7231
|
-
};
|
|
7232
|
-
}
|
|
7233
|
-
function serializeFolder(record) {
|
|
7648
|
+
function serializeHubUser(user) {
|
|
7234
7649
|
return {
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7650
|
+
id: user.id,
|
|
7651
|
+
name: user.name,
|
|
7652
|
+
role: user.role,
|
|
7653
|
+
collectionAccess: user.collectionAccess,
|
|
7654
|
+
environmentAccess: user.environmentAccess,
|
|
7655
|
+
snippetAccess: user.snippetAccess,
|
|
7656
|
+
llmAccess: user.llmAccess,
|
|
7657
|
+
llmModels: user.llmModels,
|
|
7658
|
+
llmMonthlyTokenLimit: user.llmMonthlyTokenLimit,
|
|
7659
|
+
createdAt: user.createdAt.toISOString(),
|
|
7660
|
+
updatedAt: user.updatedAt.toISOString()
|
|
7238
7661
|
};
|
|
7239
7662
|
}
|
|
7240
|
-
function
|
|
7663
|
+
function serializeApiToken(token) {
|
|
7241
7664
|
return {
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7665
|
+
id: token.id,
|
|
7666
|
+
userId: token.userId,
|
|
7667
|
+
name: token.name,
|
|
7668
|
+
tokenPrefix: token.tokenPrefix,
|
|
7669
|
+
createdAt: token.createdAt.toISOString(),
|
|
7670
|
+
lastUsedAt: token.lastUsedAt ? token.lastUsedAt.toISOString() : null,
|
|
7671
|
+
revokedAt: token.revokedAt ? token.revokedAt.toISOString() : null
|
|
7245
7672
|
};
|
|
7246
7673
|
}
|
|
7247
7674
|
|
|
@@ -7553,11 +7980,7 @@ async function registerAdminRoutes(app, options) {
|
|
|
7553
7980
|
}
|
|
7554
7981
|
const snippets = await db.listSnippets();
|
|
7555
7982
|
return reply.send({
|
|
7556
|
-
snippets: snippets.map((snippet) => (
|
|
7557
|
-
id: snippet.id,
|
|
7558
|
-
name: snippet.name,
|
|
7559
|
-
deletionLocked: snippet.deletionLocked
|
|
7560
|
-
}))
|
|
7983
|
+
snippets: snippets.map((snippet) => serializeSnippet(snippet))
|
|
7561
7984
|
});
|
|
7562
7985
|
} catch (error) {
|
|
7563
7986
|
if (handleDbError(reply, error)) {
|
|
@@ -7567,6 +7990,40 @@ async function registerAdminRoutes(app, options) {
|
|
|
7567
7990
|
}
|
|
7568
7991
|
}
|
|
7569
7992
|
});
|
|
7993
|
+
routes.route({
|
|
7994
|
+
method: "POST",
|
|
7995
|
+
url: "/admin/snippets",
|
|
7996
|
+
schema: {
|
|
7997
|
+
body: createAdminSnippetBodySchema,
|
|
7998
|
+
response: {
|
|
7999
|
+
201: adminSnippetRecordSchema,
|
|
8000
|
+
403: errorResponseSchema
|
|
8001
|
+
}
|
|
8002
|
+
},
|
|
8003
|
+
/**
|
|
8004
|
+
* Creates a snippet through the management API.
|
|
8005
|
+
*/
|
|
8006
|
+
handler: async (request, reply) => {
|
|
8007
|
+
try {
|
|
8008
|
+
const user = requireAuthenticatedUser(request);
|
|
8009
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
8010
|
+
return;
|
|
8011
|
+
}
|
|
8012
|
+
const snippet = await db.createSnippet(
|
|
8013
|
+
request.body.name,
|
|
8014
|
+
request.body.code,
|
|
8015
|
+
request.body.scope,
|
|
8016
|
+
user.id
|
|
8017
|
+
);
|
|
8018
|
+
return reply.code(201).send(serializeSnippet(snippet));
|
|
8019
|
+
} catch (error) {
|
|
8020
|
+
if (handleDbError(reply, error)) {
|
|
8021
|
+
return;
|
|
8022
|
+
}
|
|
8023
|
+
throw error;
|
|
8024
|
+
}
|
|
8025
|
+
}
|
|
8026
|
+
});
|
|
7570
8027
|
routes.route({
|
|
7571
8028
|
method: "DELETE",
|
|
7572
8029
|
url: "/admin/collections/:id",
|
|
@@ -7672,6 +8129,71 @@ async function registerAdminRoutes(app, options) {
|
|
|
7672
8129
|
}
|
|
7673
8130
|
}
|
|
7674
8131
|
});
|
|
8132
|
+
routes.route({
|
|
8133
|
+
method: "GET",
|
|
8134
|
+
url: "/admin/run-results",
|
|
8135
|
+
schema: {
|
|
8136
|
+
response: {
|
|
8137
|
+
200: listAdminRunResultsResponseSchema,
|
|
8138
|
+
403: errorResponseSchema
|
|
8139
|
+
}
|
|
8140
|
+
},
|
|
8141
|
+
/**
|
|
8142
|
+
* Lists all run results for operator management.
|
|
8143
|
+
*/
|
|
8144
|
+
handler: async (request, reply) => {
|
|
8145
|
+
try {
|
|
8146
|
+
const user = requireAuthenticatedUser(request);
|
|
8147
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
8148
|
+
return;
|
|
8149
|
+
}
|
|
8150
|
+
const runResults = await db.listAllRunResults();
|
|
8151
|
+
return reply.send({
|
|
8152
|
+
runResults: runResults.map((record) => serializeRunResult(record))
|
|
8153
|
+
});
|
|
8154
|
+
} catch (error) {
|
|
8155
|
+
if (handleDbError(reply, error)) {
|
|
8156
|
+
return;
|
|
8157
|
+
}
|
|
8158
|
+
throw error;
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
});
|
|
8162
|
+
routes.route({
|
|
8163
|
+
method: "DELETE",
|
|
8164
|
+
url: "/admin/run-results/:id",
|
|
8165
|
+
schema: {
|
|
8166
|
+
params: idParamSchema,
|
|
8167
|
+
response: {
|
|
8168
|
+
204: emptyResponseSchema,
|
|
8169
|
+
403: errorResponseSchema,
|
|
8170
|
+
404: errorResponseSchema
|
|
8171
|
+
}
|
|
8172
|
+
},
|
|
8173
|
+
/**
|
|
8174
|
+
* Deletes a run result regardless of creator ownership.
|
|
8175
|
+
*/
|
|
8176
|
+
handler: async (request, reply) => {
|
|
8177
|
+
try {
|
|
8178
|
+
const user = requireAuthenticatedUser(request);
|
|
8179
|
+
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
8180
|
+
return;
|
|
8181
|
+
}
|
|
8182
|
+
const record = await db.findRunResultById(request.params.id);
|
|
8183
|
+
if (!record) {
|
|
8184
|
+
void reply.code(404).send({ error: "Run result not found" });
|
|
8185
|
+
return;
|
|
8186
|
+
}
|
|
8187
|
+
await db.deleteRunResult(request.params.id, user.id);
|
|
8188
|
+
return reply.code(204).send(null);
|
|
8189
|
+
} catch (error) {
|
|
8190
|
+
if (handleDbError(reply, error)) {
|
|
8191
|
+
return;
|
|
8192
|
+
}
|
|
8193
|
+
throw error;
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
});
|
|
7675
8197
|
routes.route({
|
|
7676
8198
|
method: "PUT",
|
|
7677
8199
|
url: "/admin/snippets/:id",
|
|
@@ -7679,13 +8201,13 @@ async function registerAdminRoutes(app, options) {
|
|
|
7679
8201
|
params: idParamSchema,
|
|
7680
8202
|
body: updateAdminSnippetBodySchema,
|
|
7681
8203
|
response: {
|
|
7682
|
-
200:
|
|
8204
|
+
200: adminSnippetRecordSchema,
|
|
7683
8205
|
403: errorResponseSchema,
|
|
7684
8206
|
404: errorResponseSchema
|
|
7685
8207
|
}
|
|
7686
8208
|
},
|
|
7687
8209
|
/**
|
|
7688
|
-
* Updates
|
|
8210
|
+
* Updates snippet content and/or admin configuration.
|
|
7689
8211
|
*/
|
|
7690
8212
|
handler: async (request, reply) => {
|
|
7691
8213
|
try {
|
|
@@ -7693,16 +8215,20 @@ async function registerAdminRoutes(app, options) {
|
|
|
7693
8215
|
if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
|
|
7694
8216
|
return;
|
|
7695
8217
|
}
|
|
7696
|
-
const
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
}
|
|
8218
|
+
const existing = await db.findSnippetById(request.params.id);
|
|
8219
|
+
if (!existing) {
|
|
8220
|
+
void reply.code(404).send({ error: "Snippet not found" });
|
|
8221
|
+
return;
|
|
8222
|
+
}
|
|
8223
|
+
let snippet = existing;
|
|
8224
|
+
const { name, code, scope, deletionLocked } = request.body;
|
|
8225
|
+
if (name !== void 0 && code !== void 0 && scope !== void 0) {
|
|
8226
|
+
snippet = await db.updateSnippet(request.params.id, name, code, scope, user.id);
|
|
8227
|
+
}
|
|
8228
|
+
if (deletionLocked !== void 0) {
|
|
8229
|
+
snippet = await db.setSnippetDeletionLocked(request.params.id, deletionLocked, user.id);
|
|
8230
|
+
}
|
|
8231
|
+
return reply.send(serializeSnippet(snippet));
|
|
7706
8232
|
} catch (error) {
|
|
7707
8233
|
if (handleDbError(reply, error)) {
|
|
7708
8234
|
return;
|
|
@@ -9050,6 +9576,140 @@ async function registerRequestRoutes(app, db) {
|
|
|
9050
9576
|
});
|
|
9051
9577
|
}
|
|
9052
9578
|
|
|
9579
|
+
// src/server/routes/runResults.ts
|
|
9580
|
+
async function registerRunResultRoutes(app, db) {
|
|
9581
|
+
const routes = app.withTypeProvider();
|
|
9582
|
+
routes.route({
|
|
9583
|
+
method: "GET",
|
|
9584
|
+
url: "/run-results",
|
|
9585
|
+
schema: {
|
|
9586
|
+
response: {
|
|
9587
|
+
200: listRunResultsResponseSchema
|
|
9588
|
+
}
|
|
9589
|
+
},
|
|
9590
|
+
/**
|
|
9591
|
+
* Lists run results saved by the authenticated user token.
|
|
9592
|
+
*/
|
|
9593
|
+
handler: async (request, reply) => {
|
|
9594
|
+
try {
|
|
9595
|
+
const user = requireAuthenticatedUser(request);
|
|
9596
|
+
if (denyUnlessAllowed(reply, canListRunResults(user))) {
|
|
9597
|
+
return;
|
|
9598
|
+
}
|
|
9599
|
+
const runResults = await db.listRunResultsForUser(user.id);
|
|
9600
|
+
return reply.send({
|
|
9601
|
+
runResults: runResults.map((record) => serializeRunResult(record))
|
|
9602
|
+
});
|
|
9603
|
+
} catch (error) {
|
|
9604
|
+
if (handleDbError(reply, error)) {
|
|
9605
|
+
return;
|
|
9606
|
+
}
|
|
9607
|
+
throw error;
|
|
9608
|
+
}
|
|
9609
|
+
}
|
|
9610
|
+
});
|
|
9611
|
+
routes.route({
|
|
9612
|
+
method: "POST",
|
|
9613
|
+
url: "/run-results",
|
|
9614
|
+
schema: {
|
|
9615
|
+
body: createRunResultBodySchema,
|
|
9616
|
+
response: {
|
|
9617
|
+
200: runResultDetailSchema,
|
|
9618
|
+
400: errorResponseSchema
|
|
9619
|
+
}
|
|
9620
|
+
},
|
|
9621
|
+
/**
|
|
9622
|
+
* Saves a standalone run result snapshot.
|
|
9623
|
+
*/
|
|
9624
|
+
handler: async (request, reply) => {
|
|
9625
|
+
try {
|
|
9626
|
+
const user = requireAuthenticatedUser(request);
|
|
9627
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user))) {
|
|
9628
|
+
return;
|
|
9629
|
+
}
|
|
9630
|
+
const record = await db.createRunResult(request.body, user.id);
|
|
9631
|
+
return reply.send(serializeRunResultDetail(record));
|
|
9632
|
+
} catch (error) {
|
|
9633
|
+
if (handleDbError(reply, error)) {
|
|
9634
|
+
return;
|
|
9635
|
+
}
|
|
9636
|
+
throw error;
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
});
|
|
9640
|
+
routes.route({
|
|
9641
|
+
method: "GET",
|
|
9642
|
+
url: "/run-results/:id",
|
|
9643
|
+
schema: {
|
|
9644
|
+
params: idParamSchema,
|
|
9645
|
+
response: {
|
|
9646
|
+
200: runResultDetailSchema,
|
|
9647
|
+
404: errorResponseSchema
|
|
9648
|
+
}
|
|
9649
|
+
},
|
|
9650
|
+
/**
|
|
9651
|
+
* Loads a run result snapshot by id for shared deep links.
|
|
9652
|
+
*/
|
|
9653
|
+
handler: async (request, reply) => {
|
|
9654
|
+
try {
|
|
9655
|
+
const user = requireAuthenticatedUser(request);
|
|
9656
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user))) {
|
|
9657
|
+
return;
|
|
9658
|
+
}
|
|
9659
|
+
const record = await db.findRunResultById(request.params.id);
|
|
9660
|
+
if (!record) {
|
|
9661
|
+
void reply.code(404).send({ error: "Run result not found" });
|
|
9662
|
+
return;
|
|
9663
|
+
}
|
|
9664
|
+
return reply.send(serializeRunResultDetail(record));
|
|
9665
|
+
} catch (error) {
|
|
9666
|
+
if (handleDbError(reply, error)) {
|
|
9667
|
+
return;
|
|
9668
|
+
}
|
|
9669
|
+
throw error;
|
|
9670
|
+
}
|
|
9671
|
+
}
|
|
9672
|
+
});
|
|
9673
|
+
routes.route({
|
|
9674
|
+
method: "DELETE",
|
|
9675
|
+
url: "/run-results/:id",
|
|
9676
|
+
schema: {
|
|
9677
|
+
params: idParamSchema,
|
|
9678
|
+
response: {
|
|
9679
|
+
204: emptyResponseSchema,
|
|
9680
|
+
403: errorResponseSchema,
|
|
9681
|
+
404: errorResponseSchema
|
|
9682
|
+
}
|
|
9683
|
+
},
|
|
9684
|
+
/**
|
|
9685
|
+
* Deletes a run result saved by the authenticated user when permitted.
|
|
9686
|
+
*/
|
|
9687
|
+
handler: async (request, reply) => {
|
|
9688
|
+
try {
|
|
9689
|
+
const user = requireAuthenticatedUser(request);
|
|
9690
|
+
if (denyUnlessAllowed(reply, canUseDataApi(user))) {
|
|
9691
|
+
return;
|
|
9692
|
+
}
|
|
9693
|
+
const record = await db.findRunResultById(request.params.id);
|
|
9694
|
+
if (!record) {
|
|
9695
|
+
void reply.code(404).send({ error: "Run result not found" });
|
|
9696
|
+
return;
|
|
9697
|
+
}
|
|
9698
|
+
if (denyUnlessAllowed(reply, canDeleteRunResult(user, record))) {
|
|
9699
|
+
return;
|
|
9700
|
+
}
|
|
9701
|
+
await db.deleteRunResult(request.params.id, user.id);
|
|
9702
|
+
return reply.code(204).send(null);
|
|
9703
|
+
} catch (error) {
|
|
9704
|
+
if (handleDbError(reply, error)) {
|
|
9705
|
+
return;
|
|
9706
|
+
}
|
|
9707
|
+
throw error;
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
});
|
|
9711
|
+
}
|
|
9712
|
+
|
|
9053
9713
|
// src/server/llm/client.ts
|
|
9054
9714
|
var OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
9055
9715
|
var CLAUDE_BASE_URL = "https://api.anthropic.com/v1";
|
|
@@ -9692,6 +10352,7 @@ async function registerProtectedRoutes(app, options) {
|
|
|
9692
10352
|
await registerSnippetRoutes(app, options.db);
|
|
9693
10353
|
await registerFolderRoutes(app, options.db);
|
|
9694
10354
|
await registerRequestRoutes(app, options.db);
|
|
10355
|
+
await registerRunResultRoutes(app, options.db);
|
|
9695
10356
|
await registerLlmRoutes(app, { db: options.db, getLlm: options.getLlm });
|
|
9696
10357
|
await registerPluginsRoutes(app, { getPlugins: options.getPlugins });
|
|
9697
10358
|
}
|