@critical-path/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -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 +95 -13
- 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 +76 -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 +176 -38
- package/dist/store/sqlite.js.map +1 -1
- package/dist/types/index.d.ts +68 -8
- package/dist/types/index.d.ts.map +1 -1
- 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 +1 -0
- package/src/store/firebase.test.ts +8 -2
- package/src/store/firebase.ts +109 -17
- package/src/store/index.ts +110 -18
- package/src/store/sqlite.ts +272 -44
- package/src/types/index.ts +74 -9
- package/src/utils/status.ts +85 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/index.ts
CHANGED
|
@@ -4,7 +4,9 @@ export * from './firebase.js';
|
|
|
4
4
|
import type {
|
|
5
5
|
Project,
|
|
6
6
|
Task,
|
|
7
|
-
|
|
7
|
+
Iteration,
|
|
8
|
+
Team,
|
|
9
|
+
TaskContainer,
|
|
8
10
|
Comment,
|
|
9
11
|
TimeEntry,
|
|
10
12
|
Activity,
|
|
@@ -27,10 +29,26 @@ export interface StorageAdapter {
|
|
|
27
29
|
updateTask(id: string, updates: Partial<Task>): Promise<Task | null>;
|
|
28
30
|
deleteTask(id: string): Promise<boolean>;
|
|
29
31
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Teams
|
|
33
|
+
getTeams(): Promise<Team[]>;
|
|
34
|
+
getTeam(id: string): Promise<Team | null>;
|
|
35
|
+
createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team>;
|
|
36
|
+
updateTeam(id: string, updates: Partial<Team>): Promise<Team | null>;
|
|
37
|
+
deleteTeam(id: string): Promise<boolean>;
|
|
38
|
+
|
|
39
|
+
// Task Containers
|
|
40
|
+
getContainers(projectId: string): Promise<TaskContainer[]>;
|
|
41
|
+
getContainer(id: string): Promise<TaskContainer | null>;
|
|
42
|
+
createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer>;
|
|
43
|
+
updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null>;
|
|
44
|
+
deleteContainer(id: string): Promise<boolean>;
|
|
45
|
+
|
|
46
|
+
// Iterations
|
|
47
|
+
getIterations(projectId: string): Promise<Iteration[]>;
|
|
48
|
+
getIteration(id: string): Promise<Iteration | null>;
|
|
49
|
+
createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration>;
|
|
50
|
+
updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null>;
|
|
51
|
+
deleteIteration(id: string): Promise<boolean>;
|
|
34
52
|
|
|
35
53
|
// Comments & Activity
|
|
36
54
|
getComments(taskId: string): Promise<Comment[]>;
|
|
@@ -54,7 +72,9 @@ export interface StorageAdapter {
|
|
|
54
72
|
export class InMemoryStore implements StorageAdapter {
|
|
55
73
|
private projects = new Map<string, Project>();
|
|
56
74
|
private tasks = new Map<string, Task>();
|
|
57
|
-
private
|
|
75
|
+
private teams = new Map<string, Team>();
|
|
76
|
+
private containers = new Map<string, TaskContainer>();
|
|
77
|
+
private iterations = new Map<string, Iteration>();
|
|
58
78
|
private comments = new Map<string, Comment>();
|
|
59
79
|
private activities: Activity[] = [];
|
|
60
80
|
private timeEntries = new Map<string, TimeEntry>();
|
|
@@ -139,29 +159,97 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
139
159
|
return this.tasks.delete(id);
|
|
140
160
|
}
|
|
141
161
|
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
// Teams
|
|
163
|
+
async getTeams(): Promise<Team[]> {
|
|
164
|
+
return Array.from(this.teams.values());
|
|
144
165
|
}
|
|
145
166
|
|
|
146
|
-
async
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
168
|
+
return this.teams.get(id) || null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
172
|
+
const id = `team_${Math.random().toString(36).substring(2, 9)}`;
|
|
173
|
+
const now = new Date().toISOString();
|
|
174
|
+
const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
|
|
175
|
+
this.teams.set(id, newTeam);
|
|
176
|
+
return newTeam;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
180
|
+
const existing = this.teams.get(id);
|
|
181
|
+
if (!existing) return null;
|
|
182
|
+
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
183
|
+
this.teams.set(id, updated);
|
|
184
|
+
return updated;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
188
|
+
return this.teams.delete(id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Containers
|
|
192
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
193
|
+
return Array.from(this.containers.values()).filter((c) => c.projectId === projectId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
197
|
+
return this.containers.get(id) || null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
201
|
+
const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
|
|
204
|
+
this.containers.set(id, newContainer);
|
|
205
|
+
return newContainer;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
209
|
+
const existing = this.containers.get(id);
|
|
210
|
+
if (!existing) return null;
|
|
211
|
+
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
212
|
+
this.containers.set(id, updated);
|
|
213
|
+
return updated;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
217
|
+
return this.containers.delete(id);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Iterations
|
|
221
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
222
|
+
return Array.from(this.iterations.values()).filter((s) => s.projectId === projectId);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
226
|
+
return this.iterations.get(id) || null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
230
|
+
const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
|
|
231
|
+
const newIteration: Iteration = {
|
|
232
|
+
...iteration,
|
|
150
233
|
id,
|
|
151
234
|
createdAt: new Date().toISOString()
|
|
152
235
|
};
|
|
153
|
-
this.
|
|
154
|
-
return
|
|
236
|
+
this.iterations.set(id, newIteration);
|
|
237
|
+
return newIteration;
|
|
155
238
|
}
|
|
156
239
|
|
|
157
|
-
async
|
|
158
|
-
const existing = this.
|
|
240
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
241
|
+
const existing = this.iterations.get(id);
|
|
159
242
|
if (!existing) return null;
|
|
160
|
-
const updated:
|
|
161
|
-
this.
|
|
243
|
+
const updated: Iteration = { ...existing, ...updates };
|
|
244
|
+
this.iterations.set(id, updated);
|
|
162
245
|
return updated;
|
|
163
246
|
}
|
|
164
247
|
|
|
248
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
249
|
+
return this.iterations.delete(id);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Comments
|
|
165
253
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
166
254
|
return Array.from(this.comments.values()).filter((c) => c.taskId === taskId);
|
|
167
255
|
}
|
|
@@ -174,6 +262,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
174
262
|
return newComment;
|
|
175
263
|
}
|
|
176
264
|
|
|
265
|
+
// Activities
|
|
177
266
|
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
178
267
|
return this.activities.filter((a) => {
|
|
179
268
|
if (filter?.projectId && a.projectId !== filter.projectId) return false;
|
|
@@ -189,6 +278,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
189
278
|
return newAct;
|
|
190
279
|
}
|
|
191
280
|
|
|
281
|
+
// Time Entries
|
|
192
282
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
193
283
|
return Array.from(this.timeEntries.values()).filter((e) => e.taskId === taskId);
|
|
194
284
|
}
|
|
@@ -200,6 +290,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
200
290
|
return newEntry;
|
|
201
291
|
}
|
|
202
292
|
|
|
293
|
+
// Dependencies
|
|
203
294
|
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
204
295
|
return Array.from(this.dependencies.values()).filter(
|
|
205
296
|
(d) => d.taskId === taskId || d.dependsOnTaskId === taskId
|
|
@@ -213,6 +304,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
213
304
|
return newDep;
|
|
214
305
|
}
|
|
215
306
|
|
|
307
|
+
// Webhooks
|
|
216
308
|
async getWebhooks(): Promise<Webhook[]> {
|
|
217
309
|
return Array.from(this.webhooks.values());
|
|
218
310
|
}
|
package/src/store/sqlite.ts
CHANGED
|
@@ -3,7 +3,9 @@ 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,
|
|
@@ -44,11 +46,36 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
44
46
|
description TEXT,
|
|
45
47
|
ownerId TEXT,
|
|
46
48
|
members TEXT,
|
|
49
|
+
teamIds TEXT,
|
|
50
|
+
statusDefinitions TEXT,
|
|
51
|
+
priorityDefinitions TEXT,
|
|
47
52
|
customFieldDefinitions TEXT,
|
|
48
53
|
createdAt TEXT NOT NULL,
|
|
49
54
|
updatedAt TEXT NOT NULL
|
|
50
55
|
);
|
|
51
56
|
|
|
57
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
name TEXT NOT NULL,
|
|
60
|
+
description TEXT,
|
|
61
|
+
leaderId TEXT,
|
|
62
|
+
memberIds TEXT NOT NULL,
|
|
63
|
+
createdAt TEXT NOT NULL,
|
|
64
|
+
updatedAt TEXT NOT NULL
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS containers (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
projectId TEXT NOT NULL,
|
|
70
|
+
name TEXT NOT NULL,
|
|
71
|
+
description TEXT,
|
|
72
|
+
parentId TEXT,
|
|
73
|
+
type TEXT,
|
|
74
|
+
color TEXT,
|
|
75
|
+
createdAt TEXT NOT NULL,
|
|
76
|
+
updatedAt TEXT NOT NULL
|
|
77
|
+
);
|
|
78
|
+
|
|
52
79
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
53
80
|
id TEXT PRIMARY KEY,
|
|
54
81
|
projectId TEXT NOT NULL,
|
|
@@ -58,10 +85,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
58
85
|
priority TEXT NOT NULL,
|
|
59
86
|
assigneeId TEXT,
|
|
60
87
|
reporterId TEXT,
|
|
61
|
-
|
|
88
|
+
reviewerId TEXT,
|
|
89
|
+
iterationId TEXT,
|
|
90
|
+
teamId TEXT,
|
|
91
|
+
containerId TEXT,
|
|
92
|
+
plannedStartDate TEXT,
|
|
93
|
+
actualStartDate TEXT,
|
|
94
|
+
actualEndDate TEXT,
|
|
62
95
|
dueDate TEXT,
|
|
63
96
|
estimatedHours REAL,
|
|
64
97
|
loggedHours REAL,
|
|
98
|
+
actualHours REAL,
|
|
99
|
+
billableHours REAL,
|
|
100
|
+
estimatedDurationMinutes REAL,
|
|
101
|
+
actualDurationMinutes REAL,
|
|
102
|
+
billableDurationMinutes REAL,
|
|
103
|
+
progress REAL,
|
|
65
104
|
tags TEXT,
|
|
66
105
|
customFields TEXT,
|
|
67
106
|
parentId TEXT,
|
|
@@ -69,11 +108,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
69
108
|
updatedAt TEXT NOT NULL
|
|
70
109
|
);
|
|
71
110
|
|
|
72
|
-
CREATE TABLE IF NOT EXISTS
|
|
111
|
+
CREATE TABLE IF NOT EXISTS iterations (
|
|
73
112
|
id TEXT PRIMARY KEY,
|
|
74
113
|
projectId TEXT NOT NULL,
|
|
75
114
|
name TEXT NOT NULL,
|
|
76
115
|
goal TEXT,
|
|
116
|
+
type TEXT,
|
|
77
117
|
startDate TEXT,
|
|
78
118
|
endDate TEXT,
|
|
79
119
|
status TEXT NOT NULL,
|
|
@@ -104,6 +144,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
104
144
|
taskId TEXT NOT NULL,
|
|
105
145
|
userId TEXT NOT NULL,
|
|
106
146
|
hours REAL NOT NULL,
|
|
147
|
+
isBillable INTEGER,
|
|
107
148
|
description TEXT,
|
|
108
149
|
loggedAt TEXT NOT NULL
|
|
109
150
|
);
|
|
@@ -145,8 +186,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
145
186
|
const newProj: Project = { ...project, id, createdAt: now, updatedAt: now };
|
|
146
187
|
|
|
147
188
|
const stmt = this.db.prepare(`
|
|
148
|
-
INSERT INTO projects (id, key, name, description, ownerId, members, customFieldDefinitions, createdAt, updatedAt)
|
|
149
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
189
|
+
INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
|
|
190
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
150
191
|
`);
|
|
151
192
|
stmt.run(
|
|
152
193
|
newProj.id,
|
|
@@ -155,6 +196,9 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
155
196
|
newProj.description || null,
|
|
156
197
|
newProj.ownerId || null,
|
|
157
198
|
JSON.stringify(newProj.members || []),
|
|
199
|
+
JSON.stringify(newProj.teamIds || []),
|
|
200
|
+
JSON.stringify(newProj.statusDefinitions || []),
|
|
201
|
+
JSON.stringify(newProj.priorityDefinitions || []),
|
|
158
202
|
JSON.stringify(newProj.customFieldDefinitions || []),
|
|
159
203
|
newProj.createdAt,
|
|
160
204
|
newProj.updatedAt
|
|
@@ -174,7 +218,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
174
218
|
|
|
175
219
|
const stmt = this.db.prepare(`
|
|
176
220
|
UPDATE projects
|
|
177
|
-
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
221
|
+
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
178
222
|
WHERE id = ?
|
|
179
223
|
`);
|
|
180
224
|
stmt.run(
|
|
@@ -183,6 +227,9 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
183
227
|
updated.description || null,
|
|
184
228
|
updated.ownerId || null,
|
|
185
229
|
JSON.stringify(updated.members || []),
|
|
230
|
+
JSON.stringify(updated.teamIds || []),
|
|
231
|
+
JSON.stringify(updated.statusDefinitions || []),
|
|
232
|
+
JSON.stringify(updated.priorityDefinitions || []),
|
|
186
233
|
JSON.stringify(updated.customFieldDefinitions || []),
|
|
187
234
|
updated.updatedAt,
|
|
188
235
|
id
|
|
@@ -196,6 +243,129 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
196
243
|
return (result.changes ?? 0) > 0;
|
|
197
244
|
}
|
|
198
245
|
|
|
246
|
+
// --- Teams ---
|
|
247
|
+
async getTeams(): Promise<Team[]> {
|
|
248
|
+
const stmt = this.db.prepare('SELECT * FROM teams');
|
|
249
|
+
const rows = stmt.all() as any[];
|
|
250
|
+
return rows.map((r) => this.mapTeam(r));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
254
|
+
const stmt = this.db.prepare('SELECT * FROM teams WHERE id = ?');
|
|
255
|
+
const row = stmt.get(id) as any;
|
|
256
|
+
return row ? this.mapTeam(row) : null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
260
|
+
const id = `team_${Math.random().toString(36).substring(2, 9)}`;
|
|
261
|
+
const now = new Date().toISOString();
|
|
262
|
+
const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
|
|
263
|
+
|
|
264
|
+
const stmt = this.db.prepare(`
|
|
265
|
+
INSERT INTO teams (id, name, description, leaderId, memberIds, createdAt, updatedAt)
|
|
266
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
267
|
+
`);
|
|
268
|
+
stmt.run(
|
|
269
|
+
newTeam.id,
|
|
270
|
+
newTeam.name,
|
|
271
|
+
newTeam.description || null,
|
|
272
|
+
newTeam.leaderId || null,
|
|
273
|
+
JSON.stringify(newTeam.memberIds || []),
|
|
274
|
+
newTeam.createdAt,
|
|
275
|
+
newTeam.updatedAt
|
|
276
|
+
);
|
|
277
|
+
return newTeam;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
281
|
+
const existing = await this.getTeam(id);
|
|
282
|
+
if (!existing) return null;
|
|
283
|
+
|
|
284
|
+
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
285
|
+
const stmt = this.db.prepare(`
|
|
286
|
+
UPDATE teams SET name = ?, description = ?, leaderId = ?, memberIds = ?, updatedAt = ?
|
|
287
|
+
WHERE id = ?
|
|
288
|
+
`);
|
|
289
|
+
stmt.run(
|
|
290
|
+
updated.name,
|
|
291
|
+
updated.description || null,
|
|
292
|
+
updated.leaderId || null,
|
|
293
|
+
JSON.stringify(updated.memberIds || []),
|
|
294
|
+
updated.updatedAt,
|
|
295
|
+
id
|
|
296
|
+
);
|
|
297
|
+
return updated;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
301
|
+
const stmt = this.db.prepare('DELETE FROM teams WHERE id = ?');
|
|
302
|
+
const result = stmt.run(id);
|
|
303
|
+
return (result.changes ?? 0) > 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// --- Task Containers ---
|
|
307
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
308
|
+
const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
|
|
309
|
+
const rows = stmt.all(projectId) as any[];
|
|
310
|
+
return rows;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
314
|
+
const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
|
|
315
|
+
const row = stmt.get(id) as any;
|
|
316
|
+
return row || null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
320
|
+
const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
|
|
321
|
+
const now = new Date().toISOString();
|
|
322
|
+
const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
|
|
323
|
+
|
|
324
|
+
const stmt = this.db.prepare(`
|
|
325
|
+
INSERT INTO containers (id, projectId, name, description, parentId, type, color, createdAt, updatedAt)
|
|
326
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
327
|
+
`);
|
|
328
|
+
stmt.run(
|
|
329
|
+
newContainer.id,
|
|
330
|
+
newContainer.projectId,
|
|
331
|
+
newContainer.name,
|
|
332
|
+
newContainer.description || null,
|
|
333
|
+
newContainer.parentId || null,
|
|
334
|
+
newContainer.type || null,
|
|
335
|
+
newContainer.color || null,
|
|
336
|
+
newContainer.createdAt,
|
|
337
|
+
newContainer.updatedAt
|
|
338
|
+
);
|
|
339
|
+
return newContainer;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
343
|
+
const existing = await this.getContainer(id);
|
|
344
|
+
if (!existing) return null;
|
|
345
|
+
|
|
346
|
+
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
347
|
+
const stmt = this.db.prepare(`
|
|
348
|
+
UPDATE containers SET name = ?, description = ?, parentId = ?, type = ?, color = ?, updatedAt = ?
|
|
349
|
+
WHERE id = ?
|
|
350
|
+
`);
|
|
351
|
+
stmt.run(
|
|
352
|
+
updated.name,
|
|
353
|
+
updated.description || null,
|
|
354
|
+
updated.parentId || null,
|
|
355
|
+
updated.type || null,
|
|
356
|
+
updated.color || null,
|
|
357
|
+
updated.updatedAt,
|
|
358
|
+
id
|
|
359
|
+
);
|
|
360
|
+
return updated;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
364
|
+
const stmt = this.db.prepare('DELETE FROM containers WHERE id = ?');
|
|
365
|
+
const result = stmt.run(id);
|
|
366
|
+
return (result.changes ?? 0) > 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
199
369
|
// --- Tasks ---
|
|
200
370
|
async getTasks(projectId?: string): Promise<Task[]> {
|
|
201
371
|
let rows: any[];
|
|
@@ -223,8 +393,11 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
223
393
|
const stmt = this.db.prepare(`
|
|
224
394
|
INSERT INTO tasks (
|
|
225
395
|
id, projectId, title, description, status, priority, assigneeId, reporterId,
|
|
226
|
-
|
|
227
|
-
|
|
396
|
+
reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
|
|
397
|
+
dueDate, estimatedHours, loggedHours, actualHours, billableHours,
|
|
398
|
+
estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
|
|
399
|
+
tags, customFields, parentId, createdAt, updatedAt
|
|
400
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
228
401
|
`);
|
|
229
402
|
stmt.run(
|
|
230
403
|
newTask.id,
|
|
@@ -235,10 +408,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
235
408
|
newTask.priority,
|
|
236
409
|
newTask.assigneeId || null,
|
|
237
410
|
newTask.reporterId || null,
|
|
238
|
-
newTask.
|
|
411
|
+
newTask.reviewerId || null,
|
|
412
|
+
newTask.iterationId || null,
|
|
413
|
+
newTask.teamId || null,
|
|
414
|
+
newTask.containerId || null,
|
|
415
|
+
newTask.plannedStartDate || null,
|
|
416
|
+
newTask.actualStartDate || null,
|
|
417
|
+
newTask.actualEndDate || null,
|
|
239
418
|
newTask.dueDate || null,
|
|
240
419
|
newTask.estimatedHours ?? null,
|
|
241
420
|
newTask.loggedHours ?? null,
|
|
421
|
+
newTask.actualHours ?? null,
|
|
422
|
+
newTask.billableHours ?? null,
|
|
423
|
+
newTask.estimatedDurationMinutes ?? null,
|
|
424
|
+
newTask.actualDurationMinutes ?? null,
|
|
425
|
+
newTask.billableDurationMinutes ?? null,
|
|
426
|
+
newTask.progress ?? null,
|
|
242
427
|
JSON.stringify(newTask.tags || []),
|
|
243
428
|
JSON.stringify(newTask.customFields || {}),
|
|
244
429
|
newTask.parentId || null,
|
|
@@ -261,8 +446,11 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
261
446
|
const stmt = this.db.prepare(`
|
|
262
447
|
UPDATE tasks SET
|
|
263
448
|
projectId = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
264
|
-
assigneeId = ?, reporterId = ?,
|
|
265
|
-
|
|
449
|
+
assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
|
|
450
|
+
plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
|
|
451
|
+
estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
|
|
452
|
+
estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
|
|
453
|
+
tags = ?, customFields = ?, parentId = ?, updatedAt = ?
|
|
266
454
|
WHERE id = ?
|
|
267
455
|
`);
|
|
268
456
|
stmt.run(
|
|
@@ -273,10 +461,22 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
273
461
|
updated.priority,
|
|
274
462
|
updated.assigneeId || null,
|
|
275
463
|
updated.reporterId || null,
|
|
276
|
-
updated.
|
|
464
|
+
updated.reviewerId || null,
|
|
465
|
+
updated.iterationId || null,
|
|
466
|
+
updated.teamId || null,
|
|
467
|
+
updated.containerId || null,
|
|
468
|
+
updated.plannedStartDate || null,
|
|
469
|
+
updated.actualStartDate || null,
|
|
470
|
+
updated.actualEndDate || null,
|
|
277
471
|
updated.dueDate || null,
|
|
278
472
|
updated.estimatedHours ?? null,
|
|
279
473
|
updated.loggedHours ?? null,
|
|
474
|
+
updated.actualHours ?? null,
|
|
475
|
+
updated.billableHours ?? null,
|
|
476
|
+
updated.estimatedDurationMinutes ?? null,
|
|
477
|
+
updated.actualDurationMinutes ?? null,
|
|
478
|
+
updated.billableDurationMinutes ?? null,
|
|
479
|
+
updated.progress ?? null,
|
|
280
480
|
JSON.stringify(updated.tags || []),
|
|
281
481
|
JSON.stringify(updated.customFields || {}),
|
|
282
482
|
updated.parentId || null,
|
|
@@ -292,53 +492,56 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
292
492
|
return (result.changes ?? 0) > 0;
|
|
293
493
|
}
|
|
294
494
|
|
|
295
|
-
// ---
|
|
296
|
-
async
|
|
297
|
-
const stmt = this.db.prepare('SELECT * FROM
|
|
495
|
+
// --- Iterations ---
|
|
496
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
497
|
+
const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
|
|
298
498
|
const rows = stmt.all(projectId) as any[];
|
|
299
|
-
return rows
|
|
300
|
-
...r,
|
|
301
|
-
description: r.goal
|
|
302
|
-
}));
|
|
499
|
+
return rows;
|
|
303
500
|
}
|
|
304
501
|
|
|
305
|
-
async
|
|
306
|
-
const
|
|
502
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
503
|
+
const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
|
|
504
|
+
const row = stmt.get(id) as any;
|
|
505
|
+
return row || null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
509
|
+
const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
|
|
307
510
|
const now = new Date().toISOString();
|
|
308
|
-
const
|
|
511
|
+
const newIteration: Iteration = { ...iteration, id, createdAt: now };
|
|
309
512
|
|
|
310
513
|
const stmt = this.db.prepare(`
|
|
311
|
-
INSERT INTO
|
|
312
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
514
|
+
INSERT INTO iterations (id, projectId, name, goal, type, startDate, endDate, status, createdAt)
|
|
515
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
313
516
|
`);
|
|
314
517
|
stmt.run(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
518
|
+
newIteration.id,
|
|
519
|
+
newIteration.projectId,
|
|
520
|
+
newIteration.name,
|
|
521
|
+
newIteration.goal || null,
|
|
522
|
+
newIteration.type || null,
|
|
523
|
+
newIteration.startDate || null,
|
|
524
|
+
newIteration.endDate || null,
|
|
525
|
+
newIteration.status,
|
|
526
|
+
newIteration.createdAt
|
|
323
527
|
);
|
|
324
|
-
return
|
|
528
|
+
return newIteration;
|
|
325
529
|
}
|
|
326
530
|
|
|
327
|
-
async
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
if (!existingRow) return null;
|
|
531
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
532
|
+
const existing = await this.getIteration(id);
|
|
533
|
+
if (!existing) return null;
|
|
331
534
|
|
|
332
|
-
const
|
|
333
|
-
const updated: Sprint = { ...existing, ...updates };
|
|
535
|
+
const updated: Iteration = { ...existing, ...updates };
|
|
334
536
|
|
|
335
537
|
const updateStmt = this.db.prepare(`
|
|
336
|
-
UPDATE
|
|
538
|
+
UPDATE iterations SET name = ?, goal = ?, type = ?, startDate = ?, endDate = ?, status = ?
|
|
337
539
|
WHERE id = ?
|
|
338
540
|
`);
|
|
339
541
|
updateStmt.run(
|
|
340
542
|
updated.name,
|
|
341
543
|
updated.goal || null,
|
|
544
|
+
updated.type || null,
|
|
342
545
|
updated.startDate || null,
|
|
343
546
|
updated.endDate || null,
|
|
344
547
|
updated.status,
|
|
@@ -347,6 +550,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
347
550
|
return updated;
|
|
348
551
|
}
|
|
349
552
|
|
|
553
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
554
|
+
const stmt = this.db.prepare('DELETE FROM iterations WHERE id = ?');
|
|
555
|
+
const result = stmt.run(id);
|
|
556
|
+
return (result.changes ?? 0) > 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
350
559
|
// --- Comments & Activity ---
|
|
351
560
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
352
561
|
const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
|
|
@@ -417,7 +626,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
417
626
|
// --- Time Tracking ---
|
|
418
627
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
419
628
|
const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
|
|
420
|
-
|
|
629
|
+
const rows = stmt.all(taskId) as any[];
|
|
630
|
+
return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
421
631
|
}
|
|
422
632
|
|
|
423
633
|
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
|
|
@@ -426,10 +636,18 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
426
636
|
const newEntry: TimeEntry = { ...entry, id, loggedAt: now };
|
|
427
637
|
|
|
428
638
|
const stmt = this.db.prepare(`
|
|
429
|
-
INSERT INTO time_entries (id, taskId, userId, hours, description, loggedAt)
|
|
430
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
639
|
+
INSERT INTO time_entries (id, taskId, userId, hours, isBillable, description, loggedAt)
|
|
640
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
431
641
|
`);
|
|
432
|
-
stmt.run(
|
|
642
|
+
stmt.run(
|
|
643
|
+
newEntry.id,
|
|
644
|
+
newEntry.taskId,
|
|
645
|
+
newEntry.userId,
|
|
646
|
+
newEntry.hours,
|
|
647
|
+
newEntry.isBillable !== undefined ? (newEntry.isBillable ? 1 : 0) : null,
|
|
648
|
+
newEntry.description || null,
|
|
649
|
+
newEntry.loggedAt
|
|
650
|
+
);
|
|
433
651
|
return newEntry;
|
|
434
652
|
}
|
|
435
653
|
|
|
@@ -487,10 +705,20 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
487
705
|
return {
|
|
488
706
|
...row,
|
|
489
707
|
members: row.members ? JSON.parse(row.members) : [],
|
|
708
|
+
teamIds: row.teamIds ? JSON.parse(row.teamIds) : [],
|
|
709
|
+
statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
|
|
710
|
+
priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
|
|
490
711
|
customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
|
|
491
712
|
};
|
|
492
713
|
}
|
|
493
714
|
|
|
715
|
+
private mapTeam(row: any): Team {
|
|
716
|
+
return {
|
|
717
|
+
...row,
|
|
718
|
+
memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
494
722
|
private mapTask(row: any): Task {
|
|
495
723
|
return {
|
|
496
724
|
...row,
|