@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
|
@@ -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
|
|
17
|
+
const { db } = deps;
|
|
16
18
|
return {
|
|
17
|
-
listByProject(projectId) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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
|
package/dist/services/canvas.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
async createEdge(projectId, input) {
|
|
45
|
+
assertPermission(deps, 'edge', 'create');
|
|
46
|
+
try {
|
|
47
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
41
48
|
}
|
|
42
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
34
|
+
const { db } = deps;
|
|
53
35
|
return {
|
|
54
|
-
create(target, input) {
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
return undefined;
|
|
94
|
+
async listByProject(projectId, options) {
|
|
95
|
+
try {
|
|
96
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
117
97
|
}
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|