@critical-path/core 0.6.0 → 0.7.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/CHANGELOG.md +29 -0
- package/README.md +6 -0
- package/dist/domain/events.d.ts +32 -4
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/engine/index.d.ts +23 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +130 -2
- package/dist/engine/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/file-storage.d.ts +24 -0
- package/dist/storage/file-storage.d.ts.map +1 -0
- package/dist/storage/file-storage.js +73 -0
- package/dist/storage/file-storage.js.map +1 -0
- package/dist/storage/firebase-storage.d.ts +48 -0
- package/dist/storage/firebase-storage.d.ts.map +1 -0
- package/dist/storage/firebase-storage.js +142 -0
- package/dist/storage/firebase-storage.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/s3.d.ts +36 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +135 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/storage.test.d.ts +2 -0
- package/dist/storage/storage.test.d.ts.map +1 -0
- package/dist/storage/storage.test.js +117 -0
- package/dist/storage/storage.test.js.map +1 -0
- package/dist/store/firebase.d.ts +12 -1
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +63 -1
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/index.d.ts +27 -2
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +49 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +12 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +128 -6
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +47 -1
- package/dist/store/sqlite.test.js.map +1 -1
- package/dist/types/index.d.ts +52 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/domain/events.ts +28 -3
- package/src/engine/index.ts +166 -3
- package/src/index.ts +2 -0
- package/src/storage/file-storage.ts +96 -0
- package/src/storage/firebase-storage.ts +177 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/s3.ts +165 -0
- package/src/storage/storage.test.ts +146 -0
- package/src/store/firebase.ts +64 -1
- package/src/store/index.ts +64 -0
- package/src/store/sqlite.test.ts +59 -1
- package/src/store/sqlite.ts +172 -6
- package/src/types/index.ts +63 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
Team,
|
|
9
9
|
TaskContainer,
|
|
10
10
|
Comment,
|
|
11
|
+
Attachment,
|
|
11
12
|
TimeEntry,
|
|
12
13
|
Activity,
|
|
13
14
|
Webhook,
|
|
@@ -66,7 +67,17 @@ export interface IterationRepository {
|
|
|
66
67
|
|
|
67
68
|
export interface CommentRepository {
|
|
68
69
|
getComments(taskId: string): Promise<Comment[]>;
|
|
70
|
+
getComment(id: string): Promise<Comment | null>;
|
|
69
71
|
addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment>;
|
|
72
|
+
updateComment(id: string, updates: Partial<Comment>): Promise<Comment | null>;
|
|
73
|
+
deleteComment(id: string): Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AttachmentRepository {
|
|
77
|
+
getAttachments(filter?: { taskId?: string; projectId?: string; commentId?: string }): Promise<Attachment[]>;
|
|
78
|
+
getAttachment(id: string): Promise<Attachment | null>;
|
|
79
|
+
createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment>;
|
|
80
|
+
deleteAttachment(id: string): Promise<boolean>;
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
export interface ActivityRepository {
|
|
@@ -97,6 +108,7 @@ export interface StorageAdapter
|
|
|
97
108
|
ContainerRepository,
|
|
98
109
|
IterationRepository,
|
|
99
110
|
CommentRepository,
|
|
111
|
+
AttachmentRepository,
|
|
100
112
|
ActivityRepository,
|
|
101
113
|
TimeEntryRepository,
|
|
102
114
|
DependencyRepository,
|
|
@@ -110,6 +122,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
110
122
|
private containers = new Map<string, TaskContainer>();
|
|
111
123
|
private iterations = new Map<string, Iteration>();
|
|
112
124
|
private comments = new Map<string, Comment>();
|
|
125
|
+
private attachments = new Map<string, Attachment>();
|
|
113
126
|
private activities: Activity[] = [];
|
|
114
127
|
private timeEntries = new Map<string, TimeEntry>();
|
|
115
128
|
private dependencies = new Map<string, TaskDependency>();
|
|
@@ -328,6 +341,10 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
328
341
|
return Array.from(this.comments.values()).filter((c) => c.taskId === taskId);
|
|
329
342
|
}
|
|
330
343
|
|
|
344
|
+
async getComment(id: string): Promise<Comment | null> {
|
|
345
|
+
return this.comments.get(id) || null;
|
|
346
|
+
}
|
|
347
|
+
|
|
331
348
|
async addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment> {
|
|
332
349
|
const id = `cmt_${Math.random().toString(36).substring(2, 9)}`;
|
|
333
350
|
const now = new Date().toISOString();
|
|
@@ -336,6 +353,53 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
336
353
|
return newComment;
|
|
337
354
|
}
|
|
338
355
|
|
|
356
|
+
async updateComment(id: string, updates: Partial<Comment>): Promise<Comment | null> {
|
|
357
|
+
const existing = this.comments.get(id);
|
|
358
|
+
if (!existing) return null;
|
|
359
|
+
const updated: Comment = {
|
|
360
|
+
...existing,
|
|
361
|
+
...updates,
|
|
362
|
+
updatedAt: new Date().toISOString()
|
|
363
|
+
};
|
|
364
|
+
this.comments.set(id, updated);
|
|
365
|
+
return updated;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async deleteComment(id: string): Promise<boolean> {
|
|
369
|
+
return this.comments.delete(id);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Attachments
|
|
373
|
+
async getAttachments(filter?: { taskId?: string; projectId?: string; commentId?: string }): Promise<Attachment[]> {
|
|
374
|
+
return Array.from(this.attachments.values()).filter((a) => {
|
|
375
|
+
if (filter?.taskId && a.taskId !== filter.taskId) return false;
|
|
376
|
+
if (filter?.projectId && a.projectId !== filter.projectId) return false;
|
|
377
|
+
if (filter?.commentId && a.commentId !== filter.commentId) return false;
|
|
378
|
+
return true;
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getAttachment(id: string): Promise<Attachment | null> {
|
|
383
|
+
return this.attachments.get(id) || null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment> {
|
|
387
|
+
const id = `att_${Math.random().toString(36).substring(2, 9)}`;
|
|
388
|
+
const now = new Date().toISOString();
|
|
389
|
+
const newAttachment: Attachment = {
|
|
390
|
+
...attachment,
|
|
391
|
+
id,
|
|
392
|
+
createdAt: now,
|
|
393
|
+
updatedAt: now
|
|
394
|
+
};
|
|
395
|
+
this.attachments.set(id, newAttachment);
|
|
396
|
+
return newAttachment;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async deleteAttachment(id: string): Promise<boolean> {
|
|
400
|
+
return this.attachments.delete(id);
|
|
401
|
+
}
|
|
402
|
+
|
|
339
403
|
// Activities
|
|
340
404
|
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
341
405
|
return this.activities.filter((a) => {
|
package/src/store/sqlite.test.ts
CHANGED
|
@@ -57,12 +57,31 @@ describe('SQLiteStore', () => {
|
|
|
57
57
|
const comment = await store.addComment({
|
|
58
58
|
taskId: 'task_1',
|
|
59
59
|
authorId: 'user_1',
|
|
60
|
+
authorType: 'user',
|
|
60
61
|
content: 'Great work!'
|
|
61
62
|
});
|
|
62
63
|
expect(comment.id).toMatch(/^cmt_/);
|
|
64
|
+
expect(comment.authorType).toBe('user');
|
|
65
|
+
|
|
66
|
+
// Threaded reply
|
|
67
|
+
const reply = await store.addComment({
|
|
68
|
+
taskId: 'task_1',
|
|
69
|
+
authorId: 'agent_1',
|
|
70
|
+
authorType: 'agent',
|
|
71
|
+
parentId: comment.id,
|
|
72
|
+
content: 'I agree!'
|
|
73
|
+
});
|
|
74
|
+
expect(reply.parentId).toBe(comment.id);
|
|
75
|
+
expect(reply.authorType).toBe('agent');
|
|
63
76
|
|
|
64
77
|
const comments = await store.getComments('task_1');
|
|
65
|
-
expect(comments).toHaveLength(
|
|
78
|
+
expect(comments).toHaveLength(2);
|
|
79
|
+
|
|
80
|
+
const updatedComment = await store.updateComment(comment.id, { content: 'Updated content' });
|
|
81
|
+
expect(updatedComment?.content).toBe('Updated content');
|
|
82
|
+
|
|
83
|
+
const fetchedComment = await store.getComment(comment.id);
|
|
84
|
+
expect(fetchedComment?.content).toBe('Updated content');
|
|
66
85
|
|
|
67
86
|
const timeEntry = await store.logTime({
|
|
68
87
|
taskId: 'task_1',
|
|
@@ -74,6 +93,45 @@ describe('SQLiteStore', () => {
|
|
|
74
93
|
|
|
75
94
|
const entries = await store.getTimeEntries('task_1');
|
|
76
95
|
expect(entries).toHaveLength(1);
|
|
96
|
+
|
|
97
|
+
const deletedComment = await store.deleteComment(reply.id);
|
|
98
|
+
expect(deletedComment).toBe(true);
|
|
99
|
+
expect(await store.getComments('task_1')).toHaveLength(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should create, filter, retrieve, and delete attachments', async () => {
|
|
103
|
+
const attachment = await store.createAttachment({
|
|
104
|
+
filename: 'design-mockup.fig',
|
|
105
|
+
url: 'https://storage.example.com/design-mockup.fig',
|
|
106
|
+
storageKey: 'attachments/task_1/design-mockup.fig',
|
|
107
|
+
mimeType: 'application/octet-stream',
|
|
108
|
+
sizeBytes: 2048,
|
|
109
|
+
taskId: 'task_1',
|
|
110
|
+
projectId: 'proj_1',
|
|
111
|
+
uploaderId: 'user_1',
|
|
112
|
+
uploaderType: 'user'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(attachment.id).toMatch(/^att_/);
|
|
116
|
+
expect(attachment.filename).toBe('design-mockup.fig');
|
|
117
|
+
|
|
118
|
+
const fetched = await store.getAttachment(attachment.id);
|
|
119
|
+
expect(fetched?.url).toBe('https://storage.example.com/design-mockup.fig');
|
|
120
|
+
|
|
121
|
+
const taskAttachments = await store.getAttachments({ taskId: 'task_1' });
|
|
122
|
+
expect(taskAttachments).toHaveLength(1);
|
|
123
|
+
|
|
124
|
+
const projectAttachments = await store.getAttachments({ projectId: 'proj_1' });
|
|
125
|
+
expect(projectAttachments).toHaveLength(1);
|
|
126
|
+
|
|
127
|
+
const nonExistent = await store.getAttachments({ taskId: 'other_task' });
|
|
128
|
+
expect(nonExistent).toHaveLength(0);
|
|
129
|
+
|
|
130
|
+
const deleted = await store.deleteAttachment(attachment.id);
|
|
131
|
+
expect(deleted).toBe(true);
|
|
132
|
+
|
|
133
|
+
const remaining = await store.getAttachments({ taskId: 'task_1' });
|
|
134
|
+
expect(remaining).toHaveLength(0);
|
|
77
135
|
});
|
|
78
136
|
|
|
79
137
|
it('should create, retrieve, update, and delete workflows', async () => {
|
package/src/store/sqlite.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
Team,
|
|
8
8
|
TaskContainer,
|
|
9
9
|
Comment,
|
|
10
|
+
Attachment,
|
|
10
11
|
TimeEntry,
|
|
11
12
|
Activity,
|
|
12
13
|
Webhook,
|
|
@@ -150,7 +151,27 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
150
151
|
id TEXT PRIMARY KEY,
|
|
151
152
|
taskId TEXT NOT NULL,
|
|
152
153
|
authorId TEXT NOT NULL,
|
|
154
|
+
authorType TEXT,
|
|
155
|
+
parentId TEXT,
|
|
153
156
|
content TEXT NOT NULL,
|
|
157
|
+
metadata TEXT,
|
|
158
|
+
createdAt TEXT NOT NULL,
|
|
159
|
+
updatedAt TEXT NOT NULL
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
CREATE TABLE IF NOT EXISTS attachments (
|
|
163
|
+
id TEXT PRIMARY KEY,
|
|
164
|
+
taskId TEXT,
|
|
165
|
+
projectId TEXT,
|
|
166
|
+
commentId TEXT,
|
|
167
|
+
uploaderId TEXT NOT NULL,
|
|
168
|
+
uploaderType TEXT,
|
|
169
|
+
filename TEXT NOT NULL,
|
|
170
|
+
mimeType TEXT NOT NULL,
|
|
171
|
+
sizeBytes INTEGER NOT NULL,
|
|
172
|
+
url TEXT NOT NULL,
|
|
173
|
+
storageKey TEXT,
|
|
174
|
+
metadata TEXT,
|
|
154
175
|
createdAt TEXT NOT NULL,
|
|
155
176
|
updatedAt TEXT NOT NULL
|
|
156
177
|
);
|
|
@@ -660,10 +681,24 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
660
681
|
return (result.changes ?? 0) > 0;
|
|
661
682
|
}
|
|
662
683
|
|
|
663
|
-
// --- Comments
|
|
684
|
+
// --- Comments ---
|
|
664
685
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
665
|
-
const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
|
|
666
|
-
|
|
686
|
+
const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ? ORDER BY createdAt ASC');
|
|
687
|
+
const rows = stmt.all(taskId) as any[];
|
|
688
|
+
return rows.map((r) => ({
|
|
689
|
+
...r,
|
|
690
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined
|
|
691
|
+
}));
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async getComment(id: string): Promise<Comment | null> {
|
|
695
|
+
const stmt = this.db.prepare('SELECT * FROM comments WHERE id = ?');
|
|
696
|
+
const row = stmt.get(id) as any;
|
|
697
|
+
if (!row) return null;
|
|
698
|
+
return {
|
|
699
|
+
...row,
|
|
700
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
701
|
+
};
|
|
667
702
|
}
|
|
668
703
|
|
|
669
704
|
async addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment> {
|
|
@@ -672,13 +707,144 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
672
707
|
const newComment: Comment = { ...comment, id, createdAt: now, updatedAt: now };
|
|
673
708
|
|
|
674
709
|
const stmt = this.db.prepare(`
|
|
675
|
-
INSERT INTO comments (id, taskId, authorId, content, createdAt, updatedAt)
|
|
676
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
710
|
+
INSERT INTO comments (id, taskId, authorId, authorType, parentId, content, metadata, createdAt, updatedAt)
|
|
711
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
677
712
|
`);
|
|
678
|
-
stmt.run(
|
|
713
|
+
stmt.run(
|
|
714
|
+
newComment.id,
|
|
715
|
+
newComment.taskId,
|
|
716
|
+
newComment.authorId,
|
|
717
|
+
newComment.authorType || 'user',
|
|
718
|
+
newComment.parentId || null,
|
|
719
|
+
newComment.content,
|
|
720
|
+
newComment.metadata ? JSON.stringify(newComment.metadata) : null,
|
|
721
|
+
newComment.createdAt,
|
|
722
|
+
newComment.updatedAt
|
|
723
|
+
);
|
|
679
724
|
return newComment;
|
|
680
725
|
}
|
|
681
726
|
|
|
727
|
+
async updateComment(id: string, updates: Partial<Comment>): Promise<Comment | null> {
|
|
728
|
+
const existing = await this.getComment(id);
|
|
729
|
+
if (!existing) return null;
|
|
730
|
+
|
|
731
|
+
const updated: Comment = {
|
|
732
|
+
...existing,
|
|
733
|
+
...updates,
|
|
734
|
+
updatedAt: new Date().toISOString()
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const stmt = this.db.prepare(`
|
|
738
|
+
UPDATE comments SET
|
|
739
|
+
authorId = ?,
|
|
740
|
+
authorType = ?,
|
|
741
|
+
parentId = ?,
|
|
742
|
+
content = ?,
|
|
743
|
+
metadata = ?,
|
|
744
|
+
updatedAt = ?
|
|
745
|
+
WHERE id = ?
|
|
746
|
+
`);
|
|
747
|
+
stmt.run(
|
|
748
|
+
updated.authorId,
|
|
749
|
+
updated.authorType || 'user',
|
|
750
|
+
updated.parentId || null,
|
|
751
|
+
updated.content,
|
|
752
|
+
updated.metadata ? JSON.stringify(updated.metadata) : null,
|
|
753
|
+
updated.updatedAt,
|
|
754
|
+
id
|
|
755
|
+
);
|
|
756
|
+
return updated;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async deleteComment(id: string): Promise<boolean> {
|
|
760
|
+
const stmt = this.db.prepare('DELETE FROM comments WHERE id = ?');
|
|
761
|
+
const result = stmt.run(id);
|
|
762
|
+
return (result.changes ?? 0) > 0;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// --- Attachments ---
|
|
766
|
+
async getAttachments(filter?: { taskId?: string; projectId?: string; commentId?: string }): Promise<Attachment[]> {
|
|
767
|
+
let sql = 'SELECT * FROM attachments';
|
|
768
|
+
const params: any[] = [];
|
|
769
|
+
const conditions: string[] = [];
|
|
770
|
+
|
|
771
|
+
if (filter?.taskId) {
|
|
772
|
+
conditions.push('taskId = ?');
|
|
773
|
+
params.push(filter.taskId);
|
|
774
|
+
}
|
|
775
|
+
if (filter?.projectId) {
|
|
776
|
+
conditions.push('projectId = ?');
|
|
777
|
+
params.push(filter.projectId);
|
|
778
|
+
}
|
|
779
|
+
if (filter?.commentId) {
|
|
780
|
+
conditions.push('commentId = ?');
|
|
781
|
+
params.push(filter.commentId);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (conditions.length > 0) {
|
|
785
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
786
|
+
}
|
|
787
|
+
sql += ' ORDER BY createdAt DESC';
|
|
788
|
+
|
|
789
|
+
const stmt = this.db.prepare(sql);
|
|
790
|
+
const rows = stmt.all(...params) as any[];
|
|
791
|
+
return rows.map((r) => ({
|
|
792
|
+
...r,
|
|
793
|
+
sizeBytes: Number(r.sizeBytes),
|
|
794
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
async getAttachment(id: string): Promise<Attachment | null> {
|
|
799
|
+
const stmt = this.db.prepare('SELECT * FROM attachments WHERE id = ?');
|
|
800
|
+
const row = stmt.get(id) as any;
|
|
801
|
+
if (!row) return null;
|
|
802
|
+
return {
|
|
803
|
+
...row,
|
|
804
|
+
sizeBytes: Number(row.sizeBytes),
|
|
805
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment> {
|
|
810
|
+
const id = `att_${Math.random().toString(36).substring(2, 9)}`;
|
|
811
|
+
const now = new Date().toISOString();
|
|
812
|
+
const newAtt: Attachment = {
|
|
813
|
+
...attachment,
|
|
814
|
+
id,
|
|
815
|
+
createdAt: now,
|
|
816
|
+
updatedAt: now
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const stmt = this.db.prepare(`
|
|
820
|
+
INSERT INTO attachments (id, taskId, projectId, commentId, uploaderId, uploaderType, filename, mimeType, sizeBytes, url, storageKey, metadata, createdAt, updatedAt)
|
|
821
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
822
|
+
`);
|
|
823
|
+
stmt.run(
|
|
824
|
+
newAtt.id,
|
|
825
|
+
newAtt.taskId || null,
|
|
826
|
+
newAtt.projectId || null,
|
|
827
|
+
newAtt.commentId || null,
|
|
828
|
+
newAtt.uploaderId,
|
|
829
|
+
newAtt.uploaderType || 'user',
|
|
830
|
+
newAtt.filename,
|
|
831
|
+
newAtt.mimeType,
|
|
832
|
+
newAtt.sizeBytes,
|
|
833
|
+
newAtt.url,
|
|
834
|
+
newAtt.storageKey || null,
|
|
835
|
+
newAtt.metadata ? JSON.stringify(newAtt.metadata) : null,
|
|
836
|
+
newAtt.createdAt,
|
|
837
|
+
newAtt.updatedAt
|
|
838
|
+
);
|
|
839
|
+
return newAtt;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
async deleteAttachment(id: string): Promise<boolean> {
|
|
843
|
+
const stmt = this.db.prepare('DELETE FROM attachments WHERE id = ?');
|
|
844
|
+
const result = stmt.run(id);
|
|
845
|
+
return (result.changes ?? 0) > 0;
|
|
846
|
+
}
|
|
847
|
+
|
|
682
848
|
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
683
849
|
let sql = 'SELECT * FROM activities';
|
|
684
850
|
const params: any[] = [];
|
package/src/types/index.ts
CHANGED
|
@@ -174,15 +174,73 @@ export interface Iteration {
|
|
|
174
174
|
createdAt: string;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export type AuthorType = 'user' | 'agent' | 'system';
|
|
178
|
+
|
|
177
179
|
export interface Comment {
|
|
178
180
|
id: string;
|
|
179
181
|
taskId: string;
|
|
180
182
|
authorId: string;
|
|
183
|
+
authorType?: AuthorType;
|
|
184
|
+
parentId?: string; // Threaded reply support
|
|
181
185
|
content: string;
|
|
186
|
+
metadata?: Record<string, unknown>;
|
|
182
187
|
createdAt: string;
|
|
183
188
|
updatedAt: string;
|
|
184
189
|
}
|
|
185
190
|
|
|
191
|
+
export interface Attachment {
|
|
192
|
+
id: string;
|
|
193
|
+
taskId?: string;
|
|
194
|
+
projectId?: string;
|
|
195
|
+
commentId?: string;
|
|
196
|
+
uploaderId: string;
|
|
197
|
+
uploaderType?: AuthorType;
|
|
198
|
+
filename: string;
|
|
199
|
+
mimeType: string;
|
|
200
|
+
sizeBytes: number;
|
|
201
|
+
url: string;
|
|
202
|
+
storageKey?: string;
|
|
203
|
+
metadata?: Record<string, unknown>;
|
|
204
|
+
createdAt: string;
|
|
205
|
+
updatedAt: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type CreateAttachmentInput = Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>;
|
|
209
|
+
|
|
210
|
+
export interface UploadFileInput {
|
|
211
|
+
filename: string;
|
|
212
|
+
data: Uint8Array | ArrayBuffer | Buffer | Blob | string;
|
|
213
|
+
mimeType?: string;
|
|
214
|
+
pathPrefix?: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface UploadFileResult {
|
|
218
|
+
storageKey: string;
|
|
219
|
+
url: string;
|
|
220
|
+
sizeBytes: number;
|
|
221
|
+
mimeType: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface PresignedUrlOptions {
|
|
225
|
+
storageKey: string;
|
|
226
|
+
expiresInSeconds?: number;
|
|
227
|
+
contentType?: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface PresignedUploadResult {
|
|
231
|
+
uploadUrl: string;
|
|
232
|
+
storageKey: string;
|
|
233
|
+
method?: 'PUT' | 'POST';
|
|
234
|
+
headers?: Record<string, string>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface FileStorageAdapter {
|
|
238
|
+
upload(input: UploadFileInput): Promise<UploadFileResult>;
|
|
239
|
+
delete(storageKey: string): Promise<boolean>;
|
|
240
|
+
getDownloadUrl?(storageKey: string): Promise<string>;
|
|
241
|
+
getPresignedUploadUrl?(options: PresignedUrlOptions): Promise<PresignedUploadResult>;
|
|
242
|
+
}
|
|
243
|
+
|
|
186
244
|
export interface TimeEntry {
|
|
187
245
|
id: string;
|
|
188
246
|
taskId: string;
|
|
@@ -221,6 +279,10 @@ export type WebhookEvent =
|
|
|
221
279
|
| 'task.deleted'
|
|
222
280
|
| 'task.status_changed'
|
|
223
281
|
| 'comment.created'
|
|
282
|
+
| 'comment.updated'
|
|
283
|
+
| 'comment.deleted'
|
|
284
|
+
| 'attachment.created'
|
|
285
|
+
| 'attachment.deleted'
|
|
224
286
|
| 'iteration.started'
|
|
225
287
|
| 'iteration.completed'
|
|
226
288
|
| 'team.created'
|
|
@@ -250,6 +312,7 @@ export interface CriticalPathPlugin {
|
|
|
250
312
|
|
|
251
313
|
export interface CriticalPathConfig {
|
|
252
314
|
store?: 'memory' | 'sqlite' | unknown;
|
|
315
|
+
fileStorage?: FileStorageAdapter;
|
|
253
316
|
plugins?: CriticalPathPlugin[];
|
|
254
317
|
webhooks?: Omit<Webhook, 'id' | 'createdAt'>[];
|
|
255
318
|
initialData?: {
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
|