@plandesk/api 0.20.0 → 1.0.0-beta.2

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.
Files changed (101) hide show
  1. package/dist/access-control.d.ts +150 -0
  2. package/dist/access-control.js +63 -0
  3. package/dist/agent-keys.d.ts +81 -0
  4. package/dist/agent-keys.js +181 -0
  5. package/dist/auth-context.d.ts +60 -0
  6. package/dist/auth-context.js +41 -0
  7. package/dist/auth.d.ts +33 -0
  8. package/dist/auth.js +357 -0
  9. package/dist/better-auth.d.ts +66 -0
  10. package/dist/better-auth.js +106 -0
  11. package/dist/github.d.ts +57 -0
  12. package/dist/github.js +115 -0
  13. package/dist/hosted-auth.d.ts +23 -0
  14. package/dist/hosted-auth.js +49 -0
  15. package/dist/identity.d.ts +38 -0
  16. package/dist/identity.js +163 -0
  17. package/dist/index.d.ts +13 -2
  18. package/dist/index.js +15 -3
  19. package/dist/invitations.d.ts +94 -0
  20. package/dist/invitations.js +211 -0
  21. package/dist/organizations.d.ts +32 -0
  22. package/dist/organizations.js +63 -0
  23. package/dist/permissions.d.ts +19 -0
  24. package/dist/permissions.js +42 -0
  25. package/dist/projection.d.ts +1 -1
  26. package/dist/projection.js +5 -5
  27. package/dist/routes/agent-runs.js +3 -3
  28. package/dist/routes/artifacts.js +6 -6
  29. package/dist/routes/auth.d.ts +13 -0
  30. package/dist/routes/auth.js +83 -0
  31. package/dist/routes/canvas.js +5 -5
  32. package/dist/routes/comments.js +11 -11
  33. package/dist/routes/documents.js +10 -10
  34. package/dist/routes/folders.js +8 -8
  35. package/dist/routes/goals.js +11 -11
  36. package/dist/routes/notes.js +8 -8
  37. package/dist/routes/orgs.d.ts +17 -0
  38. package/dist/routes/orgs.js +241 -0
  39. package/dist/routes/projects.js +13 -13
  40. package/dist/routes/shares.js +92 -3
  41. package/dist/routes/submissions.js +7 -8
  42. package/dist/routes/tags.js +6 -6
  43. package/dist/routes/tasks.js +14 -3
  44. package/dist/routes/tokens.js +4 -4
  45. package/dist/serialize.d.ts +3 -3
  46. package/dist/server.d.ts +20 -2
  47. package/dist/server.js +57 -9
  48. package/dist/services/agent-runs.d.ts +13 -14
  49. package/dist/services/agent-runs.js +39 -41
  50. package/dist/services/artifacts.d.ts +6 -7
  51. package/dist/services/artifacts.js +30 -26
  52. package/dist/services/canvas.d.ts +11 -12
  53. package/dist/services/canvas.js +58 -41
  54. package/dist/services/comments.d.ts +11 -12
  55. package/dist/services/comments.js +44 -64
  56. package/dist/services/documents.d.ts +10 -11
  57. package/dist/services/documents.js +74 -55
  58. package/dist/services/files.d.ts +2 -1
  59. package/dist/services/files.js +13 -5
  60. package/dist/services/folders.d.ts +7 -8
  61. package/dist/services/folders.js +45 -33
  62. package/dist/services/goals.d.ts +24 -25
  63. package/dist/services/goals.js +60 -53
  64. package/dist/services/index.d.ts +2 -5
  65. package/dist/services/index.js +15 -20
  66. package/dist/services/notes.d.ts +7 -8
  67. package/dist/services/notes.js +35 -23
  68. package/dist/services/org-scope.d.ts +22 -0
  69. package/dist/services/org-scope.js +38 -0
  70. package/dist/services/projects.d.ts +12 -13
  71. package/dist/services/projects.js +90 -58
  72. package/dist/services/scope.d.ts +12 -0
  73. package/dist/services/scope.js +29 -0
  74. package/dist/services/share.d.ts +74 -10
  75. package/dist/services/share.js +213 -23
  76. package/dist/services/sync.d.ts +7 -29
  77. package/dist/services/sync.js +37 -142
  78. package/dist/services/tags.d.ts +6 -7
  79. package/dist/services/tags.js +34 -22
  80. package/dist/services/tasks.d.ts +24 -17
  81. package/dist/services/tasks.js +97 -58
  82. package/dist/services/tokens.d.ts +6 -5
  83. package/dist/services/tokens.js +13 -6
  84. package/dist/session.d.ts +5 -0
  85. package/dist/session.js +8 -0
  86. package/dist/storage/local.js +16 -10
  87. package/dist/storage/s3.js +11 -6
  88. package/dist/test-ba-org.d.ts +16 -0
  89. package/dist/test-ba-org.js +35 -0
  90. package/dist/test-helpers.d.ts +21 -5
  91. package/dist/test-helpers.js +45 -8
  92. package/dist/vercel.d.ts +3 -0
  93. package/dist/vercel.js +62 -0
  94. package/dist/worker.d.ts +44 -0
  95. package/dist/worker.js +106 -0
  96. package/package.json +8 -5
  97. package/web/assets/index-D29SyYFS.css +2 -0
  98. package/web/assets/index-tBJ_K3ur.js +358 -0
  99. package/web/index.html +2 -2
  100. package/web/assets/index-CeBvZ-aq.css +0 -2
  101. package/web/assets/index-CuRAxImC.js +0 -358
@@ -1,5 +1,7 @@
1
1
  import { createArtifact as dbCreateArtifact, getArtifact as dbGetArtifact, getProject, listArtifactsByProject as dbListArtifactsByProject, updateArtifact as dbUpdateArtifact, } from '@plandesk/db';
2
2
  import { serializeArtifact, serializeArtifactSummary, } from '../serialize.js';
3
+ import { assertPermission, resolveOrgId } from './org-scope.js';
4
+ import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
3
5
  export class InvalidArtifactError extends Error {
4
6
  constructor(message) {
5
7
  super(message);
@@ -12,58 +14,60 @@ function assertNonEmptyTitle(title) {
12
14
  }
13
15
  }
14
16
  export function createArtifactService(deps) {
15
- const { db, eventBus } = deps;
17
+ const { db } = deps;
16
18
  return {
17
- listByProject(projectId) {
18
- const project = getProject(db, projectId);
19
- if (!project) {
20
- return undefined;
19
+ async listByProject(projectId) {
20
+ try {
21
+ await assertProjectInOrg(db, projectId, resolveOrgId(deps));
22
+ }
23
+ catch (error) {
24
+ if (error instanceof ProjectNotInOrgError) {
25
+ return undefined;
26
+ }
27
+ throw error;
21
28
  }
22
- return dbListArtifactsByProject(db, projectId).map(serializeArtifactSummary);
29
+ return (await dbListArtifactsByProject(db, projectId)).map(serializeArtifactSummary);
23
30
  },
24
- create(projectId, input) {
25
- const project = getProject(db, projectId);
26
- if (!project) {
27
- return undefined;
31
+ async create(projectId, input) {
32
+ assertPermission(deps, 'document', 'create');
33
+ try {
34
+ await assertProjectInOrg(db, projectId, resolveOrgId(deps));
35
+ }
36
+ catch (error) {
37
+ if (error instanceof ProjectNotInOrgError) {
38
+ return undefined;
39
+ }
40
+ throw error;
28
41
  }
29
42
  assertNonEmptyTitle(input.title);
30
- const artifact = dbCreateArtifact(db, {
43
+ const artifact = await dbCreateArtifact(db, {
31
44
  projectId,
32
45
  title: input.title,
33
46
  kind: input.kind,
34
47
  content: input.content,
35
48
  });
36
- eventBus.emit({
37
- type: 'artifact_created',
38
- artifactId: artifact.id,
39
- projectId,
40
- });
41
49
  return serializeArtifact(artifact);
42
50
  },
43
- get(id) {
44
- const artifact = dbGetArtifact(db, id);
51
+ async get(id) {
52
+ const artifact = await dbGetArtifact(db, id);
45
53
  if (!artifact) {
46
54
  return undefined;
47
55
  }
48
56
  return serializeArtifact(artifact);
49
57
  },
50
- update(id, input) {
51
- const existing = dbGetArtifact(db, id);
58
+ async update(id, input) {
59
+ assertPermission(deps, 'document', 'update');
60
+ const existing = await dbGetArtifact(db, id);
52
61
  if (!existing) {
53
62
  return undefined;
54
63
  }
55
64
  if (input.title !== undefined) {
56
65
  assertNonEmptyTitle(input.title);
57
66
  }
58
- const artifact = dbUpdateArtifact(db, id, input);
67
+ const artifact = await dbUpdateArtifact(db, id, input);
59
68
  if (!artifact) {
60
69
  return undefined;
61
70
  }
62
- eventBus.emit({
63
- type: 'artifact_updated',
64
- artifactId: artifact.id,
65
- projectId: artifact.projectId,
66
- });
67
71
  return serializeArtifact(artifact);
68
72
  },
69
73
  };
@@ -1,8 +1,7 @@
1
1
  import { type Db } from '@plandesk/db';
2
- import type { EventBus } from '../events.js';
3
- export type CanvasServiceDeps = {
2
+ import { type OrgScopedDeps } from './org-scope.js';
3
+ export type CanvasServiceDeps = OrgScopedDeps & {
4
4
  db: Db;
5
- eventBus: EventBus;
6
5
  };
7
6
  export type CanvasNodeInput = {
8
7
  id?: string;
@@ -28,14 +27,14 @@ export declare class InvalidCanvasError extends Error {
28
27
  constructor(message: string);
29
28
  }
30
29
  export declare function createCanvasService(deps: CanvasServiceDeps): {
31
- get(projectId: string): {
30
+ get(projectId: string): Promise<{
32
31
  nodes: {
33
32
  tags?: import("../serialize.js").SerializedTag[] | undefined;
34
33
  id: string;
35
34
  project_id: string;
36
35
  goal_id: string;
37
36
  label: string;
38
- status: "scope" | "todo" | "in_progress" | "done" | "backlog";
37
+ status: "in_progress" | "done" | "todo" | "scope" | "backlog";
39
38
  description: string | null;
40
39
  x: number;
41
40
  y: number;
@@ -55,13 +54,13 @@ export declare function createCanvasService(deps: CanvasServiceDeps): {
55
54
  created_at: string;
56
55
  }[];
57
56
  layout: unknown;
58
- } | undefined;
57
+ } | undefined>;
59
58
  createEdge(projectId: string, input: {
60
59
  fromTaskId: string;
61
60
  toTaskId: string;
62
61
  label?: string | null;
63
62
  style?: string | null;
64
- }): {
63
+ }): Promise<{
65
64
  id: string;
66
65
  project_id: string;
67
66
  from_task_id: string;
@@ -70,15 +69,15 @@ export declare function createCanvasService(deps: CanvasServiceDeps): {
70
69
  arrow_direction: string | null;
71
70
  style: string | null;
72
71
  created_at: string;
73
- } | undefined;
74
- putLayout(projectId: string, payload: PutCanvasLayoutInput): {
72
+ } | undefined>;
73
+ putLayout(projectId: string, payload: PutCanvasLayoutInput): Promise<{
75
74
  nodes: {
76
75
  tags?: import("../serialize.js").SerializedTag[] | undefined;
77
76
  id: string;
78
77
  project_id: string;
79
78
  goal_id: string;
80
79
  label: string;
81
- status: "scope" | "todo" | "in_progress" | "done" | "backlog";
80
+ status: "in_progress" | "done" | "todo" | "scope" | "backlog";
82
81
  description: string | null;
83
82
  x: number;
84
83
  y: number;
@@ -98,8 +97,8 @@ export declare function createCanvasService(deps: CanvasServiceDeps): {
98
97
  created_at: string;
99
98
  }[];
100
99
  layout: unknown;
101
- } | undefined;
102
- deleteEdge(projectId: string, edgeId: string): boolean;
100
+ } | undefined>;
101
+ deleteEdge(projectId: string, edgeId: string): Promise<boolean>;
103
102
  };
104
103
  export type CanvasService = ReturnType<typeof createCanvasService>;
105
104
  //# sourceMappingURL=canvas.d.ts.map
@@ -1,5 +1,7 @@
1
- import { createEdge, createTask, getOrCreateDefaultGoal, deleteEdge as dbDeleteEdge, getEdgeByProjectAndId, getProject, getTask, listEdges, listTasks, updateEdge, updateProject, updateTask, } from '@plandesk/db';
1
+ import { withTransaction, createEdge, createTask, getOrCreateDefaultGoal, deleteEdge as dbDeleteEdge, getEdgeByProjectAndId, getProject, getTask, listEdges, listTasks, updateEdge, updateProject, updateTask, } from '@plandesk/db';
2
2
  import { serializeEdge, serializeTask } from '../serialize.js';
3
+ import { assertPermission, resolveOrgId } from './org-scope.js';
4
+ import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
3
5
  export class InvalidCanvasError extends Error {
4
6
  constructor(message) {
5
7
  super(message);
@@ -16,10 +18,10 @@ function serializeLayout(projectLayout) {
16
18
  return parseLayout(projectLayout);
17
19
  }
18
20
  export function createCanvasService(deps) {
19
- const { db, eventBus } = deps;
20
- function buildCanvas(projectId, project) {
21
- const tasks = listTasks(db, projectId);
22
- const edgeRows = listEdges(db, projectId);
21
+ const { db } = deps;
22
+ async function buildCanvas(projectId, project) {
23
+ const tasks = await listTasks(db, projectId);
24
+ const edgeRows = await listEdges(db, projectId);
23
25
  return {
24
26
  nodes: tasks.map((task) => serializeTask(task)),
25
27
  edges: edgeRows.map(serializeEdge),
@@ -27,49 +29,65 @@ export function createCanvasService(deps) {
27
29
  };
28
30
  }
29
31
  return {
30
- get(projectId) {
31
- const project = getProject(db, projectId);
32
- if (!project) {
33
- return undefined;
32
+ async get(projectId) {
33
+ try {
34
+ const project = await assertProjectInOrg(db, projectId, resolveOrgId(deps));
35
+ return buildCanvas(projectId, project);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof ProjectNotInOrgError) {
39
+ return undefined;
40
+ }
41
+ throw error;
34
42
  }
35
- return buildCanvas(projectId, project);
36
43
  },
37
- createEdge(projectId, input) {
38
- const project = getProject(db, projectId);
39
- if (!project) {
40
- return undefined;
44
+ async createEdge(projectId, input) {
45
+ assertPermission(deps, 'edge', 'create');
46
+ try {
47
+ await assertProjectInOrg(db, projectId, resolveOrgId(deps));
41
48
  }
42
- const taskIds = new Set(listTasks(db, projectId).map((task) => task.id));
49
+ catch (error) {
50
+ if (error instanceof ProjectNotInOrgError) {
51
+ return undefined;
52
+ }
53
+ throw error;
54
+ }
55
+ const taskIds = new Set((await listTasks(db, projectId)).map((task) => task.id));
43
56
  if (!taskIds.has(input.fromTaskId)) {
44
57
  throw new InvalidCanvasError('Edge references missing from task');
45
58
  }
46
59
  if (!taskIds.has(input.toTaskId)) {
47
60
  throw new InvalidCanvasError('Edge references missing to task');
48
61
  }
49
- const edge = createEdge(db, {
62
+ const edge = await createEdge(db, {
50
63
  projectId,
51
64
  fromTaskId: input.fromTaskId,
52
65
  toTaskId: input.toTaskId,
53
66
  label: input.label ?? null,
54
67
  style: input.style ?? null,
55
68
  });
56
- eventBus.emit({ type: 'canvas_updated', projectId });
57
69
  return serializeEdge(edge);
58
70
  },
59
- putLayout(projectId, payload) {
60
- const project = getProject(db, projectId);
61
- if (!project) {
62
- return undefined;
71
+ async putLayout(projectId, payload) {
72
+ assertPermission(deps, 'task', 'update');
73
+ try {
74
+ await assertProjectInOrg(db, projectId, resolveOrgId(deps));
75
+ }
76
+ catch (error) {
77
+ if (error instanceof ProjectNotInOrgError) {
78
+ return undefined;
79
+ }
80
+ throw error;
63
81
  }
64
82
  for (const node of payload.nodes) {
65
83
  if (node.id !== undefined) {
66
- const existing = getTask(db, node.id);
84
+ const existing = await getTask(db, node.id);
67
85
  if (existing && existing.projectId !== projectId) {
68
86
  throw new InvalidCanvasError('Task does not belong to project');
69
87
  }
70
88
  }
71
89
  }
72
- const taskIds = new Set(listTasks(db, projectId).map((task) => task.id));
90
+ const taskIds = new Set((await listTasks(db, projectId)).map((task) => task.id));
73
91
  for (const edge of payload.edges) {
74
92
  if (!taskIds.has(edge.from_task_id) &&
75
93
  !payload.nodes.some((n) => n.id === edge.from_task_id)) {
@@ -79,21 +97,21 @@ export function createCanvasService(deps) {
79
97
  throw new InvalidCanvasError('Edge references missing to task');
80
98
  }
81
99
  }
82
- db.transaction((tx) => {
100
+ await withTransaction(db, async (tx) => {
83
101
  for (const node of payload.nodes) {
84
102
  if (node.id !== undefined) {
85
- const existing = getTask(tx, node.id);
103
+ const existing = await getTask(tx, node.id);
86
104
  if (existing) {
87
- updateTask(tx, node.id, { x: node.x, y: node.y });
105
+ await updateTask(tx, node.id, { x: node.x, y: node.y });
88
106
  continue;
89
107
  }
90
108
  }
91
109
  if (typeof node.label !== 'string' || node.label.trim() === '') {
92
110
  throw new InvalidCanvasError('New canvas node requires label');
93
111
  }
94
- const created = createTask(tx, {
112
+ const created = await createTask(tx, {
95
113
  projectId,
96
- goalId: getOrCreateDefaultGoal(tx, projectId).id,
114
+ goalId: (await getOrCreateDefaultGoal(tx, projectId)).id,
97
115
  id: node.id,
98
116
  label: node.label,
99
117
  status: 'todo',
@@ -110,11 +128,11 @@ export function createCanvasService(deps) {
110
128
  throw new InvalidCanvasError('Edge references missing to task');
111
129
  }
112
130
  }
113
- const existingEdges = listEdges(tx, projectId);
131
+ const existingEdges = await listEdges(tx, projectId);
114
132
  const payloadEdgeIds = new Set(payload.edges.map((edge) => edge.id).filter((id) => id !== undefined));
115
133
  for (const existingEdge of existingEdges) {
116
134
  if (!payloadEdgeIds.has(existingEdge.id)) {
117
- dbDeleteEdge(tx, existingEdge.id);
135
+ await dbDeleteEdge(tx, existingEdge.id);
118
136
  }
119
137
  }
120
138
  for (const edgeInput of payload.edges) {
@@ -126,35 +144,34 @@ export function createCanvasService(deps) {
126
144
  style: edgeInput.style ?? null,
127
145
  };
128
146
  if (edgeInput.id !== undefined) {
129
- const existing = getEdgeByProjectAndId(tx, projectId, edgeInput.id);
147
+ const existing = await getEdgeByProjectAndId(tx, projectId, edgeInput.id);
130
148
  if (existing) {
131
- updateEdge(tx, edgeInput.id, edgeData);
149
+ await updateEdge(tx, edgeInput.id, edgeData);
132
150
  continue;
133
151
  }
134
- createEdge(tx, { projectId, id: edgeInput.id, ...edgeData });
152
+ await createEdge(tx, { projectId, id: edgeInput.id, ...edgeData });
135
153
  continue;
136
154
  }
137
- createEdge(tx, { projectId, ...edgeData });
155
+ await createEdge(tx, { projectId, ...edgeData });
138
156
  }
139
157
  const layoutJson = payload.layout === undefined ? undefined : JSON.stringify(payload.layout);
140
- updateProject(tx, projectId, {
158
+ await updateProject(tx, projectId, {
141
159
  ...(layoutJson !== undefined ? { canvasLayout: layoutJson } : {}),
142
160
  });
143
161
  });
144
- const updatedProject = getProject(db, projectId);
162
+ const updatedProject = await getProject(db, projectId);
145
163
  if (!updatedProject) {
146
164
  return undefined;
147
165
  }
148
- eventBus.emit({ type: 'canvas_updated', projectId });
149
166
  return buildCanvas(projectId, updatedProject);
150
167
  },
151
- deleteEdge(projectId, edgeId) {
152
- const edge = getEdgeByProjectAndId(db, projectId, edgeId);
168
+ async deleteEdge(projectId, edgeId) {
169
+ assertPermission(deps, 'edge', 'delete');
170
+ const edge = await getEdgeByProjectAndId(db, projectId, edgeId);
153
171
  if (!edge) {
154
172
  return false;
155
173
  }
156
- dbDeleteEdge(db, edgeId);
157
- eventBus.emit({ type: 'canvas_updated', projectId });
174
+ await dbDeleteEdge(db, edgeId);
158
175
  return true;
159
176
  },
160
177
  };
@@ -1,9 +1,8 @@
1
1
  import { type CommentTargetType, type Db } from '@plandesk/db';
2
- import type { EventBus } from '../events.js';
3
2
  import { type SerializedComment } from '../serialize.js';
4
- export type CommentServiceDeps = {
3
+ import { type OrgScopedDeps } from './org-scope.js';
4
+ export type CommentServiceDeps = OrgScopedDeps & {
5
5
  db: Db;
6
- eventBus: EventBus;
7
6
  };
8
7
  export type CommentTarget = {
9
8
  type: CommentTargetType;
@@ -22,23 +21,23 @@ export declare class InvalidCommentError extends Error {
22
21
  constructor(message: string);
23
22
  }
24
23
  export declare function createCommentService(deps: CommentServiceDeps): {
25
- create(target: CommentTarget, input: CreateCommentInput): SerializedComment | undefined;
26
- createForArtifact(projectId: string, artifactId: string, input: CreateCommentInput): SerializedComment | undefined;
24
+ create(target: CommentTarget, input: CreateCommentInput): Promise<SerializedComment | undefined>;
25
+ createForArtifact(projectId: string, artifactId: string, input: CreateCommentInput): Promise<SerializedComment | undefined>;
27
26
  listForArtifact(projectId: string, artifactId: string, options?: {
28
27
  includeResolved?: boolean;
29
- }): SerializedComment[] | undefined;
28
+ }): Promise<SerializedComment[] | undefined>;
30
29
  listByTarget(target: CommentTarget, options?: {
31
30
  includeResolved?: boolean;
32
- }): SerializedComment[] | undefined;
33
- resolveTargetProjectId(target: CommentTarget): string | undefined;
31
+ }): Promise<SerializedComment[] | undefined>;
32
+ resolveTargetProjectId(target: CommentTarget): Promise<string | undefined>;
34
33
  listByDocument(documentId: string, options?: {
35
34
  includeResolved?: boolean;
36
- }): SerializedComment[] | undefined;
35
+ }): Promise<SerializedComment[] | undefined>;
37
36
  listByProject(projectId: string, options?: {
38
37
  includeResolved?: boolean;
39
- }): SerializedComment[] | undefined;
40
- update(id: string, input: UpdateCommentInput): SerializedComment | undefined;
41
- delete(id: string): boolean;
38
+ }): Promise<SerializedComment[] | undefined>;
39
+ update(id: string, input: UpdateCommentInput): Promise<SerializedComment | undefined>;
40
+ delete(id: string): Promise<boolean>;
42
41
  };
43
42
  export type CommentService = ReturnType<typeof createCommentService>;
44
43
  //# sourceMappingURL=comments.d.ts.map
@@ -1,5 +1,7 @@
1
1
  import { createComment, deleteComment as dbDeleteComment, getComment as dbGetComment, getDocument as dbGetDocument, getNote as dbGetNote, getProject, getSubmission, getTask, listCommentsByProject as dbListCommentsByProject, listCommentsByTarget as dbListCommentsByTarget, updateComment as dbUpdateComment, } from '@plandesk/db';
2
2
  import { serializeComment } from '../serialize.js';
3
+ import { assertPermission, resolveOrgId } from './org-scope.js';
4
+ import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
3
5
  export class InvalidCommentError extends Error {
4
6
  constructor(message) {
5
7
  super(message);
@@ -11,16 +13,16 @@ function assertNonEmptyBody(body) {
11
13
  throw new InvalidCommentError('Comment body must not be empty');
12
14
  }
13
15
  }
14
- function targetProjectId(db, target) {
16
+ async function targetProjectId(db, target) {
15
17
  switch (target.type) {
16
18
  case 'document':
17
- return dbGetDocument(db, target.id)?.projectId;
19
+ return (await dbGetDocument(db, target.id))?.projectId;
18
20
  case 'task':
19
- return getTask(db, target.id)?.projectId;
21
+ return (await getTask(db, target.id))?.projectId;
20
22
  case 'note':
21
- return dbGetNote(db, target.id)?.projectId;
23
+ return (await dbGetNote(db, target.id))?.projectId;
22
24
  case 'submission':
23
- return getSubmission(db, target.id)?.projectId;
25
+ return (await getSubmission(db, target.id))?.projectId;
24
26
  case 'artifact':
25
27
  // An artifact is a file on disk, not a DB entity — its project is
26
28
  // supplied by the caller (the connected repo's project), not resolved
@@ -28,36 +30,17 @@ function targetProjectId(db, target) {
28
30
  return undefined;
29
31
  }
30
32
  }
31
- function emitCommentCreated(eventBus, commentId, projectId, target) {
32
- eventBus.emit({
33
- type: 'comment_created',
34
- commentId,
35
- projectId,
36
- target_type: target.type,
37
- target_id: target.id,
38
- ...(target.type === 'document' ? { documentId: target.id } : {}),
39
- });
40
- }
41
- function emitCommentUpdated(eventBus, commentId, projectId, target) {
42
- eventBus.emit({
43
- type: 'comment_updated',
44
- commentId,
45
- projectId,
46
- target_type: target.type,
47
- target_id: target.id,
48
- ...(target.type === 'document' ? { documentId: target.id } : {}),
49
- });
50
- }
51
33
  export function createCommentService(deps) {
52
- const { db, eventBus } = deps;
34
+ const { db } = deps;
53
35
  return {
54
- create(target, input) {
55
- const projectId = targetProjectId(db, target);
36
+ async create(target, input) {
37
+ assertPermission(deps, 'comment', 'create');
38
+ const projectId = await targetProjectId(db, target);
56
39
  if (!projectId) {
57
40
  return undefined;
58
41
  }
59
42
  assertNonEmptyBody(input.body);
60
- const comment = createComment(db, {
43
+ const comment = await createComment(db, {
61
44
  projectId,
62
45
  targetType: target.type,
63
46
  targetId: target.id,
@@ -65,22 +48,21 @@ export function createCommentService(deps) {
65
48
  passage: input.passage,
66
49
  anchor: input.anchor,
67
50
  });
68
- emitCommentCreated(eventBus, comment.id, projectId, target);
69
51
  return serializeComment(comment);
70
52
  },
71
53
  // Artifact comments are project-scoped: the caller supplies the project
72
54
  // (the connected repo's), and the file identity is the target id. Slashes
73
55
  // 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)) {
56
+ async createForArtifact(projectId, artifactId, input) {
57
+ assertPermission(deps, 'comment', 'create');
58
+ if (!(await getProject(db, projectId))) {
76
59
  return undefined;
77
60
  }
78
61
  if (artifactId.trim() === '') {
79
62
  throw new InvalidCommentError('Artifact id must not be empty');
80
63
  }
81
64
  assertNonEmptyBody(input.body);
82
- const target = { type: 'artifact', id: artifactId };
83
- const comment = createComment(db, {
65
+ const comment = await createComment(db, {
84
66
  projectId,
85
67
  targetType: 'artifact',
86
68
  targetId: artifactId,
@@ -88,80 +70,78 @@ export function createCommentService(deps) {
88
70
  passage: input.passage,
89
71
  anchor: input.anchor,
90
72
  });
91
- emitCommentCreated(eventBus, comment.id, projectId, target);
92
73
  return serializeComment(comment);
93
74
  },
94
- listForArtifact(projectId, artifactId, options) {
95
- if (!getProject(db, projectId)) {
75
+ async listForArtifact(projectId, artifactId, options) {
76
+ if (!(await getProject(db, projectId))) {
96
77
  return undefined;
97
78
  }
98
- return dbListCommentsByTarget(db, 'artifact', artifactId, options).map(serializeComment);
79
+ return (await dbListCommentsByTarget(db, 'artifact', artifactId, options)).map(serializeComment);
99
80
  },
100
- listByTarget(target, options) {
101
- const projectId = targetProjectId(db, target);
81
+ async listByTarget(target, options) {
82
+ const projectId = await targetProjectId(db, target);
102
83
  if (!projectId) {
103
84
  return undefined;
104
85
  }
105
- return dbListCommentsByTarget(db, target.type, target.id, options).map(serializeComment);
86
+ return (await dbListCommentsByTarget(db, target.type, target.id, options)).map(serializeComment);
106
87
  },
107
- resolveTargetProjectId(target) {
88
+ async resolveTargetProjectId(target) {
108
89
  return targetProjectId(db, target);
109
90
  },
110
- listByDocument(documentId, options) {
91
+ async listByDocument(documentId, options) {
111
92
  return this.listByTarget({ type: 'document', id: documentId }, options);
112
93
  },
113
- listByProject(projectId, options) {
114
- const project = getProject(db, projectId);
115
- if (!project) {
116
- return undefined;
94
+ async listByProject(projectId, options) {
95
+ try {
96
+ await assertProjectInOrg(db, projectId, resolveOrgId(deps));
117
97
  }
118
- return dbListCommentsByProject(db, projectId, options).map(serializeComment);
98
+ catch (error) {
99
+ if (error instanceof ProjectNotInOrgError) {
100
+ return undefined;
101
+ }
102
+ throw error;
103
+ }
104
+ return (await dbListCommentsByProject(db, projectId, options)).map(serializeComment);
119
105
  },
120
- update(id, input) {
121
- const existing = dbGetComment(db, id);
106
+ async update(id, input) {
107
+ assertPermission(deps, 'comment', 'update');
108
+ const existing = await dbGetComment(db, id);
122
109
  if (!existing) {
123
110
  return undefined;
124
111
  }
125
112
  if (input.body !== undefined) {
126
113
  assertNonEmptyBody(input.body);
127
114
  }
128
- const comment = dbUpdateComment(db, id, input);
115
+ const comment = await dbUpdateComment(db, id, input);
129
116
  if (!comment) {
130
117
  return undefined;
131
118
  }
132
- const projectId = targetProjectId(db, {
119
+ const projectId = await targetProjectId(db, {
133
120
  type: comment.targetType,
134
121
  id: comment.targetId,
135
122
  });
136
123
  if (!projectId) {
137
124
  return undefined;
138
125
  }
139
- emitCommentUpdated(eventBus, id, projectId, {
140
- type: comment.targetType,
141
- id: comment.targetId,
142
- });
143
126
  return serializeComment(comment);
144
127
  },
145
- delete(id) {
146
- const existing = dbGetComment(db, id);
128
+ async delete(id) {
129
+ assertPermission(deps, 'comment', 'delete');
130
+ const existing = await dbGetComment(db, id);
147
131
  if (!existing) {
148
132
  return false;
149
133
  }
150
- const projectId = targetProjectId(db, {
134
+ const projectId = await targetProjectId(db, {
151
135
  type: existing.targetType,
152
136
  id: existing.targetId,
153
137
  });
154
138
  if (!projectId) {
155
139
  return false;
156
140
  }
157
- const deleted = dbDeleteComment(db, id);
141
+ const deleted = await dbDeleteComment(db, id);
158
142
  if (!deleted) {
159
143
  return false;
160
144
  }
161
- emitCommentUpdated(eventBus, id, projectId, {
162
- type: existing.targetType,
163
- id: existing.targetId,
164
- });
165
145
  return true;
166
146
  },
167
147
  };
@@ -1,9 +1,8 @@
1
1
  import { type Db } from '@plandesk/db';
2
2
  import { type PaginationParams, type SerializedDocument, type SerializedDocumentFolderTree, type SerializedDocumentTree } from '../serialize.js';
3
- import type { EventBus } from '../events.js';
4
- export type DocumentServiceDeps = {
3
+ import { type OrgScopedDeps } from './org-scope.js';
4
+ export type DocumentServiceDeps = OrgScopedDeps & {
5
5
  db: Db;
6
- eventBus: EventBus;
7
6
  };
8
7
  export type CreateDocumentInput = {
9
8
  title: string;
@@ -25,14 +24,14 @@ export declare class InvalidDocumentError extends Error {
25
24
  constructor(message: string);
26
25
  }
27
26
  export declare function createDocumentService(deps: DocumentServiceDeps): {
28
- listTree(projectId: string, pagination?: PaginationParams): SerializedDocumentTree[] | undefined;
29
- listFolderTree(projectId: string): SerializedDocumentFolderTree | undefined;
30
- listByFolder(projectId: string, folderId: string): SerializedDocumentTree[] | undefined;
31
- create(projectId: string, input: CreateDocumentInput): SerializedDocument | undefined;
32
- get(id: string): SerializedDocument | undefined;
33
- update(id: string, input: UpdateDocumentInput): SerializedDocument | undefined;
34
- getByTask(taskId: string): SerializedDocument | undefined;
35
- delete(id: string): boolean;
27
+ listTree(projectId: string, pagination?: PaginationParams): Promise<SerializedDocumentTree[] | undefined>;
28
+ listFolderTree(projectId: string): Promise<SerializedDocumentFolderTree | undefined>;
29
+ listByFolder(projectId: string, folderId: string): Promise<SerializedDocumentTree[] | undefined>;
30
+ create(projectId: string, input: CreateDocumentInput): Promise<SerializedDocument | undefined>;
31
+ get(id: string): Promise<SerializedDocument | undefined>;
32
+ update(id: string, input: UpdateDocumentInput): Promise<SerializedDocument | undefined>;
33
+ getByTask(taskId: string): Promise<SerializedDocument | undefined>;
34
+ delete(id: string): Promise<boolean>;
36
35
  };
37
36
  export type DocumentService = ReturnType<typeof createDocumentService>;
38
37
  //# sourceMappingURL=documents.d.ts.map