@plandesk/api 0.7.0 → 0.9.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/events.d.ts +20 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +5 -1
- package/dist/routes/agent-runs.d.ts +1 -1
- package/dist/routes/agent-runs.js +20 -0
- package/dist/routes/documents.js +2 -0
- package/dist/routes/folders.d.ts +4 -0
- package/dist/routes/folders.js +69 -0
- package/dist/routes/goals.d.ts +4 -0
- package/dist/routes/goals.js +138 -0
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.js +23 -3
- package/dist/routes/submissions.d.ts +5 -0
- package/dist/routes/submissions.js +58 -0
- package/dist/routes/tags.d.ts +4 -0
- package/dist/routes/tags.js +62 -0
- package/dist/routes/tasks.d.ts +1 -0
- package/dist/routes/tasks.js +9 -1
- package/dist/serialize.d.ts +51 -2
- package/dist/serialize.js +101 -1
- package/dist/server.js +9 -1
- package/dist/services/canvas.d.ts +4 -0
- package/dist/services/canvas.js +3 -2
- package/dist/services/documents.d.ts +5 -1
- package/dist/services/documents.js +31 -2
- package/dist/services/folders.d.ts +27 -0
- package/dist/services/folders.js +107 -0
- package/dist/services/goals.d.ts +239 -0
- package/dist/services/goals.js +323 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +9 -0
- package/dist/services/projects.js +8 -2
- package/dist/services/sync.d.ts +5 -3
- package/dist/services/sync.js +45 -2
- package/dist/services/tags.d.ts +27 -0
- package/dist/services/tags.js +79 -0
- package/dist/services/tasks.d.ts +20 -2
- package/dist/services/tasks.js +98 -26
- package/package.json +3 -3
- package/web/assets/index-DcD7-VGi.css +1 -0
- package/web/assets/index-Dcaq3wR-.js +318 -0
- package/web/index.html +2 -2
- package/web/assets/index-CUxSE_WZ.css +0 -1
- package/web/assets/index-cyyf9-Uw.js +0 -234
package/dist/events.d.ts
CHANGED
|
@@ -24,6 +24,20 @@ export type CommentUpdatedEvent = {
|
|
|
24
24
|
documentId: string;
|
|
25
25
|
projectId: string;
|
|
26
26
|
};
|
|
27
|
+
export type FolderCreatedEvent = {
|
|
28
|
+
type: 'folder_created';
|
|
29
|
+
folderId: string;
|
|
30
|
+
projectId: string;
|
|
31
|
+
};
|
|
32
|
+
export type FolderUpdatedEvent = {
|
|
33
|
+
type: 'folder_updated';
|
|
34
|
+
folderId: string;
|
|
35
|
+
projectId: string;
|
|
36
|
+
};
|
|
37
|
+
export type TagUpdatedEvent = {
|
|
38
|
+
type: 'tag_updated';
|
|
39
|
+
projectId: string;
|
|
40
|
+
};
|
|
27
41
|
export type NoteCreatedEvent = {
|
|
28
42
|
type: 'note_created';
|
|
29
43
|
noteId: string;
|
|
@@ -53,7 +67,12 @@ export type SubmissionsPulledEvent = {
|
|
|
53
67
|
type: 'submissions_pulled';
|
|
54
68
|
projectId: string;
|
|
55
69
|
};
|
|
56
|
-
export type
|
|
70
|
+
export type GoalUpdatedEvent = {
|
|
71
|
+
type: 'goal_updated';
|
|
72
|
+
goalId: string;
|
|
73
|
+
projectId: string;
|
|
74
|
+
};
|
|
75
|
+
export type PlankDeskEvent = TaskUpdatedEvent | TagUpdatedEvent | CanvasUpdatedEvent | DocumentCreatedEvent | CommentCreatedEvent | CommentUpdatedEvent | FolderCreatedEvent | FolderUpdatedEvent | NoteCreatedEvent | NoteUpdatedEvent | AgentRunStartedEvent | AgentRunProgressEvent | AgentRunCompletedEvent | SubmissionsPulledEvent | GoalUpdatedEvent;
|
|
57
76
|
export type EventListener = (event: PlankDeskEvent) => void;
|
|
58
77
|
export type EventBus = {
|
|
59
78
|
subscribe(listener: EventListener): () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,19 +5,26 @@ export { mountStatic } from './static.js';
|
|
|
5
5
|
export { createServices, type Services, type ServicesDeps } from './services/index.js';
|
|
6
6
|
export { createEventBus, type EventBus, type PlankDeskEvent } from './events.js';
|
|
7
7
|
export type { ProjectService } from './services/projects.js';
|
|
8
|
+
export type { GoalService, VerificationEvidence } from './services/goals.js';
|
|
9
|
+
export { GoalCompletionBlockedError, GoalVerificationRequiredError, InvalidGoalTransitionError, InvalidVerificationSurfaceError, } from './services/goals.js';
|
|
8
10
|
export type { TaskService } from './services/tasks.js';
|
|
11
|
+
export { InvalidGoalReferenceError } from './services/tasks.js';
|
|
12
|
+
export type { TagService } from './services/tags.js';
|
|
9
13
|
export type { CanvasService } from './services/canvas.js';
|
|
10
14
|
export type { DocumentService } from './services/documents.js';
|
|
15
|
+
export type { FolderService } from './services/folders.js';
|
|
11
16
|
export type { NoteService } from './services/notes.js';
|
|
12
17
|
export type { CommentService } from './services/comments.js';
|
|
13
18
|
export type { AgentRunService } from './services/agent-runs.js';
|
|
14
19
|
export { InvalidDocumentError } from './services/documents.js';
|
|
20
|
+
export { InvalidFolderError } from './services/folders.js';
|
|
15
21
|
export { InvalidNoteError } from './services/notes.js';
|
|
22
|
+
export { InvalidTagError } from './services/tags.js';
|
|
16
23
|
export { InvalidCommentError } from './services/comments.js';
|
|
17
24
|
export { InvalidCanvasError } from './services/canvas.js';
|
|
18
25
|
export { InvalidAgentRunError } from './services/agent-runs.js';
|
|
19
26
|
export { InvalidScaffoldError } from './services/projects.js';
|
|
20
27
|
export { InvalidShareError } from './services/share.js';
|
|
21
|
-
export { InvalidTriageError, SyncUnavailableError, SyncUnauthorizedError, type SyncRemote, type SyncService, } from './services/sync.js';
|
|
28
|
+
export { InvalidTriageError, InvalidTriageInputError, SyncUnavailableError, SyncUnauthorizedError, type SyncRemote, type SyncService, } from './services/sync.js';
|
|
22
29
|
export declare const version: () => string;
|
|
23
30
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -4,14 +4,18 @@ export { healthRouter } from './routes/health.js';
|
|
|
4
4
|
export { mountStatic } from './static.js';
|
|
5
5
|
export { createServices } from './services/index.js';
|
|
6
6
|
export { createEventBus } from './events.js';
|
|
7
|
+
export { GoalCompletionBlockedError, GoalVerificationRequiredError, InvalidGoalTransitionError, InvalidVerificationSurfaceError, } from './services/goals.js';
|
|
8
|
+
export { InvalidGoalReferenceError } from './services/tasks.js';
|
|
7
9
|
export { InvalidDocumentError } from './services/documents.js';
|
|
10
|
+
export { InvalidFolderError } from './services/folders.js';
|
|
8
11
|
export { InvalidNoteError } from './services/notes.js';
|
|
12
|
+
export { InvalidTagError } from './services/tags.js';
|
|
9
13
|
export { InvalidCommentError } from './services/comments.js';
|
|
10
14
|
export { InvalidCanvasError } from './services/canvas.js';
|
|
11
15
|
export { InvalidAgentRunError } from './services/agent-runs.js';
|
|
12
16
|
export { InvalidScaffoldError } from './services/projects.js';
|
|
13
17
|
export { InvalidShareError } from './services/share.js';
|
|
14
|
-
export { InvalidTriageError, SyncUnavailableError, SyncUnauthorizedError, } from './services/sync.js';
|
|
18
|
+
export { InvalidTriageError, InvalidTriageInputError, SyncUnavailableError, SyncUnauthorizedError, } from './services/sync.js';
|
|
15
19
|
import { readFileSync } from 'node:fs';
|
|
16
20
|
import { dirname, join } from 'node:path';
|
|
17
21
|
import { fileURLToPath } from 'node:url';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentRunService } from '../services/agent-runs.js';
|
|
3
3
|
export declare function createAgentRunsRouter(agentRunService: AgentRunService): Hono;
|
|
4
4
|
//# sourceMappingURL=agent-runs.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
+
import { InvalidAgentRunError } from '../services/agent-runs.js';
|
|
2
3
|
import { parsePaginationParams } from '../serialize.js';
|
|
3
4
|
export function createAgentRunsRouter(agentRunService) {
|
|
4
5
|
const router = new Hono();
|
|
@@ -13,6 +14,25 @@ export function createAgentRunsRouter(agentRunService) {
|
|
|
13
14
|
}
|
|
14
15
|
return c.json(runs);
|
|
15
16
|
});
|
|
17
|
+
router.post('/agent-runs/:id/progress', async (c) => {
|
|
18
|
+
const body = await c.req.json();
|
|
19
|
+
if (typeof body.message !== 'string' || body.message.trim() === '') {
|
|
20
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const event = agentRunService.recordProgress(c.req.param('id'), body.message);
|
|
24
|
+
if (!event) {
|
|
25
|
+
return c.json({ error: 'not_found' }, 404);
|
|
26
|
+
}
|
|
27
|
+
return c.json(event, 201);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof InvalidAgentRunError) {
|
|
31
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
16
36
|
return router;
|
|
17
37
|
}
|
|
18
38
|
//# sourceMappingURL=agent-runs.js.map
|
package/dist/routes/documents.js
CHANGED
|
@@ -37,6 +37,7 @@ export function createDocumentsRouter(documentService) {
|
|
|
37
37
|
body: body.body,
|
|
38
38
|
statusLine: body.status_line,
|
|
39
39
|
parentId: body.parent_id,
|
|
40
|
+
folderId: body.folder_id,
|
|
40
41
|
linkedTaskId: resolveLinkedTaskId(body),
|
|
41
42
|
});
|
|
42
43
|
if (!document) {
|
|
@@ -67,6 +68,7 @@ export function createDocumentsRouter(documentService) {
|
|
|
67
68
|
...(body.body !== undefined ? { body: body.body } : {}),
|
|
68
69
|
...(body.status_line !== undefined ? { statusLine: body.status_line } : {}),
|
|
69
70
|
...(body.parent_id !== undefined ? { parentId: body.parent_id } : {}),
|
|
71
|
+
...(body.folder_id !== undefined ? { folderId: body.folder_id } : {}),
|
|
70
72
|
...(linkedTaskId !== undefined ? { linkedTaskId } : {}),
|
|
71
73
|
});
|
|
72
74
|
if (!document) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { InvalidFolderError } from '../services/folders.js';
|
|
3
|
+
export function createFoldersRouter(folderService) {
|
|
4
|
+
const router = new Hono();
|
|
5
|
+
router.get('/projects/:id/folders', (c) => {
|
|
6
|
+
const folders = folderService.list(c.req.param('id'));
|
|
7
|
+
if (!folders) {
|
|
8
|
+
return c.json({ error: 'not_found' }, 404);
|
|
9
|
+
}
|
|
10
|
+
return c.json(folders);
|
|
11
|
+
});
|
|
12
|
+
router.post('/projects/:id/folders', async (c) => {
|
|
13
|
+
const body = await c.req.json();
|
|
14
|
+
if (typeof body.name !== 'string' || body.name.trim() === '') {
|
|
15
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const folder = folderService.create(c.req.param('id'), {
|
|
19
|
+
name: body.name,
|
|
20
|
+
parentFolderId: body.parent_folder_id,
|
|
21
|
+
});
|
|
22
|
+
if (!folder) {
|
|
23
|
+
return c.json({ error: 'not_found' }, 404);
|
|
24
|
+
}
|
|
25
|
+
return c.json(folder, 201);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof InvalidFolderError) {
|
|
29
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
router.get('/folders/:id', (c) => {
|
|
35
|
+
const folder = folderService.get(c.req.param('id'));
|
|
36
|
+
if (!folder) {
|
|
37
|
+
return c.json({ error: 'not_found' }, 404);
|
|
38
|
+
}
|
|
39
|
+
return c.json(folder);
|
|
40
|
+
});
|
|
41
|
+
router.patch('/folders/:id', async (c) => {
|
|
42
|
+
const body = await c.req.json();
|
|
43
|
+
try {
|
|
44
|
+
const folder = folderService.update(c.req.param('id'), {
|
|
45
|
+
...(body.name !== undefined ? { name: body.name } : {}),
|
|
46
|
+
...(body.parent_folder_id !== undefined ? { parentFolderId: body.parent_folder_id } : {}),
|
|
47
|
+
});
|
|
48
|
+
if (!folder) {
|
|
49
|
+
return c.json({ error: 'not_found' }, 404);
|
|
50
|
+
}
|
|
51
|
+
return c.json(folder);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof InvalidFolderError) {
|
|
55
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
router.delete('/folders/:id', (c) => {
|
|
61
|
+
const deleted = folderService.delete(c.req.param('id'));
|
|
62
|
+
if (!deleted) {
|
|
63
|
+
return c.json({ error: 'not_found' }, 404);
|
|
64
|
+
}
|
|
65
|
+
return c.body(null, 204);
|
|
66
|
+
});
|
|
67
|
+
return router;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=folders.js.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { InvalidGoalStatusError, isGoalStatus } from '@plandesk/db';
|
|
3
|
+
import { GoalCompletionBlockedError, GoalVerificationRequiredError, InvalidGoalTransitionError, InvalidVerificationSurfaceError, } from '../services/goals.js';
|
|
4
|
+
function mapGoalInput(body) {
|
|
5
|
+
return {
|
|
6
|
+
...(body.objective !== undefined ? { objective: body.objective } : {}),
|
|
7
|
+
...(body.verification_surface !== undefined
|
|
8
|
+
? { verificationSurface: body.verification_surface }
|
|
9
|
+
: {}),
|
|
10
|
+
...(body.constraints !== undefined ? { constraints: body.constraints } : {}),
|
|
11
|
+
...(body.boundaries !== undefined ? { boundaries: body.boundaries } : {}),
|
|
12
|
+
...(body.iteration_policy !== undefined ? { iterationPolicy: body.iteration_policy } : {}),
|
|
13
|
+
...(body.stop_condition !== undefined ? { stopCondition: body.stop_condition } : {}),
|
|
14
|
+
...(body.budget !== undefined ? { budget: body.budget } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function handleGoalError(c, error) {
|
|
18
|
+
if (error instanceof InvalidGoalStatusError || error instanceof InvalidGoalTransitionError) {
|
|
19
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
20
|
+
}
|
|
21
|
+
if (error instanceof InvalidVerificationSurfaceError) {
|
|
22
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
23
|
+
}
|
|
24
|
+
if (error instanceof GoalVerificationRequiredError) {
|
|
25
|
+
return c.json({
|
|
26
|
+
error: 'verification_required',
|
|
27
|
+
required_kind: error.requiredKind,
|
|
28
|
+
}, 400);
|
|
29
|
+
}
|
|
30
|
+
if (error instanceof GoalCompletionBlockedError) {
|
|
31
|
+
return c.json({
|
|
32
|
+
error: 'blocked_by_incomplete_tasks',
|
|
33
|
+
incomplete_task_ids: error.incompleteTaskIds,
|
|
34
|
+
}, 400);
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
export function createGoalsRouter(goalService) {
|
|
39
|
+
const router = new Hono();
|
|
40
|
+
router.post('/projects/:id/goals', async (c) => {
|
|
41
|
+
const body = await c.req.json();
|
|
42
|
+
if (typeof body.objective !== 'string' || body.objective.trim() === '') {
|
|
43
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
44
|
+
}
|
|
45
|
+
if (body.status !== undefined && !isGoalStatus(body.status)) {
|
|
46
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const goal = goalService.create(c.req.param('id'), {
|
|
50
|
+
objective: body.objective,
|
|
51
|
+
...(body.status !== undefined ? { status: body.status } : {}),
|
|
52
|
+
...mapGoalInput(body),
|
|
53
|
+
});
|
|
54
|
+
if (!goal) {
|
|
55
|
+
return c.json({ error: 'not_found' }, 404);
|
|
56
|
+
}
|
|
57
|
+
return c.json(goal, 201);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return handleGoalError(c, error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
router.get('/projects/:id/goals', (c) => {
|
|
64
|
+
const goals = goalService.listByProject(c.req.param('id'));
|
|
65
|
+
if (!goals) {
|
|
66
|
+
return c.json({ error: 'not_found' }, 404);
|
|
67
|
+
}
|
|
68
|
+
return c.json(goals);
|
|
69
|
+
});
|
|
70
|
+
router.get('/goals/:id', (c) => {
|
|
71
|
+
const goal = goalService.get(c.req.param('id'));
|
|
72
|
+
if (!goal) {
|
|
73
|
+
return c.json({ error: 'not_found' }, 404);
|
|
74
|
+
}
|
|
75
|
+
return c.json(goal);
|
|
76
|
+
});
|
|
77
|
+
router.patch('/goals/:id', async (c) => {
|
|
78
|
+
const body = await c.req.json();
|
|
79
|
+
if (body.objective !== undefined && body.objective.trim() === '') {
|
|
80
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const goal = goalService.update(c.req.param('id'), mapGoalInput(body));
|
|
84
|
+
if (!goal) {
|
|
85
|
+
return c.json({ error: 'not_found' }, 404);
|
|
86
|
+
}
|
|
87
|
+
return c.json(goal);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return handleGoalError(c, error);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
router.post('/goals/:id/pause', (c) => {
|
|
94
|
+
try {
|
|
95
|
+
const goal = goalService.pause(c.req.param('id'));
|
|
96
|
+
if (!goal) {
|
|
97
|
+
return c.json({ error: 'not_found' }, 404);
|
|
98
|
+
}
|
|
99
|
+
return c.json(goal);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return handleGoalError(c, error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
router.post('/goals/:id/resume', (c) => {
|
|
106
|
+
try {
|
|
107
|
+
const goal = goalService.resume(c.req.param('id'));
|
|
108
|
+
if (!goal) {
|
|
109
|
+
return c.json({ error: 'not_found' }, 404);
|
|
110
|
+
}
|
|
111
|
+
return c.json(goal);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return handleGoalError(c, error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
router.post('/goals/:id/complete', async (c) => {
|
|
118
|
+
let body = {};
|
|
119
|
+
try {
|
|
120
|
+
body = await c.req.json();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// empty body is valid when the goal has no verification_surface
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const goal = goalService.complete(c.req.param('id'), body.evidence);
|
|
127
|
+
if (!goal) {
|
|
128
|
+
return c.json({ error: 'not_found' }, 404);
|
|
129
|
+
}
|
|
130
|
+
return c.json(goal);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return handleGoalError(c, error);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return router;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=goals.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import type { ProjectService } from '../services/projects.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type TaskService } from '../services/tasks.js';
|
|
4
4
|
export declare function createProjectsRouter(projectService: ProjectService, taskService: TaskService): Hono;
|
|
5
5
|
//# sourceMappingURL=projects.d.ts.map
|
package/dist/routes/projects.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { InvalidTaskStatusError, isTaskStatus } from '@plandesk/db';
|
|
3
|
+
import { InvalidGoalReferenceError } from '../services/tasks.js';
|
|
4
|
+
import { InvalidTagError } from '../services/tags.js';
|
|
5
|
+
import { isStringArray } from './tasks.js';
|
|
3
6
|
import { parsePaginationParams } from '../serialize.js';
|
|
4
7
|
export function createProjectsRouter(projectService, taskService) {
|
|
5
8
|
const router = new Hono();
|
|
@@ -57,6 +60,9 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
57
60
|
if (body.status !== undefined && !isTaskStatus(body.status)) {
|
|
58
61
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
59
62
|
}
|
|
63
|
+
if (body.tags !== undefined && !isStringArray(body.tags)) {
|
|
64
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
65
|
+
}
|
|
60
66
|
let dueDate;
|
|
61
67
|
if (body.due_date !== undefined && body.due_date !== null) {
|
|
62
68
|
dueDate = new Date(body.due_date);
|
|
@@ -76,6 +82,8 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
76
82
|
...(body.y !== undefined ? { y: body.y } : {}),
|
|
77
83
|
...(body.assignee !== undefined ? { assignee: body.assignee } : {}),
|
|
78
84
|
...(dueDate !== undefined ? { dueDate } : {}),
|
|
85
|
+
...(body.goal_id !== undefined ? { goalId: body.goal_id } : {}),
|
|
86
|
+
...(body.tags !== undefined ? { tags: body.tags } : {}),
|
|
79
87
|
});
|
|
80
88
|
if (!task) {
|
|
81
89
|
return c.json({ error: 'not_found' }, 404);
|
|
@@ -83,7 +91,9 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
83
91
|
return c.json(task, 201);
|
|
84
92
|
}
|
|
85
93
|
catch (error) {
|
|
86
|
-
if (error instanceof InvalidTaskStatusError
|
|
94
|
+
if (error instanceof InvalidTaskStatusError ||
|
|
95
|
+
error instanceof InvalidTagError ||
|
|
96
|
+
error instanceof InvalidGoalReferenceError) {
|
|
87
97
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
88
98
|
}
|
|
89
99
|
throw error;
|
|
@@ -96,19 +106,29 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
96
106
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
97
107
|
}
|
|
98
108
|
const status = c.req.query('status');
|
|
99
|
-
|
|
109
|
+
// Repeated ?tag= params filter with OR semantics (task matches if it has
|
|
110
|
+
// ANY of the given tags).
|
|
111
|
+
const tags = c.req.queries('tag');
|
|
112
|
+
const tasks = taskService.listByProject(c.req.param('id'), { status, ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }, pagination);
|
|
100
113
|
if (!tasks) {
|
|
101
114
|
return c.json({ error: 'not_found' }, 404);
|
|
102
115
|
}
|
|
103
116
|
return c.json(tasks);
|
|
104
117
|
}
|
|
105
118
|
catch (error) {
|
|
106
|
-
if (error instanceof InvalidTaskStatusError) {
|
|
119
|
+
if (error instanceof InvalidTaskStatusError || error instanceof InvalidTagError) {
|
|
107
120
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
108
121
|
}
|
|
109
122
|
throw error;
|
|
110
123
|
}
|
|
111
124
|
});
|
|
125
|
+
router.get('/projects/:id/next-task', (c) => {
|
|
126
|
+
const result = taskService.nextActionable(c.req.param('id'));
|
|
127
|
+
if (!result) {
|
|
128
|
+
return c.json({ error: 'not_found' }, 404);
|
|
129
|
+
}
|
|
130
|
+
return c.json(result);
|
|
131
|
+
});
|
|
112
132
|
return router;
|
|
113
133
|
}
|
|
114
134
|
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { type SyncService } from '../services/sync.js';
|
|
3
|
+
import type { ProjectService } from '../services/projects.js';
|
|
4
|
+
export declare function createSubmissionsRouter(syncService: SyncService, projectService: ProjectService): Hono;
|
|
5
|
+
//# sourceMappingURL=submissions.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { shareSubmissionStatuses } from '@plandesk/db';
|
|
3
|
+
import { InvalidTriageError, SyncUnauthorizedError, SyncUnavailableError, } from '../services/sync.js';
|
|
4
|
+
function isSubmissionStatus(value) {
|
|
5
|
+
return shareSubmissionStatuses.includes(value);
|
|
6
|
+
}
|
|
7
|
+
export function createSubmissionsRouter(syncService, projectService) {
|
|
8
|
+
const router = new Hono();
|
|
9
|
+
router.get('/projects/:id/submissions', (c) => {
|
|
10
|
+
const projectId = c.req.param('id');
|
|
11
|
+
if (!projectService.get(projectId)) {
|
|
12
|
+
return c.json({ error: 'not_found' }, 404);
|
|
13
|
+
}
|
|
14
|
+
const statusParam = c.req.query('status') ?? 'pending';
|
|
15
|
+
if (!isSubmissionStatus(statusParam)) {
|
|
16
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
17
|
+
}
|
|
18
|
+
return c.json(syncService.listTriage(projectId, statusParam));
|
|
19
|
+
});
|
|
20
|
+
router.post('/submissions/:id/triage', async (c) => {
|
|
21
|
+
const submissionId = c.req.param('id');
|
|
22
|
+
const body = await c.req.json();
|
|
23
|
+
if (body.action !== 'accept' && body.action !== 'reject') {
|
|
24
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
25
|
+
}
|
|
26
|
+
const submission = syncService.getSubmission(submissionId);
|
|
27
|
+
if (submission === undefined) {
|
|
28
|
+
return c.json({ error: 'not_found' }, 404);
|
|
29
|
+
}
|
|
30
|
+
const remote = syncService.getRemote(submission.project_id);
|
|
31
|
+
if (remote === undefined) {
|
|
32
|
+
return c.json({ error: 'not_published' }, 400);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
// Triage never creates a `todo` task — the scope->todo release is the human's own
|
|
36
|
+
// board action, enforced in syncService.triage(). A merge (link_task_id) links to
|
|
37
|
+
// an existing task instead of creating one; the two are mutually exclusive, so
|
|
38
|
+
// only one is forwarded.
|
|
39
|
+
const linkTaskId = body.action === 'accept' ? body.link_task_id : undefined;
|
|
40
|
+
const result = await syncService.triage(submissionId, body.action, remote, body.action === 'accept' && linkTaskId === undefined ? body.as_task : undefined, linkTaskId);
|
|
41
|
+
return c.json(result);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof InvalidTriageError) {
|
|
45
|
+
return c.json({ error: 'not_found' }, 404);
|
|
46
|
+
}
|
|
47
|
+
if (error instanceof SyncUnauthorizedError) {
|
|
48
|
+
return c.json({ error: 'sync_unauthorized' }, 400);
|
|
49
|
+
}
|
|
50
|
+
if (error instanceof SyncUnavailableError) {
|
|
51
|
+
return c.json({ error: 'sync_unavailable' }, 502);
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return router;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=submissions.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { InvalidTagError } from '../services/tags.js';
|
|
3
|
+
export function createTagsRouter(tagService) {
|
|
4
|
+
const router = new Hono();
|
|
5
|
+
router.get('/projects/:id/tags', (c) => {
|
|
6
|
+
const tags = tagService.list(c.req.param('id'));
|
|
7
|
+
if (!tags) {
|
|
8
|
+
return c.json({ error: 'not_found' }, 404);
|
|
9
|
+
}
|
|
10
|
+
return c.json(tags);
|
|
11
|
+
});
|
|
12
|
+
router.post('/projects/:id/tags', async (c) => {
|
|
13
|
+
const body = await c.req.json();
|
|
14
|
+
if (typeof body.name !== 'string' || body.name.trim() === '') {
|
|
15
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const tag = tagService.create(c.req.param('id'), {
|
|
19
|
+
name: body.name,
|
|
20
|
+
color: body.color,
|
|
21
|
+
});
|
|
22
|
+
if (!tag) {
|
|
23
|
+
return c.json({ error: 'not_found' }, 404);
|
|
24
|
+
}
|
|
25
|
+
return c.json(tag, 201);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof InvalidTagError) {
|
|
29
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
router.patch('/tags/:id', async (c) => {
|
|
35
|
+
const body = await c.req.json();
|
|
36
|
+
try {
|
|
37
|
+
const tag = tagService.update(c.req.param('id'), {
|
|
38
|
+
...(body.name !== undefined ? { name: body.name } : {}),
|
|
39
|
+
...(body.color !== undefined ? { color: body.color } : {}),
|
|
40
|
+
});
|
|
41
|
+
if (!tag) {
|
|
42
|
+
return c.json({ error: 'not_found' }, 404);
|
|
43
|
+
}
|
|
44
|
+
return c.json(tag);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof InvalidTagError) {
|
|
48
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
router.delete('/tags/:id', (c) => {
|
|
54
|
+
const deleted = tagService.delete(c.req.param('id'));
|
|
55
|
+
if (!deleted) {
|
|
56
|
+
return c.json({ error: 'not_found' }, 404);
|
|
57
|
+
}
|
|
58
|
+
return c.body(null, 204);
|
|
59
|
+
});
|
|
60
|
+
return router;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=tags.js.map
|
package/dist/routes/tasks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import type { TaskService } from '../services/tasks.js';
|
|
3
|
+
export declare function isStringArray(value: unknown): value is string[];
|
|
3
4
|
export declare function createTasksRouter(taskService: TaskService): Hono;
|
|
4
5
|
//# sourceMappingURL=tasks.d.ts.map
|
package/dist/routes/tasks.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { InvalidTaskStatusError, isTaskStatus } from '@plandesk/db';
|
|
3
|
+
import { InvalidTagError } from '../services/tags.js';
|
|
4
|
+
export function isStringArray(value) {
|
|
5
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
6
|
+
}
|
|
3
7
|
export function createTasksRouter(taskService) {
|
|
4
8
|
const router = new Hono();
|
|
5
9
|
router.patch('/tasks/:id', async (c) => {
|
|
@@ -7,6 +11,9 @@ export function createTasksRouter(taskService) {
|
|
|
7
11
|
if (body.status !== undefined && !isTaskStatus(body.status)) {
|
|
8
12
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
9
13
|
}
|
|
14
|
+
if (body.tags !== undefined && !isStringArray(body.tags)) {
|
|
15
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
|
+
}
|
|
10
17
|
try {
|
|
11
18
|
const task = taskService.update(c.req.param('id'), {
|
|
12
19
|
...(body.label !== undefined ? { label: body.label } : {}),
|
|
@@ -14,6 +21,7 @@ export function createTasksRouter(taskService) {
|
|
|
14
21
|
...(body.description !== undefined ? { description: body.description } : {}),
|
|
15
22
|
...(body.x !== undefined ? { x: body.x } : {}),
|
|
16
23
|
...(body.y !== undefined ? { y: body.y } : {}),
|
|
24
|
+
...(body.tags !== undefined ? { tags: body.tags } : {}),
|
|
17
25
|
});
|
|
18
26
|
if (!task) {
|
|
19
27
|
return c.json({ error: 'not_found' }, 404);
|
|
@@ -21,7 +29,7 @@ export function createTasksRouter(taskService) {
|
|
|
21
29
|
return c.json(task);
|
|
22
30
|
}
|
|
23
31
|
catch (error) {
|
|
24
|
-
if (error instanceof InvalidTaskStatusError) {
|
|
32
|
+
if (error instanceof InvalidTaskStatusError || error instanceof InvalidTagError) {
|
|
25
33
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
26
34
|
}
|
|
27
35
|
throw error;
|