@critical-path/core 0.2.0 → 0.3.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 +12 -0
- package/dist/engine/index.d.ts +19 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +129 -6
- 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/index.test.js +96 -1
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +19 -7
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +98 -14
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/firebase.test.js +5 -2
- package/dist/store/firebase.test.js.map +1 -1
- package/dist/store/index.d.ts +34 -8
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +79 -12
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +17 -4
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +179 -39
- package/dist/store/sqlite.js.map +1 -1
- package/dist/types/index.d.ts +70 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/key.d.ts +14 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +49 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/key.test.d.ts +2 -0
- package/dist/utils/key.test.d.ts.map +1 -0
- package/dist/utils/key.test.js +48 -0
- package/dist/utils/key.test.js.map +1 -0
- package/dist/utils/status.d.ts +14 -0
- package/dist/utils/status.d.ts.map +1 -0
- package/dist/utils/status.js +58 -0
- package/dist/utils/status.js.map +1 -0
- package/package.json +5 -1
- package/src/engine/index.ts +161 -8
- package/src/index.test.ts +113 -1
- package/src/index.ts +2 -0
- package/src/store/firebase.test.ts +8 -2
- package/src/store/firebase.ts +112 -18
- package/src/store/index.ts +113 -18
- package/src/store/sqlite.ts +277 -47
- package/src/types/index.ts +76 -10
- package/src/utils/index.ts +2 -0
- package/src/utils/key.test.ts +55 -0
- package/src/utils/key.ts +51 -0
- package/src/utils/status.ts +85 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/sqlite.ts
CHANGED
|
@@ -3,13 +3,16 @@ import type { StorageAdapter } from './index.js';
|
|
|
3
3
|
import type {
|
|
4
4
|
Project,
|
|
5
5
|
Task,
|
|
6
|
-
|
|
6
|
+
Iteration,
|
|
7
|
+
Team,
|
|
8
|
+
TaskContainer,
|
|
7
9
|
Comment,
|
|
8
10
|
TimeEntry,
|
|
9
11
|
Activity,
|
|
10
12
|
Webhook,
|
|
11
13
|
TaskDependency
|
|
12
14
|
} from '../types/index.js';
|
|
15
|
+
import { generateProjectKey } from '../utils/key.js';
|
|
13
16
|
|
|
14
17
|
export interface SQLiteStoreConfig {
|
|
15
18
|
/**
|
|
@@ -44,11 +47,36 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
44
47
|
description TEXT,
|
|
45
48
|
ownerId TEXT,
|
|
46
49
|
members TEXT,
|
|
50
|
+
teamIds TEXT,
|
|
51
|
+
statusDefinitions TEXT,
|
|
52
|
+
priorityDefinitions TEXT,
|
|
47
53
|
customFieldDefinitions TEXT,
|
|
48
54
|
createdAt TEXT NOT NULL,
|
|
49
55
|
updatedAt TEXT NOT NULL
|
|
50
56
|
);
|
|
51
57
|
|
|
58
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
59
|
+
id TEXT PRIMARY KEY,
|
|
60
|
+
name TEXT NOT NULL,
|
|
61
|
+
description TEXT,
|
|
62
|
+
leaderId TEXT,
|
|
63
|
+
memberIds TEXT NOT NULL,
|
|
64
|
+
createdAt TEXT NOT NULL,
|
|
65
|
+
updatedAt TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS containers (
|
|
69
|
+
id TEXT PRIMARY KEY,
|
|
70
|
+
projectId TEXT NOT NULL,
|
|
71
|
+
name TEXT NOT NULL,
|
|
72
|
+
description TEXT,
|
|
73
|
+
parentId TEXT,
|
|
74
|
+
type TEXT,
|
|
75
|
+
color TEXT,
|
|
76
|
+
createdAt TEXT NOT NULL,
|
|
77
|
+
updatedAt TEXT NOT NULL
|
|
78
|
+
);
|
|
79
|
+
|
|
52
80
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
53
81
|
id TEXT PRIMARY KEY,
|
|
54
82
|
projectId TEXT NOT NULL,
|
|
@@ -58,10 +86,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
58
86
|
priority TEXT NOT NULL,
|
|
59
87
|
assigneeId TEXT,
|
|
60
88
|
reporterId TEXT,
|
|
61
|
-
|
|
89
|
+
reviewerId TEXT,
|
|
90
|
+
iterationId TEXT,
|
|
91
|
+
teamId TEXT,
|
|
92
|
+
containerId TEXT,
|
|
93
|
+
plannedStartDate TEXT,
|
|
94
|
+
actualStartDate TEXT,
|
|
95
|
+
actualEndDate TEXT,
|
|
62
96
|
dueDate TEXT,
|
|
63
97
|
estimatedHours REAL,
|
|
64
98
|
loggedHours REAL,
|
|
99
|
+
actualHours REAL,
|
|
100
|
+
billableHours REAL,
|
|
101
|
+
estimatedDurationMinutes REAL,
|
|
102
|
+
actualDurationMinutes REAL,
|
|
103
|
+
billableDurationMinutes REAL,
|
|
104
|
+
progress REAL,
|
|
65
105
|
tags TEXT,
|
|
66
106
|
customFields TEXT,
|
|
67
107
|
parentId TEXT,
|
|
@@ -69,11 +109,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
69
109
|
updatedAt TEXT NOT NULL
|
|
70
110
|
);
|
|
71
111
|
|
|
72
|
-
CREATE TABLE IF NOT EXISTS
|
|
112
|
+
CREATE TABLE IF NOT EXISTS iterations (
|
|
73
113
|
id TEXT PRIMARY KEY,
|
|
74
114
|
projectId TEXT NOT NULL,
|
|
75
115
|
name TEXT NOT NULL,
|
|
76
116
|
goal TEXT,
|
|
117
|
+
type TEXT,
|
|
77
118
|
startDate TEXT,
|
|
78
119
|
endDate TEXT,
|
|
79
120
|
status TEXT NOT NULL,
|
|
@@ -104,6 +145,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
104
145
|
taskId TEXT NOT NULL,
|
|
105
146
|
userId TEXT NOT NULL,
|
|
106
147
|
hours REAL NOT NULL,
|
|
148
|
+
isBillable INTEGER,
|
|
107
149
|
description TEXT,
|
|
108
150
|
loggedAt TEXT NOT NULL
|
|
109
151
|
);
|
|
@@ -142,19 +184,23 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
142
184
|
async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
|
|
143
185
|
const id = `proj_${Math.random().toString(36).substring(2, 9)}`;
|
|
144
186
|
const now = new Date().toISOString();
|
|
145
|
-
const
|
|
187
|
+
const key = project.key || generateProjectKey(project.name);
|
|
188
|
+
const newProj: Project = { ...project, key, id, createdAt: now, updatedAt: now };
|
|
146
189
|
|
|
147
190
|
const stmt = this.db.prepare(`
|
|
148
|
-
INSERT INTO projects (id, key, name, description, ownerId, members, customFieldDefinitions, createdAt, updatedAt)
|
|
149
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
191
|
+
INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
|
|
192
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
150
193
|
`);
|
|
151
194
|
stmt.run(
|
|
152
195
|
newProj.id,
|
|
153
|
-
newProj.key,
|
|
196
|
+
newProj.key || '',
|
|
154
197
|
newProj.name,
|
|
155
198
|
newProj.description || null,
|
|
156
199
|
newProj.ownerId || null,
|
|
157
200
|
JSON.stringify(newProj.members || []),
|
|
201
|
+
JSON.stringify(newProj.teamIds || []),
|
|
202
|
+
JSON.stringify(newProj.statusDefinitions || []),
|
|
203
|
+
JSON.stringify(newProj.priorityDefinitions || []),
|
|
158
204
|
JSON.stringify(newProj.customFieldDefinitions || []),
|
|
159
205
|
newProj.createdAt,
|
|
160
206
|
newProj.updatedAt
|
|
@@ -174,15 +220,18 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
174
220
|
|
|
175
221
|
const stmt = this.db.prepare(`
|
|
176
222
|
UPDATE projects
|
|
177
|
-
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
223
|
+
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
178
224
|
WHERE id = ?
|
|
179
225
|
`);
|
|
180
226
|
stmt.run(
|
|
181
|
-
updated.key,
|
|
227
|
+
updated.key || '',
|
|
182
228
|
updated.name,
|
|
183
229
|
updated.description || null,
|
|
184
230
|
updated.ownerId || null,
|
|
185
231
|
JSON.stringify(updated.members || []),
|
|
232
|
+
JSON.stringify(updated.teamIds || []),
|
|
233
|
+
JSON.stringify(updated.statusDefinitions || []),
|
|
234
|
+
JSON.stringify(updated.priorityDefinitions || []),
|
|
186
235
|
JSON.stringify(updated.customFieldDefinitions || []),
|
|
187
236
|
updated.updatedAt,
|
|
188
237
|
id
|
|
@@ -196,6 +245,129 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
196
245
|
return (result.changes ?? 0) > 0;
|
|
197
246
|
}
|
|
198
247
|
|
|
248
|
+
// --- Teams ---
|
|
249
|
+
async getTeams(): Promise<Team[]> {
|
|
250
|
+
const stmt = this.db.prepare('SELECT * FROM teams');
|
|
251
|
+
const rows = stmt.all() as any[];
|
|
252
|
+
return rows.map((r) => this.mapTeam(r));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
256
|
+
const stmt = this.db.prepare('SELECT * FROM teams WHERE id = ?');
|
|
257
|
+
const row = stmt.get(id) as any;
|
|
258
|
+
return row ? this.mapTeam(row) : null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
262
|
+
const id = `team_${Math.random().toString(36).substring(2, 9)}`;
|
|
263
|
+
const now = new Date().toISOString();
|
|
264
|
+
const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
|
|
265
|
+
|
|
266
|
+
const stmt = this.db.prepare(`
|
|
267
|
+
INSERT INTO teams (id, name, description, leaderId, memberIds, createdAt, updatedAt)
|
|
268
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
269
|
+
`);
|
|
270
|
+
stmt.run(
|
|
271
|
+
newTeam.id,
|
|
272
|
+
newTeam.name,
|
|
273
|
+
newTeam.description || null,
|
|
274
|
+
newTeam.leaderId || null,
|
|
275
|
+
JSON.stringify(newTeam.memberIds || []),
|
|
276
|
+
newTeam.createdAt,
|
|
277
|
+
newTeam.updatedAt
|
|
278
|
+
);
|
|
279
|
+
return newTeam;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
283
|
+
const existing = await this.getTeam(id);
|
|
284
|
+
if (!existing) return null;
|
|
285
|
+
|
|
286
|
+
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
287
|
+
const stmt = this.db.prepare(`
|
|
288
|
+
UPDATE teams SET name = ?, description = ?, leaderId = ?, memberIds = ?, updatedAt = ?
|
|
289
|
+
WHERE id = ?
|
|
290
|
+
`);
|
|
291
|
+
stmt.run(
|
|
292
|
+
updated.name,
|
|
293
|
+
updated.description || null,
|
|
294
|
+
updated.leaderId || null,
|
|
295
|
+
JSON.stringify(updated.memberIds || []),
|
|
296
|
+
updated.updatedAt,
|
|
297
|
+
id
|
|
298
|
+
);
|
|
299
|
+
return updated;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
303
|
+
const stmt = this.db.prepare('DELETE FROM teams WHERE id = ?');
|
|
304
|
+
const result = stmt.run(id);
|
|
305
|
+
return (result.changes ?? 0) > 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- Task Containers ---
|
|
309
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
310
|
+
const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
|
|
311
|
+
const rows = stmt.all(projectId) as any[];
|
|
312
|
+
return rows;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
316
|
+
const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
|
|
317
|
+
const row = stmt.get(id) as any;
|
|
318
|
+
return row || null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
322
|
+
const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
|
|
323
|
+
const now = new Date().toISOString();
|
|
324
|
+
const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
|
|
325
|
+
|
|
326
|
+
const stmt = this.db.prepare(`
|
|
327
|
+
INSERT INTO containers (id, projectId, name, description, parentId, type, color, createdAt, updatedAt)
|
|
328
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
329
|
+
`);
|
|
330
|
+
stmt.run(
|
|
331
|
+
newContainer.id,
|
|
332
|
+
newContainer.projectId,
|
|
333
|
+
newContainer.name,
|
|
334
|
+
newContainer.description || null,
|
|
335
|
+
newContainer.parentId || null,
|
|
336
|
+
newContainer.type || null,
|
|
337
|
+
newContainer.color || null,
|
|
338
|
+
newContainer.createdAt,
|
|
339
|
+
newContainer.updatedAt
|
|
340
|
+
);
|
|
341
|
+
return newContainer;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
345
|
+
const existing = await this.getContainer(id);
|
|
346
|
+
if (!existing) return null;
|
|
347
|
+
|
|
348
|
+
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
349
|
+
const stmt = this.db.prepare(`
|
|
350
|
+
UPDATE containers SET name = ?, description = ?, parentId = ?, type = ?, color = ?, updatedAt = ?
|
|
351
|
+
WHERE id = ?
|
|
352
|
+
`);
|
|
353
|
+
stmt.run(
|
|
354
|
+
updated.name,
|
|
355
|
+
updated.description || null,
|
|
356
|
+
updated.parentId || null,
|
|
357
|
+
updated.type || null,
|
|
358
|
+
updated.color || null,
|
|
359
|
+
updated.updatedAt,
|
|
360
|
+
id
|
|
361
|
+
);
|
|
362
|
+
return updated;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
366
|
+
const stmt = this.db.prepare('DELETE FROM containers WHERE id = ?');
|
|
367
|
+
const result = stmt.run(id);
|
|
368
|
+
return (result.changes ?? 0) > 0;
|
|
369
|
+
}
|
|
370
|
+
|
|
199
371
|
// --- Tasks ---
|
|
200
372
|
async getTasks(projectId?: string): Promise<Task[]> {
|
|
201
373
|
let rows: any[];
|
|
@@ -223,8 +395,11 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
223
395
|
const stmt = this.db.prepare(`
|
|
224
396
|
INSERT INTO tasks (
|
|
225
397
|
id, projectId, title, description, status, priority, assigneeId, reporterId,
|
|
226
|
-
|
|
227
|
-
|
|
398
|
+
reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
|
|
399
|
+
dueDate, estimatedHours, loggedHours, actualHours, billableHours,
|
|
400
|
+
estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
|
|
401
|
+
tags, customFields, parentId, createdAt, updatedAt
|
|
402
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
228
403
|
`);
|
|
229
404
|
stmt.run(
|
|
230
405
|
newTask.id,
|
|
@@ -235,10 +410,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
235
410
|
newTask.priority,
|
|
236
411
|
newTask.assigneeId || null,
|
|
237
412
|
newTask.reporterId || null,
|
|
238
|
-
newTask.
|
|
413
|
+
newTask.reviewerId || null,
|
|
414
|
+
newTask.iterationId || null,
|
|
415
|
+
newTask.teamId || null,
|
|
416
|
+
newTask.containerId || null,
|
|
417
|
+
newTask.plannedStartDate || null,
|
|
418
|
+
newTask.actualStartDate || null,
|
|
419
|
+
newTask.actualEndDate || null,
|
|
239
420
|
newTask.dueDate || null,
|
|
240
421
|
newTask.estimatedHours ?? null,
|
|
241
422
|
newTask.loggedHours ?? null,
|
|
423
|
+
newTask.actualHours ?? null,
|
|
424
|
+
newTask.billableHours ?? null,
|
|
425
|
+
newTask.estimatedDurationMinutes ?? null,
|
|
426
|
+
newTask.actualDurationMinutes ?? null,
|
|
427
|
+
newTask.billableDurationMinutes ?? null,
|
|
428
|
+
newTask.progress ?? null,
|
|
242
429
|
JSON.stringify(newTask.tags || []),
|
|
243
430
|
JSON.stringify(newTask.customFields || {}),
|
|
244
431
|
newTask.parentId || null,
|
|
@@ -261,8 +448,11 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
261
448
|
const stmt = this.db.prepare(`
|
|
262
449
|
UPDATE tasks SET
|
|
263
450
|
projectId = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
264
|
-
assigneeId = ?, reporterId = ?,
|
|
265
|
-
|
|
451
|
+
assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
|
|
452
|
+
plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
|
|
453
|
+
estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
|
|
454
|
+
estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
|
|
455
|
+
tags = ?, customFields = ?, parentId = ?, updatedAt = ?
|
|
266
456
|
WHERE id = ?
|
|
267
457
|
`);
|
|
268
458
|
stmt.run(
|
|
@@ -273,10 +463,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
273
463
|
updated.priority,
|
|
274
464
|
updated.assigneeId || null,
|
|
275
465
|
updated.reporterId || null,
|
|
276
|
-
updated.
|
|
466
|
+
updated.reviewerId || null,
|
|
467
|
+
updated.iterationId || null,
|
|
468
|
+
updated.teamId || null,
|
|
469
|
+
updated.containerId || null,
|
|
470
|
+
updated.plannedStartDate || null,
|
|
471
|
+
updated.actualStartDate || null,
|
|
472
|
+
updated.actualEndDate || null,
|
|
277
473
|
updated.dueDate || null,
|
|
278
474
|
updated.estimatedHours ?? null,
|
|
279
475
|
updated.loggedHours ?? null,
|
|
476
|
+
updated.actualHours ?? null,
|
|
477
|
+
updated.billableHours ?? null,
|
|
478
|
+
updated.estimatedDurationMinutes ?? null,
|
|
479
|
+
updated.actualDurationMinutes ?? null,
|
|
480
|
+
updated.billableDurationMinutes ?? null,
|
|
481
|
+
updated.progress ?? null,
|
|
280
482
|
JSON.stringify(updated.tags || []),
|
|
281
483
|
JSON.stringify(updated.customFields || {}),
|
|
282
484
|
updated.parentId || null,
|
|
@@ -292,53 +494,56 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
292
494
|
return (result.changes ?? 0) > 0;
|
|
293
495
|
}
|
|
294
496
|
|
|
295
|
-
// ---
|
|
296
|
-
async
|
|
297
|
-
const stmt = this.db.prepare('SELECT * FROM
|
|
497
|
+
// --- Iterations ---
|
|
498
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
499
|
+
const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
|
|
298
500
|
const rows = stmt.all(projectId) as any[];
|
|
299
|
-
return rows
|
|
300
|
-
...r,
|
|
301
|
-
description: r.goal
|
|
302
|
-
}));
|
|
501
|
+
return rows;
|
|
303
502
|
}
|
|
304
503
|
|
|
305
|
-
async
|
|
306
|
-
const
|
|
504
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
505
|
+
const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
|
|
506
|
+
const row = stmt.get(id) as any;
|
|
507
|
+
return row || null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
511
|
+
const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
|
|
307
512
|
const now = new Date().toISOString();
|
|
308
|
-
const
|
|
513
|
+
const newIteration: Iteration = { ...iteration, id, createdAt: now };
|
|
309
514
|
|
|
310
515
|
const stmt = this.db.prepare(`
|
|
311
|
-
INSERT INTO
|
|
312
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
516
|
+
INSERT INTO iterations (id, projectId, name, goal, type, startDate, endDate, status, createdAt)
|
|
517
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
313
518
|
`);
|
|
314
519
|
stmt.run(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
520
|
+
newIteration.id,
|
|
521
|
+
newIteration.projectId,
|
|
522
|
+
newIteration.name,
|
|
523
|
+
newIteration.goal || null,
|
|
524
|
+
newIteration.type || null,
|
|
525
|
+
newIteration.startDate || null,
|
|
526
|
+
newIteration.endDate || null,
|
|
527
|
+
newIteration.status,
|
|
528
|
+
newIteration.createdAt
|
|
323
529
|
);
|
|
324
|
-
return
|
|
530
|
+
return newIteration;
|
|
325
531
|
}
|
|
326
532
|
|
|
327
|
-
async
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
if (!existingRow) return null;
|
|
533
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
534
|
+
const existing = await this.getIteration(id);
|
|
535
|
+
if (!existing) return null;
|
|
331
536
|
|
|
332
|
-
const
|
|
333
|
-
const updated: Sprint = { ...existing, ...updates };
|
|
537
|
+
const updated: Iteration = { ...existing, ...updates };
|
|
334
538
|
|
|
335
539
|
const updateStmt = this.db.prepare(`
|
|
336
|
-
UPDATE
|
|
540
|
+
UPDATE iterations SET name = ?, goal = ?, type = ?, startDate = ?, endDate = ?, status = ?
|
|
337
541
|
WHERE id = ?
|
|
338
542
|
`);
|
|
339
543
|
updateStmt.run(
|
|
340
544
|
updated.name,
|
|
341
545
|
updated.goal || null,
|
|
546
|
+
updated.type || null,
|
|
342
547
|
updated.startDate || null,
|
|
343
548
|
updated.endDate || null,
|
|
344
549
|
updated.status,
|
|
@@ -347,6 +552,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
347
552
|
return updated;
|
|
348
553
|
}
|
|
349
554
|
|
|
555
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
556
|
+
const stmt = this.db.prepare('DELETE FROM iterations WHERE id = ?');
|
|
557
|
+
const result = stmt.run(id);
|
|
558
|
+
return (result.changes ?? 0) > 0;
|
|
559
|
+
}
|
|
560
|
+
|
|
350
561
|
// --- Comments & Activity ---
|
|
351
562
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
352
563
|
const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
|
|
@@ -417,7 +628,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
417
628
|
// --- Time Tracking ---
|
|
418
629
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
419
630
|
const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
|
|
420
|
-
|
|
631
|
+
const rows = stmt.all(taskId) as any[];
|
|
632
|
+
return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
421
633
|
}
|
|
422
634
|
|
|
423
635
|
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
|
|
@@ -426,10 +638,18 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
426
638
|
const newEntry: TimeEntry = { ...entry, id, loggedAt: now };
|
|
427
639
|
|
|
428
640
|
const stmt = this.db.prepare(`
|
|
429
|
-
INSERT INTO time_entries (id, taskId, userId, hours, description, loggedAt)
|
|
430
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
641
|
+
INSERT INTO time_entries (id, taskId, userId, hours, isBillable, description, loggedAt)
|
|
642
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
431
643
|
`);
|
|
432
|
-
stmt.run(
|
|
644
|
+
stmt.run(
|
|
645
|
+
newEntry.id,
|
|
646
|
+
newEntry.taskId,
|
|
647
|
+
newEntry.userId,
|
|
648
|
+
newEntry.hours,
|
|
649
|
+
newEntry.isBillable !== undefined ? (newEntry.isBillable ? 1 : 0) : null,
|
|
650
|
+
newEntry.description || null,
|
|
651
|
+
newEntry.loggedAt
|
|
652
|
+
);
|
|
433
653
|
return newEntry;
|
|
434
654
|
}
|
|
435
655
|
|
|
@@ -487,10 +707,20 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
487
707
|
return {
|
|
488
708
|
...row,
|
|
489
709
|
members: row.members ? JSON.parse(row.members) : [],
|
|
710
|
+
teamIds: row.teamIds ? JSON.parse(row.teamIds) : [],
|
|
711
|
+
statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
|
|
712
|
+
priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
|
|
490
713
|
customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
|
|
491
714
|
};
|
|
492
715
|
}
|
|
493
716
|
|
|
717
|
+
private mapTeam(row: any): Team {
|
|
718
|
+
return {
|
|
719
|
+
...row,
|
|
720
|
+
memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
494
724
|
private mapTask(row: any): Task {
|
|
495
725
|
return {
|
|
496
726
|
...row,
|
package/src/types/index.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type DefaultPriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';
|
|
2
|
+
export type Priority = DefaultPriority | (string & {});
|
|
2
3
|
|
|
3
|
-
export type
|
|
4
|
+
export type DefaultTaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';
|
|
5
|
+
export type TaskStatus = DefaultTaskStatus | (string & {});
|
|
6
|
+
|
|
7
|
+
export type CompletionState = 'done' | 'not_done';
|
|
8
|
+
export type ExecutionState = 'active' | 'inactive';
|
|
9
|
+
|
|
10
|
+
export interface StatusDefinition {
|
|
11
|
+
key: string;
|
|
12
|
+
label: string;
|
|
13
|
+
completionState: CompletionState;
|
|
14
|
+
executionState: ExecutionState;
|
|
15
|
+
isCancelled?: boolean;
|
|
16
|
+
}
|
|
4
17
|
|
|
5
18
|
export type Role = 'admin' | 'project_manager' | 'contributor' | 'viewer';
|
|
6
19
|
|
|
@@ -13,6 +26,16 @@ export interface User {
|
|
|
13
26
|
createdAt: string;
|
|
14
27
|
}
|
|
15
28
|
|
|
29
|
+
export interface Team {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
leaderId?: string;
|
|
34
|
+
memberIds: string[];
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
export interface CustomFieldDefinition {
|
|
17
40
|
id: string;
|
|
18
41
|
key: string;
|
|
@@ -25,16 +48,31 @@ export interface CustomFieldDefinition {
|
|
|
25
48
|
|
|
26
49
|
export interface Project {
|
|
27
50
|
id: string;
|
|
28
|
-
key
|
|
51
|
+
key?: string; // e.g. "CP" or "PROJ"
|
|
29
52
|
name: string;
|
|
30
53
|
description?: string;
|
|
31
54
|
ownerId?: string;
|
|
32
55
|
members?: string[]; // user IDs
|
|
56
|
+
teamIds?: string[]; // team IDs
|
|
57
|
+
statusDefinitions?: StatusDefinition[];
|
|
58
|
+
priorityDefinitions?: Array<{ key: string; label: string; level?: number }>;
|
|
33
59
|
customFieldDefinitions?: CustomFieldDefinition[];
|
|
34
60
|
createdAt: string;
|
|
35
61
|
updatedAt: string;
|
|
36
62
|
}
|
|
37
63
|
|
|
64
|
+
export interface TaskContainer {
|
|
65
|
+
id: string;
|
|
66
|
+
projectId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
parentId?: string; // nested container hierarchy (e.g., folder -> epic -> group)
|
|
70
|
+
type?: 'epic' | 'group' | 'section' | 'folder' | string;
|
|
71
|
+
color?: string;
|
|
72
|
+
createdAt: string;
|
|
73
|
+
updatedAt: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
38
76
|
export interface TaskDependency {
|
|
39
77
|
id: string;
|
|
40
78
|
taskId: string;
|
|
@@ -42,19 +80,41 @@ export interface TaskDependency {
|
|
|
42
80
|
type: 'blocking' | 'blocked_by' | 'relates_to';
|
|
43
81
|
}
|
|
44
82
|
|
|
83
|
+
export interface TaskDependencyGraph {
|
|
84
|
+
taskId: string;
|
|
85
|
+
upstreamTasks: Task[]; // tasks that this task depends on
|
|
86
|
+
downstreamTasks: Task[]; // tasks that depend on this task
|
|
87
|
+
dependencies: TaskDependency[];
|
|
88
|
+
}
|
|
89
|
+
|
|
45
90
|
export interface Task {
|
|
46
91
|
id: string;
|
|
47
92
|
projectId: string;
|
|
93
|
+
key?: string; // e.g. "MOB-12"
|
|
48
94
|
title: string;
|
|
49
95
|
description?: string;
|
|
50
96
|
status: TaskStatus;
|
|
51
97
|
priority: Priority;
|
|
52
98
|
assigneeId?: string;
|
|
53
99
|
reporterId?: string;
|
|
54
|
-
|
|
100
|
+
reviewerId?: string;
|
|
101
|
+
iterationId?: string;
|
|
102
|
+
teamId?: string;
|
|
103
|
+
containerId?: string;
|
|
104
|
+
plannedStartDate?: string;
|
|
105
|
+
actualStartDate?: string;
|
|
106
|
+
actualEndDate?: string;
|
|
55
107
|
dueDate?: string;
|
|
108
|
+
// Duration & Effort (in hours and/or minutes)
|
|
56
109
|
estimatedHours?: number;
|
|
57
110
|
loggedHours?: number;
|
|
111
|
+
actualHours?: number;
|
|
112
|
+
billableHours?: number;
|
|
113
|
+
estimatedDurationMinutes?: number;
|
|
114
|
+
actualDurationMinutes?: number;
|
|
115
|
+
billableDurationMinutes?: number;
|
|
116
|
+
// Progress (0 to 100 percentage)
|
|
117
|
+
progress?: number;
|
|
58
118
|
tags?: string[];
|
|
59
119
|
customFields?: Record<string, unknown>;
|
|
60
120
|
parentId?: string; // Subtask support
|
|
@@ -62,13 +122,14 @@ export interface Task {
|
|
|
62
122
|
updatedAt: string;
|
|
63
123
|
}
|
|
64
124
|
|
|
65
|
-
export interface
|
|
125
|
+
export interface Iteration {
|
|
66
126
|
id: string;
|
|
67
127
|
projectId: string;
|
|
68
128
|
name: string;
|
|
69
129
|
goal?: string;
|
|
70
|
-
|
|
71
|
-
|
|
130
|
+
type?: 'sprint' | 'cycle' | 'milestone' | string;
|
|
131
|
+
startDate?: string;
|
|
132
|
+
endDate?: string;
|
|
72
133
|
status: 'planning' | 'active' | 'completed';
|
|
73
134
|
createdAt: string;
|
|
74
135
|
}
|
|
@@ -87,6 +148,7 @@ export interface TimeEntry {
|
|
|
87
148
|
taskId: string;
|
|
88
149
|
userId: string;
|
|
89
150
|
hours: number;
|
|
151
|
+
isBillable?: boolean;
|
|
90
152
|
description?: string;
|
|
91
153
|
loggedAt: string;
|
|
92
154
|
}
|
|
@@ -119,8 +181,10 @@ export type WebhookEvent =
|
|
|
119
181
|
| 'task.deleted'
|
|
120
182
|
| 'task.status_changed'
|
|
121
183
|
| 'comment.created'
|
|
122
|
-
| '
|
|
123
|
-
| '
|
|
184
|
+
| 'iteration.started'
|
|
185
|
+
| 'iteration.completed'
|
|
186
|
+
| 'team.created'
|
|
187
|
+
| 'container.created';
|
|
124
188
|
|
|
125
189
|
export interface PluginHooks {
|
|
126
190
|
beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
|
|
@@ -149,6 +213,8 @@ export interface CriticalPathConfig {
|
|
|
149
213
|
projects?: Project[];
|
|
150
214
|
tasks?: Task[];
|
|
151
215
|
users?: User[];
|
|
152
|
-
|
|
216
|
+
iterations?: Iteration[];
|
|
217
|
+
teams?: Team[];
|
|
218
|
+
containers?: TaskContainer[];
|
|
153
219
|
};
|
|
154
220
|
}
|