@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.
@@ -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 {
@@ -95,6 +95,7 @@ export type SerializedComment = {
95
95
  target_id: string;
96
96
  document_id: string | null;
97
97
  passage: string | null;
98
+ anchor: string | null;
98
99
  body: string;
99
100
  resolved: boolean;
100
101
  created_at: string;
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.10.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.8.0",
22
- "@plandesk/sync-server": "0.5.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",