@critical-path/core 0.1.2 → 0.2.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 +13 -0
- package/README.md +43 -26
- package/dist/store/firebase.d.ts +101 -0
- package/dist/store/firebase.d.ts.map +1 -0
- package/dist/store/firebase.js +237 -0
- package/dist/store/firebase.js.map +1 -0
- package/dist/store/firebase.test.d.ts +2 -0
- package/dist/store/firebase.test.d.ts.map +1 -0
- package/dist/store/firebase.test.js +42 -0
- package/dist/store/firebase.test.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +48 -0
- package/dist/store/sqlite.d.ts.map +1 -0
- package/dist/store/sqlite.js +354 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/sqlite.test.d.ts +2 -0
- package/dist/store/sqlite.test.d.ts.map +1 -0
- package/dist/store/sqlite.test.js +63 -0
- package/dist/store/sqlite.test.js.map +1 -0
- package/package.json +5 -1
- package/src/store/firebase.test.ts +53 -0
- package/src/store/firebase.ts +289 -0
- package/src/store/index.ts +3 -1
- package/src/store/sqlite.test.ts +78 -0
- package/src/store/sqlite.ts +501 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { StorageAdapter } from './index.js';
|
|
3
|
+
import type {
|
|
4
|
+
Project,
|
|
5
|
+
Task,
|
|
6
|
+
Sprint,
|
|
7
|
+
Comment,
|
|
8
|
+
TimeEntry,
|
|
9
|
+
Activity,
|
|
10
|
+
Webhook,
|
|
11
|
+
TaskDependency
|
|
12
|
+
} from '../types/index.js';
|
|
13
|
+
|
|
14
|
+
export interface SQLiteStoreConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Database file path (e.g., 'critical-path.db' or ':memory:').
|
|
17
|
+
* Defaults to ':memory:'.
|
|
18
|
+
*/
|
|
19
|
+
filename?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Existing DatabaseSync instance or custom SQLite instance.
|
|
22
|
+
*/
|
|
23
|
+
db?: DatabaseSync;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class SQLiteStore implements StorageAdapter {
|
|
27
|
+
private db: DatabaseSync;
|
|
28
|
+
|
|
29
|
+
constructor(config: SQLiteStoreConfig = {}) {
|
|
30
|
+
if (config.db) {
|
|
31
|
+
this.db = config.db;
|
|
32
|
+
} else {
|
|
33
|
+
this.db = new DatabaseSync(config.filename || ':memory:');
|
|
34
|
+
}
|
|
35
|
+
this.initTables();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private initTables(): void {
|
|
39
|
+
this.db.exec(`
|
|
40
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
key TEXT NOT NULL,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
description TEXT,
|
|
45
|
+
ownerId TEXT,
|
|
46
|
+
members TEXT,
|
|
47
|
+
customFieldDefinitions TEXT,
|
|
48
|
+
createdAt TEXT NOT NULL,
|
|
49
|
+
updatedAt TEXT NOT NULL
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
projectId TEXT NOT NULL,
|
|
55
|
+
title TEXT NOT NULL,
|
|
56
|
+
description TEXT,
|
|
57
|
+
status TEXT NOT NULL,
|
|
58
|
+
priority TEXT NOT NULL,
|
|
59
|
+
assigneeId TEXT,
|
|
60
|
+
reporterId TEXT,
|
|
61
|
+
sprintId TEXT,
|
|
62
|
+
dueDate TEXT,
|
|
63
|
+
estimatedHours REAL,
|
|
64
|
+
loggedHours REAL,
|
|
65
|
+
tags TEXT,
|
|
66
|
+
customFields TEXT,
|
|
67
|
+
parentId TEXT,
|
|
68
|
+
createdAt TEXT NOT NULL,
|
|
69
|
+
updatedAt TEXT NOT NULL
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS sprints (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
projectId TEXT NOT NULL,
|
|
75
|
+
name TEXT NOT NULL,
|
|
76
|
+
goal TEXT,
|
|
77
|
+
startDate TEXT,
|
|
78
|
+
endDate TEXT,
|
|
79
|
+
status TEXT NOT NULL,
|
|
80
|
+
createdAt TEXT NOT NULL
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
84
|
+
id TEXT PRIMARY KEY,
|
|
85
|
+
taskId TEXT NOT NULL,
|
|
86
|
+
authorId TEXT NOT NULL,
|
|
87
|
+
content TEXT NOT NULL,
|
|
88
|
+
createdAt TEXT NOT NULL,
|
|
89
|
+
updatedAt TEXT NOT NULL
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS activities (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
projectId TEXT,
|
|
95
|
+
taskId TEXT,
|
|
96
|
+
actorId TEXT NOT NULL,
|
|
97
|
+
action TEXT NOT NULL,
|
|
98
|
+
details TEXT,
|
|
99
|
+
createdAt TEXT NOT NULL
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE TABLE IF NOT EXISTS time_entries (
|
|
103
|
+
id TEXT PRIMARY KEY,
|
|
104
|
+
taskId TEXT NOT NULL,
|
|
105
|
+
userId TEXT NOT NULL,
|
|
106
|
+
hours REAL NOT NULL,
|
|
107
|
+
description TEXT,
|
|
108
|
+
loggedAt TEXT NOT NULL
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE IF NOT EXISTS dependencies (
|
|
112
|
+
id TEXT PRIMARY KEY,
|
|
113
|
+
taskId TEXT NOT NULL,
|
|
114
|
+
dependsOnTaskId TEXT NOT NULL,
|
|
115
|
+
type TEXT NOT NULL
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
119
|
+
id TEXT PRIMARY KEY,
|
|
120
|
+
url TEXT NOT NULL,
|
|
121
|
+
events TEXT NOT NULL,
|
|
122
|
+
secret TEXT,
|
|
123
|
+
active INTEGER NOT NULL,
|
|
124
|
+
createdAt TEXT NOT NULL
|
|
125
|
+
);
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- Projects ---
|
|
130
|
+
async getProjects(): Promise<Project[]> {
|
|
131
|
+
const stmt = this.db.prepare('SELECT * FROM projects');
|
|
132
|
+
const rows = stmt.all() as any[];
|
|
133
|
+
return rows.map((r) => this.mapProject(r));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getProject(id: string): Promise<Project | null> {
|
|
137
|
+
const stmt = this.db.prepare('SELECT * FROM projects WHERE id = ?');
|
|
138
|
+
const row = stmt.get(id) as any;
|
|
139
|
+
return row ? this.mapProject(row) : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
|
|
143
|
+
const id = `proj_${Math.random().toString(36).substring(2, 9)}`;
|
|
144
|
+
const now = new Date().toISOString();
|
|
145
|
+
const newProj: Project = { ...project, id, createdAt: now, updatedAt: now };
|
|
146
|
+
|
|
147
|
+
const stmt = this.db.prepare(`
|
|
148
|
+
INSERT INTO projects (id, key, name, description, ownerId, members, customFieldDefinitions, createdAt, updatedAt)
|
|
149
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
150
|
+
`);
|
|
151
|
+
stmt.run(
|
|
152
|
+
newProj.id,
|
|
153
|
+
newProj.key,
|
|
154
|
+
newProj.name,
|
|
155
|
+
newProj.description || null,
|
|
156
|
+
newProj.ownerId || null,
|
|
157
|
+
JSON.stringify(newProj.members || []),
|
|
158
|
+
JSON.stringify(newProj.customFieldDefinitions || []),
|
|
159
|
+
newProj.createdAt,
|
|
160
|
+
newProj.updatedAt
|
|
161
|
+
);
|
|
162
|
+
return newProj;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async updateProject(id: string, updates: Partial<Project>): Promise<Project | null> {
|
|
166
|
+
const existing = await this.getProject(id);
|
|
167
|
+
if (!existing) return null;
|
|
168
|
+
|
|
169
|
+
const updated: Project = {
|
|
170
|
+
...existing,
|
|
171
|
+
...updates,
|
|
172
|
+
updatedAt: new Date().toISOString()
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const stmt = this.db.prepare(`
|
|
176
|
+
UPDATE projects
|
|
177
|
+
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
178
|
+
WHERE id = ?
|
|
179
|
+
`);
|
|
180
|
+
stmt.run(
|
|
181
|
+
updated.key,
|
|
182
|
+
updated.name,
|
|
183
|
+
updated.description || null,
|
|
184
|
+
updated.ownerId || null,
|
|
185
|
+
JSON.stringify(updated.members || []),
|
|
186
|
+
JSON.stringify(updated.customFieldDefinitions || []),
|
|
187
|
+
updated.updatedAt,
|
|
188
|
+
id
|
|
189
|
+
);
|
|
190
|
+
return updated;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async deleteProject(id: string): Promise<boolean> {
|
|
194
|
+
const stmt = this.db.prepare('DELETE FROM projects WHERE id = ?');
|
|
195
|
+
const result = stmt.run(id);
|
|
196
|
+
return (result.changes ?? 0) > 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// --- Tasks ---
|
|
200
|
+
async getTasks(projectId?: string): Promise<Task[]> {
|
|
201
|
+
let rows: any[];
|
|
202
|
+
if (projectId) {
|
|
203
|
+
const stmt = this.db.prepare('SELECT * FROM tasks WHERE projectId = ?');
|
|
204
|
+
rows = stmt.all(projectId) as any[];
|
|
205
|
+
} else {
|
|
206
|
+
const stmt = this.db.prepare('SELECT * FROM tasks');
|
|
207
|
+
rows = stmt.all() as any[];
|
|
208
|
+
}
|
|
209
|
+
return rows.map((r) => this.mapTask(r));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async getTask(id: string): Promise<Task | null> {
|
|
213
|
+
const stmt = this.db.prepare('SELECT * FROM tasks WHERE id = ?');
|
|
214
|
+
const row = stmt.get(id) as any;
|
|
215
|
+
return row ? this.mapTask(row) : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async createTask(task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
|
|
219
|
+
const id = `task_${Math.random().toString(36).substring(2, 9)}`;
|
|
220
|
+
const now = new Date().toISOString();
|
|
221
|
+
const newTask: Task = { ...task, id, createdAt: now, updatedAt: now };
|
|
222
|
+
|
|
223
|
+
const stmt = this.db.prepare(`
|
|
224
|
+
INSERT INTO tasks (
|
|
225
|
+
id, projectId, title, description, status, priority, assigneeId, reporterId,
|
|
226
|
+
sprintId, dueDate, estimatedHours, loggedHours, tags, customFields, parentId, createdAt, updatedAt
|
|
227
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
228
|
+
`);
|
|
229
|
+
stmt.run(
|
|
230
|
+
newTask.id,
|
|
231
|
+
newTask.projectId,
|
|
232
|
+
newTask.title,
|
|
233
|
+
newTask.description || null,
|
|
234
|
+
newTask.status,
|
|
235
|
+
newTask.priority,
|
|
236
|
+
newTask.assigneeId || null,
|
|
237
|
+
newTask.reporterId || null,
|
|
238
|
+
newTask.sprintId || null,
|
|
239
|
+
newTask.dueDate || null,
|
|
240
|
+
newTask.estimatedHours ?? null,
|
|
241
|
+
newTask.loggedHours ?? null,
|
|
242
|
+
JSON.stringify(newTask.tags || []),
|
|
243
|
+
JSON.stringify(newTask.customFields || {}),
|
|
244
|
+
newTask.parentId || null,
|
|
245
|
+
newTask.createdAt,
|
|
246
|
+
newTask.updatedAt
|
|
247
|
+
);
|
|
248
|
+
return newTask;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async updateTask(id: string, updates: Partial<Task>): Promise<Task | null> {
|
|
252
|
+
const existing = await this.getTask(id);
|
|
253
|
+
if (!existing) return null;
|
|
254
|
+
|
|
255
|
+
const updated: Task = {
|
|
256
|
+
...existing,
|
|
257
|
+
...updates,
|
|
258
|
+
updatedAt: new Date().toISOString()
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const stmt = this.db.prepare(`
|
|
262
|
+
UPDATE tasks SET
|
|
263
|
+
projectId = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
264
|
+
assigneeId = ?, reporterId = ?, sprintId = ?, dueDate = ?,
|
|
265
|
+
estimatedHours = ?, loggedHours = ?, tags = ?, customFields = ?, parentId = ?, updatedAt = ?
|
|
266
|
+
WHERE id = ?
|
|
267
|
+
`);
|
|
268
|
+
stmt.run(
|
|
269
|
+
updated.projectId,
|
|
270
|
+
updated.title,
|
|
271
|
+
updated.description || null,
|
|
272
|
+
updated.status,
|
|
273
|
+
updated.priority,
|
|
274
|
+
updated.assigneeId || null,
|
|
275
|
+
updated.reporterId || null,
|
|
276
|
+
updated.sprintId || null,
|
|
277
|
+
updated.dueDate || null,
|
|
278
|
+
updated.estimatedHours ?? null,
|
|
279
|
+
updated.loggedHours ?? null,
|
|
280
|
+
JSON.stringify(updated.tags || []),
|
|
281
|
+
JSON.stringify(updated.customFields || {}),
|
|
282
|
+
updated.parentId || null,
|
|
283
|
+
updated.updatedAt,
|
|
284
|
+
id
|
|
285
|
+
);
|
|
286
|
+
return updated;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async deleteTask(id: string): Promise<boolean> {
|
|
290
|
+
const stmt = this.db.prepare('DELETE FROM tasks WHERE id = ?');
|
|
291
|
+
const result = stmt.run(id);
|
|
292
|
+
return (result.changes ?? 0) > 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// --- Sprints ---
|
|
296
|
+
async getSprints(projectId: string): Promise<Sprint[]> {
|
|
297
|
+
const stmt = this.db.prepare('SELECT * FROM sprints WHERE projectId = ?');
|
|
298
|
+
const rows = stmt.all(projectId) as any[];
|
|
299
|
+
return rows.map((r) => ({
|
|
300
|
+
...r,
|
|
301
|
+
description: r.goal
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
|
|
306
|
+
const id = `sprint_${Math.random().toString(36).substring(2, 9)}`;
|
|
307
|
+
const now = new Date().toISOString();
|
|
308
|
+
const newSprint: Sprint = { ...sprint, id, createdAt: now };
|
|
309
|
+
|
|
310
|
+
const stmt = this.db.prepare(`
|
|
311
|
+
INSERT INTO sprints (id, projectId, name, goal, startDate, endDate, status, createdAt)
|
|
312
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
313
|
+
`);
|
|
314
|
+
stmt.run(
|
|
315
|
+
newSprint.id,
|
|
316
|
+
newSprint.projectId,
|
|
317
|
+
newSprint.name,
|
|
318
|
+
newSprint.goal || null,
|
|
319
|
+
newSprint.startDate || null,
|
|
320
|
+
newSprint.endDate || null,
|
|
321
|
+
newSprint.status,
|
|
322
|
+
newSprint.createdAt
|
|
323
|
+
);
|
|
324
|
+
return newSprint;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null> {
|
|
328
|
+
const stmt = this.db.prepare('SELECT * FROM sprints WHERE id = ?');
|
|
329
|
+
const existingRow = stmt.get(id) as any;
|
|
330
|
+
if (!existingRow) return null;
|
|
331
|
+
|
|
332
|
+
const existing: Sprint = { ...existingRow, goal: existingRow.goal };
|
|
333
|
+
const updated: Sprint = { ...existing, ...updates };
|
|
334
|
+
|
|
335
|
+
const updateStmt = this.db.prepare(`
|
|
336
|
+
UPDATE sprints SET name = ?, goal = ?, startDate = ?, endDate = ?, status = ?
|
|
337
|
+
WHERE id = ?
|
|
338
|
+
`);
|
|
339
|
+
updateStmt.run(
|
|
340
|
+
updated.name,
|
|
341
|
+
updated.goal || null,
|
|
342
|
+
updated.startDate || null,
|
|
343
|
+
updated.endDate || null,
|
|
344
|
+
updated.status,
|
|
345
|
+
id
|
|
346
|
+
);
|
|
347
|
+
return updated;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// --- Comments & Activity ---
|
|
351
|
+
async getComments(taskId: string): Promise<Comment[]> {
|
|
352
|
+
const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
|
|
353
|
+
return stmt.all(taskId) as any[];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment> {
|
|
357
|
+
const id = `cmt_${Math.random().toString(36).substring(2, 9)}`;
|
|
358
|
+
const now = new Date().toISOString();
|
|
359
|
+
const newComment: Comment = { ...comment, id, createdAt: now, updatedAt: now };
|
|
360
|
+
|
|
361
|
+
const stmt = this.db.prepare(`
|
|
362
|
+
INSERT INTO comments (id, taskId, authorId, content, createdAt, updatedAt)
|
|
363
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
364
|
+
`);
|
|
365
|
+
stmt.run(newComment.id, newComment.taskId, newComment.authorId, newComment.content, newComment.createdAt, newComment.updatedAt);
|
|
366
|
+
return newComment;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
370
|
+
let sql = 'SELECT * FROM activities';
|
|
371
|
+
const params: any[] = [];
|
|
372
|
+
const conditions: string[] = [];
|
|
373
|
+
|
|
374
|
+
if (filter?.projectId) {
|
|
375
|
+
conditions.push('projectId = ?');
|
|
376
|
+
params.push(filter.projectId);
|
|
377
|
+
}
|
|
378
|
+
if (filter?.taskId) {
|
|
379
|
+
conditions.push('taskId = ?');
|
|
380
|
+
params.push(filter.taskId);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (conditions.length > 0) {
|
|
384
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
385
|
+
}
|
|
386
|
+
sql += ' ORDER BY createdAt DESC';
|
|
387
|
+
|
|
388
|
+
const stmt = this.db.prepare(sql);
|
|
389
|
+
const rows = stmt.all(...params) as any[];
|
|
390
|
+
return rows.map((r) => ({
|
|
391
|
+
...r,
|
|
392
|
+
details: r.details ? JSON.parse(r.details) : undefined
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async logActivity(activity: Omit<Activity, 'id' | 'createdAt'>): Promise<Activity> {
|
|
397
|
+
const id = `act_${Math.random().toString(36).substring(2, 9)}`;
|
|
398
|
+
const now = new Date().toISOString();
|
|
399
|
+
const newAct: Activity = { ...activity, id, createdAt: now };
|
|
400
|
+
|
|
401
|
+
const stmt = this.db.prepare(`
|
|
402
|
+
INSERT INTO activities (id, projectId, taskId, actorId, action, details, createdAt)
|
|
403
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
404
|
+
`);
|
|
405
|
+
stmt.run(
|
|
406
|
+
newAct.id,
|
|
407
|
+
newAct.projectId || null,
|
|
408
|
+
newAct.taskId || null,
|
|
409
|
+
newAct.actorId,
|
|
410
|
+
newAct.action,
|
|
411
|
+
newAct.details ? JSON.stringify(newAct.details) : null,
|
|
412
|
+
newAct.createdAt
|
|
413
|
+
);
|
|
414
|
+
return newAct;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// --- Time Tracking ---
|
|
418
|
+
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
419
|
+
const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
|
|
420
|
+
return stmt.all(taskId) as any[];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
|
|
424
|
+
const id = `time_${Math.random().toString(36).substring(2, 9)}`;
|
|
425
|
+
const now = new Date().toISOString();
|
|
426
|
+
const newEntry: TimeEntry = { ...entry, id, loggedAt: now };
|
|
427
|
+
|
|
428
|
+
const stmt = this.db.prepare(`
|
|
429
|
+
INSERT INTO time_entries (id, taskId, userId, hours, description, loggedAt)
|
|
430
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
431
|
+
`);
|
|
432
|
+
stmt.run(newEntry.id, newEntry.taskId, newEntry.userId, newEntry.hours, newEntry.description || null, newEntry.loggedAt);
|
|
433
|
+
return newEntry;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// --- Dependencies ---
|
|
437
|
+
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
438
|
+
const stmt = this.db.prepare('SELECT * FROM dependencies WHERE taskId = ? OR dependsOnTaskId = ?');
|
|
439
|
+
return stmt.all(taskId, taskId) as any[];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
443
|
+
const id = `dep_${Math.random().toString(36).substring(2, 9)}`;
|
|
444
|
+
const newDep: TaskDependency = { ...dep, id };
|
|
445
|
+
|
|
446
|
+
const stmt = this.db.prepare(`
|
|
447
|
+
INSERT INTO dependencies (id, taskId, dependsOnTaskId, type)
|
|
448
|
+
VALUES (?, ?, ?, ?)
|
|
449
|
+
`);
|
|
450
|
+
stmt.run(newDep.id, newDep.taskId, newDep.dependsOnTaskId, newDep.type);
|
|
451
|
+
return newDep;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// --- Webhooks ---
|
|
455
|
+
async getWebhooks(): Promise<Webhook[]> {
|
|
456
|
+
const stmt = this.db.prepare('SELECT * FROM webhooks');
|
|
457
|
+
const rows = stmt.all() as any[];
|
|
458
|
+
return rows.map((r) => ({
|
|
459
|
+
...r,
|
|
460
|
+
events: JSON.parse(r.events),
|
|
461
|
+
active: Boolean(r.active)
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async addWebhook(webhook: Omit<Webhook, 'id' | 'createdAt'>): Promise<Webhook> {
|
|
466
|
+
const id = `wh_${Math.random().toString(36).substring(2, 9)}`;
|
|
467
|
+
const now = new Date().toISOString();
|
|
468
|
+
const newWh: Webhook = { ...webhook, id, createdAt: now };
|
|
469
|
+
|
|
470
|
+
const stmt = this.db.prepare(`
|
|
471
|
+
INSERT INTO webhooks (id, url, events, secret, active, createdAt)
|
|
472
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
473
|
+
`);
|
|
474
|
+
stmt.run(
|
|
475
|
+
newWh.id,
|
|
476
|
+
newWh.url,
|
|
477
|
+
JSON.stringify(newWh.events),
|
|
478
|
+
newWh.secret || null,
|
|
479
|
+
newWh.active ? 1 : 0,
|
|
480
|
+
newWh.createdAt
|
|
481
|
+
);
|
|
482
|
+
return newWh;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Helper mappers
|
|
486
|
+
private mapProject(row: any): Project {
|
|
487
|
+
return {
|
|
488
|
+
...row,
|
|
489
|
+
members: row.members ? JSON.parse(row.members) : [],
|
|
490
|
+
customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private mapTask(row: any): Task {
|
|
495
|
+
return {
|
|
496
|
+
...row,
|
|
497
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
498
|
+
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/index.ts","./src/types/index.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.test.ts","./src/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"],"version":"5.9.3"}
|