@plandesk/api 0.10.0 → 0.13.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/routes/comments.js +41 -0
- package/dist/serialize.d.ts +1 -0
- package/dist/serialize.js +1 -0
- package/dist/services/comments.d.ts +5 -0
- package/dist/services/comments.js +35 -0
- package/package.json +3 -3
package/dist/routes/comments.js
CHANGED
|
@@ -12,6 +12,7 @@ async function handleCreateComment(c, commentService, target) {
|
|
|
12
12
|
const comment = commentService.create(target, {
|
|
13
13
|
body: body.body,
|
|
14
14
|
passage: body.passage,
|
|
15
|
+
anchor: body.anchor,
|
|
15
16
|
});
|
|
16
17
|
if (!comment) {
|
|
17
18
|
return c.json({ error: 'not_found' }, 404);
|
|
@@ -51,6 +52,46 @@ export function createCommentsRouter(commentService) {
|
|
|
51
52
|
}
|
|
52
53
|
return c.json(comments);
|
|
53
54
|
});
|
|
55
|
+
// Artifact comments are project-scoped. The file identity (content-hash+path,
|
|
56
|
+
// which may contain slashes) travels in the JSON body / query, not a path
|
|
57
|
+
// segment, so it never collides with routing.
|
|
58
|
+
router.post('/projects/:id/artifact-comments', async (c) => {
|
|
59
|
+
const body = await c.req.json();
|
|
60
|
+
if (typeof body.body !== 'string' || typeof body.artifact_id !== 'string') {
|
|
61
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const comment = commentService.createForArtifact(c.req.param('id'), body.artifact_id, {
|
|
65
|
+
body: body.body,
|
|
66
|
+
passage: body.passage,
|
|
67
|
+
anchor: body.anchor,
|
|
68
|
+
});
|
|
69
|
+
if (!comment) {
|
|
70
|
+
return c.json({ error: 'not_found' }, 404);
|
|
71
|
+
}
|
|
72
|
+
return c.json(comment, 201);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof InvalidCommentError) {
|
|
76
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
router.get('/projects/:id/artifact-comments', (c) => {
|
|
82
|
+
const artifactId = c.req.query('artifact_id');
|
|
83
|
+
if (artifactId === undefined || artifactId === '') {
|
|
84
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
85
|
+
}
|
|
86
|
+
const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
|
|
87
|
+
const comments = commentService.listForArtifact(c.req.param('id'), artifactId, {
|
|
88
|
+
includeResolved,
|
|
89
|
+
});
|
|
90
|
+
if (!comments) {
|
|
91
|
+
return c.json({ error: 'not_found' }, 404);
|
|
92
|
+
}
|
|
93
|
+
return c.json(comments);
|
|
94
|
+
});
|
|
54
95
|
router.patch('/comments/:id', async (c) => {
|
|
55
96
|
const body = await c.req.json();
|
|
56
97
|
try {
|
package/dist/serialize.d.ts
CHANGED
package/dist/serialize.js
CHANGED
|
@@ -127,6 +127,7 @@ export function serializeComment(comment) {
|
|
|
127
127
|
target_id: comment.targetId,
|
|
128
128
|
document_id: comment.targetType === 'document' ? comment.targetId : null,
|
|
129
129
|
passage: comment.passage,
|
|
130
|
+
anchor: comment.anchor,
|
|
130
131
|
body: comment.body,
|
|
131
132
|
resolved: comment.resolved,
|
|
132
133
|
created_at: comment.createdAt.toISOString(),
|
|
@@ -12,6 +12,7 @@ export type CommentTarget = {
|
|
|
12
12
|
export type CreateCommentInput = {
|
|
13
13
|
body: string;
|
|
14
14
|
passage?: string | null;
|
|
15
|
+
anchor?: string | null;
|
|
15
16
|
};
|
|
16
17
|
export type UpdateCommentInput = {
|
|
17
18
|
body?: string;
|
|
@@ -22,6 +23,10 @@ export declare class InvalidCommentError extends Error {
|
|
|
22
23
|
}
|
|
23
24
|
export declare function createCommentService(deps: CommentServiceDeps): {
|
|
24
25
|
create(target: CommentTarget, input: CreateCommentInput): SerializedComment | undefined;
|
|
26
|
+
createForArtifact(projectId: string, artifactId: string, input: CreateCommentInput): SerializedComment | undefined;
|
|
27
|
+
listForArtifact(projectId: string, artifactId: string, options?: {
|
|
28
|
+
includeResolved?: boolean;
|
|
29
|
+
}): SerializedComment[] | undefined;
|
|
25
30
|
listByTarget(target: CommentTarget, options?: {
|
|
26
31
|
includeResolved?: boolean;
|
|
27
32
|
}): SerializedComment[] | undefined;
|
|
@@ -21,6 +21,11 @@ function targetProjectId(db, target) {
|
|
|
21
21
|
return dbGetNote(db, target.id)?.projectId;
|
|
22
22
|
case 'submission':
|
|
23
23
|
return getSubmission(db, target.id)?.projectId;
|
|
24
|
+
case 'artifact':
|
|
25
|
+
// An artifact is a file on disk, not a DB entity — its project is
|
|
26
|
+
// supplied by the caller (the connected repo's project), not resolved
|
|
27
|
+
// from the target id. Artifact comments go through createForArtifact.
|
|
28
|
+
return undefined;
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
function emitCommentCreated(eventBus, commentId, projectId, target) {
|
|
@@ -58,10 +63,40 @@ export function createCommentService(deps) {
|
|
|
58
63
|
targetId: target.id,
|
|
59
64
|
body: input.body,
|
|
60
65
|
passage: input.passage,
|
|
66
|
+
anchor: input.anchor,
|
|
61
67
|
});
|
|
62
68
|
emitCommentCreated(eventBus, comment.id, projectId, target);
|
|
63
69
|
return serializeComment(comment);
|
|
64
70
|
},
|
|
71
|
+
// Artifact comments are project-scoped: the caller supplies the project
|
|
72
|
+
// (the connected repo's), and the file identity is the target id. Slashes
|
|
73
|
+
// in the file identity mean it travels in the body/query, never a path seg.
|
|
74
|
+
createForArtifact(projectId, artifactId, input) {
|
|
75
|
+
if (!getProject(db, projectId)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
if (artifactId.trim() === '') {
|
|
79
|
+
throw new InvalidCommentError('Artifact id must not be empty');
|
|
80
|
+
}
|
|
81
|
+
assertNonEmptyBody(input.body);
|
|
82
|
+
const target = { type: 'artifact', id: artifactId };
|
|
83
|
+
const comment = createComment(db, {
|
|
84
|
+
projectId,
|
|
85
|
+
targetType: 'artifact',
|
|
86
|
+
targetId: artifactId,
|
|
87
|
+
body: input.body,
|
|
88
|
+
passage: input.passage,
|
|
89
|
+
anchor: input.anchor,
|
|
90
|
+
});
|
|
91
|
+
emitCommentCreated(eventBus, comment.id, projectId, target);
|
|
92
|
+
return serializeComment(comment);
|
|
93
|
+
},
|
|
94
|
+
listForArtifact(projectId, artifactId, options) {
|
|
95
|
+
if (!getProject(db, projectId)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return dbListCommentsByTarget(db, 'artifact', artifactId, options).map(serializeComment);
|
|
99
|
+
},
|
|
65
100
|
listByTarget(target, options) {
|
|
66
101
|
const projectId = targetProjectId(db, target);
|
|
67
102
|
if (!projectId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plandesk/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"typescript": "^6.0.3",
|
|
20
20
|
"vitest": "^3.2.6",
|
|
21
|
-
"@plandesk/db": "0.
|
|
22
|
-
"@plandesk/sync-server": "0.
|
|
21
|
+
"@plandesk/db": "0.13.1",
|
|
22
|
+
"@plandesk/sync-server": "0.13.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@hono/node-server": "^2.0.4",
|