@critical-path/server 0.14.0 → 0.15.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/CHANGELOG.md +24 -0
- package/README.md +31 -0
- package/dist/adapters/sveltekit.d.ts +18 -20
- package/dist/adapters/sveltekit.d.ts.map +1 -1
- package/dist/adapters/sveltekit.js +5 -4
- package/dist/adapters/sveltekit.js.map +1 -1
- package/dist/adapters/universal.d.ts +8 -0
- package/dist/adapters/universal.d.ts.map +1 -0
- package/dist/adapters/universal.js +12 -0
- package/dist/adapters/universal.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +121 -0
- package/dist/index.test.js.map +1 -1
- package/dist/router.d.ts +54 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +154 -84
- package/dist/router.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sveltekit.ts +21 -7
- package/src/adapters/universal.ts +16 -0
- package/src/index.test.ts +143 -0
- package/src/index.ts +1 -0
- package/src/router.ts +197 -85
- package/tsconfig.tsbuildinfo +1 -1
package/dist/router.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { CriticalPathEngine } from '@critical-path/core';
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
5
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
6
|
-
};
|
|
2
|
+
const CORS_ALLOW_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
|
|
3
|
+
const DEFAULT_CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization'];
|
|
7
4
|
class BadRequestError extends Error {
|
|
8
5
|
constructor(message) {
|
|
9
6
|
super(message);
|
|
@@ -17,6 +14,10 @@ export class CriticalPathRouter {
|
|
|
17
14
|
engine;
|
|
18
15
|
onError;
|
|
19
16
|
exposeErrors;
|
|
17
|
+
getContext;
|
|
18
|
+
requireAuth;
|
|
19
|
+
basePath;
|
|
20
|
+
cors;
|
|
20
21
|
constructor(configOrEngine, options = {}) {
|
|
21
22
|
if (configOrEngine instanceof CriticalPathEngine) {
|
|
22
23
|
this.engine = configOrEngine;
|
|
@@ -26,49 +27,118 @@ export class CriticalPathRouter {
|
|
|
26
27
|
}
|
|
27
28
|
this.onError = options.onError;
|
|
28
29
|
this.exposeErrors = options.exposeErrors ?? isDevelopment();
|
|
30
|
+
this.getContext = options.getContext;
|
|
31
|
+
this.requireAuth = options.requireAuth ?? false;
|
|
32
|
+
this.basePath = options.basePath ? '/' + options.basePath.replace(/^\/+|\/+$/g, '') : undefined;
|
|
33
|
+
this.cors = options.cors === undefined ? { origins: '*' } : options.cors;
|
|
34
|
+
if (this.cors && this.cors.origins === '*' && this.cors.credentials) {
|
|
35
|
+
throw new Error('CORS credentials cannot be combined with origins "*"; list the allowed origins instead.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Handles a request. Adapters that resolve identity from something other than the `Request`
|
|
40
|
+
* (e.g. SvelteKit `locals`) pass `init.getContext`, which replaces the router's `getContext`
|
|
41
|
+
* for this request.
|
|
42
|
+
*/
|
|
43
|
+
async handleRequest(request, init = {}) {
|
|
44
|
+
const response = await this.dispatch(request, init);
|
|
45
|
+
return this.applyCors(request, response);
|
|
46
|
+
}
|
|
47
|
+
/** Returns the path after the mount point, or `null` when the request is outside `basePath`. */
|
|
48
|
+
resolveSubpath(pathname) {
|
|
49
|
+
if (this.basePath) {
|
|
50
|
+
if (pathname !== this.basePath && !pathname.startsWith(this.basePath + '/'))
|
|
51
|
+
return null;
|
|
52
|
+
return pathname.slice(this.basePath.length).replace(/^\/+/, '');
|
|
53
|
+
}
|
|
54
|
+
// Extract subpath after /critical-path/ or /api/critical-path/
|
|
55
|
+
return pathname.replace(/^.*?\/critical-path\/?/, '').replace(/^\/+/, '');
|
|
56
|
+
}
|
|
57
|
+
applyCors(request, response) {
|
|
58
|
+
if (!this.cors)
|
|
59
|
+
return response;
|
|
60
|
+
const origin = request.headers.get('Origin');
|
|
61
|
+
let allowOrigin;
|
|
62
|
+
if (this.cors.origins === '*') {
|
|
63
|
+
allowOrigin = '*';
|
|
64
|
+
}
|
|
65
|
+
else if (origin && this.cors.origins.includes(origin)) {
|
|
66
|
+
allowOrigin = origin;
|
|
67
|
+
}
|
|
68
|
+
// Copy so headers are mutable even if a hook returned a Response with immutable headers.
|
|
69
|
+
const result = new Response(response.body, response);
|
|
70
|
+
if (this.cors.origins !== '*')
|
|
71
|
+
result.headers.append('Vary', 'Origin');
|
|
72
|
+
if (!allowOrigin)
|
|
73
|
+
return result;
|
|
74
|
+
result.headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
75
|
+
result.headers.set('Access-Control-Allow-Methods', CORS_ALLOW_METHODS);
|
|
76
|
+
result.headers.set('Access-Control-Allow-Headers', (this.cors.allowHeaders ?? DEFAULT_CORS_ALLOW_HEADERS).join(', '));
|
|
77
|
+
if (this.cors.credentials)
|
|
78
|
+
result.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
79
|
+
if (request.method.toUpperCase() === 'OPTIONS' && this.cors.maxAge !== undefined) {
|
|
80
|
+
result.headers.set('Access-Control-Max-Age', String(this.cors.maxAge));
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
29
83
|
}
|
|
30
|
-
async
|
|
84
|
+
async dispatch(request, init) {
|
|
31
85
|
const url = new URL(request.url);
|
|
32
86
|
const pathname = url.pathname;
|
|
33
87
|
const method = request.method.toUpperCase();
|
|
34
|
-
|
|
35
|
-
|
|
88
|
+
const subpath = this.resolveSubpath(pathname);
|
|
89
|
+
if (subpath === null) {
|
|
90
|
+
return this.jsonResponse({ error: `Route not found: ${method} ${pathname}` }, 404);
|
|
91
|
+
}
|
|
36
92
|
const segments = subpath.split('/').filter(Boolean);
|
|
37
|
-
// CORS preflight
|
|
93
|
+
// CORS preflight (browsers send it without credentials, so it is answered before auth)
|
|
38
94
|
if (method === 'OPTIONS') {
|
|
39
|
-
return new Response(null, { status: 204
|
|
95
|
+
return new Response(null, { status: 204 });
|
|
96
|
+
}
|
|
97
|
+
let context;
|
|
98
|
+
try {
|
|
99
|
+
const resolve = init.getContext ?? this.getContext;
|
|
100
|
+
context = resolve ? await resolve(request) : undefined;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return this.errorResponse(err, request);
|
|
104
|
+
}
|
|
105
|
+
if (this.requireAuth && !context?.userId) {
|
|
106
|
+
return this.jsonResponse({ error: 'Authentication required' }, 401);
|
|
40
107
|
}
|
|
108
|
+
const engine = context?.userId
|
|
109
|
+
? this.engine.withActor({ userId: context.userId, username: context.userName, actorType: context.actorType })
|
|
110
|
+
: this.engine;
|
|
41
111
|
try {
|
|
42
112
|
// Workflows API
|
|
43
113
|
if (segments[0] === 'workflows') {
|
|
44
114
|
const workflowId = segments[1];
|
|
45
115
|
if (!workflowId) {
|
|
46
116
|
if (method === 'GET') {
|
|
47
|
-
const workflows = await
|
|
117
|
+
const workflows = await engine.getWorkflows();
|
|
48
118
|
return this.jsonResponse({ workflows });
|
|
49
119
|
}
|
|
50
120
|
if (method === 'POST') {
|
|
51
121
|
const body = await this.readJson(request);
|
|
52
|
-
const workflow = await
|
|
122
|
+
const workflow = await engine.createWorkflow(body);
|
|
53
123
|
return this.jsonResponse({ workflow }, 201);
|
|
54
124
|
}
|
|
55
125
|
}
|
|
56
126
|
else {
|
|
57
127
|
if (method === 'GET') {
|
|
58
|
-
const workflow = await
|
|
128
|
+
const workflow = await engine.getWorkflow(workflowId);
|
|
59
129
|
if (!workflow)
|
|
60
130
|
return this.jsonResponse({ error: 'Workflow not found' }, 404);
|
|
61
131
|
return this.jsonResponse({ workflow });
|
|
62
132
|
}
|
|
63
133
|
if (method === 'PATCH' || method === 'PUT') {
|
|
64
134
|
const body = await this.readJson(request);
|
|
65
|
-
const updated = await
|
|
135
|
+
const updated = await engine.updateWorkflow(workflowId, body);
|
|
66
136
|
if (!updated)
|
|
67
137
|
return this.jsonResponse({ error: 'Workflow not found' }, 404);
|
|
68
138
|
return this.jsonResponse({ workflow: updated });
|
|
69
139
|
}
|
|
70
140
|
if (method === 'DELETE') {
|
|
71
|
-
const deleted = await
|
|
141
|
+
const deleted = await engine.deleteWorkflow(workflowId);
|
|
72
142
|
if (!deleted)
|
|
73
143
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
74
144
|
return this.jsonResponse({ success: true });
|
|
@@ -81,18 +151,18 @@ export class CriticalPathRouter {
|
|
|
81
151
|
const subResource = segments[2];
|
|
82
152
|
if (!projectId) {
|
|
83
153
|
if (method === 'GET') {
|
|
84
|
-
const projects = await
|
|
154
|
+
const projects = await engine.getProjects();
|
|
85
155
|
return this.jsonResponse({ projects });
|
|
86
156
|
}
|
|
87
157
|
if (method === 'POST') {
|
|
88
158
|
const body = await this.readJson(request);
|
|
89
|
-
const project = await
|
|
159
|
+
const project = await engine.createProject(body);
|
|
90
160
|
return this.jsonResponse({ project }, 201);
|
|
91
161
|
}
|
|
92
162
|
}
|
|
93
163
|
else if (subResource === 'critical-path') {
|
|
94
164
|
if (method === 'GET') {
|
|
95
|
-
const analysis = await
|
|
165
|
+
const analysis = await engine.calculateCriticalPath(projectId);
|
|
96
166
|
return this.jsonResponse({ analysis });
|
|
97
167
|
}
|
|
98
168
|
}
|
|
@@ -101,7 +171,7 @@ export class CriticalPathRouter {
|
|
|
101
171
|
const level = (url.searchParams.get('level') || 'all');
|
|
102
172
|
const containerId = url.searchParams.get('containerId') || undefined;
|
|
103
173
|
const iterationId = url.searchParams.get('iterationId') || undefined;
|
|
104
|
-
const ladder = await
|
|
174
|
+
const ladder = await engine.getTimelineLadder(projectId, { level, containerId, iterationId });
|
|
105
175
|
return this.jsonResponse({ ladder });
|
|
106
176
|
}
|
|
107
177
|
}
|
|
@@ -115,7 +185,7 @@ export class CriticalPathRouter {
|
|
|
115
185
|
const defaultWeeklyCapacityHours = url.searchParams.get('defaultWeeklyCapacityHours')
|
|
116
186
|
? parseFloat(url.searchParams.get('defaultWeeklyCapacityHours'))
|
|
117
187
|
: undefined;
|
|
118
|
-
const workload = await
|
|
188
|
+
const workload = await engine.getWorkloadDistribution(projectId, {
|
|
119
189
|
startDate,
|
|
120
190
|
endDate,
|
|
121
191
|
interval,
|
|
@@ -128,20 +198,20 @@ export class CriticalPathRouter {
|
|
|
128
198
|
}
|
|
129
199
|
else {
|
|
130
200
|
if (method === 'GET') {
|
|
131
|
-
const project = await
|
|
201
|
+
const project = await engine.getProject(projectId);
|
|
132
202
|
if (!project)
|
|
133
203
|
return this.jsonResponse({ error: 'Project not found' }, 404);
|
|
134
204
|
return this.jsonResponse({ project });
|
|
135
205
|
}
|
|
136
206
|
if (method === 'PATCH' || method === 'PUT') {
|
|
137
207
|
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...updates } = await this.readJson(request);
|
|
138
|
-
const project = await
|
|
208
|
+
const project = await engine.updateProject(projectId, updates);
|
|
139
209
|
if (!project)
|
|
140
210
|
return this.jsonResponse({ error: 'Project not found' }, 404);
|
|
141
211
|
return this.jsonResponse({ project });
|
|
142
212
|
}
|
|
143
213
|
if (method === 'DELETE') {
|
|
144
|
-
const deleted = await
|
|
214
|
+
const deleted = await engine.deleteProject(projectId);
|
|
145
215
|
if (!deleted)
|
|
146
216
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
147
217
|
return this.jsonResponse({ success: true });
|
|
@@ -155,40 +225,40 @@ export class CriticalPathRouter {
|
|
|
155
225
|
if (!taskId) {
|
|
156
226
|
if (method === 'GET') {
|
|
157
227
|
const projectId = url.searchParams.get('projectId') || undefined;
|
|
158
|
-
const tasks = await
|
|
228
|
+
const tasks = await engine.getTasks(projectId);
|
|
159
229
|
return this.jsonResponse({ tasks });
|
|
160
230
|
}
|
|
161
231
|
if (method === 'POST') {
|
|
162
232
|
const body = await this.readJson(request);
|
|
163
|
-
const task = await
|
|
233
|
+
const task = await engine.createTask(body);
|
|
164
234
|
return this.jsonResponse({ task }, 201);
|
|
165
235
|
}
|
|
166
236
|
}
|
|
167
237
|
else if (subResource === 'comments') {
|
|
168
238
|
if (method === 'GET') {
|
|
169
|
-
const comments = await
|
|
239
|
+
const comments = await engine.getComments(taskId);
|
|
170
240
|
return this.jsonResponse({ comments });
|
|
171
241
|
}
|
|
172
242
|
if (method === 'POST') {
|
|
173
243
|
const body = await this.readJson(request);
|
|
174
|
-
const comment = await
|
|
244
|
+
const comment = await engine.addComment({ ...body, taskId });
|
|
175
245
|
return this.jsonResponse({ comment }, 201);
|
|
176
246
|
}
|
|
177
247
|
}
|
|
178
248
|
else if (subResource === 'attachments') {
|
|
179
249
|
if (method === 'GET') {
|
|
180
|
-
const attachments = await
|
|
250
|
+
const attachments = await engine.getAttachments({ taskId });
|
|
181
251
|
return this.jsonResponse({ attachments });
|
|
182
252
|
}
|
|
183
253
|
if (method === 'POST') {
|
|
184
254
|
const body = await this.readJson(request);
|
|
185
|
-
const attachment = await
|
|
255
|
+
const attachment = await engine.createAttachment({ ...body, taskId });
|
|
186
256
|
return this.jsonResponse({ attachment }, 201);
|
|
187
257
|
}
|
|
188
258
|
}
|
|
189
259
|
else if (subResource === 'dependencies') {
|
|
190
260
|
if (method === 'GET') {
|
|
191
|
-
const graph = await
|
|
261
|
+
const graph = await engine.getTaskDependencyGraph(taskId);
|
|
192
262
|
return this.jsonResponse({ graph });
|
|
193
263
|
}
|
|
194
264
|
if (method === 'POST') {
|
|
@@ -196,7 +266,7 @@ export class CriticalPathRouter {
|
|
|
196
266
|
if (typeof body.dependsOnTaskId !== 'string' || !body.dependsOnTaskId) {
|
|
197
267
|
return this.jsonResponse({ error: 'dependsOnTaskId is required' }, 400);
|
|
198
268
|
}
|
|
199
|
-
const dep = await
|
|
269
|
+
const dep = await engine.addDependency({
|
|
200
270
|
taskId,
|
|
201
271
|
dependsOnTaskId: body.dependsOnTaskId,
|
|
202
272
|
type: body.type || 'blocking'
|
|
@@ -206,7 +276,7 @@ export class CriticalPathRouter {
|
|
|
206
276
|
}
|
|
207
277
|
else if (subResource === 'state' || subResource === 'lifecycle') {
|
|
208
278
|
if (method === 'GET') {
|
|
209
|
-
const state = await
|
|
279
|
+
const state = await engine.getTaskLifecycleState(taskId);
|
|
210
280
|
if (!state)
|
|
211
281
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
212
282
|
return this.jsonResponse({ state });
|
|
@@ -214,14 +284,14 @@ export class CriticalPathRouter {
|
|
|
214
284
|
}
|
|
215
285
|
else if (subResource === 'transitions' || subResource === 'allowed-transitions') {
|
|
216
286
|
if (method === 'GET') {
|
|
217
|
-
const allowedNextStatuses = await
|
|
218
|
-
const allowedPreviousStatuses = await
|
|
287
|
+
const allowedNextStatuses = await engine.getAllowedTaskTransitions(taskId);
|
|
288
|
+
const allowedPreviousStatuses = await engine.getAllowedPreviousTaskTransitions(taskId);
|
|
219
289
|
return this.jsonResponse({ allowedNextStatuses, allowedPreviousStatuses });
|
|
220
290
|
}
|
|
221
291
|
}
|
|
222
292
|
else if (subResource === 'ladder') {
|
|
223
293
|
if (method === 'GET') {
|
|
224
|
-
const taskLadder = await
|
|
294
|
+
const taskLadder = await engine.getTaskLadder(taskId);
|
|
225
295
|
if (!taskLadder)
|
|
226
296
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
227
297
|
return this.jsonResponse({ taskLadder });
|
|
@@ -229,7 +299,7 @@ export class CriticalPathRouter {
|
|
|
229
299
|
}
|
|
230
300
|
else if (subResource === 'metrics') {
|
|
231
301
|
if (method === 'GET') {
|
|
232
|
-
const metrics = await
|
|
302
|
+
const metrics = await engine.getTaskMetrics(taskId);
|
|
233
303
|
if (!metrics)
|
|
234
304
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
235
305
|
return this.jsonResponse({ metrics });
|
|
@@ -237,7 +307,7 @@ export class CriticalPathRouter {
|
|
|
237
307
|
}
|
|
238
308
|
else if (subResource === 'progress-history') {
|
|
239
309
|
if (method === 'GET') {
|
|
240
|
-
const progressHistory = await
|
|
310
|
+
const progressHistory = await engine.getTaskProgressHistory(taskId);
|
|
241
311
|
if (!progressHistory)
|
|
242
312
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
243
313
|
return this.jsonResponse({ progressHistory });
|
|
@@ -245,20 +315,20 @@ export class CriticalPathRouter {
|
|
|
245
315
|
}
|
|
246
316
|
else {
|
|
247
317
|
if (method === 'GET') {
|
|
248
|
-
const task = await
|
|
318
|
+
const task = await engine.getTask(taskId);
|
|
249
319
|
if (!task)
|
|
250
320
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
251
321
|
return this.jsonResponse({ task });
|
|
252
322
|
}
|
|
253
323
|
if (method === 'PATCH' || method === 'PUT') {
|
|
254
324
|
const body = await this.readJson(request);
|
|
255
|
-
const updated = await
|
|
325
|
+
const updated = await engine.updateTask(taskId, body);
|
|
256
326
|
if (!updated)
|
|
257
327
|
return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
258
328
|
return this.jsonResponse({ task: updated });
|
|
259
329
|
}
|
|
260
330
|
if (method === 'DELETE') {
|
|
261
|
-
const deleted = await
|
|
331
|
+
const deleted = await engine.deleteTask(taskId);
|
|
262
332
|
if (!deleted)
|
|
263
333
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
264
334
|
return this.jsonResponse({ success: true });
|
|
@@ -270,31 +340,31 @@ export class CriticalPathRouter {
|
|
|
270
340
|
const teamId = segments[1];
|
|
271
341
|
if (!teamId) {
|
|
272
342
|
if (method === 'GET') {
|
|
273
|
-
const teams = await
|
|
343
|
+
const teams = await engine.getTeams();
|
|
274
344
|
return this.jsonResponse({ teams });
|
|
275
345
|
}
|
|
276
346
|
if (method === 'POST') {
|
|
277
347
|
const body = await this.readJson(request);
|
|
278
|
-
const team = await
|
|
348
|
+
const team = await engine.createTeam(body);
|
|
279
349
|
return this.jsonResponse({ team }, 201);
|
|
280
350
|
}
|
|
281
351
|
}
|
|
282
352
|
else {
|
|
283
353
|
if (method === 'GET') {
|
|
284
|
-
const team = await
|
|
354
|
+
const team = await engine.getTeam(teamId);
|
|
285
355
|
if (!team)
|
|
286
356
|
return this.jsonResponse({ error: 'Team not found' }, 404);
|
|
287
357
|
return this.jsonResponse({ team });
|
|
288
358
|
}
|
|
289
359
|
if (method === 'PATCH' || method === 'PUT') {
|
|
290
360
|
const body = await this.readJson(request);
|
|
291
|
-
const updated = await
|
|
361
|
+
const updated = await engine.updateTeam(teamId, body);
|
|
292
362
|
if (!updated)
|
|
293
363
|
return this.jsonResponse({ error: 'Team not found' }, 404);
|
|
294
364
|
return this.jsonResponse({ team: updated });
|
|
295
365
|
}
|
|
296
366
|
if (method === 'DELETE') {
|
|
297
|
-
const deleted = await
|
|
367
|
+
const deleted = await engine.deleteTeam(teamId);
|
|
298
368
|
if (!deleted)
|
|
299
369
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
300
370
|
return this.jsonResponse({ success: true });
|
|
@@ -309,31 +379,31 @@ export class CriticalPathRouter {
|
|
|
309
379
|
const projectId = url.searchParams.get('projectId');
|
|
310
380
|
if (!projectId)
|
|
311
381
|
return this.jsonResponse({ error: 'projectId parameter required' }, 400);
|
|
312
|
-
const containers = await
|
|
382
|
+
const containers = await engine.getContainers(projectId);
|
|
313
383
|
return this.jsonResponse({ containers });
|
|
314
384
|
}
|
|
315
385
|
if (method === 'POST') {
|
|
316
386
|
const body = await this.readJson(request);
|
|
317
|
-
const container = await
|
|
387
|
+
const container = await engine.createContainer(body);
|
|
318
388
|
return this.jsonResponse({ container }, 201);
|
|
319
389
|
}
|
|
320
390
|
}
|
|
321
391
|
else {
|
|
322
392
|
if (method === 'GET') {
|
|
323
|
-
const container = await
|
|
393
|
+
const container = await engine.getContainer(containerId);
|
|
324
394
|
if (!container)
|
|
325
395
|
return this.jsonResponse({ error: 'Container not found' }, 404);
|
|
326
396
|
return this.jsonResponse({ container });
|
|
327
397
|
}
|
|
328
398
|
if (method === 'PATCH' || method === 'PUT') {
|
|
329
399
|
const body = await this.readJson(request);
|
|
330
|
-
const updated = await
|
|
400
|
+
const updated = await engine.updateContainer(containerId, body);
|
|
331
401
|
if (!updated)
|
|
332
402
|
return this.jsonResponse({ error: 'Container not found' }, 404);
|
|
333
403
|
return this.jsonResponse({ container: updated });
|
|
334
404
|
}
|
|
335
405
|
if (method === 'DELETE') {
|
|
336
|
-
const deleted = await
|
|
406
|
+
const deleted = await engine.deleteContainer(containerId);
|
|
337
407
|
if (!deleted)
|
|
338
408
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
339
409
|
return this.jsonResponse({ success: true });
|
|
@@ -348,18 +418,18 @@ export class CriticalPathRouter {
|
|
|
348
418
|
const projectId = url.searchParams.get('projectId');
|
|
349
419
|
if (!projectId)
|
|
350
420
|
return this.jsonResponse({ error: 'projectId parameter required' }, 400);
|
|
351
|
-
const deliverables = await
|
|
421
|
+
const deliverables = await engine.getDeliverables(projectId);
|
|
352
422
|
return this.jsonResponse({ deliverables });
|
|
353
423
|
}
|
|
354
424
|
if (method === 'POST') {
|
|
355
425
|
const body = await this.readJson(request);
|
|
356
|
-
const deliverable = await
|
|
426
|
+
const deliverable = await engine.createDeliverable(body);
|
|
357
427
|
return this.jsonResponse({ deliverable }, 201);
|
|
358
428
|
}
|
|
359
429
|
}
|
|
360
430
|
else if (segments[2] === 'summary') {
|
|
361
431
|
if (method === 'GET') {
|
|
362
|
-
const summary = await
|
|
432
|
+
const summary = await engine.getDeliverableSummary(deliverableId);
|
|
363
433
|
if (!summary)
|
|
364
434
|
return this.jsonResponse({ error: 'Deliverable not found' }, 404);
|
|
365
435
|
return this.jsonResponse({ summary });
|
|
@@ -367,20 +437,20 @@ export class CriticalPathRouter {
|
|
|
367
437
|
}
|
|
368
438
|
else {
|
|
369
439
|
if (method === 'GET') {
|
|
370
|
-
const deliverable = await
|
|
440
|
+
const deliverable = await engine.getDeliverable(deliverableId);
|
|
371
441
|
if (!deliverable)
|
|
372
442
|
return this.jsonResponse({ error: 'Deliverable not found' }, 404);
|
|
373
443
|
return this.jsonResponse({ deliverable });
|
|
374
444
|
}
|
|
375
445
|
if (method === 'PATCH' || method === 'PUT') {
|
|
376
446
|
const body = await this.readJson(request);
|
|
377
|
-
const updated = await
|
|
447
|
+
const updated = await engine.updateDeliverable(deliverableId, body);
|
|
378
448
|
if (!updated)
|
|
379
449
|
return this.jsonResponse({ error: 'Deliverable not found' }, 404);
|
|
380
450
|
return this.jsonResponse({ deliverable: updated });
|
|
381
451
|
}
|
|
382
452
|
if (method === 'DELETE') {
|
|
383
|
-
const deleted = await
|
|
453
|
+
const deleted = await engine.deleteDeliverable(deliverableId);
|
|
384
454
|
if (!deleted)
|
|
385
455
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
386
456
|
return this.jsonResponse({ success: true });
|
|
@@ -395,31 +465,31 @@ export class CriticalPathRouter {
|
|
|
395
465
|
const projectId = url.searchParams.get('projectId');
|
|
396
466
|
if (!projectId)
|
|
397
467
|
return this.jsonResponse({ error: 'projectId parameter required' }, 400);
|
|
398
|
-
const iterations = await
|
|
468
|
+
const iterations = await engine.getIterations(projectId);
|
|
399
469
|
return this.jsonResponse({ iterations });
|
|
400
470
|
}
|
|
401
471
|
if (method === 'POST') {
|
|
402
472
|
const body = await this.readJson(request);
|
|
403
|
-
const iteration = await
|
|
473
|
+
const iteration = await engine.createIteration(body);
|
|
404
474
|
return this.jsonResponse({ iteration }, 201);
|
|
405
475
|
}
|
|
406
476
|
}
|
|
407
477
|
else {
|
|
408
478
|
if (method === 'GET') {
|
|
409
|
-
const iteration = await
|
|
479
|
+
const iteration = await engine.getIteration(iterationId);
|
|
410
480
|
if (!iteration)
|
|
411
481
|
return this.jsonResponse({ error: 'Iteration not found' }, 404);
|
|
412
482
|
return this.jsonResponse({ iteration });
|
|
413
483
|
}
|
|
414
484
|
if (method === 'PATCH' || method === 'PUT') {
|
|
415
485
|
const body = await this.readJson(request);
|
|
416
|
-
const updated = await
|
|
486
|
+
const updated = await engine.updateIteration(iterationId, body);
|
|
417
487
|
if (!updated)
|
|
418
488
|
return this.jsonResponse({ error: 'Iteration not found' }, 404);
|
|
419
489
|
return this.jsonResponse({ iteration: updated });
|
|
420
490
|
}
|
|
421
491
|
if (method === 'DELETE') {
|
|
422
|
-
const deleted = await
|
|
492
|
+
const deleted = await engine.deleteIteration(iterationId);
|
|
423
493
|
if (!deleted)
|
|
424
494
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
425
495
|
return this.jsonResponse({ success: true });
|
|
@@ -431,7 +501,7 @@ export class CriticalPathRouter {
|
|
|
431
501
|
if (method === 'GET') {
|
|
432
502
|
const projectId = url.searchParams.get('projectId') || undefined;
|
|
433
503
|
const taskId = url.searchParams.get('taskId') || undefined;
|
|
434
|
-
const activities = await
|
|
504
|
+
const activities = await engine.store.getActivities({ projectId, taskId });
|
|
435
505
|
return this.jsonResponse({ activities });
|
|
436
506
|
}
|
|
437
507
|
}
|
|
@@ -442,10 +512,11 @@ export class CriticalPathRouter {
|
|
|
442
512
|
if (commentId && subResource === 'reactions') {
|
|
443
513
|
if (method === 'POST') {
|
|
444
514
|
const body = await this.readJson(request);
|
|
445
|
-
|
|
515
|
+
const userId = context?.userId ?? body.userId;
|
|
516
|
+
if (!body.emoji || !userId) {
|
|
446
517
|
return this.jsonResponse({ error: 'emoji and userId are required' }, 400);
|
|
447
518
|
}
|
|
448
|
-
const comment = await
|
|
519
|
+
const comment = await engine.addCommentReaction(commentId, { emoji: body.emoji, userId });
|
|
449
520
|
if (!comment)
|
|
450
521
|
return this.jsonResponse({ error: 'Comment not found' }, 404);
|
|
451
522
|
return this.jsonResponse({ comment }, 200);
|
|
@@ -459,11 +530,11 @@ export class CriticalPathRouter {
|
|
|
459
530
|
// Body may be empty on DELETE, fallback to searchParams
|
|
460
531
|
}
|
|
461
532
|
const emoji = body.emoji || url.searchParams.get('emoji');
|
|
462
|
-
const userId = body.userId || url.searchParams.get('userId');
|
|
533
|
+
const userId = context?.userId ?? (body.userId || url.searchParams.get('userId'));
|
|
463
534
|
if (!emoji || !userId) {
|
|
464
535
|
return this.jsonResponse({ error: 'emoji and userId are required' }, 400);
|
|
465
536
|
}
|
|
466
|
-
const comment = await
|
|
537
|
+
const comment = await engine.removeCommentReaction(commentId, { emoji, userId });
|
|
467
538
|
if (!comment)
|
|
468
539
|
return this.jsonResponse({ error: 'Comment not found' }, 404);
|
|
469
540
|
return this.jsonResponse({ comment }, 200);
|
|
@@ -474,31 +545,31 @@ export class CriticalPathRouter {
|
|
|
474
545
|
const taskId = url.searchParams.get('taskId');
|
|
475
546
|
if (!taskId)
|
|
476
547
|
return this.jsonResponse({ error: 'taskId parameter required' }, 400);
|
|
477
|
-
const comments = await
|
|
548
|
+
const comments = await engine.getComments(taskId);
|
|
478
549
|
return this.jsonResponse({ comments });
|
|
479
550
|
}
|
|
480
551
|
if (method === 'POST') {
|
|
481
552
|
const body = await this.readJson(request);
|
|
482
|
-
const comment = await
|
|
553
|
+
const comment = await engine.addComment(body);
|
|
483
554
|
return this.jsonResponse({ comment }, 201);
|
|
484
555
|
}
|
|
485
556
|
}
|
|
486
557
|
else {
|
|
487
558
|
if (method === 'GET') {
|
|
488
|
-
const comment = await
|
|
559
|
+
const comment = await engine.getComment(commentId);
|
|
489
560
|
if (!comment)
|
|
490
561
|
return this.jsonResponse({ error: 'Comment not found' }, 404);
|
|
491
562
|
return this.jsonResponse({ comment });
|
|
492
563
|
}
|
|
493
564
|
if (method === 'PATCH' || method === 'PUT') {
|
|
494
565
|
const body = await this.readJson(request);
|
|
495
|
-
const updated = await
|
|
566
|
+
const updated = await engine.updateComment(commentId, body);
|
|
496
567
|
if (!updated)
|
|
497
568
|
return this.jsonResponse({ error: 'Comment not found' }, 404);
|
|
498
569
|
return this.jsonResponse({ comment: updated });
|
|
499
570
|
}
|
|
500
571
|
if (method === 'DELETE') {
|
|
501
|
-
const deleted = await
|
|
572
|
+
const deleted = await engine.deleteComment(commentId);
|
|
502
573
|
if (!deleted)
|
|
503
574
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
504
575
|
return this.jsonResponse({ success: true });
|
|
@@ -511,14 +582,14 @@ export class CriticalPathRouter {
|
|
|
511
582
|
if (attachmentId === 'presign') {
|
|
512
583
|
if (method === 'POST') {
|
|
513
584
|
const body = await this.readJson(request);
|
|
514
|
-
const presigned = await
|
|
585
|
+
const presigned = await engine.getPresignedAttachmentUploadUrl(body);
|
|
515
586
|
return this.jsonResponse({ presigned });
|
|
516
587
|
}
|
|
517
588
|
}
|
|
518
589
|
else if (attachmentId === 'upload') {
|
|
519
590
|
if (method === 'POST') {
|
|
520
591
|
const body = await this.readJson(request);
|
|
521
|
-
const attachment = await
|
|
592
|
+
const attachment = await engine.uploadAttachmentFile(body);
|
|
522
593
|
return this.jsonResponse({ attachment }, 201);
|
|
523
594
|
}
|
|
524
595
|
}
|
|
@@ -527,24 +598,24 @@ export class CriticalPathRouter {
|
|
|
527
598
|
const taskId = url.searchParams.get('taskId') || undefined;
|
|
528
599
|
const projectId = url.searchParams.get('projectId') || undefined;
|
|
529
600
|
const commentId = url.searchParams.get('commentId') || undefined;
|
|
530
|
-
const attachments = await
|
|
601
|
+
const attachments = await engine.getAttachments({ taskId, projectId, commentId });
|
|
531
602
|
return this.jsonResponse({ attachments });
|
|
532
603
|
}
|
|
533
604
|
if (method === 'POST') {
|
|
534
605
|
const body = await this.readJson(request);
|
|
535
|
-
const attachment = await
|
|
606
|
+
const attachment = await engine.createAttachment(body);
|
|
536
607
|
return this.jsonResponse({ attachment }, 201);
|
|
537
608
|
}
|
|
538
609
|
}
|
|
539
610
|
else {
|
|
540
611
|
if (method === 'GET') {
|
|
541
|
-
const attachment = await
|
|
612
|
+
const attachment = await engine.getAttachment(attachmentId);
|
|
542
613
|
if (!attachment)
|
|
543
614
|
return this.jsonResponse({ error: 'Attachment not found' }, 404);
|
|
544
615
|
return this.jsonResponse({ attachment });
|
|
545
616
|
}
|
|
546
617
|
if (method === 'DELETE') {
|
|
547
|
-
const deleted = await
|
|
618
|
+
const deleted = await engine.deleteAttachment(attachmentId);
|
|
548
619
|
if (!deleted)
|
|
549
620
|
return this.jsonResponse({ error: 'Not found' }, 404);
|
|
550
621
|
return this.jsonResponse({ success: true });
|
|
@@ -557,12 +628,12 @@ export class CriticalPathRouter {
|
|
|
557
628
|
const taskId = url.searchParams.get('taskId');
|
|
558
629
|
if (!taskId)
|
|
559
630
|
return this.jsonResponse({ error: 'taskId parameter required' }, 400);
|
|
560
|
-
const entries = await
|
|
631
|
+
const entries = await engine.store.getTimeEntries(taskId);
|
|
561
632
|
return this.jsonResponse({ timeEntries: entries });
|
|
562
633
|
}
|
|
563
634
|
if (method === 'POST') {
|
|
564
635
|
const body = await this.readJson(request);
|
|
565
|
-
const entry = await
|
|
636
|
+
const entry = await engine.logTime(body);
|
|
566
637
|
return this.jsonResponse({ timeEntry: entry }, 201);
|
|
567
638
|
}
|
|
568
639
|
}
|
|
@@ -578,7 +649,7 @@ export class CriticalPathRouter {
|
|
|
578
649
|
const defaultWeeklyCapacityHours = url.searchParams.get('defaultWeeklyCapacityHours')
|
|
579
650
|
? parseFloat(url.searchParams.get('defaultWeeklyCapacityHours'))
|
|
580
651
|
: undefined;
|
|
581
|
-
const workload = await
|
|
652
|
+
const workload = await engine.getWorkloadDistribution(projectId, {
|
|
582
653
|
startDate,
|
|
583
654
|
endDate,
|
|
584
655
|
interval,
|
|
@@ -594,8 +665,8 @@ export class CriticalPathRouter {
|
|
|
594
665
|
if (method === 'POST') {
|
|
595
666
|
const body = await this.readJson(request);
|
|
596
667
|
const status = typeof body.status === 'string' ? body.status : 'active';
|
|
597
|
-
if (
|
|
598
|
-
|
|
668
|
+
if (engine.events) {
|
|
669
|
+
engine.events.publish({
|
|
599
670
|
type: 'agent.status_updated',
|
|
600
671
|
aggregateId: body.taskId || body.projectId || 'system',
|
|
601
672
|
payload: {
|
|
@@ -668,8 +739,7 @@ export class CriticalPathRouter {
|
|
|
668
739
|
return new Response(JSON.stringify(data), {
|
|
669
740
|
status,
|
|
670
741
|
headers: {
|
|
671
|
-
'Content-Type': 'application/json'
|
|
672
|
-
...CORS_HEADERS
|
|
742
|
+
'Content-Type': 'application/json'
|
|
673
743
|
}
|
|
674
744
|
});
|
|
675
745
|
}
|