@stacknet/stacks 0.1.1 → 0.2.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/{billing-BaJlf_S8.d.cts → billing-eQZIWeNW.d.cts} +31 -1
- package/dist/{billing-BaJlf_S8.d.ts → billing-eQZIWeNW.d.ts} +31 -1
- package/dist/clients/index.cjs +4 -4
- package/dist/clients/index.d.cts +35 -1
- package/dist/clients/index.d.ts +35 -1
- package/dist/clients/index.js +4 -4
- package/dist/{index-DVzKiF_0.d.cts → index-B_dUFmAg.d.cts} +31 -6
- package/dist/{index-DVzKiF_0.d.ts → index-B_dUFmAg.d.ts} +31 -6
- package/dist/index.cjs +12 -16
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -16
- package/dist/proxy/index.cjs +2 -2
- package/dist/proxy/index.d.cts +1 -1
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.js +2 -2
- package/dist/streaming/index.cjs +8 -12
- package/dist/streaming/index.js +8 -12
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/clients/agents.ts +23 -6
- package/src/clients/billing.ts +40 -0
- package/src/managers/task-manager.ts +25 -3
- package/src/proxy/forwarder.ts +105 -16
- package/src/proxy/route-handlers.ts +273 -116
- package/src/streaming/sse.ts +65 -40
- package/src/types/agent.ts +2 -0
- package/src/types/billing.ts +34 -0
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Next.js API Route Handler Factories
|
|
3
3
|
*
|
|
4
|
-
* Create pre-configured route handlers for common patterns
|
|
4
|
+
* Create pre-configured route handlers for common patterns.
|
|
5
|
+
*
|
|
6
|
+
* SECURITY NOTES
|
|
7
|
+
* - Every path-position parameter is run through `validateId` before being
|
|
8
|
+
* interpolated into the upstream path. Without this, characters like `?`,
|
|
9
|
+
* `&`, `#`, or `/` in an id would smuggle extra query params or escape
|
|
10
|
+
* the intended namespace.
|
|
11
|
+
* - Every factory supports `extractAuth`; the forwarder receives an
|
|
12
|
+
* `Authorization` header so the upstream can attribute the request to
|
|
13
|
+
* the real caller instead of treating it as anonymous.
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
16
|
import { forwardRequest, forwardJSON, type ForwarderConfig } from './forwarder';
|
|
@@ -9,6 +18,11 @@ import { forwardRequest, forwardJSON, type ForwarderConfig } from './forwarder';
|
|
|
9
18
|
export interface RouteHandlerConfig extends ForwarderConfig {
|
|
10
19
|
requireAuth?: boolean;
|
|
11
20
|
enrichResponse?: (data: unknown) => Promise<unknown>;
|
|
21
|
+
/** Extract the caller's bearer token from the incoming Request and
|
|
22
|
+
* forward it upstream as `Authorization: Bearer <token>`. If unset,
|
|
23
|
+
* the handler passes through any inbound `Authorization` header
|
|
24
|
+
* verbatim. Without one of these the upstream sees no auth at all. */
|
|
25
|
+
extractAuth?: (request: Request) => string | null;
|
|
12
26
|
}
|
|
13
27
|
|
|
14
28
|
export type RouteHandler = (request: Request, context?: { params?: Record<string, string> }) => Promise<Response>;
|
|
@@ -20,6 +34,65 @@ export interface CRUDRouteHandlers {
|
|
|
20
34
|
DELETE?: RouteHandler;
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
/** Back-compat: `StackRouteHandlerConfig` used to be distinct; now every
|
|
38
|
+
* factory accepts `extractAuth`, so the two configs are identical. */
|
|
39
|
+
export type StackRouteHandlerConfig = RouteHandlerConfig;
|
|
40
|
+
|
|
41
|
+
// ----------------------------------------------------------------------------
|
|
42
|
+
// Shared helpers
|
|
43
|
+
// ----------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Validate that an id/param is a safe alphanumeric/dash/underscore/dot string.
|
|
46
|
+
* Blocks path traversal (`..`, `/`), query smuggling (`?`, `&`, `#`), and
|
|
47
|
+
* header injection (`\r`, `\n`) in one shot. */
|
|
48
|
+
function validateId(id: string | null | undefined, name: string): string {
|
|
49
|
+
if (!id || typeof id !== 'string') {
|
|
50
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
if (!/^[\w.\-]+$/.test(id)) {
|
|
53
|
+
throw new Error(`Invalid ${name}: contains disallowed characters`);
|
|
54
|
+
}
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Convert a thrown validation error into a 400 response so handlers don't
|
|
59
|
+
* have to pepper try/catch everywhere. */
|
|
60
|
+
function badRequest(err: unknown): Response {
|
|
61
|
+
const message = err instanceof Error ? err.message : 'Bad request';
|
|
62
|
+
return Response.json({ error: message }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function authHeaders(config: RouteHandlerConfig, request: Request): Record<string, string> {
|
|
66
|
+
const headers: Record<string, string> = {};
|
|
67
|
+
if (config.extractAuth) {
|
|
68
|
+
const token = config.extractAuth(request);
|
|
69
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
70
|
+
} else {
|
|
71
|
+
const auth = request.headers.get('Authorization');
|
|
72
|
+
if (auth) headers['Authorization'] = auth;
|
|
73
|
+
}
|
|
74
|
+
return headers;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Pull an id from the Next-style context first, then fall back to the
|
|
78
|
+
* last path segment. Runs through validateId either way. */
|
|
79
|
+
function idFromContext(
|
|
80
|
+
request: Request,
|
|
81
|
+
context: { params?: Record<string, string | undefined> } | undefined,
|
|
82
|
+
key: string,
|
|
83
|
+
segmentFromEnd = 1,
|
|
84
|
+
): string {
|
|
85
|
+
const fromCtx = context?.params?.[key];
|
|
86
|
+
if (fromCtx) return validateId(fromCtx, key);
|
|
87
|
+
const segs = new URL(request.url).pathname.split('/').filter(Boolean);
|
|
88
|
+
const candidate = segs[segs.length - segmentFromEnd];
|
|
89
|
+
return validateId(candidate, key);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Agent Route Handlers
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
23
96
|
/**
|
|
24
97
|
* Create agent route handlers
|
|
25
98
|
*/
|
|
@@ -35,12 +108,15 @@ export function createAgentRoutes(config: RouteHandlerConfig = {}): CRUDRouteHan
|
|
|
35
108
|
const path = userMid
|
|
36
109
|
? `/agents?creator_mid=${encodeURIComponent(userMid)}`
|
|
37
110
|
: visibility
|
|
38
|
-
? `/agents?visibility=${visibility}`
|
|
111
|
+
? `/agents?visibility=${encodeURIComponent(visibility)}`
|
|
39
112
|
: '/agents';
|
|
40
113
|
|
|
41
|
-
const { data, status } = await forwardJSON(
|
|
114
|
+
const { data, status } = await forwardJSON(
|
|
115
|
+
path,
|
|
116
|
+
{ headers: authHeaders(config, request) },
|
|
117
|
+
{ baseUrl },
|
|
118
|
+
);
|
|
42
119
|
|
|
43
|
-
// Optional response enrichment
|
|
44
120
|
let responseData = data;
|
|
45
121
|
if (config.enrichResponse) {
|
|
46
122
|
responseData = await config.enrichResponse(data);
|
|
@@ -53,7 +129,7 @@ export function createAgentRoutes(config: RouteHandlerConfig = {}): CRUDRouteHan
|
|
|
53
129
|
const body = await request.json();
|
|
54
130
|
const { data, status } = await forwardJSON(
|
|
55
131
|
'/agents',
|
|
56
|
-
{ method: 'POST', body },
|
|
132
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
57
133
|
{ baseUrl }
|
|
58
134
|
);
|
|
59
135
|
return Response.json(data, { status });
|
|
@@ -69,8 +145,13 @@ export function createAgentDetailRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
69
145
|
|
|
70
146
|
return {
|
|
71
147
|
GET: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
72
|
-
|
|
73
|
-
|
|
148
|
+
let id: string;
|
|
149
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
150
|
+
const { data, status } = await forwardJSON(
|
|
151
|
+
`/agents/${id}`,
|
|
152
|
+
{ headers: authHeaders(config, request) },
|
|
153
|
+
{ baseUrl },
|
|
154
|
+
);
|
|
74
155
|
|
|
75
156
|
let responseData = data;
|
|
76
157
|
if (config.enrichResponse) {
|
|
@@ -81,32 +162,35 @@ export function createAgentDetailRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
81
162
|
},
|
|
82
163
|
|
|
83
164
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
84
|
-
|
|
165
|
+
let id: string;
|
|
166
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
85
167
|
const body = await request.json();
|
|
86
168
|
const { data, status } = await forwardJSON(
|
|
87
169
|
`/agents/${id}`,
|
|
88
|
-
{ method: 'POST', body },
|
|
170
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
89
171
|
{ baseUrl }
|
|
90
172
|
);
|
|
91
173
|
return Response.json(data, { status });
|
|
92
174
|
},
|
|
93
175
|
|
|
94
176
|
PUT: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
95
|
-
|
|
177
|
+
let id: string;
|
|
178
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
96
179
|
const body = await request.json();
|
|
97
180
|
const { data, status } = await forwardJSON(
|
|
98
181
|
`/agents/${id}`,
|
|
99
|
-
{ method: 'PUT', body },
|
|
182
|
+
{ method: 'PUT', body, headers: authHeaders(config, request) },
|
|
100
183
|
{ baseUrl }
|
|
101
184
|
);
|
|
102
185
|
return Response.json(data, { status });
|
|
103
186
|
},
|
|
104
187
|
|
|
105
188
|
DELETE: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
106
|
-
|
|
189
|
+
let id: string;
|
|
190
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
107
191
|
const { data, status } = await forwardJSON(
|
|
108
192
|
`/agents/${id}`,
|
|
109
|
-
{ method: 'DELETE' },
|
|
193
|
+
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
110
194
|
{ baseUrl }
|
|
111
195
|
);
|
|
112
196
|
return Response.json(data, { status });
|
|
@@ -122,12 +206,13 @@ export function createAgentExecuteRoute(config: RouteHandlerConfig = {}): { POST
|
|
|
122
206
|
|
|
123
207
|
return {
|
|
124
208
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
125
|
-
|
|
209
|
+
let id: string;
|
|
210
|
+
try { id = idFromContext(request, context, 'id', 2); } catch (e) { return badRequest(e); }
|
|
126
211
|
const body = await request.json();
|
|
127
212
|
|
|
128
213
|
const response = await forwardRequest(
|
|
129
214
|
`/agents/${id}/execute`,
|
|
130
|
-
{ method: 'POST', body, stream: true },
|
|
215
|
+
{ method: 'POST', body, stream: true, headers: authHeaders(config, request) },
|
|
131
216
|
{ baseUrl }
|
|
132
217
|
);
|
|
133
218
|
|
|
@@ -162,10 +247,11 @@ export function createAgentToggleRoutes(config: RouteHandlerConfig = {}): {
|
|
|
162
247
|
return {
|
|
163
248
|
enable: {
|
|
164
249
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
165
|
-
|
|
250
|
+
let id: string;
|
|
251
|
+
try { id = idFromContext(request, context, 'id', 2); } catch (e) { return badRequest(e); }
|
|
166
252
|
const { data, status } = await forwardJSON(
|
|
167
253
|
`/agents/${id}/enable`,
|
|
168
|
-
{ method: 'POST' },
|
|
254
|
+
{ method: 'POST', headers: authHeaders(config, request) },
|
|
169
255
|
{ baseUrl }
|
|
170
256
|
);
|
|
171
257
|
return Response.json(data, { status });
|
|
@@ -173,10 +259,11 @@ export function createAgentToggleRoutes(config: RouteHandlerConfig = {}): {
|
|
|
173
259
|
},
|
|
174
260
|
disable: {
|
|
175
261
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
176
|
-
|
|
262
|
+
let id: string;
|
|
263
|
+
try { id = idFromContext(request, context, 'id', 2); } catch (e) { return badRequest(e); }
|
|
177
264
|
const { data, status } = await forwardJSON(
|
|
178
265
|
`/agents/${id}/disable`,
|
|
179
|
-
{ method: 'POST' },
|
|
266
|
+
{ method: 'POST', headers: authHeaders(config, request) },
|
|
180
267
|
{ baseUrl }
|
|
181
268
|
);
|
|
182
269
|
return Response.json(data, { status });
|
|
@@ -185,6 +272,10 @@ export function createAgentToggleRoutes(config: RouteHandlerConfig = {}): {
|
|
|
185
272
|
};
|
|
186
273
|
}
|
|
187
274
|
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Skill Route Handlers
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
188
279
|
/**
|
|
189
280
|
* Create skill route handlers
|
|
190
281
|
*/
|
|
@@ -201,7 +292,11 @@ export function createSkillRoutes(config: RouteHandlerConfig = {}): CRUDRouteHan
|
|
|
201
292
|
if (scope) path += `?scope=${encodeURIComponent(scope)}`;
|
|
202
293
|
else if (creatorMid) path += `?creator_mid=${encodeURIComponent(creatorMid)}`;
|
|
203
294
|
|
|
204
|
-
const { data, status } = await forwardJSON(
|
|
295
|
+
const { data, status } = await forwardJSON(
|
|
296
|
+
path,
|
|
297
|
+
{ headers: authHeaders(config, request) },
|
|
298
|
+
{ baseUrl },
|
|
299
|
+
);
|
|
205
300
|
return Response.json(data, { status });
|
|
206
301
|
},
|
|
207
302
|
|
|
@@ -209,7 +304,7 @@ export function createSkillRoutes(config: RouteHandlerConfig = {}): CRUDRouteHan
|
|
|
209
304
|
const body = await request.json();
|
|
210
305
|
const { data, status } = await forwardJSON(
|
|
211
306
|
'/skills',
|
|
212
|
-
{ method: 'POST', body },
|
|
307
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
213
308
|
{ baseUrl }
|
|
214
309
|
);
|
|
215
310
|
return Response.json(data, { status });
|
|
@@ -225,38 +320,46 @@ export function createSkillDetailRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
225
320
|
|
|
226
321
|
return {
|
|
227
322
|
GET: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
228
|
-
|
|
229
|
-
|
|
323
|
+
let id: string;
|
|
324
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
325
|
+
const { data, status } = await forwardJSON(
|
|
326
|
+
`/skills/${id}`,
|
|
327
|
+
{ headers: authHeaders(config, request) },
|
|
328
|
+
{ baseUrl },
|
|
329
|
+
);
|
|
230
330
|
return Response.json(data, { status });
|
|
231
331
|
},
|
|
232
332
|
|
|
233
333
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
234
|
-
|
|
334
|
+
let id: string;
|
|
335
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
235
336
|
const body = await request.json();
|
|
236
337
|
const { data, status } = await forwardJSON(
|
|
237
338
|
`/skills/${id}`,
|
|
238
|
-
{ method: 'POST', body },
|
|
339
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
239
340
|
{ baseUrl }
|
|
240
341
|
);
|
|
241
342
|
return Response.json(data, { status });
|
|
242
343
|
},
|
|
243
344
|
|
|
244
345
|
PUT: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
245
|
-
|
|
346
|
+
let id: string;
|
|
347
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
246
348
|
const body = await request.json();
|
|
247
349
|
const { data, status } = await forwardJSON(
|
|
248
350
|
`/skills/${id}`,
|
|
249
|
-
{ method: 'PUT', body },
|
|
351
|
+
{ method: 'PUT', body, headers: authHeaders(config, request) },
|
|
250
352
|
{ baseUrl }
|
|
251
353
|
);
|
|
252
354
|
return Response.json(data, { status });
|
|
253
355
|
},
|
|
254
356
|
|
|
255
357
|
DELETE: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
256
|
-
|
|
358
|
+
let id: string;
|
|
359
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
257
360
|
const { data, status } = await forwardJSON(
|
|
258
361
|
`/skills/${id}`,
|
|
259
|
-
{ method: 'DELETE' },
|
|
362
|
+
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
260
363
|
{ baseUrl }
|
|
261
364
|
);
|
|
262
365
|
return Response.json(data, { status });
|
|
@@ -264,6 +367,10 @@ export function createSkillDetailRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
264
367
|
};
|
|
265
368
|
}
|
|
266
369
|
|
|
370
|
+
// ============================================================================
|
|
371
|
+
// Widget Route Handlers
|
|
372
|
+
// ============================================================================
|
|
373
|
+
|
|
267
374
|
/**
|
|
268
375
|
* Create widget route handlers (same pattern as skills)
|
|
269
376
|
*/
|
|
@@ -280,7 +387,11 @@ export function createWidgetRoutes(config: RouteHandlerConfig = {}): CRUDRouteHa
|
|
|
280
387
|
if (scope) path += `?scope=${encodeURIComponent(scope)}`;
|
|
281
388
|
else if (creatorMid) path += `?creator_mid=${encodeURIComponent(creatorMid)}`;
|
|
282
389
|
|
|
283
|
-
const { data, status } = await forwardJSON(
|
|
390
|
+
const { data, status } = await forwardJSON(
|
|
391
|
+
path,
|
|
392
|
+
{ headers: authHeaders(config, request) },
|
|
393
|
+
{ baseUrl },
|
|
394
|
+
);
|
|
284
395
|
return Response.json(data, { status });
|
|
285
396
|
},
|
|
286
397
|
|
|
@@ -288,7 +399,7 @@ export function createWidgetRoutes(config: RouteHandlerConfig = {}): CRUDRouteHa
|
|
|
288
399
|
const body = await request.json();
|
|
289
400
|
const { data, status } = await forwardJSON(
|
|
290
401
|
'/widgets',
|
|
291
|
-
{ method: 'POST', body },
|
|
402
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
292
403
|
{ baseUrl }
|
|
293
404
|
);
|
|
294
405
|
return Response.json(data, { status });
|
|
@@ -304,38 +415,46 @@ export function createWidgetDetailRoutes(config: RouteHandlerConfig = {}): CRUDR
|
|
|
304
415
|
|
|
305
416
|
return {
|
|
306
417
|
GET: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
307
|
-
|
|
308
|
-
|
|
418
|
+
let id: string;
|
|
419
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
420
|
+
const { data, status } = await forwardJSON(
|
|
421
|
+
`/widgets/${id}`,
|
|
422
|
+
{ headers: authHeaders(config, request) },
|
|
423
|
+
{ baseUrl },
|
|
424
|
+
);
|
|
309
425
|
return Response.json(data, { status });
|
|
310
426
|
},
|
|
311
427
|
|
|
312
428
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
313
|
-
|
|
429
|
+
let id: string;
|
|
430
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
314
431
|
const body = await request.json();
|
|
315
432
|
const { data, status } = await forwardJSON(
|
|
316
433
|
`/widgets/${id}`,
|
|
317
|
-
{ method: 'POST', body },
|
|
434
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
318
435
|
{ baseUrl }
|
|
319
436
|
);
|
|
320
437
|
return Response.json(data, { status });
|
|
321
438
|
},
|
|
322
439
|
|
|
323
440
|
PUT: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
324
|
-
|
|
441
|
+
let id: string;
|
|
442
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
325
443
|
const body = await request.json();
|
|
326
444
|
const { data, status } = await forwardJSON(
|
|
327
445
|
`/widgets/${id}`,
|
|
328
|
-
{ method: 'PUT', body },
|
|
446
|
+
{ method: 'PUT', body, headers: authHeaders(config, request) },
|
|
329
447
|
{ baseUrl }
|
|
330
448
|
);
|
|
331
449
|
return Response.json(data, { status });
|
|
332
450
|
},
|
|
333
451
|
|
|
334
452
|
DELETE: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
335
|
-
|
|
453
|
+
let id: string;
|
|
454
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
336
455
|
const { data, status } = await forwardJSON(
|
|
337
456
|
`/widgets/${id}`,
|
|
338
|
-
{ method: 'DELETE' },
|
|
457
|
+
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
339
458
|
{ baseUrl }
|
|
340
459
|
);
|
|
341
460
|
return Response.json(data, { status });
|
|
@@ -343,6 +462,10 @@ export function createWidgetDetailRoutes(config: RouteHandlerConfig = {}): CRUDR
|
|
|
343
462
|
};
|
|
344
463
|
}
|
|
345
464
|
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Imagination Route Handlers
|
|
467
|
+
// ============================================================================
|
|
468
|
+
|
|
346
469
|
/**
|
|
347
470
|
* Create imagination route handlers
|
|
348
471
|
*/
|
|
@@ -352,14 +475,26 @@ export function createImaginationRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
352
475
|
return {
|
|
353
476
|
GET: async (request: Request) => {
|
|
354
477
|
const url = new URL(request.url);
|
|
355
|
-
const
|
|
478
|
+
const idParam = url.searchParams.get('id');
|
|
356
479
|
const creatorMid = url.searchParams.get('creator_mid');
|
|
357
480
|
|
|
358
481
|
let path = '/imaginations';
|
|
359
|
-
if (
|
|
360
|
-
|
|
482
|
+
if (idParam) {
|
|
483
|
+
try {
|
|
484
|
+
const id = validateId(idParam, 'id');
|
|
485
|
+
path = `/imaginations/${id}`;
|
|
486
|
+
} catch (e) {
|
|
487
|
+
return badRequest(e);
|
|
488
|
+
}
|
|
489
|
+
} else if (creatorMid) {
|
|
490
|
+
path += `?creator_mid=${encodeURIComponent(creatorMid)}`;
|
|
491
|
+
}
|
|
361
492
|
|
|
362
|
-
const { data, status } = await forwardJSON(
|
|
493
|
+
const { data, status } = await forwardJSON(
|
|
494
|
+
path,
|
|
495
|
+
{ headers: authHeaders(config, request) },
|
|
496
|
+
{ baseUrl },
|
|
497
|
+
);
|
|
363
498
|
return Response.json(data, { status });
|
|
364
499
|
},
|
|
365
500
|
|
|
@@ -367,7 +502,7 @@ export function createImaginationRoutes(config: RouteHandlerConfig = {}): CRUDRo
|
|
|
367
502
|
const body = await request.json();
|
|
368
503
|
const { data, status } = await forwardJSON(
|
|
369
504
|
'/imaginations',
|
|
370
|
-
{ method: 'POST', body },
|
|
505
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
371
506
|
{ baseUrl }
|
|
372
507
|
);
|
|
373
508
|
return Response.json(data, { status });
|
|
@@ -397,7 +532,11 @@ export function createCoderSessionRoutes(config: RouteHandlerConfig = {}): CRUDR
|
|
|
397
532
|
if (limit) params.push(`limit=${encodeURIComponent(limit)}`);
|
|
398
533
|
if (params.length) path += `?${params.join('&')}`;
|
|
399
534
|
|
|
400
|
-
const { data, status: resStatus } = await forwardJSON(
|
|
535
|
+
const { data, status: resStatus } = await forwardJSON(
|
|
536
|
+
path,
|
|
537
|
+
{ headers: authHeaders(config, request) },
|
|
538
|
+
{ baseUrl },
|
|
539
|
+
);
|
|
401
540
|
return Response.json(data, { status: resStatus });
|
|
402
541
|
},
|
|
403
542
|
|
|
@@ -405,7 +544,7 @@ export function createCoderSessionRoutes(config: RouteHandlerConfig = {}): CRUDR
|
|
|
405
544
|
const body = await request.json();
|
|
406
545
|
const { data, status } = await forwardJSON(
|
|
407
546
|
'/coder/sessions',
|
|
408
|
-
{ method: 'POST', body },
|
|
547
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
409
548
|
{ baseUrl }
|
|
410
549
|
);
|
|
411
550
|
return Response.json(data, { status });
|
|
@@ -423,27 +562,34 @@ export function createCoderSessionDetailRoutes(config: RouteHandlerConfig = {}):
|
|
|
423
562
|
|
|
424
563
|
return {
|
|
425
564
|
GET: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
426
|
-
|
|
427
|
-
|
|
565
|
+
let id: string;
|
|
566
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
567
|
+
const { data, status } = await forwardJSON(
|
|
568
|
+
`/coder/sessions/${id}`,
|
|
569
|
+
{ headers: authHeaders(config, request) },
|
|
570
|
+
{ baseUrl },
|
|
571
|
+
);
|
|
428
572
|
return Response.json(data, { status });
|
|
429
573
|
},
|
|
430
574
|
|
|
431
575
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
432
|
-
|
|
576
|
+
let id: string;
|
|
577
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
433
578
|
const body = await request.json();
|
|
434
579
|
const { data, status } = await forwardJSON(
|
|
435
580
|
`/coder/sessions/${id}`,
|
|
436
|
-
{ method: 'POST', body },
|
|
581
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
437
582
|
{ baseUrl }
|
|
438
583
|
);
|
|
439
584
|
return Response.json(data, { status });
|
|
440
585
|
},
|
|
441
586
|
|
|
442
587
|
DELETE: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
443
|
-
|
|
588
|
+
let id: string;
|
|
589
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
444
590
|
const { data, status } = await forwardJSON(
|
|
445
591
|
`/coder/sessions/${id}`,
|
|
446
|
-
{ method: 'DELETE' },
|
|
592
|
+
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
447
593
|
{ baseUrl }
|
|
448
594
|
);
|
|
449
595
|
return Response.json(data, { status });
|
|
@@ -451,10 +597,11 @@ export function createCoderSessionDetailRoutes(config: RouteHandlerConfig = {}):
|
|
|
451
597
|
|
|
452
598
|
abort: {
|
|
453
599
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
454
|
-
|
|
600
|
+
let id: string;
|
|
601
|
+
try { id = idFromContext(request, context, 'id', 2); } catch (e) { return badRequest(e); }
|
|
455
602
|
const { data, status } = await forwardJSON(
|
|
456
603
|
`/coder/sessions/${id}/abort`,
|
|
457
|
-
{ method: 'POST' },
|
|
604
|
+
{ method: 'POST', headers: authHeaders(config, request) },
|
|
458
605
|
{ baseUrl }
|
|
459
606
|
);
|
|
460
607
|
return Response.json(data, { status });
|
|
@@ -475,7 +622,7 @@ export function createCoderExecuteRoute(config: RouteHandlerConfig = {}): { POST
|
|
|
475
622
|
|
|
476
623
|
const response = await forwardRequest(
|
|
477
624
|
'/coder/execute',
|
|
478
|
-
{ method: 'POST', body, stream: body.stream },
|
|
625
|
+
{ method: 'POST', body, stream: body.stream, headers: authHeaders(config, request) },
|
|
479
626
|
{ baseUrl }
|
|
480
627
|
);
|
|
481
628
|
|
|
@@ -508,18 +655,23 @@ export function createCoderToolsRoutes(config: RouteHandlerConfig = {}): {
|
|
|
508
655
|
const baseUrl = config.baseUrl;
|
|
509
656
|
|
|
510
657
|
return {
|
|
511
|
-
GET: async () => {
|
|
512
|
-
const { data, status } = await forwardJSON(
|
|
658
|
+
GET: async (request: Request) => {
|
|
659
|
+
const { data, status } = await forwardJSON(
|
|
660
|
+
'/coder/tools',
|
|
661
|
+
{ headers: authHeaders(config, request) },
|
|
662
|
+
{ baseUrl },
|
|
663
|
+
);
|
|
513
664
|
return Response.json(data, { status });
|
|
514
665
|
},
|
|
515
666
|
|
|
516
667
|
call: {
|
|
517
668
|
POST: async (request: Request, context?: { params?: { tool?: string } }) => {
|
|
518
|
-
|
|
669
|
+
let tool: string;
|
|
670
|
+
try { tool = idFromContext(request, context, 'tool'); } catch (e) { return badRequest(e); }
|
|
519
671
|
const body = await request.json();
|
|
520
672
|
const { data, status } = await forwardJSON(
|
|
521
673
|
`/coder/tools/${tool}`,
|
|
522
|
-
{ method: 'POST', body },
|
|
674
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
523
675
|
{ baseUrl }
|
|
524
676
|
);
|
|
525
677
|
return Response.json(data, { status });
|
|
@@ -552,7 +704,11 @@ export function createCoderFilesRoutes(config: RouteHandlerConfig = {}): {
|
|
|
552
704
|
if (sessionId) apiPath += `&sessionId=${encodeURIComponent(sessionId)}`;
|
|
553
705
|
if (sandboxId) apiPath += `&sandboxId=${encodeURIComponent(sandboxId)}`;
|
|
554
706
|
|
|
555
|
-
const { data, status } = await forwardJSON(
|
|
707
|
+
const { data, status } = await forwardJSON(
|
|
708
|
+
apiPath,
|
|
709
|
+
{ headers: authHeaders(config, request) },
|
|
710
|
+
{ baseUrl },
|
|
711
|
+
);
|
|
556
712
|
return Response.json(data, { status });
|
|
557
713
|
},
|
|
558
714
|
},
|
|
@@ -562,7 +718,7 @@ export function createCoderFilesRoutes(config: RouteHandlerConfig = {}): {
|
|
|
562
718
|
const body = await request.json();
|
|
563
719
|
const { data, status } = await forwardJSON(
|
|
564
720
|
'/coder/files/write',
|
|
565
|
-
{ method: 'POST', body },
|
|
721
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
566
722
|
{ baseUrl }
|
|
567
723
|
);
|
|
568
724
|
return Response.json(data, { status });
|
|
@@ -579,12 +735,16 @@ export function createCoderFilesRoutes(config: RouteHandlerConfig = {}): {
|
|
|
579
735
|
const sandboxId = url.searchParams.get('sandboxId');
|
|
580
736
|
|
|
581
737
|
let apiPath = `/coder/files/list?path=${encodeURIComponent(path)}`;
|
|
582
|
-
if (recursive) apiPath += `&recursive=${recursive}`;
|
|
583
|
-
if (maxDepth) apiPath += `&maxDepth=${maxDepth}`;
|
|
738
|
+
if (recursive) apiPath += `&recursive=${encodeURIComponent(recursive)}`;
|
|
739
|
+
if (maxDepth) apiPath += `&maxDepth=${encodeURIComponent(maxDepth)}`;
|
|
584
740
|
if (sessionId) apiPath += `&sessionId=${encodeURIComponent(sessionId)}`;
|
|
585
741
|
if (sandboxId) apiPath += `&sandboxId=${encodeURIComponent(sandboxId)}`;
|
|
586
742
|
|
|
587
|
-
const { data, status } = await forwardJSON(
|
|
743
|
+
const { data, status } = await forwardJSON(
|
|
744
|
+
apiPath,
|
|
745
|
+
{ headers: authHeaders(config, request) },
|
|
746
|
+
{ baseUrl },
|
|
747
|
+
);
|
|
588
748
|
return Response.json(data, { status });
|
|
589
749
|
},
|
|
590
750
|
},
|
|
@@ -604,7 +764,11 @@ export function createCoderFilesRoutes(config: RouteHandlerConfig = {}): {
|
|
|
604
764
|
if (sessionId) apiPath += `&sessionId=${encodeURIComponent(sessionId)}`;
|
|
605
765
|
if (sandboxId) apiPath += `&sandboxId=${encodeURIComponent(sandboxId)}`;
|
|
606
766
|
|
|
607
|
-
const { data, status } = await forwardJSON(
|
|
767
|
+
const { data, status } = await forwardJSON(
|
|
768
|
+
apiPath,
|
|
769
|
+
{ headers: authHeaders(config, request) },
|
|
770
|
+
{ baseUrl },
|
|
771
|
+
);
|
|
608
772
|
return Response.json(data, { status });
|
|
609
773
|
},
|
|
610
774
|
},
|
|
@@ -614,7 +778,7 @@ export function createCoderFilesRoutes(config: RouteHandlerConfig = {}): {
|
|
|
614
778
|
const body = await request.json();
|
|
615
779
|
const { data, status } = await forwardJSON(
|
|
616
780
|
'/coder/files/diff',
|
|
617
|
-
{ method: 'POST', body },
|
|
781
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
618
782
|
{ baseUrl }
|
|
619
783
|
);
|
|
620
784
|
return Response.json(data, { status });
|
|
@@ -640,7 +804,7 @@ export function createCoderSandboxRoutes(config: RouteHandlerConfig = {}): {
|
|
|
640
804
|
const body = await request.json();
|
|
641
805
|
const { data, status } = await forwardJSON(
|
|
642
806
|
'/coder/sandbox',
|
|
643
|
-
{ method: 'POST', body },
|
|
807
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
644
808
|
{ baseUrl }
|
|
645
809
|
);
|
|
646
810
|
return Response.json(data, { status });
|
|
@@ -649,20 +813,26 @@ export function createCoderSandboxRoutes(config: RouteHandlerConfig = {}): {
|
|
|
649
813
|
|
|
650
814
|
get: {
|
|
651
815
|
GET: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
652
|
-
|
|
653
|
-
|
|
816
|
+
let id: string;
|
|
817
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
818
|
+
const { data, status } = await forwardJSON(
|
|
819
|
+
`/coder/sandbox/${id}`,
|
|
820
|
+
{ headers: authHeaders(config, request) },
|
|
821
|
+
{ baseUrl },
|
|
822
|
+
);
|
|
654
823
|
return Response.json(data, { status });
|
|
655
824
|
},
|
|
656
825
|
},
|
|
657
826
|
|
|
658
827
|
exec: {
|
|
659
828
|
POST: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
660
|
-
|
|
829
|
+
let id: string;
|
|
830
|
+
try { id = idFromContext(request, context, 'id', 2); } catch (e) { return badRequest(e); }
|
|
661
831
|
const body = await request.json();
|
|
662
832
|
|
|
663
833
|
const response = await forwardRequest(
|
|
664
834
|
`/coder/sandbox/${id}/exec`,
|
|
665
|
-
{ method: 'POST', body, stream: body.stream },
|
|
835
|
+
{ method: 'POST', body, stream: body.stream, headers: authHeaders(config, request) },
|
|
666
836
|
{ baseUrl }
|
|
667
837
|
);
|
|
668
838
|
|
|
@@ -686,10 +856,11 @@ export function createCoderSandboxRoutes(config: RouteHandlerConfig = {}): {
|
|
|
686
856
|
|
|
687
857
|
destroy: {
|
|
688
858
|
DELETE: async (request: Request, context?: { params?: { id?: string } }) => {
|
|
689
|
-
|
|
859
|
+
let id: string;
|
|
860
|
+
try { id = idFromContext(request, context, 'id'); } catch (e) { return badRequest(e); }
|
|
690
861
|
const { data, status } = await forwardJSON(
|
|
691
862
|
`/coder/sandbox/${id}`,
|
|
692
|
-
{ method: 'DELETE' },
|
|
863
|
+
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
693
864
|
{ baseUrl }
|
|
694
865
|
);
|
|
695
866
|
return Response.json(data, { status });
|
|
@@ -712,7 +883,7 @@ export function createCoderExecRoutes(config: RouteHandlerConfig = {}): {
|
|
|
712
883
|
const body = await request.json();
|
|
713
884
|
const { data, status } = await forwardJSON(
|
|
714
885
|
'/coder/exec',
|
|
715
|
-
{ method: 'POST', body },
|
|
886
|
+
{ method: 'POST', body, headers: authHeaders(config, request) },
|
|
716
887
|
{ baseUrl }
|
|
717
888
|
);
|
|
718
889
|
return Response.json(data, { status });
|
|
@@ -724,7 +895,7 @@ export function createCoderExecRoutes(config: RouteHandlerConfig = {}): {
|
|
|
724
895
|
|
|
725
896
|
const response = await forwardRequest(
|
|
726
897
|
'/coder/exec/stream',
|
|
727
|
-
{ method: 'POST', body, stream: true },
|
|
898
|
+
{ method: 'POST', body, stream: true, headers: authHeaders(config, request) },
|
|
728
899
|
{ baseUrl }
|
|
729
900
|
);
|
|
730
901
|
|
|
@@ -750,35 +921,6 @@ export function createCoderExecRoutes(config: RouteHandlerConfig = {}): {
|
|
|
750
921
|
// Stack Management Route Handlers
|
|
751
922
|
// ============================================================================
|
|
752
923
|
|
|
753
|
-
export interface StackRouteHandlerConfig extends RouteHandlerConfig {
|
|
754
|
-
/** Extract JWT from incoming request and forward as Authorization header */
|
|
755
|
-
extractAuth?: (request: Request) => string | null;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/** Validate that an ID parameter is a safe alphanumeric/dash/underscore string */
|
|
759
|
-
function validateId(id: string | null | undefined, name: string): string {
|
|
760
|
-
if (!id || typeof id !== 'string') {
|
|
761
|
-
throw new Error(`Missing required parameter: ${name}`);
|
|
762
|
-
}
|
|
763
|
-
// Allow alphanumeric, dashes, underscores, dots — block path traversal
|
|
764
|
-
if (!/^[\w.\-]+$/.test(id)) {
|
|
765
|
-
throw new Error(`Invalid ${name}: contains disallowed characters`);
|
|
766
|
-
}
|
|
767
|
-
return id;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function authHeaders(config: StackRouteHandlerConfig, request: Request): Record<string, string> {
|
|
771
|
-
const headers: Record<string, string> = {};
|
|
772
|
-
if (config.extractAuth) {
|
|
773
|
-
const token = config.extractAuth(request);
|
|
774
|
-
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
775
|
-
} else {
|
|
776
|
-
const auth = request.headers.get('Authorization');
|
|
777
|
-
if (auth) headers['Authorization'] = auth;
|
|
778
|
-
}
|
|
779
|
-
return headers;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
924
|
/**
|
|
783
925
|
* Create stack CRUD route handlers (list + create)
|
|
784
926
|
*/
|
|
@@ -815,7 +957,8 @@ export function createStackDetailRoutes(config: StackRouteHandlerConfig = {}): C
|
|
|
815
957
|
|
|
816
958
|
return {
|
|
817
959
|
GET: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
818
|
-
|
|
960
|
+
let stackId: string;
|
|
961
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
819
962
|
const { data, status } = await forwardJSON(
|
|
820
963
|
`/api/v2/stacks/${stackId}`,
|
|
821
964
|
{ headers: authHeaders(config, request) },
|
|
@@ -827,7 +970,8 @@ export function createStackDetailRoutes(config: StackRouteHandlerConfig = {}): C
|
|
|
827
970
|
POST: async () => Response.json({ error: 'Method not allowed' }, { status: 405 }),
|
|
828
971
|
|
|
829
972
|
PUT: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
830
|
-
|
|
973
|
+
let stackId: string;
|
|
974
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
831
975
|
const body = await request.json();
|
|
832
976
|
const { data, status } = await forwardJSON(
|
|
833
977
|
`/api/v2/stacks/${stackId}`,
|
|
@@ -838,7 +982,8 @@ export function createStackDetailRoutes(config: StackRouteHandlerConfig = {}): C
|
|
|
838
982
|
},
|
|
839
983
|
|
|
840
984
|
DELETE: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
841
|
-
|
|
985
|
+
let stackId: string;
|
|
986
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
842
987
|
const { data, status } = await forwardJSON(
|
|
843
988
|
`/api/v2/stacks/${stackId}`,
|
|
844
989
|
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
@@ -857,7 +1002,8 @@ export function createStackKeysRoutes(config: StackRouteHandlerConfig = {}): CRU
|
|
|
857
1002
|
|
|
858
1003
|
return {
|
|
859
1004
|
GET: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
860
|
-
|
|
1005
|
+
let stackId: string;
|
|
1006
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
861
1007
|
const { data, status } = await forwardJSON(
|
|
862
1008
|
`/api/v2/stacks/${stackId}/keys`,
|
|
863
1009
|
{ headers: authHeaders(config, request) },
|
|
@@ -867,7 +1013,8 @@ export function createStackKeysRoutes(config: StackRouteHandlerConfig = {}): CRU
|
|
|
867
1013
|
},
|
|
868
1014
|
|
|
869
1015
|
POST: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
870
|
-
|
|
1016
|
+
let stackId: string;
|
|
1017
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
871
1018
|
const body = await request.json();
|
|
872
1019
|
const { data, status } = await forwardJSON(
|
|
873
1020
|
`/api/v2/stacks/${stackId}/keys`,
|
|
@@ -878,8 +1025,12 @@ export function createStackKeysRoutes(config: StackRouteHandlerConfig = {}): CRU
|
|
|
878
1025
|
},
|
|
879
1026
|
|
|
880
1027
|
DELETE: async (request: Request, context?: { params?: { stackId?: string; keyId?: string } }) => {
|
|
881
|
-
|
|
882
|
-
|
|
1028
|
+
let stackId: string;
|
|
1029
|
+
let keyId: string;
|
|
1030
|
+
try {
|
|
1031
|
+
stackId = validateId(context?.params?.stackId, 'stackId');
|
|
1032
|
+
keyId = validateId(context?.params?.keyId, 'keyId');
|
|
1033
|
+
} catch (e) { return badRequest(e); }
|
|
883
1034
|
const { data, status } = await forwardJSON(
|
|
884
1035
|
`/api/v2/stacks/${stackId}/keys/${keyId}`,
|
|
885
1036
|
{ method: 'DELETE', headers: authHeaders(config, request) },
|
|
@@ -902,7 +1053,8 @@ export function createStackMembersRoutes(config: StackRouteHandlerConfig = {}):
|
|
|
902
1053
|
|
|
903
1054
|
return {
|
|
904
1055
|
GET: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
905
|
-
|
|
1056
|
+
let stackId: string;
|
|
1057
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
906
1058
|
const url = new URL(request.url);
|
|
907
1059
|
const params = new URLSearchParams();
|
|
908
1060
|
const limit = url.searchParams.get('limit');
|
|
@@ -923,7 +1075,8 @@ export function createStackMembersRoutes(config: StackRouteHandlerConfig = {}):
|
|
|
923
1075
|
|
|
924
1076
|
stats: {
|
|
925
1077
|
GET: async (request: Request, context?: { params?: { stackId?: string } }) => {
|
|
926
|
-
|
|
1078
|
+
let stackId: string;
|
|
1079
|
+
try { stackId = validateId(context?.params?.stackId, 'stackId'); } catch (e) { return badRequest(e); }
|
|
927
1080
|
const { data, status } = await forwardJSON(
|
|
928
1081
|
`/api/v2/stacks/${stackId}/members/stats`,
|
|
929
1082
|
{ headers: authHeaders(config, request) },
|
|
@@ -935,11 +1088,15 @@ export function createStackMembersRoutes(config: StackRouteHandlerConfig = {}):
|
|
|
935
1088
|
|
|
936
1089
|
updateRole: {
|
|
937
1090
|
PATCH: async (request: Request, context?: { params?: { stackId?: string; userId?: string } }) => {
|
|
938
|
-
|
|
939
|
-
|
|
1091
|
+
let stackId: string;
|
|
1092
|
+
let userId: string;
|
|
1093
|
+
try {
|
|
1094
|
+
stackId = validateId(context?.params?.stackId, 'stackId');
|
|
1095
|
+
userId = validateId(context?.params?.userId, 'userId');
|
|
1096
|
+
} catch (e) { return badRequest(e); }
|
|
940
1097
|
const body = await request.json();
|
|
941
1098
|
const { data, status } = await forwardJSON(
|
|
942
|
-
`/api/v2/stacks/${stackId}/members/${
|
|
1099
|
+
`/api/v2/stacks/${stackId}/members/${userId}/role`,
|
|
943
1100
|
{ method: 'PATCH', body, headers: authHeaders(config, request) },
|
|
944
1101
|
{ baseUrl }
|
|
945
1102
|
);
|