@sprintdock/backend 0.4.2
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 +88 -0
- package/README.md +252 -0
- package/SERVER.md +25 -0
- package/dist/index.d.ts +1536 -0
- package/dist/index.js +4103 -0
- package/drizzle/0000_fresh_roxanne_simpson.sql +51 -0
- package/drizzle/0001_sprint_markdown_content.sql +1 -0
- package/drizzle/0002_task_touched_files.sql +8 -0
- package/drizzle/meta/0000_snapshot.json +372 -0
- package/drizzle/meta/0001_snapshot.json +379 -0
- package/drizzle/meta/_journal.json +27 -0
- package/drizzle.config.ts +14 -0
- package/package.json +40 -0
- package/src/application/container.ts +44 -0
- package/src/application/dto/plan-sprint-analytics.dto.ts +30 -0
- package/src/application/plan.service.ts +123 -0
- package/src/application/sprint.service.ts +118 -0
- package/src/application/task.service.ts +389 -0
- package/src/db/connection.ts +25 -0
- package/src/db/migrator.ts +46 -0
- package/src/db/schema/index.ts +14 -0
- package/src/db/schema/plans.ts +18 -0
- package/src/db/schema/relations.ts +36 -0
- package/src/db/schema/sprints.ts +33 -0
- package/src/db/schema/tasks.ts +62 -0
- package/src/domain/entities/index.ts +30 -0
- package/src/domain/entities/plan.entity.ts +33 -0
- package/src/domain/entities/sprint.entity.ts +44 -0
- package/src/domain/entities/task.entity.ts +80 -0
- package/src/domain/repositories/index.ts +9 -0
- package/src/domain/repositories/plan.repository.ts +21 -0
- package/src/domain/repositories/sprint.repository.ts +19 -0
- package/src/domain/repositories/task.repository.ts +35 -0
- package/src/domain/services/index.ts +9 -0
- package/src/domain/services/plan-domain.service.ts +44 -0
- package/src/domain/services/sprint-domain.service.ts +44 -0
- package/src/domain/services/task-domain.service.ts +136 -0
- package/src/errors/backend-errors.ts +75 -0
- package/src/http/app-factory.ts +55 -0
- package/src/http/controllers/health.controller.ts +33 -0
- package/src/http/controllers/plan.controller.ts +153 -0
- package/src/http/controllers/sprint.controller.ts +111 -0
- package/src/http/controllers/task.controller.ts +158 -0
- package/src/http/express-augmentation.d.ts +20 -0
- package/src/http/middleware/cors.ts +41 -0
- package/src/http/middleware/error-handler.ts +50 -0
- package/src/http/middleware/request-id.ts +28 -0
- package/src/http/middleware/validate.ts +54 -0
- package/src/http/routes/v1/index.ts +39 -0
- package/src/http/routes/v1/plan.routes.ts +51 -0
- package/src/http/routes/v1/schemas.ts +175 -0
- package/src/http/routes/v1/sprint.routes.ts +49 -0
- package/src/http/routes/v1/task.routes.ts +64 -0
- package/src/index.ts +34 -0
- package/src/infrastructure/observability/audit-log.ts +34 -0
- package/src/infrastructure/observability/request-correlation.ts +20 -0
- package/src/infrastructure/repositories/drizzle/drizzle-plan.repository.ts +138 -0
- package/src/infrastructure/repositories/drizzle/drizzle-sprint.repository.ts +137 -0
- package/src/infrastructure/repositories/drizzle/drizzle-task.repository.ts +403 -0
- package/src/infrastructure/repositories/drizzle/index.ts +16 -0
- package/src/infrastructure/repositories/drizzle/row-mappers.ts +106 -0
- package/src/infrastructure/repositories/drizzle/sqlite-db.ts +13 -0
- package/src/infrastructure/repositories/repository-factory.ts +54 -0
- package/src/infrastructure/security/auth-context.ts +35 -0
- package/src/infrastructure/security/input-guard.ts +21 -0
- package/src/infrastructure/security/rate-limiter.ts +65 -0
- package/src/mcp/bootstrap-sprintdock-sqlite.ts +45 -0
- package/src/mcp/mcp-query-helpers.ts +89 -0
- package/src/mcp/mcp-text-formatters.ts +204 -0
- package/src/mcp/mcp-tool-error.ts +24 -0
- package/src/mcp/plugins/context-tools.plugin.ts +107 -0
- package/src/mcp/plugins/default-plugins.ts +23 -0
- package/src/mcp/plugins/index.ts +21 -0
- package/src/mcp/plugins/mcp-tool-kit.ts +90 -0
- package/src/mcp/plugins/plan-tools.plugin.ts +426 -0
- package/src/mcp/plugins/sprint-tools.plugin.ts +396 -0
- package/src/mcp/plugins/task-tools.plugin.ts +528 -0
- package/src/mcp/plugins/task-workflow.plugin.ts +275 -0
- package/src/mcp/plugins/types.ts +45 -0
- package/src/mcp/register-sprintdock-mcp-tools.ts +50 -0
- package/src/mcp/sprintdock-mcp-capabilities.ts +14 -0
- package/src/mcp/sprintdock-mcp-runtime.ts +119 -0
- package/src/mcp/tool-guard.ts +58 -0
- package/src/mcp/transports/http-app-factory.ts +31 -0
- package/src/mcp/transports/http-entry.ts +27 -0
- package/src/mcp/transports/stdio-entry.ts +17 -0
- package/tests/application/container.test.ts +36 -0
- package/tests/application/plan.service.test.ts +114 -0
- package/tests/application/sprint.service.test.ts +138 -0
- package/tests/application/task.service.test.ts +325 -0
- package/tests/db/test-db.test.ts +112 -0
- package/tests/domain/plan-domain.service.test.ts +44 -0
- package/tests/domain/sprint-domain.service.test.ts +38 -0
- package/tests/domain/task-domain.service.test.ts +105 -0
- package/tests/errors/backend-errors.test.ts +44 -0
- package/tests/helpers/test-db.ts +43 -0
- package/tests/http/error-handler.test.ts +37 -0
- package/tests/http/plan.routes.test.ts +128 -0
- package/tests/http/sprint.routes.test.ts +72 -0
- package/tests/http/task.routes.test.ts +130 -0
- package/tests/http/test-app.ts +17 -0
- package/tests/infrastructure/drizzle-plan.repository.test.ts +62 -0
- package/tests/infrastructure/drizzle-sprint.repository.test.ts +49 -0
- package/tests/infrastructure/drizzle-task.repository.test.ts +132 -0
- package/tests/mcp/mcp-text-formatters.test.ts +246 -0
- package/tests/mcp/register-sprintdock-mcp-tools.test.ts +207 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "6",
|
|
3
|
+
"dialect": "sqlite",
|
|
4
|
+
"id": "6b3a33b8-8d7c-456a-98ce-725ef426babe",
|
|
5
|
+
"prevId": "83aff8bf-5357-4a7d-a572-042fd77858fb",
|
|
6
|
+
"tables": {
|
|
7
|
+
"plans": {
|
|
8
|
+
"name": "plans",
|
|
9
|
+
"columns": {
|
|
10
|
+
"id": {
|
|
11
|
+
"name": "id",
|
|
12
|
+
"type": "text",
|
|
13
|
+
"primaryKey": true,
|
|
14
|
+
"notNull": true,
|
|
15
|
+
"autoincrement": false
|
|
16
|
+
},
|
|
17
|
+
"slug": {
|
|
18
|
+
"name": "slug",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true,
|
|
22
|
+
"autoincrement": false
|
|
23
|
+
},
|
|
24
|
+
"title": {
|
|
25
|
+
"name": "title",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true,
|
|
29
|
+
"autoincrement": false
|
|
30
|
+
},
|
|
31
|
+
"description": {
|
|
32
|
+
"name": "description",
|
|
33
|
+
"type": "text",
|
|
34
|
+
"primaryKey": false,
|
|
35
|
+
"notNull": false,
|
|
36
|
+
"autoincrement": false
|
|
37
|
+
},
|
|
38
|
+
"markdown_content": {
|
|
39
|
+
"name": "markdown_content",
|
|
40
|
+
"type": "text",
|
|
41
|
+
"primaryKey": false,
|
|
42
|
+
"notNull": false,
|
|
43
|
+
"autoincrement": false
|
|
44
|
+
},
|
|
45
|
+
"status": {
|
|
46
|
+
"name": "status",
|
|
47
|
+
"type": "text",
|
|
48
|
+
"primaryKey": false,
|
|
49
|
+
"notNull": true,
|
|
50
|
+
"autoincrement": false
|
|
51
|
+
},
|
|
52
|
+
"is_active": {
|
|
53
|
+
"name": "is_active",
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"primaryKey": false,
|
|
56
|
+
"notNull": true,
|
|
57
|
+
"autoincrement": false,
|
|
58
|
+
"default": false
|
|
59
|
+
},
|
|
60
|
+
"created_at": {
|
|
61
|
+
"name": "created_at",
|
|
62
|
+
"type": "text",
|
|
63
|
+
"primaryKey": false,
|
|
64
|
+
"notNull": true,
|
|
65
|
+
"autoincrement": false
|
|
66
|
+
},
|
|
67
|
+
"updated_at": {
|
|
68
|
+
"name": "updated_at",
|
|
69
|
+
"type": "text",
|
|
70
|
+
"primaryKey": false,
|
|
71
|
+
"notNull": true,
|
|
72
|
+
"autoincrement": false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"indexes": {
|
|
76
|
+
"plans_slug_unique": {
|
|
77
|
+
"name": "plans_slug_unique",
|
|
78
|
+
"columns": [
|
|
79
|
+
"slug"
|
|
80
|
+
],
|
|
81
|
+
"isUnique": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"foreignKeys": {},
|
|
85
|
+
"compositePrimaryKeys": {},
|
|
86
|
+
"uniqueConstraints": {},
|
|
87
|
+
"checkConstraints": {}
|
|
88
|
+
},
|
|
89
|
+
"sprints": {
|
|
90
|
+
"name": "sprints",
|
|
91
|
+
"columns": {
|
|
92
|
+
"id": {
|
|
93
|
+
"name": "id",
|
|
94
|
+
"type": "text",
|
|
95
|
+
"primaryKey": true,
|
|
96
|
+
"notNull": true,
|
|
97
|
+
"autoincrement": false
|
|
98
|
+
},
|
|
99
|
+
"slug": {
|
|
100
|
+
"name": "slug",
|
|
101
|
+
"type": "text",
|
|
102
|
+
"primaryKey": false,
|
|
103
|
+
"notNull": true,
|
|
104
|
+
"autoincrement": false
|
|
105
|
+
},
|
|
106
|
+
"plan_id": {
|
|
107
|
+
"name": "plan_id",
|
|
108
|
+
"type": "text",
|
|
109
|
+
"primaryKey": false,
|
|
110
|
+
"notNull": true,
|
|
111
|
+
"autoincrement": false
|
|
112
|
+
},
|
|
113
|
+
"name": {
|
|
114
|
+
"name": "name",
|
|
115
|
+
"type": "text",
|
|
116
|
+
"primaryKey": false,
|
|
117
|
+
"notNull": true,
|
|
118
|
+
"autoincrement": false
|
|
119
|
+
},
|
|
120
|
+
"goal": {
|
|
121
|
+
"name": "goal",
|
|
122
|
+
"type": "text",
|
|
123
|
+
"primaryKey": false,
|
|
124
|
+
"notNull": true,
|
|
125
|
+
"autoincrement": false
|
|
126
|
+
},
|
|
127
|
+
"markdown_content": {
|
|
128
|
+
"name": "markdown_content",
|
|
129
|
+
"type": "text",
|
|
130
|
+
"primaryKey": false,
|
|
131
|
+
"notNull": false,
|
|
132
|
+
"autoincrement": false
|
|
133
|
+
},
|
|
134
|
+
"status": {
|
|
135
|
+
"name": "status",
|
|
136
|
+
"type": "text",
|
|
137
|
+
"primaryKey": false,
|
|
138
|
+
"notNull": true,
|
|
139
|
+
"autoincrement": false
|
|
140
|
+
},
|
|
141
|
+
"order": {
|
|
142
|
+
"name": "order",
|
|
143
|
+
"type": "integer",
|
|
144
|
+
"primaryKey": false,
|
|
145
|
+
"notNull": true,
|
|
146
|
+
"autoincrement": false,
|
|
147
|
+
"default": 0
|
|
148
|
+
},
|
|
149
|
+
"start_date": {
|
|
150
|
+
"name": "start_date",
|
|
151
|
+
"type": "text",
|
|
152
|
+
"primaryKey": false,
|
|
153
|
+
"notNull": false,
|
|
154
|
+
"autoincrement": false
|
|
155
|
+
},
|
|
156
|
+
"end_date": {
|
|
157
|
+
"name": "end_date",
|
|
158
|
+
"type": "text",
|
|
159
|
+
"primaryKey": false,
|
|
160
|
+
"notNull": false,
|
|
161
|
+
"autoincrement": false
|
|
162
|
+
},
|
|
163
|
+
"created_at": {
|
|
164
|
+
"name": "created_at",
|
|
165
|
+
"type": "text",
|
|
166
|
+
"primaryKey": false,
|
|
167
|
+
"notNull": true,
|
|
168
|
+
"autoincrement": false
|
|
169
|
+
},
|
|
170
|
+
"updated_at": {
|
|
171
|
+
"name": "updated_at",
|
|
172
|
+
"type": "text",
|
|
173
|
+
"primaryKey": false,
|
|
174
|
+
"notNull": true,
|
|
175
|
+
"autoincrement": false
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"indexes": {
|
|
179
|
+
"sprints_plan_id_slug_unique": {
|
|
180
|
+
"name": "sprints_plan_id_slug_unique",
|
|
181
|
+
"columns": [
|
|
182
|
+
"plan_id",
|
|
183
|
+
"slug"
|
|
184
|
+
],
|
|
185
|
+
"isUnique": true
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"foreignKeys": {
|
|
189
|
+
"sprints_plan_id_plans_id_fk": {
|
|
190
|
+
"name": "sprints_plan_id_plans_id_fk",
|
|
191
|
+
"tableFrom": "sprints",
|
|
192
|
+
"tableTo": "plans",
|
|
193
|
+
"columnsFrom": [
|
|
194
|
+
"plan_id"
|
|
195
|
+
],
|
|
196
|
+
"columnsTo": [
|
|
197
|
+
"id"
|
|
198
|
+
],
|
|
199
|
+
"onDelete": "cascade",
|
|
200
|
+
"onUpdate": "no action"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"compositePrimaryKeys": {},
|
|
204
|
+
"uniqueConstraints": {},
|
|
205
|
+
"checkConstraints": {}
|
|
206
|
+
},
|
|
207
|
+
"task_dependencies": {
|
|
208
|
+
"name": "task_dependencies",
|
|
209
|
+
"columns": {
|
|
210
|
+
"task_id": {
|
|
211
|
+
"name": "task_id",
|
|
212
|
+
"type": "text",
|
|
213
|
+
"primaryKey": false,
|
|
214
|
+
"notNull": true,
|
|
215
|
+
"autoincrement": false
|
|
216
|
+
},
|
|
217
|
+
"depends_on_task_id": {
|
|
218
|
+
"name": "depends_on_task_id",
|
|
219
|
+
"type": "text",
|
|
220
|
+
"primaryKey": false,
|
|
221
|
+
"notNull": true,
|
|
222
|
+
"autoincrement": false
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"indexes": {},
|
|
226
|
+
"foreignKeys": {
|
|
227
|
+
"task_dependencies_task_id_tasks_id_fk": {
|
|
228
|
+
"name": "task_dependencies_task_id_tasks_id_fk",
|
|
229
|
+
"tableFrom": "task_dependencies",
|
|
230
|
+
"tableTo": "tasks",
|
|
231
|
+
"columnsFrom": [
|
|
232
|
+
"task_id"
|
|
233
|
+
],
|
|
234
|
+
"columnsTo": [
|
|
235
|
+
"id"
|
|
236
|
+
],
|
|
237
|
+
"onDelete": "cascade",
|
|
238
|
+
"onUpdate": "no action"
|
|
239
|
+
},
|
|
240
|
+
"task_dependencies_depends_on_task_id_tasks_id_fk": {
|
|
241
|
+
"name": "task_dependencies_depends_on_task_id_tasks_id_fk",
|
|
242
|
+
"tableFrom": "task_dependencies",
|
|
243
|
+
"tableTo": "tasks",
|
|
244
|
+
"columnsFrom": [
|
|
245
|
+
"depends_on_task_id"
|
|
246
|
+
],
|
|
247
|
+
"columnsTo": [
|
|
248
|
+
"id"
|
|
249
|
+
],
|
|
250
|
+
"onDelete": "cascade",
|
|
251
|
+
"onUpdate": "no action"
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"compositePrimaryKeys": {
|
|
255
|
+
"task_dependencies_task_id_depends_on_task_id_pk": {
|
|
256
|
+
"columns": [
|
|
257
|
+
"task_id",
|
|
258
|
+
"depends_on_task_id"
|
|
259
|
+
],
|
|
260
|
+
"name": "task_dependencies_task_id_depends_on_task_id_pk"
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"uniqueConstraints": {},
|
|
264
|
+
"checkConstraints": {}
|
|
265
|
+
},
|
|
266
|
+
"tasks": {
|
|
267
|
+
"name": "tasks",
|
|
268
|
+
"columns": {
|
|
269
|
+
"id": {
|
|
270
|
+
"name": "id",
|
|
271
|
+
"type": "text",
|
|
272
|
+
"primaryKey": true,
|
|
273
|
+
"notNull": true,
|
|
274
|
+
"autoincrement": false
|
|
275
|
+
},
|
|
276
|
+
"sprint_id": {
|
|
277
|
+
"name": "sprint_id",
|
|
278
|
+
"type": "text",
|
|
279
|
+
"primaryKey": false,
|
|
280
|
+
"notNull": true,
|
|
281
|
+
"autoincrement": false
|
|
282
|
+
},
|
|
283
|
+
"title": {
|
|
284
|
+
"name": "title",
|
|
285
|
+
"type": "text",
|
|
286
|
+
"primaryKey": false,
|
|
287
|
+
"notNull": true,
|
|
288
|
+
"autoincrement": false
|
|
289
|
+
},
|
|
290
|
+
"description": {
|
|
291
|
+
"name": "description",
|
|
292
|
+
"type": "text",
|
|
293
|
+
"primaryKey": false,
|
|
294
|
+
"notNull": false,
|
|
295
|
+
"autoincrement": false
|
|
296
|
+
},
|
|
297
|
+
"status": {
|
|
298
|
+
"name": "status",
|
|
299
|
+
"type": "text",
|
|
300
|
+
"primaryKey": false,
|
|
301
|
+
"notNull": true,
|
|
302
|
+
"autoincrement": false
|
|
303
|
+
},
|
|
304
|
+
"priority": {
|
|
305
|
+
"name": "priority",
|
|
306
|
+
"type": "text",
|
|
307
|
+
"primaryKey": false,
|
|
308
|
+
"notNull": true,
|
|
309
|
+
"autoincrement": false
|
|
310
|
+
},
|
|
311
|
+
"order": {
|
|
312
|
+
"name": "order",
|
|
313
|
+
"type": "integer",
|
|
314
|
+
"primaryKey": false,
|
|
315
|
+
"notNull": true,
|
|
316
|
+
"autoincrement": false,
|
|
317
|
+
"default": 0
|
|
318
|
+
},
|
|
319
|
+
"assignee": {
|
|
320
|
+
"name": "assignee",
|
|
321
|
+
"type": "text",
|
|
322
|
+
"primaryKey": false,
|
|
323
|
+
"notNull": false,
|
|
324
|
+
"autoincrement": false
|
|
325
|
+
},
|
|
326
|
+
"tags": {
|
|
327
|
+
"name": "tags",
|
|
328
|
+
"type": "text",
|
|
329
|
+
"primaryKey": false,
|
|
330
|
+
"notNull": false,
|
|
331
|
+
"autoincrement": false
|
|
332
|
+
},
|
|
333
|
+
"created_at": {
|
|
334
|
+
"name": "created_at",
|
|
335
|
+
"type": "text",
|
|
336
|
+
"primaryKey": false,
|
|
337
|
+
"notNull": true,
|
|
338
|
+
"autoincrement": false
|
|
339
|
+
},
|
|
340
|
+
"updated_at": {
|
|
341
|
+
"name": "updated_at",
|
|
342
|
+
"type": "text",
|
|
343
|
+
"primaryKey": false,
|
|
344
|
+
"notNull": true,
|
|
345
|
+
"autoincrement": false
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"indexes": {},
|
|
349
|
+
"foreignKeys": {
|
|
350
|
+
"tasks_sprint_id_sprints_id_fk": {
|
|
351
|
+
"name": "tasks_sprint_id_sprints_id_fk",
|
|
352
|
+
"tableFrom": "tasks",
|
|
353
|
+
"tableTo": "sprints",
|
|
354
|
+
"columnsFrom": [
|
|
355
|
+
"sprint_id"
|
|
356
|
+
],
|
|
357
|
+
"columnsTo": [
|
|
358
|
+
"id"
|
|
359
|
+
],
|
|
360
|
+
"onDelete": "cascade",
|
|
361
|
+
"onUpdate": "no action"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"compositePrimaryKeys": {},
|
|
365
|
+
"uniqueConstraints": {},
|
|
366
|
+
"checkConstraints": {}
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
"views": {},
|
|
370
|
+
"enums": {},
|
|
371
|
+
"_meta": {
|
|
372
|
+
"schemas": {},
|
|
373
|
+
"tables": {},
|
|
374
|
+
"columns": {}
|
|
375
|
+
},
|
|
376
|
+
"internal": {
|
|
377
|
+
"indexes": {}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "7",
|
|
3
|
+
"dialect": "sqlite",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"idx": 0,
|
|
7
|
+
"version": "6",
|
|
8
|
+
"when": 1774083318369,
|
|
9
|
+
"tag": "0000_fresh_roxanne_simpson",
|
|
10
|
+
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "6",
|
|
15
|
+
"when": 1774179892580,
|
|
16
|
+
"tag": "0001_sprint_markdown_content",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "6",
|
|
22
|
+
"when": 1775200000000,
|
|
23
|
+
"tag": "0002_task_touched_files",
|
|
24
|
+
"breakpoints": true
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package: @sprintdock/backend
|
|
3
|
+
* author: vikash sharma
|
|
4
|
+
* description: Drizzle Kit configuration for SQLite schema and migrations.
|
|
5
|
+
*/
|
|
6
|
+
import { defineConfig, type Config } from "drizzle-kit";
|
|
7
|
+
|
|
8
|
+
const config: Config = defineConfig({
|
|
9
|
+
dialect: "sqlite",
|
|
10
|
+
schema: "./src/db/schema/index.ts",
|
|
11
|
+
out: "./drizzle"
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sprintdock/backend",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
16
|
+
"better-sqlite3": "^12.8.0",
|
|
17
|
+
"drizzle-orm": "^0.38.4",
|
|
18
|
+
"express": "^5.1.0",
|
|
19
|
+
"zod": "^4.1.11",
|
|
20
|
+
"@sprintdock/env-manager": "0.2.2",
|
|
21
|
+
"@sprintdock/shared": "0.2.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
25
|
+
"@types/express": "^5.0.6",
|
|
26
|
+
"@types/sql.js": "^1.4.10",
|
|
27
|
+
"@types/supertest": "^7.2.0",
|
|
28
|
+
"drizzle-kit": "^0.30.5",
|
|
29
|
+
"sql.js": "^1.12.0",
|
|
30
|
+
"supertest": "^7.2.2",
|
|
31
|
+
"tsx": "^4.21.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts --dts --format esm --clean",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"test": "node --import tsx --test tests/**/*.test.ts",
|
|
37
|
+
"db:generate": "drizzle-kit generate",
|
|
38
|
+
"clean": "rimraf dist"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package: @sprintdock/backend
|
|
3
|
+
* author: vikash sharma
|
|
4
|
+
* description: Factory wiring application services and domain services for a repository set.
|
|
5
|
+
*/
|
|
6
|
+
import type { RepositorySet } from "../infrastructure/repositories/repository-factory.js";
|
|
7
|
+
import { PlanDomainService } from "../domain/services/plan-domain.service.js";
|
|
8
|
+
import { SprintDomainService } from "../domain/services/sprint-domain.service.js";
|
|
9
|
+
import { TaskDomainService } from "../domain/services/task-domain.service.js";
|
|
10
|
+
import { PlanService } from "./plan.service.js";
|
|
11
|
+
import { SprintService } from "./sprint.service.js";
|
|
12
|
+
import { TaskService } from "./task.service.js";
|
|
13
|
+
|
|
14
|
+
/** Application-layer services exposed to protocol adapters. */
|
|
15
|
+
export interface ServiceSet {
|
|
16
|
+
planService: PlanService;
|
|
17
|
+
sprintService: SprintService;
|
|
18
|
+
taskService: TaskService;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds domain services and wires application services for the given repositories.
|
|
23
|
+
*/
|
|
24
|
+
export function createApplicationServices(repos: RepositorySet): ServiceSet {
|
|
25
|
+
const planDomain = new PlanDomainService();
|
|
26
|
+
const sprintDomain = new SprintDomainService();
|
|
27
|
+
const taskDomain = new TaskDomainService();
|
|
28
|
+
|
|
29
|
+
const planService = new PlanService(repos.plans, planDomain);
|
|
30
|
+
const sprintService = new SprintService(
|
|
31
|
+
repos.sprints,
|
|
32
|
+
repos.plans,
|
|
33
|
+
planService,
|
|
34
|
+
sprintDomain
|
|
35
|
+
);
|
|
36
|
+
const taskService = new TaskService(
|
|
37
|
+
repos.tasks,
|
|
38
|
+
repos.sprints,
|
|
39
|
+
planService,
|
|
40
|
+
taskDomain
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return { planService, sprintService, taskService };
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package: @sprintdock/backend
|
|
3
|
+
* author: vikash sharma
|
|
4
|
+
* description: API payload for per-sprint task status analytics within a plan.
|
|
5
|
+
*/
|
|
6
|
+
import type { Sprint } from "../../domain/entities/sprint.entity";
|
|
7
|
+
|
|
8
|
+
/** Counts keyed by task status for JSON responses. */
|
|
9
|
+
export interface TaskStatusCountsDto {
|
|
10
|
+
todo: number;
|
|
11
|
+
in_progress: number;
|
|
12
|
+
blocked: number;
|
|
13
|
+
done: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SprintTaskAnalyticsDto {
|
|
17
|
+
sprint: Sprint;
|
|
18
|
+
tasksByStatus: TaskStatusCountsDto;
|
|
19
|
+
totalTasks: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PlanSprintTaskAnalyticsDto {
|
|
23
|
+
planId: string;
|
|
24
|
+
sprints: SprintTaskAnalyticsDto[];
|
|
25
|
+
rollup: {
|
|
26
|
+
tasksByStatus: TaskStatusCountsDto;
|
|
27
|
+
totalTasks: number;
|
|
28
|
+
sprintCount: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package: @sprintdock/backend
|
|
3
|
+
* author: vikash sharma
|
|
4
|
+
* description: Application service orchestrating plan operations and resolution.
|
|
5
|
+
*/
|
|
6
|
+
import { NotFoundError } from "../errors/backend-errors.js";
|
|
7
|
+
import type {
|
|
8
|
+
CreatePlanInput,
|
|
9
|
+
Plan,
|
|
10
|
+
UpdatePlanInput
|
|
11
|
+
} from "../domain/entities/plan.entity";
|
|
12
|
+
import type { PlanRepository } from "../domain/repositories/plan.repository";
|
|
13
|
+
import type { PlanDomainService } from "../domain/services/plan-domain.service";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Coordinates plan persistence with domain transition rules.
|
|
17
|
+
*/
|
|
18
|
+
export class PlanService {
|
|
19
|
+
public constructor(
|
|
20
|
+
private readonly planRepo: PlanRepository,
|
|
21
|
+
private readonly planDomain: PlanDomainService
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/** @returns All plans in creation order. */
|
|
25
|
+
public async listPlans(): Promise<Plan[]> {
|
|
26
|
+
return this.planRepo.list();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a plan and activates it when it is the only plan in storage.
|
|
31
|
+
*/
|
|
32
|
+
public async createPlan(input: CreatePlanInput): Promise<Plan> {
|
|
33
|
+
const beforeCount = (await this.planRepo.list()).length;
|
|
34
|
+
const plan = await this.planRepo.create(input);
|
|
35
|
+
if (beforeCount === 0) {
|
|
36
|
+
await this.planRepo.setActive(plan.id);
|
|
37
|
+
const refreshed = await this.planRepo.findById(plan.id);
|
|
38
|
+
if (!refreshed) {
|
|
39
|
+
throw new NotFoundError("Plan", plan.id);
|
|
40
|
+
}
|
|
41
|
+
return refreshed;
|
|
42
|
+
}
|
|
43
|
+
return plan;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Loads a plan by id, then by slug.
|
|
48
|
+
*
|
|
49
|
+
* @throws NotFoundError when neither matches.
|
|
50
|
+
*/
|
|
51
|
+
public async getPlan(idOrSlug: string): Promise<Plan> {
|
|
52
|
+
const byId = await this.planRepo.findById(idOrSlug);
|
|
53
|
+
if (byId) {
|
|
54
|
+
return byId;
|
|
55
|
+
}
|
|
56
|
+
const bySlug = await this.planRepo.findBySlug(idOrSlug);
|
|
57
|
+
if (bySlug) {
|
|
58
|
+
return bySlug;
|
|
59
|
+
}
|
|
60
|
+
throw new NotFoundError("Plan", idOrSlug);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @throws NotFoundError when no plan is marked active.
|
|
65
|
+
*/
|
|
66
|
+
public async getActivePlan(): Promise<Plan> {
|
|
67
|
+
const active = await this.planRepo.findActive();
|
|
68
|
+
if (!active) {
|
|
69
|
+
throw new NotFoundError("Active plan", "none");
|
|
70
|
+
}
|
|
71
|
+
return active;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Marks the resolved plan as the sole active plan.
|
|
76
|
+
*/
|
|
77
|
+
public async setActivePlan(idOrSlug: string): Promise<void> {
|
|
78
|
+
const plan = await this.getPlan(idOrSlug);
|
|
79
|
+
await this.planRepo.setActive(plan.id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Updates fields; validates plan status transitions through the domain service.
|
|
84
|
+
*/
|
|
85
|
+
public async updatePlan(
|
|
86
|
+
idOrSlug: string,
|
|
87
|
+
input: UpdatePlanInput
|
|
88
|
+
): Promise<Plan> {
|
|
89
|
+
const existing = await this.getPlan(idOrSlug);
|
|
90
|
+
if (input.status !== undefined && input.status !== existing.status) {
|
|
91
|
+
this.planDomain.validateTransition(existing.status, input.status);
|
|
92
|
+
}
|
|
93
|
+
const updated = await this.planRepo.update(existing.id, input);
|
|
94
|
+
if (!updated) {
|
|
95
|
+
throw new NotFoundError("Plan", existing.id);
|
|
96
|
+
}
|
|
97
|
+
return updated;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Deletes a plan by id.
|
|
102
|
+
*
|
|
103
|
+
* @throws NotFoundError when the plan does not exist.
|
|
104
|
+
*/
|
|
105
|
+
public async deletePlan(id: string): Promise<void> {
|
|
106
|
+
const deleted = await this.planRepo.delete(id);
|
|
107
|
+
if (!deleted) {
|
|
108
|
+
throw new NotFoundError("Plan", id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolves the current plan from an optional id or slug, otherwise the active plan.
|
|
114
|
+
*
|
|
115
|
+
* @throws NotFoundError when lookup fails or no active plan exists.
|
|
116
|
+
*/
|
|
117
|
+
public async resolvePlan(idOrSlug?: string): Promise<Plan> {
|
|
118
|
+
if (idOrSlug === undefined || idOrSlug === "") {
|
|
119
|
+
return this.getActivePlan();
|
|
120
|
+
}
|
|
121
|
+
return this.getPlan(idOrSlug);
|
|
122
|
+
}
|
|
123
|
+
}
|