@plandesk/api 0.16.0 → 0.18.0
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/events.d.ts +11 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/routes/artifacts.d.ts +4 -0
- package/dist/routes/artifacts.js +74 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/files.js +66 -0
- package/dist/routes/shares.d.ts +4 -0
- package/dist/routes/shares.js +24 -0
- package/dist/serialize.d.ts +18 -1
- package/dist/serialize.js +19 -0
- package/dist/server.js +7 -1
- package/dist/services/artifacts.d.ts +28 -0
- package/dist/services/artifacts.js +71 -0
- package/dist/services/files.d.ts +25 -0
- package/dist/services/files.js +31 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +8 -0
- package/dist/services/share.d.ts +27 -0
- package/dist/services/share.js +170 -1
- package/dist/storage/adapter.d.ts +23 -0
- package/dist/storage/adapter.js +4 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.js +17 -0
- package/dist/storage/local.d.ts +7 -0
- package/dist/storage/local.js +34 -0
- package/dist/storage/s3.d.ts +16 -0
- package/dist/storage/s3.js +37 -0
- package/package.json +3 -3
- package/web/assets/index-CZs-KaPG.css +2 -0
- package/web/assets/{index--cBq1E2L.js → index-zUcwwGtr.js} +87 -87
- package/web/index.html +2 -2
- package/web/assets/index-RxcVWu3D.css +0 -2
package/dist/events.d.ts
CHANGED
|
@@ -52,6 +52,16 @@ export type NoteUpdatedEvent = {
|
|
|
52
52
|
noteId: string;
|
|
53
53
|
projectId: string;
|
|
54
54
|
};
|
|
55
|
+
export type ArtifactCreatedEvent = {
|
|
56
|
+
type: 'artifact_created';
|
|
57
|
+
artifactId: string;
|
|
58
|
+
projectId: string;
|
|
59
|
+
};
|
|
60
|
+
export type ArtifactUpdatedEvent = {
|
|
61
|
+
type: 'artifact_updated';
|
|
62
|
+
artifactId: string;
|
|
63
|
+
projectId: string;
|
|
64
|
+
};
|
|
55
65
|
export type AgentRunStartedEvent = {
|
|
56
66
|
type: 'agent_run_started';
|
|
57
67
|
runId: string;
|
|
@@ -76,7 +86,7 @@ export type GoalUpdatedEvent = {
|
|
|
76
86
|
goalId: string;
|
|
77
87
|
projectId: string;
|
|
78
88
|
};
|
|
79
|
-
export type PlankDeskEvent = TaskUpdatedEvent | TagUpdatedEvent | CanvasUpdatedEvent | DocumentCreatedEvent | CommentCreatedEvent | CommentUpdatedEvent | FolderCreatedEvent | FolderUpdatedEvent | NoteCreatedEvent | NoteUpdatedEvent | AgentRunStartedEvent | AgentRunProgressEvent | AgentRunCompletedEvent | SubmissionsPulledEvent | GoalUpdatedEvent;
|
|
89
|
+
export type PlankDeskEvent = TaskUpdatedEvent | TagUpdatedEvent | CanvasUpdatedEvent | DocumentCreatedEvent | CommentCreatedEvent | CommentUpdatedEvent | FolderCreatedEvent | FolderUpdatedEvent | NoteCreatedEvent | NoteUpdatedEvent | ArtifactCreatedEvent | ArtifactUpdatedEvent | AgentRunStartedEvent | AgentRunProgressEvent | AgentRunCompletedEvent | SubmissionsPulledEvent | GoalUpdatedEvent;
|
|
80
90
|
export type EventListener = (event: PlankDeskEvent) => void;
|
|
81
91
|
export type EventBus = {
|
|
82
92
|
subscribe(listener: EventListener): () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,17 +14,20 @@ export type { CanvasService } from './services/canvas.js';
|
|
|
14
14
|
export type { DocumentService } from './services/documents.js';
|
|
15
15
|
export type { FolderService } from './services/folders.js';
|
|
16
16
|
export type { NoteService } from './services/notes.js';
|
|
17
|
+
export type { FileService } from './services/files.js';
|
|
18
|
+
export type { ArtifactService } from './services/artifacts.js';
|
|
17
19
|
export type { CommentService } from './services/comments.js';
|
|
18
20
|
export type { AgentRunService } from './services/agent-runs.js';
|
|
19
21
|
export { InvalidDocumentError } from './services/documents.js';
|
|
20
22
|
export { InvalidFolderError } from './services/folders.js';
|
|
21
23
|
export { InvalidNoteError } from './services/notes.js';
|
|
24
|
+
export { InvalidArtifactError } from './services/artifacts.js';
|
|
22
25
|
export { InvalidTagError } from './services/tags.js';
|
|
23
26
|
export { InvalidCommentError } from './services/comments.js';
|
|
24
27
|
export { InvalidCanvasError } from './services/canvas.js';
|
|
25
28
|
export { InvalidAgentRunError } from './services/agent-runs.js';
|
|
26
29
|
export { InvalidScaffoldError } from './services/projects.js';
|
|
27
|
-
export { InvalidShareError } from './services/share.js';
|
|
30
|
+
export { InvalidShareError, type ShareService } from './services/share.js';
|
|
28
31
|
export { InvalidTriageError, InvalidTriageInputError, SyncUnavailableError, SyncUnauthorizedError, type SyncRemote, type SyncService, } from './services/sync.js';
|
|
29
32
|
export declare const version: () => string;
|
|
30
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { InvalidGoalReferenceError } from './services/tasks.js';
|
|
|
9
9
|
export { InvalidDocumentError } from './services/documents.js';
|
|
10
10
|
export { InvalidFolderError } from './services/folders.js';
|
|
11
11
|
export { InvalidNoteError } from './services/notes.js';
|
|
12
|
+
export { InvalidArtifactError } from './services/artifacts.js';
|
|
12
13
|
export { InvalidTagError } from './services/tags.js';
|
|
13
14
|
export { InvalidCommentError } from './services/comments.js';
|
|
14
15
|
export { InvalidCanvasError } from './services/canvas.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { artifactKinds } from '@plandesk/db';
|
|
3
|
+
import { InvalidArtifactError } from '../services/artifacts.js';
|
|
4
|
+
function isValidKind(kind) {
|
|
5
|
+
return artifactKinds.includes(kind);
|
|
6
|
+
}
|
|
7
|
+
export function createArtifactsRouter(artifactService) {
|
|
8
|
+
const router = new Hono();
|
|
9
|
+
router.get('/projects/:id/artifacts', (c) => {
|
|
10
|
+
const artifacts = artifactService.listByProject(c.req.param('id'));
|
|
11
|
+
if (!artifacts) {
|
|
12
|
+
return c.json({ error: 'not_found' }, 404);
|
|
13
|
+
}
|
|
14
|
+
return c.json(artifacts);
|
|
15
|
+
});
|
|
16
|
+
router.post('/projects/:id/artifacts', async (c) => {
|
|
17
|
+
const body = await c.req.json();
|
|
18
|
+
if (typeof body.title !== 'string' || body.title.trim() === '') {
|
|
19
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
20
|
+
}
|
|
21
|
+
if (body.kind !== undefined && !isValidKind(body.kind)) {
|
|
22
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const artifact = artifactService.create(c.req.param('id'), {
|
|
26
|
+
title: body.title,
|
|
27
|
+
kind: body.kind,
|
|
28
|
+
content: body.content,
|
|
29
|
+
});
|
|
30
|
+
if (!artifact) {
|
|
31
|
+
return c.json({ error: 'not_found' }, 404);
|
|
32
|
+
}
|
|
33
|
+
return c.json(artifact, 201);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof InvalidArtifactError) {
|
|
37
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
router.get('/artifacts/:id', (c) => {
|
|
43
|
+
const artifact = artifactService.get(c.req.param('id'));
|
|
44
|
+
if (!artifact) {
|
|
45
|
+
return c.json({ error: 'not_found' }, 404);
|
|
46
|
+
}
|
|
47
|
+
return c.json(artifact);
|
|
48
|
+
});
|
|
49
|
+
router.patch('/artifacts/:id', async (c) => {
|
|
50
|
+
const body = await c.req.json();
|
|
51
|
+
if (body.kind !== undefined && !isValidKind(body.kind)) {
|
|
52
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const artifact = artifactService.update(c.req.param('id'), {
|
|
56
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
57
|
+
...(body.kind !== undefined ? { kind: body.kind } : {}),
|
|
58
|
+
...(body.content !== undefined ? { content: body.content } : {}),
|
|
59
|
+
});
|
|
60
|
+
if (!artifact) {
|
|
61
|
+
return c.json({ error: 'not_found' }, 404);
|
|
62
|
+
}
|
|
63
|
+
return c.json(artifact);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof InvalidArtifactError) {
|
|
67
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return router;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
3
|
+
// Content-Disposition is a quoted-string (RFC 6266): escape backslash/quote
|
|
4
|
+
// and strip anything outside printable ASCII so a hostile filename (CRLF,
|
|
5
|
+
// control chars) can't break out of the header.
|
|
6
|
+
function sanitizeFilenameForHeader(filename) {
|
|
7
|
+
return filename
|
|
8
|
+
.replace(/[^\x20-\x7E]/g, '_')
|
|
9
|
+
.replace(/\\/g, '\\\\')
|
|
10
|
+
.replace(/"/g, '\\"');
|
|
11
|
+
}
|
|
12
|
+
export function createFilesRouter(fileService) {
|
|
13
|
+
const router = new Hono();
|
|
14
|
+
router.post('/projects/:id/files', async (c) => {
|
|
15
|
+
const body = await c.req.json();
|
|
16
|
+
if (typeof body.filename !== 'string' || body.filename.trim() === '') {
|
|
17
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
18
|
+
}
|
|
19
|
+
if (typeof body.mime !== 'string' || body.mime.trim() === '') {
|
|
20
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
21
|
+
}
|
|
22
|
+
if (typeof body.content_base64 !== 'string' || body.content_base64.length === 0) {
|
|
23
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
24
|
+
}
|
|
25
|
+
const bytes = Buffer.from(body.content_base64, 'base64');
|
|
26
|
+
if (bytes.length > MAX_FILE_BYTES) {
|
|
27
|
+
return c.json({ error: 'file_too_large' }, 413);
|
|
28
|
+
}
|
|
29
|
+
const file = await fileService.create({
|
|
30
|
+
projectId: c.req.param('id'),
|
|
31
|
+
filename: body.filename,
|
|
32
|
+
mime: body.mime,
|
|
33
|
+
bytes,
|
|
34
|
+
});
|
|
35
|
+
if (!file) {
|
|
36
|
+
return c.json({ error: 'not_found' }, 404);
|
|
37
|
+
}
|
|
38
|
+
return c.json(file, 201);
|
|
39
|
+
});
|
|
40
|
+
router.get('/files/:id', async (c) => {
|
|
41
|
+
const resolved = await fileService.get(c.req.param('id'));
|
|
42
|
+
if (!resolved) {
|
|
43
|
+
return c.json({ error: 'not_found' }, 404);
|
|
44
|
+
}
|
|
45
|
+
if ('redirectUrl' in resolved) {
|
|
46
|
+
return c.redirect(resolved.redirectUrl, 302);
|
|
47
|
+
}
|
|
48
|
+
const headers = {
|
|
49
|
+
'X-Content-Type-Options': 'nosniff',
|
|
50
|
+
};
|
|
51
|
+
// Only image/* is safe to render inline; every other mime (including
|
|
52
|
+
// text/html) is forced to download so a user-uploaded file can never
|
|
53
|
+
// execute as active content in the browser.
|
|
54
|
+
if (resolved.mime.startsWith('image/')) {
|
|
55
|
+
headers['Content-Type'] = resolved.mime;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
headers['Content-Type'] = 'application/octet-stream';
|
|
59
|
+
headers['Content-Disposition'] =
|
|
60
|
+
`attachment; filename="${sanitizeFilenameForHeader(resolved.filename)}"`;
|
|
61
|
+
}
|
|
62
|
+
return c.body(new Uint8Array(resolved.bytes), 200, headers);
|
|
63
|
+
});
|
|
64
|
+
return router;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
export function createSharesRouter(shareService) {
|
|
3
|
+
const router = new Hono();
|
|
4
|
+
// Hono doesn't match a literal `.md` suffix inside a param, so the route
|
|
5
|
+
// takes the raw segment and the handler enforces + strips the extension.
|
|
6
|
+
router.get('/share/:tokenWithExt', (c) => {
|
|
7
|
+
const raw = c.req.param('tokenWithExt');
|
|
8
|
+
if (!raw.endsWith('.md')) {
|
|
9
|
+
return c.notFound();
|
|
10
|
+
}
|
|
11
|
+
const token = raw.slice(0, -'.md'.length);
|
|
12
|
+
const origin = new URL(c.req.url).origin;
|
|
13
|
+
const result = shareService.getResourceMarkdown(token, origin);
|
|
14
|
+
if (result.status === 'not_found') {
|
|
15
|
+
return c.json({ error: 'not_found' }, 404);
|
|
16
|
+
}
|
|
17
|
+
if (result.status === 'gone') {
|
|
18
|
+
return c.json({ error: 'gone' }, 410);
|
|
19
|
+
}
|
|
20
|
+
return c.body(result.markdown, 200, { 'Content-Type': 'text/markdown; charset=utf-8' });
|
|
21
|
+
});
|
|
22
|
+
return router;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=shares.js.map
|
package/dist/serialize.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentRun, AgentRunEvent, Document, Comment, Edge, Folder, Goal, Note, Project, Tag, Task, TaskStatus } from '@plandesk/db';
|
|
1
|
+
import type { AgentRun, AgentRunEvent, Artifact, Document, Comment, Edge, Folder, Goal, Note, Project, Tag, Task, TaskStatus } from '@plandesk/db';
|
|
2
2
|
export type PaginationParams = {
|
|
3
3
|
limit?: number;
|
|
4
4
|
offset?: number;
|
|
@@ -89,6 +89,23 @@ export type SerializedNote = {
|
|
|
89
89
|
updated_at: string;
|
|
90
90
|
};
|
|
91
91
|
export declare function serializeNote(note: Note): SerializedNote;
|
|
92
|
+
export type SerializedArtifact = {
|
|
93
|
+
id: string;
|
|
94
|
+
project_id: string;
|
|
95
|
+
title: string;
|
|
96
|
+
kind: Artifact['kind'];
|
|
97
|
+
content: string;
|
|
98
|
+
created_at: string;
|
|
99
|
+
updated_at: string;
|
|
100
|
+
};
|
|
101
|
+
export type SerializedArtifactSummary = {
|
|
102
|
+
id: string;
|
|
103
|
+
title: string;
|
|
104
|
+
kind: Artifact['kind'];
|
|
105
|
+
updated_at: string;
|
|
106
|
+
};
|
|
107
|
+
export declare function serializeArtifact(artifact: Artifact): SerializedArtifact;
|
|
108
|
+
export declare function serializeArtifactSummary(artifact: Artifact): SerializedArtifactSummary;
|
|
92
109
|
export type SerializedComment = {
|
|
93
110
|
id: string;
|
|
94
111
|
target_type: Comment['targetType'];
|
package/dist/serialize.js
CHANGED
|
@@ -120,6 +120,25 @@ export function serializeNote(note) {
|
|
|
120
120
|
updated_at: note.updatedAt.toISOString(),
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
+
export function serializeArtifact(artifact) {
|
|
124
|
+
return {
|
|
125
|
+
id: artifact.id,
|
|
126
|
+
project_id: artifact.projectId,
|
|
127
|
+
title: artifact.title,
|
|
128
|
+
kind: artifact.kind,
|
|
129
|
+
content: artifact.content,
|
|
130
|
+
created_at: artifact.createdAt.toISOString(),
|
|
131
|
+
updated_at: artifact.updatedAt.toISOString(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function serializeArtifactSummary(artifact) {
|
|
135
|
+
return {
|
|
136
|
+
id: artifact.id,
|
|
137
|
+
title: artifact.title,
|
|
138
|
+
kind: artifact.kind,
|
|
139
|
+
updated_at: artifact.updatedAt.toISOString(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
123
142
|
export function serializeComment(comment) {
|
|
124
143
|
return {
|
|
125
144
|
id: comment.id,
|
package/dist/server.js
CHANGED
|
@@ -7,8 +7,11 @@ import { createTagsRouter } from './routes/tags.js';
|
|
|
7
7
|
import { createCanvasRouter } from './routes/canvas.js';
|
|
8
8
|
import { createCommentsRouter } from './routes/comments.js';
|
|
9
9
|
import { createDocumentsRouter } from './routes/documents.js';
|
|
10
|
+
import { createArtifactsRouter } from './routes/artifacts.js';
|
|
11
|
+
import { createFilesRouter } from './routes/files.js';
|
|
10
12
|
import { createFoldersRouter } from './routes/folders.js';
|
|
11
13
|
import { createNotesRouter } from './routes/notes.js';
|
|
14
|
+
import { createSharesRouter } from './routes/shares.js';
|
|
12
15
|
import { createEventsRouter } from './routes/events.js';
|
|
13
16
|
import { createTokensRouter } from './routes/tokens.js';
|
|
14
17
|
import { createAgentRunsRouter } from './routes/agent-runs.js';
|
|
@@ -18,7 +21,7 @@ import { mountStatic } from './static.js';
|
|
|
18
21
|
import { createServices } from './services/index.js';
|
|
19
22
|
export function createApp(deps) {
|
|
20
23
|
const services = deps.services ?? createServices({ db: deps.db, eventBus: deps.eventBus });
|
|
21
|
-
const { eventBus, projectService, goalService, taskService, tagService, canvasService, documentService, folderService, noteService, commentService, agentRunService, tokenService, syncService, } = services;
|
|
24
|
+
const { eventBus, projectService, goalService, taskService, tagService, canvasService, documentService, folderService, noteService, commentService, agentRunService, tokenService, syncService, fileService, artifactService, shareService, } = services;
|
|
22
25
|
const app = new Hono();
|
|
23
26
|
if (deps.authPassword !== undefined && deps.authPassword.length > 0) {
|
|
24
27
|
app.use('*', createAuthMiddleware(deps.authPassword));
|
|
@@ -30,8 +33,11 @@ export function createApp(deps) {
|
|
|
30
33
|
app.route('/api/v1', createTagsRouter(tagService));
|
|
31
34
|
app.route('/api/v1', createCanvasRouter(canvasService));
|
|
32
35
|
app.route('/api/v1', createDocumentsRouter(documentService));
|
|
36
|
+
app.route('/api/v1', createFilesRouter(fileService));
|
|
37
|
+
app.route('/api/v1', createArtifactsRouter(artifactService));
|
|
33
38
|
app.route('/api/v1', createFoldersRouter(folderService));
|
|
34
39
|
app.route('/api/v1', createNotesRouter(noteService));
|
|
40
|
+
app.route('/api/v1', createSharesRouter(shareService));
|
|
35
41
|
app.route('/api/v1', createCommentsRouter(commentService));
|
|
36
42
|
app.route('/api/v1', createTokensRouter(tokenService));
|
|
37
43
|
app.route('/api/v1', createAgentRunsRouter(agentRunService));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ArtifactKind, type Db } from '@plandesk/db';
|
|
2
|
+
import { type SerializedArtifact, type SerializedArtifactSummary } from '../serialize.js';
|
|
3
|
+
import type { EventBus } from '../events.js';
|
|
4
|
+
export type ArtifactServiceDeps = {
|
|
5
|
+
db: Db;
|
|
6
|
+
eventBus: EventBus;
|
|
7
|
+
};
|
|
8
|
+
export type CreateArtifactInput = {
|
|
9
|
+
title: string;
|
|
10
|
+
kind?: ArtifactKind;
|
|
11
|
+
content?: string;
|
|
12
|
+
};
|
|
13
|
+
export type UpdateArtifactInput = {
|
|
14
|
+
title?: string;
|
|
15
|
+
kind?: ArtifactKind;
|
|
16
|
+
content?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare class InvalidArtifactError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function createArtifactService(deps: ArtifactServiceDeps): {
|
|
22
|
+
listByProject(projectId: string): SerializedArtifactSummary[] | undefined;
|
|
23
|
+
create(projectId: string, input: CreateArtifactInput): SerializedArtifact | undefined;
|
|
24
|
+
get(id: string): SerializedArtifact | undefined;
|
|
25
|
+
update(id: string, input: UpdateArtifactInput): SerializedArtifact | undefined;
|
|
26
|
+
};
|
|
27
|
+
export type ArtifactService = ReturnType<typeof createArtifactService>;
|
|
28
|
+
//# sourceMappingURL=artifacts.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createArtifact as dbCreateArtifact, getArtifact as dbGetArtifact, getProject, listArtifactsByProject as dbListArtifactsByProject, updateArtifact as dbUpdateArtifact, } from '@plandesk/db';
|
|
2
|
+
import { serializeArtifact, serializeArtifactSummary, } from '../serialize.js';
|
|
3
|
+
export class InvalidArtifactError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'InvalidArtifactError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function assertNonEmptyTitle(title) {
|
|
10
|
+
if (title.trim() === '') {
|
|
11
|
+
throw new InvalidArtifactError('Artifact title must not be empty');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function createArtifactService(deps) {
|
|
15
|
+
const { db, eventBus } = deps;
|
|
16
|
+
return {
|
|
17
|
+
listByProject(projectId) {
|
|
18
|
+
const project = getProject(db, projectId);
|
|
19
|
+
if (!project) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return dbListArtifactsByProject(db, projectId).map(serializeArtifactSummary);
|
|
23
|
+
},
|
|
24
|
+
create(projectId, input) {
|
|
25
|
+
const project = getProject(db, projectId);
|
|
26
|
+
if (!project) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
assertNonEmptyTitle(input.title);
|
|
30
|
+
const artifact = dbCreateArtifact(db, {
|
|
31
|
+
projectId,
|
|
32
|
+
title: input.title,
|
|
33
|
+
kind: input.kind,
|
|
34
|
+
content: input.content,
|
|
35
|
+
});
|
|
36
|
+
eventBus.emit({
|
|
37
|
+
type: 'artifact_created',
|
|
38
|
+
artifactId: artifact.id,
|
|
39
|
+
projectId,
|
|
40
|
+
});
|
|
41
|
+
return serializeArtifact(artifact);
|
|
42
|
+
},
|
|
43
|
+
get(id) {
|
|
44
|
+
const artifact = dbGetArtifact(db, id);
|
|
45
|
+
if (!artifact) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return serializeArtifact(artifact);
|
|
49
|
+
},
|
|
50
|
+
update(id, input) {
|
|
51
|
+
const existing = dbGetArtifact(db, id);
|
|
52
|
+
if (!existing) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (input.title !== undefined) {
|
|
56
|
+
assertNonEmptyTitle(input.title);
|
|
57
|
+
}
|
|
58
|
+
const artifact = dbUpdateArtifact(db, id, input);
|
|
59
|
+
if (!artifact) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
eventBus.emit({
|
|
63
|
+
type: 'artifact_updated',
|
|
64
|
+
artifactId: artifact.id,
|
|
65
|
+
projectId: artifact.projectId,
|
|
66
|
+
});
|
|
67
|
+
return serializeArtifact(artifact);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Db } from '@plandesk/db';
|
|
2
|
+
import type { StorageAdapter, StorageResolveResult } from '../storage/adapter.js';
|
|
3
|
+
export type FileServiceDeps = {
|
|
4
|
+
db: Db;
|
|
5
|
+
storage: StorageAdapter;
|
|
6
|
+
};
|
|
7
|
+
export type CreateFileInput = {
|
|
8
|
+
projectId: string;
|
|
9
|
+
filename: string;
|
|
10
|
+
mime: string;
|
|
11
|
+
bytes: Buffer;
|
|
12
|
+
};
|
|
13
|
+
export type CreatedFile = {
|
|
14
|
+
id: string;
|
|
15
|
+
url: string;
|
|
16
|
+
filename: string;
|
|
17
|
+
mime: string;
|
|
18
|
+
size: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function createFileService(deps: FileServiceDeps): {
|
|
21
|
+
create(input: CreateFileInput): Promise<CreatedFile | undefined>;
|
|
22
|
+
get(id: string): Promise<StorageResolveResult | undefined>;
|
|
23
|
+
};
|
|
24
|
+
export type FileService = ReturnType<typeof createFileService>;
|
|
25
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getFile, getProject } from '@plandesk/db';
|
|
2
|
+
export function createFileService(deps) {
|
|
3
|
+
const { db, storage } = deps;
|
|
4
|
+
return {
|
|
5
|
+
async create(input) {
|
|
6
|
+
const project = getProject(db, input.projectId);
|
|
7
|
+
if (!project) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const { id, url } = await storage.put({
|
|
11
|
+
projectId: input.projectId,
|
|
12
|
+
bytes: input.bytes,
|
|
13
|
+
filename: input.filename,
|
|
14
|
+
mime: input.mime,
|
|
15
|
+
});
|
|
16
|
+
// Content-addressed dedup means the persisted row may reflect an
|
|
17
|
+
// earlier upload of the same bytes — read it back rather than echoing
|
|
18
|
+
// this call's input, so the response always matches what is stored.
|
|
19
|
+
const file = getFile(db, id);
|
|
20
|
+
if (!file) {
|
|
21
|
+
throw new Error(`Storage adapter did not persist file metadata for ${id}`);
|
|
22
|
+
}
|
|
23
|
+
return { id, url, filename: file.filename, mime: file.mime, size: file.size };
|
|
24
|
+
},
|
|
25
|
+
async get(id) {
|
|
26
|
+
const resolved = await storage.resolve(id);
|
|
27
|
+
return resolved ?? undefined;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=files.js.map
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Db } from '@plandesk/db';
|
|
2
2
|
import { type EventBus } from '../events.js';
|
|
3
|
+
import { type StorageAdapter } from '../storage/index.js';
|
|
3
4
|
import { type CanvasService } from './canvas.js';
|
|
4
5
|
import { type CommentService } from './comments.js';
|
|
5
6
|
import { type DocumentService } from './documents.js';
|
|
7
|
+
import { type ArtifactService } from './artifacts.js';
|
|
8
|
+
import { type FileService } from './files.js';
|
|
6
9
|
import { type FolderService } from './folders.js';
|
|
7
10
|
import { type NoteService } from './notes.js';
|
|
8
11
|
import { type ProjectService } from './projects.js';
|
|
@@ -16,6 +19,7 @@ import { type SyncService } from './sync.js';
|
|
|
16
19
|
export type ServicesDeps = {
|
|
17
20
|
db: Db;
|
|
18
21
|
eventBus?: EventBus;
|
|
22
|
+
storage?: StorageAdapter;
|
|
19
23
|
};
|
|
20
24
|
export type Services = {
|
|
21
25
|
eventBus: EventBus;
|
|
@@ -32,6 +36,8 @@ export type Services = {
|
|
|
32
36
|
tokenService: TokenService;
|
|
33
37
|
shareService: ShareService;
|
|
34
38
|
syncService: SyncService;
|
|
39
|
+
fileService: FileService;
|
|
40
|
+
artifactService: ArtifactService;
|
|
35
41
|
};
|
|
36
42
|
export declare function createServices(deps: ServicesDeps): Services;
|
|
37
43
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/services/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createEventBus } from '../events.js';
|
|
2
|
+
import { createStorageAdapter } from '../storage/index.js';
|
|
2
3
|
import { createCanvasService } from './canvas.js';
|
|
3
4
|
import { createCommentService } from './comments.js';
|
|
4
5
|
import { createDocumentService } from './documents.js';
|
|
6
|
+
import { createArtifactService } from './artifacts.js';
|
|
7
|
+
import { createFileService } from './files.js';
|
|
5
8
|
import { createFolderService } from './folders.js';
|
|
6
9
|
import { createNoteService } from './notes.js';
|
|
7
10
|
import { createProjectService } from './projects.js';
|
|
@@ -14,6 +17,7 @@ import { createShareService } from './share.js';
|
|
|
14
17
|
import { createSyncService } from './sync.js';
|
|
15
18
|
export function createServices(deps) {
|
|
16
19
|
const eventBus = deps.eventBus ?? createEventBus();
|
|
20
|
+
const storage = deps.storage ?? createStorageAdapter({ db: deps.db });
|
|
17
21
|
const projectService = createProjectService({ db: deps.db, eventBus });
|
|
18
22
|
const goalService = createGoalService({ db: deps.db, eventBus });
|
|
19
23
|
const taskService = createTaskService({ db: deps.db, eventBus });
|
|
@@ -27,6 +31,8 @@ export function createServices(deps) {
|
|
|
27
31
|
const tokenService = createTokenService({ db: deps.db });
|
|
28
32
|
const shareService = createShareService({ db: deps.db, eventBus });
|
|
29
33
|
const syncService = createSyncService({ db: deps.db, eventBus, taskService, shareService });
|
|
34
|
+
const fileService = createFileService({ db: deps.db, storage });
|
|
35
|
+
const artifactService = createArtifactService({ db: deps.db, eventBus });
|
|
30
36
|
return {
|
|
31
37
|
eventBus,
|
|
32
38
|
projectService,
|
|
@@ -42,6 +48,8 @@ export function createServices(deps) {
|
|
|
42
48
|
tokenService,
|
|
43
49
|
shareService,
|
|
44
50
|
syncService,
|
|
51
|
+
fileService,
|
|
52
|
+
artifactService,
|
|
45
53
|
};
|
|
46
54
|
}
|
|
47
55
|
//# sourceMappingURL=index.js.map
|
package/dist/services/share.d.ts
CHANGED
|
@@ -28,6 +28,31 @@ export type CreateShareInput = {
|
|
|
28
28
|
invitedEmails?: string[];
|
|
29
29
|
expiresAt?: Date;
|
|
30
30
|
};
|
|
31
|
+
export type ShareResourceRef = {
|
|
32
|
+
kind: 'task';
|
|
33
|
+
id: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'document';
|
|
36
|
+
id: string;
|
|
37
|
+
};
|
|
38
|
+
export type CreateResourceShareInput = {
|
|
39
|
+
resource: ShareResourceRef;
|
|
40
|
+
expiresAt?: Date | null;
|
|
41
|
+
};
|
|
42
|
+
export type ResourceShareResult = {
|
|
43
|
+
token: string;
|
|
44
|
+
url: string;
|
|
45
|
+
markdownUrl: string;
|
|
46
|
+
expiresAt: string | null;
|
|
47
|
+
};
|
|
48
|
+
export type ResourceMarkdownResult = {
|
|
49
|
+
status: 'ok';
|
|
50
|
+
markdown: string;
|
|
51
|
+
} | {
|
|
52
|
+
status: 'not_found';
|
|
53
|
+
} | {
|
|
54
|
+
status: 'gone';
|
|
55
|
+
};
|
|
31
56
|
export declare function createShareService(deps: ShareServiceDeps): {
|
|
32
57
|
createShare(projectId: string, input: CreateShareInput): {
|
|
33
58
|
share: SerializedShare;
|
|
@@ -36,6 +61,8 @@ export declare function createShareService(deps: ShareServiceDeps): {
|
|
|
36
61
|
listShares(projectId: string): SerializedShare[] | undefined;
|
|
37
62
|
revokeShare(id: string): boolean;
|
|
38
63
|
buildClientView(projectId: string, shareId: string): ClientView | undefined;
|
|
64
|
+
createResourceShare(input: CreateResourceShareInput, origin: string): ResourceShareResult | undefined;
|
|
65
|
+
getResourceMarkdown(token: string, origin: string): ResourceMarkdownResult;
|
|
39
66
|
};
|
|
40
67
|
export type ShareService = ReturnType<typeof createShareService>;
|
|
41
68
|
//# sourceMappingURL=share.d.ts.map
|