@plandesk/api 0.20.0 → 1.0.0-beta.1
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/access-control.d.ts +150 -0
- package/dist/access-control.js +63 -0
- package/dist/agent-keys.d.ts +81 -0
- package/dist/agent-keys.js +181 -0
- package/dist/auth-context.d.ts +60 -0
- package/dist/auth-context.js +41 -0
- package/dist/auth.d.ts +33 -0
- package/dist/auth.js +357 -0
- package/dist/better-auth.d.ts +66 -0
- package/dist/better-auth.js +106 -0
- package/dist/github.d.ts +57 -0
- package/dist/github.js +115 -0
- package/dist/hosted-auth.d.ts +23 -0
- package/dist/hosted-auth.js +49 -0
- package/dist/identity.d.ts +38 -0
- package/dist/identity.js +163 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +12 -2
- package/dist/invitations.d.ts +94 -0
- package/dist/invitations.js +211 -0
- package/dist/organizations.d.ts +32 -0
- package/dist/organizations.js +63 -0
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.js +42 -0
- package/dist/projection.d.ts +1 -1
- package/dist/projection.js +5 -5
- package/dist/routes/agent-runs.js +3 -3
- package/dist/routes/artifacts.js +6 -6
- package/dist/routes/auth.d.ts +13 -0
- package/dist/routes/auth.js +83 -0
- package/dist/routes/canvas.js +5 -5
- package/dist/routes/comments.js +11 -11
- package/dist/routes/documents.js +10 -10
- package/dist/routes/folders.js +8 -8
- package/dist/routes/goals.js +11 -11
- package/dist/routes/notes.js +8 -8
- package/dist/routes/orgs.d.ts +17 -0
- package/dist/routes/orgs.js +241 -0
- package/dist/routes/projects.js +13 -13
- package/dist/routes/shares.js +92 -3
- package/dist/routes/submissions.js +7 -8
- package/dist/routes/tags.js +6 -6
- package/dist/routes/tasks.js +14 -3
- package/dist/routes/tokens.js +4 -4
- package/dist/serialize.d.ts +3 -3
- package/dist/server.d.ts +20 -2
- package/dist/server.js +57 -9
- package/dist/services/agent-runs.d.ts +13 -14
- package/dist/services/agent-runs.js +39 -41
- package/dist/services/artifacts.d.ts +6 -7
- package/dist/services/artifacts.js +30 -26
- package/dist/services/canvas.d.ts +11 -12
- package/dist/services/canvas.js +58 -41
- package/dist/services/comments.d.ts +11 -12
- package/dist/services/comments.js +44 -64
- package/dist/services/documents.d.ts +10 -11
- package/dist/services/documents.js +74 -55
- package/dist/services/files.d.ts +2 -1
- package/dist/services/files.js +13 -5
- package/dist/services/folders.d.ts +7 -8
- package/dist/services/folders.js +45 -33
- package/dist/services/goals.d.ts +24 -25
- package/dist/services/goals.js +60 -53
- package/dist/services/index.d.ts +2 -5
- package/dist/services/index.js +15 -20
- package/dist/services/notes.d.ts +7 -8
- package/dist/services/notes.js +35 -23
- package/dist/services/org-scope.d.ts +22 -0
- package/dist/services/org-scope.js +38 -0
- package/dist/services/projects.d.ts +12 -13
- package/dist/services/projects.js +90 -58
- package/dist/services/scope.d.ts +12 -0
- package/dist/services/scope.js +29 -0
- package/dist/services/share.d.ts +74 -10
- package/dist/services/share.js +213 -23
- package/dist/services/sync.d.ts +7 -29
- package/dist/services/sync.js +37 -142
- package/dist/services/tags.d.ts +6 -7
- package/dist/services/tags.js +34 -22
- package/dist/services/tasks.d.ts +24 -17
- package/dist/services/tasks.js +97 -58
- package/dist/services/tokens.d.ts +6 -5
- package/dist/services/tokens.js +13 -6
- package/dist/session.d.ts +5 -0
- package/dist/session.js +8 -0
- package/dist/storage/local.js +16 -10
- package/dist/storage/s3.js +11 -6
- package/dist/test-ba-org.d.ts +16 -0
- package/dist/test-ba-org.js +35 -0
- package/dist/test-helpers.d.ts +21 -5
- package/dist/test-helpers.js +45 -8
- package/dist/vercel.d.ts +3 -0
- package/dist/vercel.js +62 -0
- package/dist/worker.d.ts +44 -0
- package/dist/worker.js +101 -0
- package/package.json +8 -5
- package/web/assets/index-D29SyYFS.css +2 -0
- package/web/assets/index-tBJ_K3ur.js +358 -0
- package/web/index.html +2 -2
- package/web/assets/index-CeBvZ-aq.css +0 -2
- package/web/assets/index-CuRAxImC.js +0 -358
package/dist/routes/comments.js
CHANGED
|
@@ -9,7 +9,7 @@ async function handleCreateComment(c, commentService, target) {
|
|
|
9
9
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
10
10
|
}
|
|
11
11
|
try {
|
|
12
|
-
const comment = commentService.create(target, {
|
|
12
|
+
const comment = await commentService.create(target, {
|
|
13
13
|
body: body.body,
|
|
14
14
|
passage: body.passage,
|
|
15
15
|
anchor: body.anchor,
|
|
@@ -26,9 +26,9 @@ async function handleCreateComment(c, commentService, target) {
|
|
|
26
26
|
throw error;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
function handleListComments(c, commentService, target) {
|
|
29
|
+
async function handleListComments(c, commentService, target) {
|
|
30
30
|
const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
|
|
31
|
-
const comments = commentService.listByTarget(target, { includeResolved });
|
|
31
|
+
const comments = await commentService.listByTarget(target, { includeResolved });
|
|
32
32
|
if (!comments) {
|
|
33
33
|
return c.json({ error: 'not_found' }, 404);
|
|
34
34
|
}
|
|
@@ -44,9 +44,9 @@ export function createCommentsRouter(commentService) {
|
|
|
44
44
|
router.get('/notes/:id/comments', (c) => handleListComments(c, commentService, { type: 'note', id: c.req.param('id') }));
|
|
45
45
|
router.post('/submissions/:id/comments', (c) => handleCreateComment(c, commentService, { type: 'submission', id: c.req.param('id') }));
|
|
46
46
|
router.get('/submissions/:id/comments', (c) => handleListComments(c, commentService, { type: 'submission', id: c.req.param('id') }));
|
|
47
|
-
router.get('/projects/:id/comments', (c) => {
|
|
47
|
+
router.get('/projects/:id/comments', async (c) => {
|
|
48
48
|
const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
|
|
49
|
-
const comments = commentService.listByProject(c.req.param('id'), { includeResolved });
|
|
49
|
+
const comments = await commentService.listByProject(c.req.param('id'), { includeResolved });
|
|
50
50
|
if (!comments) {
|
|
51
51
|
return c.json({ error: 'not_found' }, 404);
|
|
52
52
|
}
|
|
@@ -61,7 +61,7 @@ export function createCommentsRouter(commentService) {
|
|
|
61
61
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
62
62
|
}
|
|
63
63
|
try {
|
|
64
|
-
const comment = commentService.createForArtifact(c.req.param('id'), body.artifact_id, {
|
|
64
|
+
const comment = await commentService.createForArtifact(c.req.param('id'), body.artifact_id, {
|
|
65
65
|
body: body.body,
|
|
66
66
|
passage: body.passage,
|
|
67
67
|
anchor: body.anchor,
|
|
@@ -78,13 +78,13 @@ export function createCommentsRouter(commentService) {
|
|
|
78
78
|
throw error;
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
-
router.get('/projects/:id/artifact-comments', (c) => {
|
|
81
|
+
router.get('/projects/:id/artifact-comments', async (c) => {
|
|
82
82
|
const artifactId = c.req.query('artifact_id');
|
|
83
83
|
if (artifactId === undefined || artifactId === '') {
|
|
84
84
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
85
85
|
}
|
|
86
86
|
const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
|
|
87
|
-
const comments = commentService.listForArtifact(c.req.param('id'), artifactId, {
|
|
87
|
+
const comments = await commentService.listForArtifact(c.req.param('id'), artifactId, {
|
|
88
88
|
includeResolved,
|
|
89
89
|
});
|
|
90
90
|
if (!comments) {
|
|
@@ -95,7 +95,7 @@ export function createCommentsRouter(commentService) {
|
|
|
95
95
|
router.patch('/comments/:id', async (c) => {
|
|
96
96
|
const body = await c.req.json();
|
|
97
97
|
try {
|
|
98
|
-
const comment = commentService.update(c.req.param('id'), {
|
|
98
|
+
const comment = await commentService.update(c.req.param('id'), {
|
|
99
99
|
...(body.body !== undefined ? { body: body.body } : {}),
|
|
100
100
|
...(body.resolved !== undefined ? { resolved: body.resolved } : {}),
|
|
101
101
|
});
|
|
@@ -111,8 +111,8 @@ export function createCommentsRouter(commentService) {
|
|
|
111
111
|
throw error;
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
|
-
router.delete('/comments/:id', (c) => {
|
|
115
|
-
const deleted = commentService.delete(c.req.param('id'));
|
|
114
|
+
router.delete('/comments/:id', async (c) => {
|
|
115
|
+
const deleted = await commentService.delete(c.req.param('id'));
|
|
116
116
|
if (!deleted) {
|
|
117
117
|
return c.json({ error: 'not_found' }, 404);
|
|
118
118
|
}
|
package/dist/routes/documents.js
CHANGED
|
@@ -15,12 +15,12 @@ function resolveLinkedTaskId(body) {
|
|
|
15
15
|
}
|
|
16
16
|
export function createDocumentsRouter(documentService) {
|
|
17
17
|
const router = new Hono();
|
|
18
|
-
router.get('/projects/:id/documents', (c) => {
|
|
18
|
+
router.get('/projects/:id/documents', async (c) => {
|
|
19
19
|
const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
|
|
20
20
|
if (pagination === 'invalid') {
|
|
21
21
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
22
22
|
}
|
|
23
|
-
const tree = documentService.listTree(c.req.param('id'), pagination);
|
|
23
|
+
const tree = await documentService.listTree(c.req.param('id'), pagination);
|
|
24
24
|
if (!tree) {
|
|
25
25
|
return c.json({ error: 'not_found' }, 404);
|
|
26
26
|
}
|
|
@@ -32,7 +32,7 @@ export function createDocumentsRouter(documentService) {
|
|
|
32
32
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
33
33
|
}
|
|
34
34
|
try {
|
|
35
|
-
const document = documentService.create(c.req.param('id'), {
|
|
35
|
+
const document = await documentService.create(c.req.param('id'), {
|
|
36
36
|
title: body.title,
|
|
37
37
|
body: body.body,
|
|
38
38
|
statusLine: body.status_line,
|
|
@@ -52,8 +52,8 @@ export function createDocumentsRouter(documentService) {
|
|
|
52
52
|
throw error;
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
|
-
router.get('/documents/:id', (c) => {
|
|
56
|
-
const document = documentService.get(c.req.param('id'));
|
|
55
|
+
router.get('/documents/:id', async (c) => {
|
|
56
|
+
const document = await documentService.get(c.req.param('id'));
|
|
57
57
|
if (!document) {
|
|
58
58
|
return c.json({ error: 'not_found' }, 404);
|
|
59
59
|
}
|
|
@@ -63,7 +63,7 @@ export function createDocumentsRouter(documentService) {
|
|
|
63
63
|
const body = await c.req.json();
|
|
64
64
|
const linkedTaskId = resolveLinkedTaskId(body);
|
|
65
65
|
try {
|
|
66
|
-
const document = documentService.update(c.req.param('id'), {
|
|
66
|
+
const document = await documentService.update(c.req.param('id'), {
|
|
67
67
|
...(body.title !== undefined ? { title: body.title } : {}),
|
|
68
68
|
...(body.body !== undefined ? { body: body.body } : {}),
|
|
69
69
|
...(body.status_line !== undefined ? { statusLine: body.status_line } : {}),
|
|
@@ -83,15 +83,15 @@ export function createDocumentsRouter(documentService) {
|
|
|
83
83
|
throw error;
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
|
-
router.delete('/documents/:id', (c) => {
|
|
87
|
-
const deleted = documentService.delete(c.req.param('id'));
|
|
86
|
+
router.delete('/documents/:id', async (c) => {
|
|
87
|
+
const deleted = await documentService.delete(c.req.param('id'));
|
|
88
88
|
if (!deleted) {
|
|
89
89
|
return c.json({ error: 'not_found' }, 404);
|
|
90
90
|
}
|
|
91
91
|
return c.body(null, 204);
|
|
92
92
|
});
|
|
93
|
-
router.get('/tasks/:id/document', (c) => {
|
|
94
|
-
const document = documentService.getByTask(c.req.param('id'));
|
|
93
|
+
router.get('/tasks/:id/document', async (c) => {
|
|
94
|
+
const document = await documentService.getByTask(c.req.param('id'));
|
|
95
95
|
if (!document) {
|
|
96
96
|
return c.json({ error: 'not_found' }, 404);
|
|
97
97
|
}
|
package/dist/routes/folders.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { InvalidFolderError } from '../services/folders.js';
|
|
3
3
|
export function createFoldersRouter(folderService) {
|
|
4
4
|
const router = new Hono();
|
|
5
|
-
router.get('/projects/:id/folders', (c) => {
|
|
6
|
-
const folders = folderService.list(c.req.param('id'));
|
|
5
|
+
router.get('/projects/:id/folders', async (c) => {
|
|
6
|
+
const folders = await folderService.list(c.req.param('id'));
|
|
7
7
|
if (!folders) {
|
|
8
8
|
return c.json({ error: 'not_found' }, 404);
|
|
9
9
|
}
|
|
@@ -15,7 +15,7 @@ export function createFoldersRouter(folderService) {
|
|
|
15
15
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const folder = folderService.create(c.req.param('id'), {
|
|
18
|
+
const folder = await folderService.create(c.req.param('id'), {
|
|
19
19
|
name: body.name,
|
|
20
20
|
parentFolderId: body.parent_folder_id,
|
|
21
21
|
});
|
|
@@ -31,8 +31,8 @@ export function createFoldersRouter(folderService) {
|
|
|
31
31
|
throw error;
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
router.get('/folders/:id', (c) => {
|
|
35
|
-
const folder = folderService.get(c.req.param('id'));
|
|
34
|
+
router.get('/folders/:id', async (c) => {
|
|
35
|
+
const folder = await folderService.get(c.req.param('id'));
|
|
36
36
|
if (!folder) {
|
|
37
37
|
return c.json({ error: 'not_found' }, 404);
|
|
38
38
|
}
|
|
@@ -41,7 +41,7 @@ export function createFoldersRouter(folderService) {
|
|
|
41
41
|
router.patch('/folders/:id', async (c) => {
|
|
42
42
|
const body = await c.req.json();
|
|
43
43
|
try {
|
|
44
|
-
const folder = folderService.update(c.req.param('id'), {
|
|
44
|
+
const folder = await folderService.update(c.req.param('id'), {
|
|
45
45
|
...(body.name !== undefined ? { name: body.name } : {}),
|
|
46
46
|
...(body.parent_folder_id !== undefined ? { parentFolderId: body.parent_folder_id } : {}),
|
|
47
47
|
});
|
|
@@ -57,8 +57,8 @@ export function createFoldersRouter(folderService) {
|
|
|
57
57
|
throw error;
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
-
router.delete('/folders/:id', (c) => {
|
|
61
|
-
const deleted = folderService.delete(c.req.param('id'));
|
|
60
|
+
router.delete('/folders/:id', async (c) => {
|
|
61
|
+
const deleted = await folderService.delete(c.req.param('id'));
|
|
62
62
|
if (!deleted) {
|
|
63
63
|
return c.json({ error: 'not_found' }, 404);
|
|
64
64
|
}
|
package/dist/routes/goals.js
CHANGED
|
@@ -46,7 +46,7 @@ export function createGoalsRouter(goalService) {
|
|
|
46
46
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
47
47
|
}
|
|
48
48
|
try {
|
|
49
|
-
const goal = goalService.create(c.req.param('id'), {
|
|
49
|
+
const goal = await goalService.create(c.req.param('id'), {
|
|
50
50
|
objective: body.objective,
|
|
51
51
|
...(body.status !== undefined ? { status: body.status } : {}),
|
|
52
52
|
...mapGoalInput(body),
|
|
@@ -60,15 +60,15 @@ export function createGoalsRouter(goalService) {
|
|
|
60
60
|
return handleGoalError(c, error);
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
|
-
router.get('/projects/:id/goals', (c) => {
|
|
64
|
-
const goals = goalService.listByProject(c.req.param('id'));
|
|
63
|
+
router.get('/projects/:id/goals', async (c) => {
|
|
64
|
+
const goals = await goalService.listByProject(c.req.param('id'));
|
|
65
65
|
if (!goals) {
|
|
66
66
|
return c.json({ error: 'not_found' }, 404);
|
|
67
67
|
}
|
|
68
68
|
return c.json(goals);
|
|
69
69
|
});
|
|
70
|
-
router.get('/goals/:id', (c) => {
|
|
71
|
-
const goal = goalService.get(c.req.param('id'));
|
|
70
|
+
router.get('/goals/:id', async (c) => {
|
|
71
|
+
const goal = await goalService.get(c.req.param('id'));
|
|
72
72
|
if (!goal) {
|
|
73
73
|
return c.json({ error: 'not_found' }, 404);
|
|
74
74
|
}
|
|
@@ -80,7 +80,7 @@ export function createGoalsRouter(goalService) {
|
|
|
80
80
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
81
81
|
}
|
|
82
82
|
try {
|
|
83
|
-
const goal = goalService.update(c.req.param('id'), mapGoalInput(body));
|
|
83
|
+
const goal = await goalService.update(c.req.param('id'), mapGoalInput(body));
|
|
84
84
|
if (!goal) {
|
|
85
85
|
return c.json({ error: 'not_found' }, 404);
|
|
86
86
|
}
|
|
@@ -90,9 +90,9 @@ export function createGoalsRouter(goalService) {
|
|
|
90
90
|
return handleGoalError(c, error);
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
|
-
router.post('/goals/:id/pause', (c) => {
|
|
93
|
+
router.post('/goals/:id/pause', async (c) => {
|
|
94
94
|
try {
|
|
95
|
-
const goal = goalService.pause(c.req.param('id'));
|
|
95
|
+
const goal = await goalService.pause(c.req.param('id'));
|
|
96
96
|
if (!goal) {
|
|
97
97
|
return c.json({ error: 'not_found' }, 404);
|
|
98
98
|
}
|
|
@@ -102,9 +102,9 @@ export function createGoalsRouter(goalService) {
|
|
|
102
102
|
return handleGoalError(c, error);
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
|
-
router.post('/goals/:id/resume', (c) => {
|
|
105
|
+
router.post('/goals/:id/resume', async (c) => {
|
|
106
106
|
try {
|
|
107
|
-
const goal = goalService.resume(c.req.param('id'));
|
|
107
|
+
const goal = await goalService.resume(c.req.param('id'));
|
|
108
108
|
if (!goal) {
|
|
109
109
|
return c.json({ error: 'not_found' }, 404);
|
|
110
110
|
}
|
|
@@ -123,7 +123,7 @@ export function createGoalsRouter(goalService) {
|
|
|
123
123
|
// empty body is valid when the goal has no verification_surface
|
|
124
124
|
}
|
|
125
125
|
try {
|
|
126
|
-
const goal = goalService.complete(c.req.param('id'), body.evidence);
|
|
126
|
+
const goal = await goalService.complete(c.req.param('id'), body.evidence);
|
|
127
127
|
if (!goal) {
|
|
128
128
|
return c.json({ error: 'not_found' }, 404);
|
|
129
129
|
}
|
package/dist/routes/notes.js
CHANGED
|
@@ -3,12 +3,12 @@ import { InvalidNoteError } from '../services/notes.js';
|
|
|
3
3
|
import { parsePaginationParams } from '../serialize.js';
|
|
4
4
|
export function createNotesRouter(noteService) {
|
|
5
5
|
const router = new Hono();
|
|
6
|
-
router.get('/projects/:id/notes', (c) => {
|
|
6
|
+
router.get('/projects/:id/notes', async (c) => {
|
|
7
7
|
const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
|
|
8
8
|
if (pagination === 'invalid') {
|
|
9
9
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
10
10
|
}
|
|
11
|
-
const notes = noteService.list(c.req.param('id'), pagination);
|
|
11
|
+
const notes = await noteService.list(c.req.param('id'), pagination);
|
|
12
12
|
if (!notes) {
|
|
13
13
|
return c.json({ error: 'not_found' }, 404);
|
|
14
14
|
}
|
|
@@ -20,7 +20,7 @@ export function createNotesRouter(noteService) {
|
|
|
20
20
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
|
-
const note = noteService.create(c.req.param('id'), {
|
|
23
|
+
const note = await noteService.create(c.req.param('id'), {
|
|
24
24
|
title: body.title,
|
|
25
25
|
body: body.body,
|
|
26
26
|
});
|
|
@@ -36,8 +36,8 @@ export function createNotesRouter(noteService) {
|
|
|
36
36
|
throw error;
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
|
-
router.get('/notes/:id', (c) => {
|
|
40
|
-
const note = noteService.get(c.req.param('id'));
|
|
39
|
+
router.get('/notes/:id', async (c) => {
|
|
40
|
+
const note = await noteService.get(c.req.param('id'));
|
|
41
41
|
if (!note) {
|
|
42
42
|
return c.json({ error: 'not_found' }, 404);
|
|
43
43
|
}
|
|
@@ -46,7 +46,7 @@ export function createNotesRouter(noteService) {
|
|
|
46
46
|
router.patch('/notes/:id', async (c) => {
|
|
47
47
|
const body = await c.req.json();
|
|
48
48
|
try {
|
|
49
|
-
const note = noteService.update(c.req.param('id'), {
|
|
49
|
+
const note = await noteService.update(c.req.param('id'), {
|
|
50
50
|
...(body.title !== undefined ? { title: body.title } : {}),
|
|
51
51
|
...(body.body !== undefined ? { body: body.body } : {}),
|
|
52
52
|
});
|
|
@@ -62,8 +62,8 @@ export function createNotesRouter(noteService) {
|
|
|
62
62
|
throw error;
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
|
-
router.delete('/notes/:id', (c) => {
|
|
66
|
-
const deleted = noteService.delete(c.req.param('id'));
|
|
65
|
+
router.delete('/notes/:id', async (c) => {
|
|
66
|
+
const deleted = await noteService.delete(c.req.param('id'));
|
|
67
67
|
if (!deleted) {
|
|
68
68
|
return c.json({ error: 'not_found' }, 404);
|
|
69
69
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { type Db } from '@plandesk/db';
|
|
3
|
+
import type { BetterAuthInstance } from '../better-auth.js';
|
|
4
|
+
export type OrgsRouterOptions = {
|
|
5
|
+
betterAuth?: BetterAuthInstance;
|
|
6
|
+
/** Base URL for claim links returned from invitations (no trailing slash required). */
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* There is deliberately no `POST /orgs`.
|
|
11
|
+
*
|
|
12
|
+
* An org is only ever created by better-auth identity provisioning (BA4c) or by
|
|
13
|
+
* `ensureLocalBetterAuthOrganization` at `serve` boot for the local single-org case.
|
|
14
|
+
* Both bound creation by construction, which is why no quota is needed to bound it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createOrgsRouter(db: Db, options?: OrgsRouterOptions): Hono;
|
|
17
|
+
//# sourceMappingURL=orgs.d.ts.map
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { getProjectInOrg, importProject, InvalidExportVersionError, PLANDESK_EXPORT_VERSION, } from '@plandesk/db';
|
|
3
|
+
import { createScopedAgentKey } from '../agent-keys.js';
|
|
4
|
+
import { getAuthContext, getOrgAuthContext } from '../auth-context.js';
|
|
5
|
+
import { acceptOrganizationInvitation, createOrganizationInvitation, isAuthApiError, isInvitationRole, } from '../invitations.js';
|
|
6
|
+
import { getOrganizationById } from '../organizations.js';
|
|
7
|
+
import { requirePermission } from '../permissions.js';
|
|
8
|
+
function isPermissionSet(value) {
|
|
9
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
for (const actions of Object.values(value)) {
|
|
13
|
+
if (!Array.isArray(actions) ||
|
|
14
|
+
!actions.every((action) => typeof action === 'string')) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the better-auth user id that will own the minted agent key.
|
|
22
|
+
* Apikey (owner key) carries userId; session needs a better-auth session cookie.
|
|
23
|
+
*/
|
|
24
|
+
async function resolveMintUserId(betterAuth, headers) {
|
|
25
|
+
const ctx = getAuthContext();
|
|
26
|
+
if (ctx.kind === 'apikey') {
|
|
27
|
+
return ctx.userId;
|
|
28
|
+
}
|
|
29
|
+
if (ctx.kind === 'session') {
|
|
30
|
+
const baSession = await betterAuth.api.getSession({ headers });
|
|
31
|
+
if (baSession === null) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return baSession.user.id;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* There is deliberately no `POST /orgs`.
|
|
40
|
+
*
|
|
41
|
+
* An org is only ever created by better-auth identity provisioning (BA4c) or by
|
|
42
|
+
* `ensureLocalBetterAuthOrganization` at `serve` boot for the local single-org case.
|
|
43
|
+
* Both bound creation by construction, which is why no quota is needed to bound it.
|
|
44
|
+
*/
|
|
45
|
+
export function createOrgsRouter(db, options = {}) {
|
|
46
|
+
const router = new Hono();
|
|
47
|
+
const betterAuth = options.betterAuth;
|
|
48
|
+
const baseURL = options.baseURL ?? 'http://127.0.0.1';
|
|
49
|
+
async function requireKnownOrg(orgId) {
|
|
50
|
+
if (getOrgAuthContext().orgId !== orgId) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (betterAuth === undefined) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return (await getOrganizationById(betterAuth, orgId)) !== undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* BA4b-3: mint a project-scoped agent key for `plandesk connect --to`.
|
|
60
|
+
* Caller must hold apiKey:create (owner key or session owner) — agent keys cannot.
|
|
61
|
+
* Raw key returned once; metadata is { projectId, orgId } (agent profile).
|
|
62
|
+
*/
|
|
63
|
+
router.post('/orgs/:orgId/agent-keys', async (c) => {
|
|
64
|
+
if (betterAuth === undefined) {
|
|
65
|
+
return c.json({ error: 'unavailable' }, 503);
|
|
66
|
+
}
|
|
67
|
+
const orgId = c.req.param('orgId');
|
|
68
|
+
if (!(await requireKnownOrg(orgId))) {
|
|
69
|
+
return c.json({ error: 'not_found' }, 404);
|
|
70
|
+
}
|
|
71
|
+
requirePermission(getOrgAuthContext(), 'apiKey', 'create');
|
|
72
|
+
const body = await c.req.json();
|
|
73
|
+
if (typeof body.project_id !== 'string' || body.project_id.trim() === '') {
|
|
74
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
75
|
+
}
|
|
76
|
+
const projectId = body.project_id.trim();
|
|
77
|
+
const project = await getProjectInOrg(db, projectId, orgId);
|
|
78
|
+
if (project === undefined) {
|
|
79
|
+
return c.json({ error: 'not_found' }, 404);
|
|
80
|
+
}
|
|
81
|
+
let permissions;
|
|
82
|
+
if (body.permissions !== undefined) {
|
|
83
|
+
if (!isPermissionSet(body.permissions)) {
|
|
84
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
85
|
+
}
|
|
86
|
+
permissions = body.permissions;
|
|
87
|
+
}
|
|
88
|
+
let name;
|
|
89
|
+
if (body.name !== undefined) {
|
|
90
|
+
if (typeof body.name !== 'string' || body.name.trim() === '') {
|
|
91
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
92
|
+
}
|
|
93
|
+
name = body.name.trim();
|
|
94
|
+
}
|
|
95
|
+
const userId = await resolveMintUserId(betterAuth, c.req.raw.headers);
|
|
96
|
+
if (userId === undefined) {
|
|
97
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
98
|
+
}
|
|
99
|
+
const mintInput = {
|
|
100
|
+
auth: betterAuth,
|
|
101
|
+
userId,
|
|
102
|
+
orgId,
|
|
103
|
+
projectId,
|
|
104
|
+
...(permissions !== undefined ? { permissions } : {}),
|
|
105
|
+
...(name !== undefined ? { name } : {}),
|
|
106
|
+
};
|
|
107
|
+
const minted = await createScopedAgentKey(mintInput);
|
|
108
|
+
return c.json({ token: minted.key, project_id: projectId }, 200);
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* BA3c: invite by email (link-only, no mailer). Session owner only
|
|
112
|
+
* (member:create). Returns claimUrl for the inviter to deliver by hand.
|
|
113
|
+
*/
|
|
114
|
+
router.post('/orgs/:id/invitations', async (c) => {
|
|
115
|
+
const orgId = c.req.param('id');
|
|
116
|
+
if (!(await requireKnownOrg(orgId))) {
|
|
117
|
+
return c.json({ error: 'not_found' }, 404);
|
|
118
|
+
}
|
|
119
|
+
const authCtx = getOrgAuthContext();
|
|
120
|
+
// Session owner only — token/loopback cannot drive better-auth createInvitation.
|
|
121
|
+
if (authCtx.kind !== 'session') {
|
|
122
|
+
return c.json({ error: 'forbidden' }, 403);
|
|
123
|
+
}
|
|
124
|
+
requirePermission(authCtx, 'member', 'create');
|
|
125
|
+
if (betterAuth === undefined) {
|
|
126
|
+
return c.json({ error: 'unavailable' }, 503);
|
|
127
|
+
}
|
|
128
|
+
const body = await c.req.json();
|
|
129
|
+
if (typeof body.email !== 'string' || body.email.trim() === '') {
|
|
130
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
131
|
+
}
|
|
132
|
+
if (typeof body.role !== 'string' || !isInvitationRole(body.role)) {
|
|
133
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const created = await createOrganizationInvitation(betterAuth, {
|
|
137
|
+
email: body.email,
|
|
138
|
+
role: body.role,
|
|
139
|
+
organizationId: orgId,
|
|
140
|
+
headers: c.req.raw.headers,
|
|
141
|
+
baseURL,
|
|
142
|
+
});
|
|
143
|
+
return c.json({
|
|
144
|
+
invitationId: created.invitationId,
|
|
145
|
+
claimUrl: created.claimUrl,
|
|
146
|
+
}, 201);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (isAuthApiError(err)) {
|
|
150
|
+
if (err.statusCode === 403 || err.status === 'FORBIDDEN') {
|
|
151
|
+
return c.json({ error: 'forbidden' }, 403);
|
|
152
|
+
}
|
|
153
|
+
return c.json({ error: 'invalid_argument', message: err.message }, 400);
|
|
154
|
+
}
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* BA3c: accept invitation. Session-gated at the handler (public path so
|
|
160
|
+
* org-less invitees can reach it); not org-gated. Single-use → 410 on retry.
|
|
161
|
+
*/
|
|
162
|
+
router.post('/invitations/:invitationId/accept', async (c) => {
|
|
163
|
+
if (betterAuth === undefined) {
|
|
164
|
+
return c.json({ error: 'unavailable' }, 503);
|
|
165
|
+
}
|
|
166
|
+
const invitationId = c.req.param('invitationId');
|
|
167
|
+
if (invitationId.trim() === '') {
|
|
168
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
169
|
+
}
|
|
170
|
+
const session = await betterAuth.api.getSession({ headers: c.req.raw.headers });
|
|
171
|
+
if (session === null) {
|
|
172
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const result = await acceptOrganizationInvitation(betterAuth, {
|
|
176
|
+
invitationId,
|
|
177
|
+
headers: c.req.raw.headers,
|
|
178
|
+
});
|
|
179
|
+
return c.json({
|
|
180
|
+
invitationId: result.invitation.id,
|
|
181
|
+
organizationId: result.member.organizationId,
|
|
182
|
+
role: result.member.role,
|
|
183
|
+
userId: result.member.userId,
|
|
184
|
+
}, 200);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
if (isAuthApiError(err)) {
|
|
188
|
+
// Already accepted / expired / missing → single-use CAS failure.
|
|
189
|
+
if (err.statusCode === 400 ||
|
|
190
|
+
err.status === 'BAD_REQUEST' ||
|
|
191
|
+
err.message.toLowerCase().includes('invitation')) {
|
|
192
|
+
return c.json({ error: 'gone', message: err.message }, 410);
|
|
193
|
+
}
|
|
194
|
+
if (err.statusCode === 403 || err.status === 'FORBIDDEN') {
|
|
195
|
+
return c.json({ error: 'forbidden' }, 403);
|
|
196
|
+
}
|
|
197
|
+
return c.json({ error: 'invalid_argument', message: err.message }, 400);
|
|
198
|
+
}
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// Promote a portable export into this org (one-way authority handoff).
|
|
203
|
+
router.post('/orgs/:id/import', async (c) => {
|
|
204
|
+
const orgId = c.req.param('id');
|
|
205
|
+
// Org-scoped: token for org-B cannot import into org-A.
|
|
206
|
+
if (!(await requireKnownOrg(orgId))) {
|
|
207
|
+
return c.json({ error: 'not_found' }, 404);
|
|
208
|
+
}
|
|
209
|
+
requirePermission(getOrgAuthContext(), 'organization', 'update');
|
|
210
|
+
let body;
|
|
211
|
+
try {
|
|
212
|
+
body = await c.req.json();
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
216
|
+
}
|
|
217
|
+
if (body === null || typeof body !== 'object') {
|
|
218
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
219
|
+
}
|
|
220
|
+
const data = body;
|
|
221
|
+
if (typeof data.version !== 'string' || data.version !== PLANDESK_EXPORT_VERSION) {
|
|
222
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
223
|
+
}
|
|
224
|
+
if (data.project === undefined || typeof data.project !== 'object') {
|
|
225
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
// orgId always from authenticated path/context — never from the body.
|
|
229
|
+
const { projectId } = await importProject(db, data, { orgId });
|
|
230
|
+
return c.json({ globalProjectId: projectId }, 201);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
if (err instanceof InvalidExportVersionError) {
|
|
234
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return router;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=orgs.js.map
|
package/dist/routes/projects.js
CHANGED
|
@@ -6,26 +6,26 @@ import { isStringArray } from './tasks.js';
|
|
|
6
6
|
import { parsePaginationParams } from '../serialize.js';
|
|
7
7
|
export function createProjectsRouter(projectService, taskService) {
|
|
8
8
|
const router = new Hono();
|
|
9
|
-
router.get('/projects', (c) => {
|
|
9
|
+
router.get('/projects', async (c) => {
|
|
10
10
|
const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
|
|
11
11
|
if (pagination === 'invalid') {
|
|
12
12
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
13
13
|
}
|
|
14
|
-
return c.json(projectService.list(pagination));
|
|
14
|
+
return c.json(await projectService.list(pagination));
|
|
15
15
|
});
|
|
16
16
|
router.post('/projects', async (c) => {
|
|
17
17
|
const body = await c.req.json();
|
|
18
18
|
if (typeof body.name !== 'string' || body.name.trim() === '') {
|
|
19
19
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
20
20
|
}
|
|
21
|
-
const project = projectService.create({
|
|
21
|
+
const project = await projectService.create({
|
|
22
22
|
name: body.name,
|
|
23
23
|
description: body.description,
|
|
24
24
|
});
|
|
25
25
|
return c.json(project, 201);
|
|
26
26
|
});
|
|
27
|
-
router.get('/projects/:id', (c) => {
|
|
28
|
-
const project = projectService.get(c.req.param('id'));
|
|
27
|
+
router.get('/projects/:id', async (c) => {
|
|
28
|
+
const project = await projectService.get(c.req.param('id'));
|
|
29
29
|
if (!project) {
|
|
30
30
|
return c.json({ error: 'not_found' }, 404);
|
|
31
31
|
}
|
|
@@ -36,7 +36,7 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
36
36
|
if (body.name !== undefined && (typeof body.name !== 'string' || body.name.trim() === '')) {
|
|
37
37
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
38
38
|
}
|
|
39
|
-
const project = projectService.update(c.req.param('id'), {
|
|
39
|
+
const project = await projectService.update(c.req.param('id'), {
|
|
40
40
|
...(body.name !== undefined ? { name: body.name } : {}),
|
|
41
41
|
...(body.description !== undefined ? { description: body.description } : {}),
|
|
42
42
|
});
|
|
@@ -45,8 +45,8 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
45
45
|
}
|
|
46
46
|
return c.json(project);
|
|
47
47
|
});
|
|
48
|
-
router.delete('/projects/:id', (c) => {
|
|
49
|
-
const deleted = projectService.delete(c.req.param('id'));
|
|
48
|
+
router.delete('/projects/:id', async (c) => {
|
|
49
|
+
const deleted = await projectService.delete(c.req.param('id'));
|
|
50
50
|
if (!deleted) {
|
|
51
51
|
return c.json({ error: 'not_found' }, 404);
|
|
52
52
|
}
|
|
@@ -74,7 +74,7 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
74
74
|
dueDate = null;
|
|
75
75
|
}
|
|
76
76
|
try {
|
|
77
|
-
const task = taskService.create(c.req.param('id'), {
|
|
77
|
+
const task = await taskService.create(c.req.param('id'), {
|
|
78
78
|
label: body.label,
|
|
79
79
|
...(body.status !== undefined ? { status: body.status } : {}),
|
|
80
80
|
...(body.description !== undefined ? { description: body.description } : {}),
|
|
@@ -99,7 +99,7 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
99
99
|
throw error;
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
|
-
router.get('/projects/:id/tasks', (c) => {
|
|
102
|
+
router.get('/projects/:id/tasks', async (c) => {
|
|
103
103
|
try {
|
|
104
104
|
const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
|
|
105
105
|
if (pagination === 'invalid') {
|
|
@@ -109,7 +109,7 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
109
109
|
// Repeated ?tag= params filter with OR semantics (task matches if it has
|
|
110
110
|
// ANY of the given tags).
|
|
111
111
|
const tags = c.req.queries('tag');
|
|
112
|
-
const tasks = taskService.listByProject(c.req.param('id'), { status, ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }, pagination);
|
|
112
|
+
const tasks = await taskService.listByProject(c.req.param('id'), { status, ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }, pagination);
|
|
113
113
|
if (!tasks) {
|
|
114
114
|
return c.json({ error: 'not_found' }, 404);
|
|
115
115
|
}
|
|
@@ -122,8 +122,8 @@ export function createProjectsRouter(projectService, taskService) {
|
|
|
122
122
|
throw error;
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
|
-
router.get('/projects/:id/next-task', (c) => {
|
|
126
|
-
const result = taskService.nextActionable(c.req.param('id'));
|
|
125
|
+
router.get('/projects/:id/next-task', async (c) => {
|
|
126
|
+
const result = await taskService.nextActionable(c.req.param('id'));
|
|
127
127
|
if (!result) {
|
|
128
128
|
return c.json({ error: 'not_found' }, 404);
|
|
129
129
|
}
|