teamcopilot 0.3.4 → 0.3.5
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/.env.example +3 -0
- package/dist/frontend/assets/{cssMode-CB9iAlw8.js → cssMode-DUnSc-N1.js} +1 -1
- package/dist/frontend/assets/{freemarker2-DI2xjASY.js → freemarker2-C_BuPYAu.js} +1 -1
- package/dist/frontend/assets/{handlebars-CmcQby9J.js → handlebars-DEEie70Y.js} +1 -1
- package/dist/frontend/assets/{html-ClP1XQBQ.js → html-BFks4r5R.js} +1 -1
- package/dist/frontend/assets/{htmlMode-DIlZsDL4.js → htmlMode-D8q9uv51.js} +1 -1
- package/dist/frontend/assets/index-D1Hcz_bo.css +1 -0
- package/dist/frontend/assets/{index-NFAjAMOV.js → index-DSsnnTDn.js} +224 -219
- package/dist/frontend/assets/{javascript-CbZA1Ase.js → javascript-LbJP7XES.js} +1 -1
- package/dist/frontend/assets/{jsonMode-VYZo-ljl.js → jsonMode-CTDSTkzw.js} +1 -1
- package/dist/frontend/assets/{liquid-I2CXh5hp.js → liquid-DMVVOmiW.js} +1 -1
- package/dist/frontend/assets/{mdx-BrO6zg6G.js → mdx-CG2zKU-F.js} +1 -1
- package/dist/frontend/assets/{python-S8m0lqgl.js → python-DupB8Ozc.js} +1 -1
- package/dist/frontend/assets/{razor-CV1dlAuV.js → razor-DNRGk0B3.js} +1 -1
- package/dist/frontend/assets/{tsMode-mSUzzhBd.js → tsMode-cWXCeSel.js} +1 -1
- package/dist/frontend/assets/{typescript-BOhssVGT.js → typescript-D_Aw0wph.js} +1 -1
- package/dist/frontend/assets/{xml-DF5nbc8Y.js → xml-DPt8oeKw.js} +1 -1
- package/dist/frontend/assets/{yaml-CjFdxQq7.js → yaml-Ct0xMYlA.js} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/index.js +2 -0
- package/dist/utils/external-host.js +22 -0
- package/dist/utils/secrets.js +15 -0
- package/dist/utils/workflow-api-keys.js +88 -0
- package/dist/utils/workflow-interruption.js +2 -2
- package/dist/utils/workflow-runner.js +11 -2
- package/dist/utils/workflow.js +3 -0
- package/dist/workflow-api.js +185 -0
- package/dist/workflows/index.js +35 -0
- package/package.json +1 -1
- package/prisma/generated/client/edge.js +15 -4
- package/prisma/generated/client/index-browser.js +12 -1
- package/prisma/generated/client/index.d.ts +2002 -270
- package/prisma/generated/client/index.js +15 -4
- package/prisma/generated/client/package.json +1 -1
- package/prisma/generated/client/schema.prisma +28 -12
- package/prisma/generated/client/wasm.js +15 -4
- package/prisma/migrations/20260430033133_add_workflow_api_keys/migration.sql +46 -0
- package/prisma/migrations/20260430161041_remove_run_source_default/migration.sql +28 -0
- package/prisma/schema.prisma +18 -2
- package/dist/frontend/assets/index-bnKwfDfp.css +0 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertCanManageWorkflowApiKeys = assertCanManageWorkflowApiKeys;
|
|
7
|
+
exports.listWorkflowApiKeys = listWorkflowApiKeys;
|
|
8
|
+
exports.createWorkflowApiKey = createWorkflowApiKey;
|
|
9
|
+
exports.deleteWorkflowApiKey = deleteWorkflowApiKey;
|
|
10
|
+
const crypto_1 = require("crypto");
|
|
11
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
12
|
+
const resource_access_1 = require("./resource-access");
|
|
13
|
+
const workflow_approval_snapshot_1 = require("./workflow-approval-snapshot");
|
|
14
|
+
const workflow_1 = require("./workflow");
|
|
15
|
+
async function assertCanManageWorkflowApiKeys(slug, userId) {
|
|
16
|
+
await (0, workflow_1.readWorkflowManifestAndEnsurePermissions)(slug);
|
|
17
|
+
const approvalState = await (0, workflow_approval_snapshot_1.getWorkflowSnapshotApprovalState)(slug);
|
|
18
|
+
if (!approvalState.is_current_code_approved) {
|
|
19
|
+
throw {
|
|
20
|
+
status: 403,
|
|
21
|
+
message: "Workflow must be approved before managing API keys"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const access = await (0, resource_access_1.getResourceAccessSummary)("workflow", slug, userId);
|
|
25
|
+
if (!access.can_edit) {
|
|
26
|
+
throw {
|
|
27
|
+
status: 403,
|
|
28
|
+
message: "You do not have permission to manage API keys for this workflow"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function ensureWorkflowApiKey(slug, createdByUserId) {
|
|
33
|
+
const existing = await client_1.default.workflow_api_keys.findFirst({
|
|
34
|
+
where: { workflow_slug: slug },
|
|
35
|
+
orderBy: { created_at: "asc" }
|
|
36
|
+
});
|
|
37
|
+
if (existing) {
|
|
38
|
+
return existing;
|
|
39
|
+
}
|
|
40
|
+
return await client_1.default.workflow_api_keys.create({
|
|
41
|
+
data: {
|
|
42
|
+
workflow_slug: slug,
|
|
43
|
+
api_key: (0, crypto_1.randomUUID)(),
|
|
44
|
+
created_by_user_id: createdByUserId,
|
|
45
|
+
created_at: BigInt(Date.now()),
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function listWorkflowApiKeys(slug, createdByUserId) {
|
|
50
|
+
await ensureWorkflowApiKey(slug, createdByUserId);
|
|
51
|
+
return await client_1.default.workflow_api_keys.findMany({
|
|
52
|
+
where: { workflow_slug: slug },
|
|
53
|
+
orderBy: { created_at: "asc" }
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function createWorkflowApiKey(slug, createdByUserId) {
|
|
57
|
+
return await client_1.default.workflow_api_keys.create({
|
|
58
|
+
data: {
|
|
59
|
+
workflow_slug: slug,
|
|
60
|
+
api_key: (0, crypto_1.randomUUID)(),
|
|
61
|
+
created_by_user_id: createdByUserId,
|
|
62
|
+
created_at: BigInt(Date.now()),
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function deleteWorkflowApiKey(slug, keyId) {
|
|
67
|
+
const key = await client_1.default.workflow_api_keys.findUnique({
|
|
68
|
+
where: { id: keyId }
|
|
69
|
+
});
|
|
70
|
+
if (!key || key.workflow_slug !== slug) {
|
|
71
|
+
throw {
|
|
72
|
+
status: 404,
|
|
73
|
+
message: "Workflow API key not found"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const keyCount = await client_1.default.workflow_api_keys.count({
|
|
77
|
+
where: { workflow_slug: slug }
|
|
78
|
+
});
|
|
79
|
+
if (keyCount <= 1) {
|
|
80
|
+
throw {
|
|
81
|
+
status: 400,
|
|
82
|
+
message: "Each workflow must always have at least one API key"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
await client_1.default.workflow_api_keys.delete({
|
|
86
|
+
where: { id: keyId }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -8,8 +8,8 @@ exports.markWorkflowSessionAborted = markWorkflowSessionAborted;
|
|
|
8
8
|
const client_1 = __importDefault(require("../prisma/client"));
|
|
9
9
|
const opencode_client_1 = require("./opencode-client");
|
|
10
10
|
async function isWorkflowSessionInterrupted(sessionId, workspaceDir) {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
11
|
+
const usesDatabaseAbortMarker = sessionId.startsWith("manual-") || sessionId.startsWith("api-");
|
|
12
|
+
if (usesDatabaseAbortMarker) {
|
|
13
13
|
const aborted = await client_1.default.workflow_aborted_sessions.findUnique({
|
|
14
14
|
where: { session_id: sessionId }
|
|
15
15
|
});
|
|
@@ -382,9 +382,16 @@ async function startWorkflowRunViaBackend(options) {
|
|
|
382
382
|
if (!validation.valid) {
|
|
383
383
|
throw new Error(`Input validation failed: ${JSON.stringify(validation.errors)}`);
|
|
384
384
|
}
|
|
385
|
-
const
|
|
385
|
+
const secretResolutionMode = options.secretResolutionMode;
|
|
386
|
+
if (secretResolutionMode === "user" && !options.authUserId) {
|
|
387
|
+
throw new Error("authUserId is required for user secret resolution.");
|
|
388
|
+
}
|
|
389
|
+
const secretResolution = secretResolutionMode === "global"
|
|
390
|
+
? await (0, secrets_1.resolveGlobalSecrets)(requiredSecrets)
|
|
391
|
+
: await (0, secrets_1.resolveSecretsForUser)(options.authUserId, requiredSecrets);
|
|
386
392
|
if (secretResolution.missingKeys.length > 0) {
|
|
387
|
-
|
|
393
|
+
const secretLocation = secretResolutionMode === "global" ? "global secrets" : "your profile secrets";
|
|
394
|
+
throw new Error(`Missing required secrets: ${secretResolution.missingKeys.join(", ")}. Add these keys in ${secretLocation} before running this workflow.`);
|
|
388
395
|
}
|
|
389
396
|
const timeoutSeconds = parseTimeoutSeconds(workflowJson.runtime?.timeout_seconds);
|
|
390
397
|
if (!timeoutSeconds) {
|
|
@@ -400,6 +407,8 @@ async function startWorkflowRunViaBackend(options) {
|
|
|
400
407
|
args: JSON.stringify(options.inputs),
|
|
401
408
|
session_id: options.sessionId,
|
|
402
409
|
message_id: options.messageId,
|
|
410
|
+
run_source: options.runSource,
|
|
411
|
+
workflow_api_key_id: options.workflowApiKeyId ?? null,
|
|
403
412
|
}
|
|
404
413
|
});
|
|
405
414
|
const workflowRunsDir = path.join(options.workspaceDir, "workflow-runs");
|
package/dist/utils/workflow.js
CHANGED
|
@@ -45,6 +45,9 @@ async function deleteWorkflow(slug) {
|
|
|
45
45
|
await client_1.default.workflow_runs.deleteMany({
|
|
46
46
|
where: { workflow_slug: slug }
|
|
47
47
|
});
|
|
48
|
+
await client_1.default.workflow_api_keys.deleteMany({
|
|
49
|
+
where: { workflow_slug: slug }
|
|
50
|
+
});
|
|
48
51
|
await client_1.default.resource_metadata.deleteMany({
|
|
49
52
|
where: {
|
|
50
53
|
resource_kind: "workflow",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const client_1 = __importDefault(require("./prisma/client"));
|
|
11
|
+
const workflow_1 = require("./utils/workflow");
|
|
12
|
+
const workflow_approval_snapshot_1 = require("./utils/workflow-approval-snapshot");
|
|
13
|
+
const workspace_sync_1 = require("./utils/workspace-sync");
|
|
14
|
+
const workflow_runner_1 = require("./utils/workflow-runner");
|
|
15
|
+
const workflow_interruption_1 = require("./utils/workflow-interruption");
|
|
16
|
+
function sanitizeFilenamePart(value) {
|
|
17
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
18
|
+
}
|
|
19
|
+
function isPathInside(childPath, parentPath) {
|
|
20
|
+
const parent = path_1.default.resolve(parentPath) + path_1.default.sep;
|
|
21
|
+
const child = path_1.default.resolve(childPath) + path_1.default.sep;
|
|
22
|
+
return child.startsWith(parent);
|
|
23
|
+
}
|
|
24
|
+
function workflowApiHandler(handler) {
|
|
25
|
+
return async (req, res, next) => {
|
|
26
|
+
try {
|
|
27
|
+
const authHeader = req.headers.authorization;
|
|
28
|
+
if (!authHeader) {
|
|
29
|
+
throw {
|
|
30
|
+
status: 401,
|
|
31
|
+
message: "Missing authorization header. Please pass the workflow API key as an authorization bearer token."
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const rawToken = authHeader.split(" ")[1];
|
|
35
|
+
if (!rawToken) {
|
|
36
|
+
throw {
|
|
37
|
+
status: 401,
|
|
38
|
+
message: "Missing authorization bearer token"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const key = await client_1.default.workflow_api_keys.findUnique({
|
|
42
|
+
where: { api_key: rawToken },
|
|
43
|
+
select: { id: true, workflow_slug: true, api_key: true }
|
|
44
|
+
});
|
|
45
|
+
if (!key) {
|
|
46
|
+
throw {
|
|
47
|
+
status: 401,
|
|
48
|
+
message: "Invalid workflow API key"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const apiReq = req;
|
|
52
|
+
apiReq.workflowApiKey = key;
|
|
53
|
+
await handler(apiReq, res);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
next(err);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function assertWorkflowCanRunViaApi(slug) {
|
|
61
|
+
await (0, workflow_1.readWorkflowManifestAndEnsurePermissions)(slug);
|
|
62
|
+
const approvalState = await (0, workflow_approval_snapshot_1.getWorkflowSnapshotApprovalState)(slug);
|
|
63
|
+
if (!approvalState.is_current_code_approved) {
|
|
64
|
+
throw {
|
|
65
|
+
status: 403,
|
|
66
|
+
message: "Workflow is not approved for the current code version"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function assertApiKeyCanAccessRun(req, runHandle) {
|
|
71
|
+
const run = await client_1.default.workflow_runs.findUnique({
|
|
72
|
+
where: { id: runHandle }
|
|
73
|
+
});
|
|
74
|
+
if (!run) {
|
|
75
|
+
throw {
|
|
76
|
+
status: 404,
|
|
77
|
+
message: "Workflow run not found"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (req.workflowApiKey.workflow_slug !== run.workflow_slug) {
|
|
81
|
+
throw {
|
|
82
|
+
status: 403,
|
|
83
|
+
message: "Workflow API key does not have access to this run"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return run;
|
|
87
|
+
}
|
|
88
|
+
async function readWorkflowRunLogs(run) {
|
|
89
|
+
if (!run.session_id || !run.message_id) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const workspaceDir = (0, workspace_sync_1.getWorkspaceDirFromEnv)();
|
|
93
|
+
const workflowRunsDir = path_1.default.join(workspaceDir, "workflow-runs");
|
|
94
|
+
const logPath = path_1.default.join(workflowRunsDir, `${sanitizeFilenamePart(run.session_id)}-${sanitizeFilenamePart(run.message_id)}.txt`);
|
|
95
|
+
if (!isPathInside(logPath, workflowRunsDir)) {
|
|
96
|
+
throw {
|
|
97
|
+
status: 400,
|
|
98
|
+
message: "Invalid log path"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return await promises_1.default.readFile(logPath, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function parseRunInputs(args) {
|
|
109
|
+
if (args === null || args === "") {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
return JSON.parse(args);
|
|
113
|
+
}
|
|
114
|
+
const workflowApiRouter = express_1.default.Router();
|
|
115
|
+
workflowApiRouter.post("/runs", workflowApiHandler(async (req, res) => {
|
|
116
|
+
const body = req.body;
|
|
117
|
+
if (typeof body.workflow_slug !== "string" || body.workflow_slug.trim().length === 0) {
|
|
118
|
+
throw {
|
|
119
|
+
status: 400,
|
|
120
|
+
message: "workflow_slug is required"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (body.inputs !== undefined && (!body.inputs || typeof body.inputs !== "object" || Array.isArray(body.inputs))) {
|
|
124
|
+
throw {
|
|
125
|
+
status: 400,
|
|
126
|
+
message: "inputs must be an object"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const slug = body.workflow_slug;
|
|
130
|
+
if (req.workflowApiKey.workflow_slug !== slug) {
|
|
131
|
+
throw {
|
|
132
|
+
status: 403,
|
|
133
|
+
message: "Workflow API key does not belong to this workflow"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
await assertWorkflowCanRunViaApi(slug);
|
|
137
|
+
const startedRun = await (0, workflow_runner_1.startWorkflowRunViaBackend)({
|
|
138
|
+
workspaceDir: (0, workspace_sync_1.getWorkspaceDirFromEnv)(),
|
|
139
|
+
slug,
|
|
140
|
+
inputs: (body.inputs ?? {}),
|
|
141
|
+
authUserId: null,
|
|
142
|
+
sessionId: `api-${req.workflowApiKey.id}-${(0, crypto_1.randomUUID)()}`,
|
|
143
|
+
messageId: `api-message-${(0, crypto_1.randomUUID)()}`,
|
|
144
|
+
callId: `api-call-${(0, crypto_1.randomUUID)()}`,
|
|
145
|
+
requirePermissionPrompt: false,
|
|
146
|
+
secretResolutionMode: "global",
|
|
147
|
+
runSource: "api",
|
|
148
|
+
workflowApiKeyId: req.workflowApiKey.id,
|
|
149
|
+
});
|
|
150
|
+
void startedRun.completion.catch(() => undefined);
|
|
151
|
+
res.json({
|
|
152
|
+
run_handle: startedRun.runId
|
|
153
|
+
});
|
|
154
|
+
}));
|
|
155
|
+
workflowApiRouter.get("/runs/:runHandle", workflowApiHandler(async (req, res) => {
|
|
156
|
+
const runHandle = req.params.runHandle;
|
|
157
|
+
const run = await assertApiKeyCanAccessRun(req, runHandle);
|
|
158
|
+
const logs = await readWorkflowRunLogs(run);
|
|
159
|
+
const inputs = parseRunInputs(run.args);
|
|
160
|
+
res.json({
|
|
161
|
+
run_handle: run.id,
|
|
162
|
+
workflow_slug: run.workflow_slug,
|
|
163
|
+
status: run.status,
|
|
164
|
+
logs,
|
|
165
|
+
error_message: run.error_message,
|
|
166
|
+
started_at: run.started_at,
|
|
167
|
+
completed_at: run.completed_at,
|
|
168
|
+
inputs,
|
|
169
|
+
});
|
|
170
|
+
}));
|
|
171
|
+
workflowApiRouter.post("/runs/:runHandle/stop", workflowApiHandler(async (req, res) => {
|
|
172
|
+
const runHandle = req.params.runHandle;
|
|
173
|
+
const run = await assertApiKeyCanAccessRun(req, runHandle);
|
|
174
|
+
if (run.status === "running") {
|
|
175
|
+
if (!run.session_id) {
|
|
176
|
+
throw {
|
|
177
|
+
status: 404,
|
|
178
|
+
message: "Workflow run session not found"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
await (0, workflow_interruption_1.markWorkflowSessionAborted)(run.session_id);
|
|
182
|
+
}
|
|
183
|
+
res.json({ success: true });
|
|
184
|
+
}));
|
|
185
|
+
exports.default = workflowApiRouter;
|
package/dist/workflows/index.js
CHANGED
|
@@ -25,6 +25,8 @@ const resource_file_routes_1 = require("../utils/resource-file-routes");
|
|
|
25
25
|
const resource_access_1 = require("../utils/resource-access");
|
|
26
26
|
const secrets_1 = require("../utils/secrets");
|
|
27
27
|
const secret_contract_validation_1 = require("../utils/secret-contract-validation");
|
|
28
|
+
const workflow_api_keys_1 = require("../utils/workflow-api-keys");
|
|
29
|
+
const external_host_1 = require("../utils/external-host");
|
|
28
30
|
const router = express_1.default.Router({ mergeParams: true });
|
|
29
31
|
const uploadTmpDir = path_1.default.join(os_1.default.tmpdir(), "teamcopilot-workflow-uploads");
|
|
30
32
|
fs_1.default.mkdirSync(uploadTmpDir, { recursive: true });
|
|
@@ -317,6 +319,8 @@ router.post('/:slug/manual-run', (0, index_1.apiHandler)(async (req, res) => {
|
|
|
317
319
|
messageId: manualMessageId,
|
|
318
320
|
callId: manualCallId,
|
|
319
321
|
requirePermissionPrompt: false,
|
|
322
|
+
runSource: "user",
|
|
323
|
+
secretResolutionMode: "user",
|
|
320
324
|
});
|
|
321
325
|
void startedRun.completion.catch(() => undefined);
|
|
322
326
|
res.json({
|
|
@@ -356,6 +360,8 @@ router.post('/execute', (0, index_1.apiHandler)(async (req, res) => {
|
|
|
356
360
|
messageId,
|
|
357
361
|
callId,
|
|
358
362
|
requirePermissionPrompt: true,
|
|
363
|
+
runSource: "user",
|
|
364
|
+
secretResolutionMode: "user",
|
|
359
365
|
});
|
|
360
366
|
const executionId = (0, crypto_1.randomUUID)();
|
|
361
367
|
const executionRecord = {
|
|
@@ -532,6 +538,35 @@ router.delete('/:slug', (0, index_1.apiHandler)(async (req, res) => {
|
|
|
532
538
|
await (0, workflow_1.deleteWorkflow)(slug);
|
|
533
539
|
res.json({ success: true });
|
|
534
540
|
}, true));
|
|
541
|
+
// GET /api/workflows/:slug/api-keys - List API keys for an approved workflow
|
|
542
|
+
router.get('/:slug/api-keys', (0, index_1.apiHandler)(async (req, res) => {
|
|
543
|
+
const slug = req.params.slug;
|
|
544
|
+
await (0, workflow_api_keys_1.assertCanManageWorkflowApiKeys)(slug, req.userId);
|
|
545
|
+
const apiKeys = await (0, workflow_api_keys_1.listWorkflowApiKeys)(slug, req.userId);
|
|
546
|
+
res.locals.skipResponseSanitization = true;
|
|
547
|
+
res.json({
|
|
548
|
+
api_base_url: (0, external_host_1.getWorkflowApiBaseUrl)(),
|
|
549
|
+
api_keys: apiKeys,
|
|
550
|
+
});
|
|
551
|
+
}, true));
|
|
552
|
+
// POST /api/workflows/:slug/api-keys - Add an API key for an approved workflow
|
|
553
|
+
router.post('/:slug/api-keys', (0, index_1.apiHandler)(async (req, res) => {
|
|
554
|
+
const slug = req.params.slug;
|
|
555
|
+
await (0, workflow_api_keys_1.assertCanManageWorkflowApiKeys)(slug, req.userId);
|
|
556
|
+
const apiKey = await (0, workflow_api_keys_1.createWorkflowApiKey)(slug, req.userId);
|
|
557
|
+
res.locals.skipResponseSanitization = true;
|
|
558
|
+
res.json({
|
|
559
|
+
api_key: apiKey,
|
|
560
|
+
});
|
|
561
|
+
}, true));
|
|
562
|
+
// DELETE /api/workflows/:slug/api-keys/:keyId - Remove an API key
|
|
563
|
+
router.delete('/:slug/api-keys/:keyId', (0, index_1.apiHandler)(async (req, res) => {
|
|
564
|
+
const slug = req.params.slug;
|
|
565
|
+
const keyId = req.params.keyId;
|
|
566
|
+
await (0, workflow_api_keys_1.assertCanManageWorkflowApiKeys)(slug, req.userId);
|
|
567
|
+
await (0, workflow_api_keys_1.deleteWorkflowApiKey)(slug, keyId);
|
|
568
|
+
res.json({ success: true });
|
|
569
|
+
}, true));
|
|
535
570
|
// GET /api/workflows/:slug/approval-diff - Preview current code diff vs approved snapshot
|
|
536
571
|
router.get('/:slug/approval-diff', (0, index_1.apiHandler)(async (req, res) => {
|
|
537
572
|
const slug = req.params.slug;
|