kiro-spec-engine 1.2.3 → 1.4.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 +135 -0
- package/README.md +239 -213
- package/README.zh.md +0 -330
- package/bin/kiro-spec-engine.js +62 -0
- package/docs/README.md +223 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/command-reference.md +252 -0
- package/docs/cross-tool-guide.md +554 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +696 -0
- package/docs/integration-modes.md +525 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/manual-workflows-guide.md +417 -0
- package/docs/quick-start-with-ai-tools.md +374 -0
- package/docs/quick-start.md +711 -0
- package/docs/spec-workflow.md +453 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/tools/claude-guide.md +653 -0
- package/docs/tools/cursor-guide.md +705 -0
- package/docs/tools/generic-guide.md +445 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +444 -0
- package/docs/tools/windsurf-guide.md +390 -0
- package/docs/troubleshooting.md +795 -0
- package/docs/zh/README.md +275 -0
- package/docs/zh/quick-start.md +711 -0
- package/docs/zh/tools/claude-guide.md +348 -0
- package/docs/zh/tools/cursor-guide.md +280 -0
- package/docs/zh/tools/generic-guide.md +498 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +448 -0
- package/docs/zh/tools/windsurf-guide.md +377 -0
- package/lib/adoption/detection-engine.js +14 -4
- package/lib/commands/adopt.js +117 -3
- package/lib/commands/context.js +99 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/status.js +225 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/watch.js +569 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/task-claimer.js +430 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/package.json +3 -1
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
# RESTful API with Authentication - Design
|
|
2
|
+
|
|
3
|
+
> Technical design and architecture for the task management API
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Version**: 1.0.0
|
|
8
|
+
**Last Updated**: 2026-01-23
|
|
9
|
+
**Spec Type**: Example - API Feature
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
This design document specifies the technical implementation of a RESTful task management API with JWT authentication. The architecture follows Express.js best practices with a layered approach: routes → controllers → services → repositories.
|
|
16
|
+
|
|
17
|
+
**Architecture Pattern:** Layered Architecture (MVC-inspired)
|
|
18
|
+
**Technology Stack:** Node.js + Express + PostgreSQL + JWT
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Architecture Diagram
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
graph TD
|
|
26
|
+
Client[Client Application] --> Router[Express Router]
|
|
27
|
+
Router --> AuthMW[Auth Middleware]
|
|
28
|
+
AuthMW --> Controller[Controllers]
|
|
29
|
+
Controller --> Service[Services]
|
|
30
|
+
Service --> Repository[Repositories]
|
|
31
|
+
Repository --> DB[(PostgreSQL)]
|
|
32
|
+
|
|
33
|
+
Controller --> Validator[Validators]
|
|
34
|
+
Controller --> ErrorHandler[Error Handler]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## System Architecture
|
|
40
|
+
|
|
41
|
+
### Layer Responsibilities
|
|
42
|
+
|
|
43
|
+
**1. Routes Layer** (`routes/`)
|
|
44
|
+
- Define API endpoints
|
|
45
|
+
- Map HTTP methods to controller functions
|
|
46
|
+
- Apply middleware (authentication, validation)
|
|
47
|
+
|
|
48
|
+
**2. Controllers Layer** (`controllers/`)
|
|
49
|
+
- Handle HTTP requests and responses
|
|
50
|
+
- Validate input using express-validator
|
|
51
|
+
- Call service layer for business logic
|
|
52
|
+
- Format responses
|
|
53
|
+
|
|
54
|
+
**3. Services Layer** (`services/`)
|
|
55
|
+
- Implement business logic
|
|
56
|
+
- Coordinate between multiple repositories
|
|
57
|
+
- Handle transactions
|
|
58
|
+
- Throw domain-specific errors
|
|
59
|
+
|
|
60
|
+
**4. Repositories Layer** (`repositories/`)
|
|
61
|
+
- Database access layer
|
|
62
|
+
- Execute SQL queries
|
|
63
|
+
- Map database rows to domain objects
|
|
64
|
+
- Handle database errors
|
|
65
|
+
|
|
66
|
+
**5. Middleware Layer** (`middleware/`)
|
|
67
|
+
- Authentication (JWT verification)
|
|
68
|
+
- Error handling
|
|
69
|
+
- Request logging
|
|
70
|
+
- Rate limiting
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Component Design
|
|
75
|
+
|
|
76
|
+
### 1. Authentication System
|
|
77
|
+
|
|
78
|
+
#### AuthController
|
|
79
|
+
**File:** `src/controllers/auth.controller.js`
|
|
80
|
+
|
|
81
|
+
**Responsibilities:**
|
|
82
|
+
- Handle registration and login requests
|
|
83
|
+
- Validate input data
|
|
84
|
+
- Return JWT tokens
|
|
85
|
+
|
|
86
|
+
**Methods:**
|
|
87
|
+
```javascript
|
|
88
|
+
class AuthController {
|
|
89
|
+
/**
|
|
90
|
+
* Register new user
|
|
91
|
+
* POST /api/v1/auth/register
|
|
92
|
+
*/
|
|
93
|
+
async register(req, res, next) {
|
|
94
|
+
// 1. Validate input (email, password)
|
|
95
|
+
// 2. Call authService.register()
|
|
96
|
+
// 3. Return success response
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Login user
|
|
101
|
+
* POST /api/v1/auth/login
|
|
102
|
+
*/
|
|
103
|
+
async login(req, res, next) {
|
|
104
|
+
// 1. Validate input (email, password)
|
|
105
|
+
// 2. Call authService.login()
|
|
106
|
+
// 3. Return JWT token
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
#### AuthService
|
|
114
|
+
**File:** `src/services/auth.service.js`
|
|
115
|
+
|
|
116
|
+
**Responsibilities:**
|
|
117
|
+
- User registration logic
|
|
118
|
+
- User authentication logic
|
|
119
|
+
- Password hashing and verification
|
|
120
|
+
- JWT token generation
|
|
121
|
+
|
|
122
|
+
**Methods:**
|
|
123
|
+
```javascript
|
|
124
|
+
class AuthService {
|
|
125
|
+
/**
|
|
126
|
+
* Register new user
|
|
127
|
+
* @param {string} email
|
|
128
|
+
* @param {string} password
|
|
129
|
+
* @returns {Promise<User>}
|
|
130
|
+
* @throws {ConflictError} if email exists
|
|
131
|
+
*/
|
|
132
|
+
async register(email, password) {
|
|
133
|
+
// 1. Check if email exists
|
|
134
|
+
// 2. Hash password with bcrypt
|
|
135
|
+
// 3. Create user via userRepository
|
|
136
|
+
// 4. Return user (without password)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Authenticate user and generate token
|
|
141
|
+
* @param {string} email
|
|
142
|
+
* @param {string} password
|
|
143
|
+
* @returns {Promise<{token: string, user: User}>}
|
|
144
|
+
* @throws {UnauthorizedError} if credentials invalid
|
|
145
|
+
*/
|
|
146
|
+
async login(email, password) {
|
|
147
|
+
// 1. Find user by email
|
|
148
|
+
// 2. Verify password with bcrypt
|
|
149
|
+
// 3. Generate JWT token
|
|
150
|
+
// 4. Return token and user
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generate JWT token
|
|
155
|
+
* @param {User} user
|
|
156
|
+
* @returns {string} JWT token
|
|
157
|
+
*/
|
|
158
|
+
generateToken(user) {
|
|
159
|
+
// Use jsonwebtoken to create token
|
|
160
|
+
// Payload: { userId, email }
|
|
161
|
+
// Expiration: 24 hours
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
#### AuthMiddleware
|
|
169
|
+
**File:** `src/middleware/auth.middleware.js`
|
|
170
|
+
|
|
171
|
+
**Responsibilities:**
|
|
172
|
+
- Verify JWT tokens
|
|
173
|
+
- Extract user from token
|
|
174
|
+
- Attach user to request object
|
|
175
|
+
|
|
176
|
+
**Implementation:**
|
|
177
|
+
```javascript
|
|
178
|
+
const jwt = require('jsonwebtoken');
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Verify JWT token and attach user to request
|
|
182
|
+
*/
|
|
183
|
+
async function authenticate(req, res, next) {
|
|
184
|
+
try {
|
|
185
|
+
// 1. Extract token from Authorization header
|
|
186
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
187
|
+
|
|
188
|
+
if (!token) {
|
|
189
|
+
throw new UnauthorizedError('No token provided');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 2. Verify token
|
|
193
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
194
|
+
|
|
195
|
+
// 3. Attach user to request
|
|
196
|
+
req.user = {
|
|
197
|
+
id: decoded.userId,
|
|
198
|
+
email: decoded.email
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
next();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
next(new UnauthorizedError('Invalid token'));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### 2. Task Management System
|
|
211
|
+
|
|
212
|
+
#### TaskController
|
|
213
|
+
**File:** `src/controllers/task.controller.js`
|
|
214
|
+
|
|
215
|
+
**Responsibilities:**
|
|
216
|
+
- Handle task CRUD requests
|
|
217
|
+
- Validate input data
|
|
218
|
+
- Enforce authorization
|
|
219
|
+
|
|
220
|
+
**Methods:**
|
|
221
|
+
```javascript
|
|
222
|
+
class TaskController {
|
|
223
|
+
/**
|
|
224
|
+
* Create new task
|
|
225
|
+
* POST /api/v1/tasks
|
|
226
|
+
*/
|
|
227
|
+
async create(req, res, next) {
|
|
228
|
+
// 1. Validate input (title, description, status)
|
|
229
|
+
// 2. Call taskService.create() with req.user.id
|
|
230
|
+
// 3. Return created task
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* List all user's tasks
|
|
235
|
+
* GET /api/v1/tasks
|
|
236
|
+
*/
|
|
237
|
+
async list(req, res, next) {
|
|
238
|
+
// 1. Call taskService.findByUserId(req.user.id)
|
|
239
|
+
// 2. Return tasks array
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get single task
|
|
244
|
+
* GET /api/v1/tasks/:id
|
|
245
|
+
*/
|
|
246
|
+
async getById(req, res, next) {
|
|
247
|
+
// 1. Call taskService.findById(req.params.id)
|
|
248
|
+
// 2. Check if task belongs to req.user.id
|
|
249
|
+
// 3. Return task or 403 Forbidden
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Update task
|
|
254
|
+
* PUT /api/v1/tasks/:id
|
|
255
|
+
*/
|
|
256
|
+
async update(req, res, next) {
|
|
257
|
+
// 1. Validate input
|
|
258
|
+
// 2. Check task ownership
|
|
259
|
+
// 3. Call taskService.update()
|
|
260
|
+
// 4. Return updated task
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Delete task
|
|
265
|
+
* DELETE /api/v1/tasks/:id
|
|
266
|
+
*/
|
|
267
|
+
async delete(req, res, next) {
|
|
268
|
+
// 1. Check task ownership
|
|
269
|
+
// 2. Call taskService.delete()
|
|
270
|
+
// 3. Return 204 No Content
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
#### TaskService
|
|
278
|
+
**File:** `src/services/task.service.js`
|
|
279
|
+
|
|
280
|
+
**Responsibilities:**
|
|
281
|
+
- Task business logic
|
|
282
|
+
- Coordinate with taskRepository
|
|
283
|
+
- Validate business rules
|
|
284
|
+
|
|
285
|
+
**Methods:**
|
|
286
|
+
```javascript
|
|
287
|
+
class TaskService {
|
|
288
|
+
/**
|
|
289
|
+
* Create new task
|
|
290
|
+
* @param {string} userId
|
|
291
|
+
* @param {Object} taskData
|
|
292
|
+
* @returns {Promise<Task>}
|
|
293
|
+
*/
|
|
294
|
+
async create(userId, taskData) {
|
|
295
|
+
// 1. Validate business rules
|
|
296
|
+
// 2. Create task via taskRepository
|
|
297
|
+
// 3. Return created task
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Find all tasks for user
|
|
302
|
+
* @param {string} userId
|
|
303
|
+
* @returns {Promise<Task[]>}
|
|
304
|
+
*/
|
|
305
|
+
async findByUserId(userId) {
|
|
306
|
+
// 1. Query taskRepository
|
|
307
|
+
// 2. Sort by createdAt DESC
|
|
308
|
+
// 3. Return tasks
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Find task by ID
|
|
313
|
+
* @param {string} taskId
|
|
314
|
+
* @returns {Promise<Task>}
|
|
315
|
+
* @throws {NotFoundError} if task doesn't exist
|
|
316
|
+
*/
|
|
317
|
+
async findById(taskId) {
|
|
318
|
+
// 1. Query taskRepository
|
|
319
|
+
// 2. Throw NotFoundError if not found
|
|
320
|
+
// 3. Return task
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Update task
|
|
325
|
+
* @param {string} taskId
|
|
326
|
+
* @param {Object} updates
|
|
327
|
+
* @returns {Promise<Task>}
|
|
328
|
+
*/
|
|
329
|
+
async update(taskId, updates) {
|
|
330
|
+
// 1. Find task
|
|
331
|
+
// 2. Apply updates
|
|
332
|
+
// 3. Save via taskRepository
|
|
333
|
+
// 4. Return updated task
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Delete task
|
|
338
|
+
* @param {string} taskId
|
|
339
|
+
* @returns {Promise<void>}
|
|
340
|
+
*/
|
|
341
|
+
async delete(taskId) {
|
|
342
|
+
// 1. Delete via taskRepository
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### 3. Data Access Layer
|
|
350
|
+
|
|
351
|
+
#### UserRepository
|
|
352
|
+
**File:** `src/repositories/user.repository.js`
|
|
353
|
+
|
|
354
|
+
**Responsibilities:**
|
|
355
|
+
- User database operations
|
|
356
|
+
- SQL query execution
|
|
357
|
+
|
|
358
|
+
**Methods:**
|
|
359
|
+
```javascript
|
|
360
|
+
class UserRepository {
|
|
361
|
+
/**
|
|
362
|
+
* Create new user
|
|
363
|
+
* @param {Object} userData
|
|
364
|
+
* @returns {Promise<User>}
|
|
365
|
+
*/
|
|
366
|
+
async create(userData) {
|
|
367
|
+
const query = `
|
|
368
|
+
INSERT INTO users (id, email, password, created_at, updated_at)
|
|
369
|
+
VALUES ($1, $2, $3, NOW(), NOW())
|
|
370
|
+
RETURNING id, email, created_at, updated_at
|
|
371
|
+
`;
|
|
372
|
+
// Execute query and return user
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Find user by email
|
|
377
|
+
* @param {string} email
|
|
378
|
+
* @returns {Promise<User|null>}
|
|
379
|
+
*/
|
|
380
|
+
async findByEmail(email) {
|
|
381
|
+
const query = `
|
|
382
|
+
SELECT id, email, password, created_at, updated_at
|
|
383
|
+
FROM users
|
|
384
|
+
WHERE email = $1
|
|
385
|
+
`;
|
|
386
|
+
// Execute query and return user or null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Find user by ID
|
|
391
|
+
* @param {string} userId
|
|
392
|
+
* @returns {Promise<User|null>}
|
|
393
|
+
*/
|
|
394
|
+
async findById(userId) {
|
|
395
|
+
const query = `
|
|
396
|
+
SELECT id, email, created_at, updated_at
|
|
397
|
+
FROM users
|
|
398
|
+
WHERE id = $1
|
|
399
|
+
`;
|
|
400
|
+
// Execute query and return user or null
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
#### TaskRepository
|
|
408
|
+
**File:** `src/repositories/task.repository.js`
|
|
409
|
+
|
|
410
|
+
**Responsibilities:**
|
|
411
|
+
- Task database operations
|
|
412
|
+
- SQL query execution
|
|
413
|
+
|
|
414
|
+
**Methods:**
|
|
415
|
+
```javascript
|
|
416
|
+
class TaskRepository {
|
|
417
|
+
/**
|
|
418
|
+
* Create new task
|
|
419
|
+
* @param {Object} taskData
|
|
420
|
+
* @returns {Promise<Task>}
|
|
421
|
+
*/
|
|
422
|
+
async create(taskData) {
|
|
423
|
+
const query = `
|
|
424
|
+
INSERT INTO tasks (id, user_id, title, description, status, created_at, updated_at)
|
|
425
|
+
VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
|
|
426
|
+
RETURNING *
|
|
427
|
+
`;
|
|
428
|
+
// Execute query and return task
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Find all tasks for user
|
|
433
|
+
* @param {string} userId
|
|
434
|
+
* @returns {Promise<Task[]>}
|
|
435
|
+
*/
|
|
436
|
+
async findByUserId(userId) {
|
|
437
|
+
const query = `
|
|
438
|
+
SELECT * FROM tasks
|
|
439
|
+
WHERE user_id = $1
|
|
440
|
+
ORDER BY created_at DESC
|
|
441
|
+
`;
|
|
442
|
+
// Execute query and return tasks
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Find task by ID
|
|
447
|
+
* @param {string} taskId
|
|
448
|
+
* @returns {Promise<Task|null>}
|
|
449
|
+
*/
|
|
450
|
+
async findById(taskId) {
|
|
451
|
+
const query = `
|
|
452
|
+
SELECT * FROM tasks
|
|
453
|
+
WHERE id = $1
|
|
454
|
+
`;
|
|
455
|
+
// Execute query and return task or null
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Update task
|
|
460
|
+
* @param {string} taskId
|
|
461
|
+
* @param {Object} updates
|
|
462
|
+
* @returns {Promise<Task>}
|
|
463
|
+
*/
|
|
464
|
+
async update(taskId, updates) {
|
|
465
|
+
const query = `
|
|
466
|
+
UPDATE tasks
|
|
467
|
+
SET title = $1, description = $2, status = $3, updated_at = NOW()
|
|
468
|
+
WHERE id = $4
|
|
469
|
+
RETURNING *
|
|
470
|
+
`;
|
|
471
|
+
// Execute query and return updated task
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Delete task
|
|
476
|
+
* @param {string} taskId
|
|
477
|
+
* @returns {Promise<void>}
|
|
478
|
+
*/
|
|
479
|
+
async delete(taskId) {
|
|
480
|
+
const query = `DELETE FROM tasks WHERE id = $1`;
|
|
481
|
+
// Execute query
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## API Routes
|
|
489
|
+
|
|
490
|
+
### Authentication Routes
|
|
491
|
+
**File:** `src/routes/auth.routes.js`
|
|
492
|
+
|
|
493
|
+
```javascript
|
|
494
|
+
const express = require('express');
|
|
495
|
+
const router = express.Router();
|
|
496
|
+
const authController = require('../controllers/auth.controller');
|
|
497
|
+
const { validateRegistration, validateLogin } = require('../validators/auth.validator');
|
|
498
|
+
|
|
499
|
+
// POST /api/v1/auth/register
|
|
500
|
+
router.post('/register', validateRegistration, authController.register);
|
|
501
|
+
|
|
502
|
+
// POST /api/v1/auth/login
|
|
503
|
+
router.post('/login', validateLogin, authController.login);
|
|
504
|
+
|
|
505
|
+
module.exports = router;
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
### Task Routes
|
|
511
|
+
**File:** `src/routes/task.routes.js`
|
|
512
|
+
|
|
513
|
+
```javascript
|
|
514
|
+
const express = require('express');
|
|
515
|
+
const router = express.Router();
|
|
516
|
+
const taskController = require('../controllers/task.controller');
|
|
517
|
+
const { authenticate } = require('../middleware/auth.middleware');
|
|
518
|
+
const { validateTask, validateTaskUpdate } = require('../validators/task.validator');
|
|
519
|
+
|
|
520
|
+
// All task routes require authentication
|
|
521
|
+
router.use(authenticate);
|
|
522
|
+
|
|
523
|
+
// POST /api/v1/tasks
|
|
524
|
+
router.post('/', validateTask, taskController.create);
|
|
525
|
+
|
|
526
|
+
// GET /api/v1/tasks
|
|
527
|
+
router.get('/', taskController.list);
|
|
528
|
+
|
|
529
|
+
// GET /api/v1/tasks/:id
|
|
530
|
+
router.get('/:id', taskController.getById);
|
|
531
|
+
|
|
532
|
+
// PUT /api/v1/tasks/:id
|
|
533
|
+
router.put('/:id', validateTaskUpdate, taskController.update);
|
|
534
|
+
|
|
535
|
+
// DELETE /api/v1/tasks/:id
|
|
536
|
+
router.delete('/:id', taskController.delete);
|
|
537
|
+
|
|
538
|
+
module.exports = router;
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Database Schema
|
|
544
|
+
|
|
545
|
+
### Users Table
|
|
546
|
+
```sql
|
|
547
|
+
CREATE TABLE users (
|
|
548
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
549
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
550
|
+
password VARCHAR(255) NOT NULL,
|
|
551
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
552
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Tasks Table
|
|
559
|
+
```sql
|
|
560
|
+
CREATE TABLE tasks (
|
|
561
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
562
|
+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
563
|
+
title VARCHAR(200) NOT NULL,
|
|
564
|
+
description TEXT,
|
|
565
|
+
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
566
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
567
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
568
|
+
|
|
569
|
+
CONSTRAINT chk_status CHECK (status IN ('pending', 'in_progress', 'completed'))
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
|
|
573
|
+
CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC);
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Error Handling
|
|
579
|
+
|
|
580
|
+
### Custom Error Classes
|
|
581
|
+
**File:** `src/errors/index.js`
|
|
582
|
+
|
|
583
|
+
```javascript
|
|
584
|
+
class AppError extends Error {
|
|
585
|
+
constructor(message, statusCode, errorCode) {
|
|
586
|
+
super(message);
|
|
587
|
+
this.statusCode = statusCode;
|
|
588
|
+
this.errorCode = errorCode;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
class ValidationError extends AppError {
|
|
593
|
+
constructor(message, details = {}) {
|
|
594
|
+
super(message, 400, 'INVALID_INPUT');
|
|
595
|
+
this.details = details;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
class UnauthorizedError extends AppError {
|
|
600
|
+
constructor(message = 'Unauthorized') {
|
|
601
|
+
super(message, 401, 'UNAUTHORIZED');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
class ForbiddenError extends AppError {
|
|
606
|
+
constructor(message = 'Forbidden') {
|
|
607
|
+
super(message, 403, 'FORBIDDEN');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
class NotFoundError extends AppError {
|
|
612
|
+
constructor(message = 'Resource not found') {
|
|
613
|
+
super(message, 404, 'NOT_FOUND');
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
class ConflictError extends AppError {
|
|
618
|
+
constructor(message = 'Resource already exists') {
|
|
619
|
+
super(message, 409, 'CONFLICT');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
### Error Handler Middleware
|
|
627
|
+
**File:** `src/middleware/error.middleware.js`
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
function errorHandler(err, req, res, next) {
|
|
631
|
+
// Log error
|
|
632
|
+
console.error(err);
|
|
633
|
+
|
|
634
|
+
// Handle known errors
|
|
635
|
+
if (err instanceof AppError) {
|
|
636
|
+
return res.status(err.statusCode).json({
|
|
637
|
+
error: {
|
|
638
|
+
code: err.errorCode,
|
|
639
|
+
message: err.message,
|
|
640
|
+
details: err.details || {}
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Handle unknown errors
|
|
646
|
+
res.status(500).json({
|
|
647
|
+
error: {
|
|
648
|
+
code: 'INTERNAL_ERROR',
|
|
649
|
+
message: 'An unexpected error occurred'
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Input Validation
|
|
658
|
+
|
|
659
|
+
### Auth Validators
|
|
660
|
+
**File:** `src/validators/auth.validator.js`
|
|
661
|
+
|
|
662
|
+
```javascript
|
|
663
|
+
const { body, validationResult } = require('express-validator');
|
|
664
|
+
|
|
665
|
+
const validateRegistration = [
|
|
666
|
+
body('email')
|
|
667
|
+
.isEmail().withMessage('Invalid email format')
|
|
668
|
+
.isLength({ max: 255 }).withMessage('Email too long'),
|
|
669
|
+
body('password')
|
|
670
|
+
.isLength({ min: 8, max: 128 }).withMessage('Password must be 8-128 characters'),
|
|
671
|
+
|
|
672
|
+
(req, res, next) => {
|
|
673
|
+
const errors = validationResult(req);
|
|
674
|
+
if (!errors.isEmpty()) {
|
|
675
|
+
throw new ValidationError('Validation failed', errors.mapped());
|
|
676
|
+
}
|
|
677
|
+
next();
|
|
678
|
+
}
|
|
679
|
+
];
|
|
680
|
+
|
|
681
|
+
const validateLogin = [
|
|
682
|
+
body('email').isEmail(),
|
|
683
|
+
body('password').notEmpty(),
|
|
684
|
+
|
|
685
|
+
(req, res, next) => {
|
|
686
|
+
const errors = validationResult(req);
|
|
687
|
+
if (!errors.isEmpty()) {
|
|
688
|
+
throw new ValidationError('Validation failed', errors.mapped());
|
|
689
|
+
}
|
|
690
|
+
next();
|
|
691
|
+
}
|
|
692
|
+
];
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
### Task Validators
|
|
698
|
+
**File:** `src/validators/task.validator.js`
|
|
699
|
+
|
|
700
|
+
```javascript
|
|
701
|
+
const { body, validationResult } = require('express-validator');
|
|
702
|
+
|
|
703
|
+
const validateTask = [
|
|
704
|
+
body('title')
|
|
705
|
+
.notEmpty().withMessage('Title is required')
|
|
706
|
+
.isLength({ max: 200 }).withMessage('Title too long'),
|
|
707
|
+
body('description')
|
|
708
|
+
.optional()
|
|
709
|
+
.isLength({ max: 2000 }).withMessage('Description too long'),
|
|
710
|
+
body('status')
|
|
711
|
+
.optional()
|
|
712
|
+
.isIn(['pending', 'in_progress', 'completed']).withMessage('Invalid status'),
|
|
713
|
+
|
|
714
|
+
(req, res, next) => {
|
|
715
|
+
const errors = validationResult(req);
|
|
716
|
+
if (!errors.isEmpty()) {
|
|
717
|
+
throw new ValidationError('Validation failed', errors.mapped());
|
|
718
|
+
}
|
|
719
|
+
next();
|
|
720
|
+
}
|
|
721
|
+
];
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Configuration
|
|
727
|
+
|
|
728
|
+
### Environment Variables
|
|
729
|
+
**File:** `.env`
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
# Server
|
|
733
|
+
PORT=3000
|
|
734
|
+
NODE_ENV=development
|
|
735
|
+
|
|
736
|
+
# Database
|
|
737
|
+
DB_HOST=localhost
|
|
738
|
+
DB_PORT=5432
|
|
739
|
+
DB_NAME=taskmanager
|
|
740
|
+
DB_USER=postgres
|
|
741
|
+
DB_PASSWORD=password
|
|
742
|
+
|
|
743
|
+
# JWT
|
|
744
|
+
JWT_SECRET=your-secret-key-change-in-production
|
|
745
|
+
JWT_EXPIRATION=24h
|
|
746
|
+
|
|
747
|
+
# Rate Limiting
|
|
748
|
+
RATE_LIMIT_WINDOW_MS=60000
|
|
749
|
+
RATE_LIMIT_MAX_REQUESTS=100
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## Requirements Traceability
|
|
755
|
+
|
|
756
|
+
| Requirement | Design Component | Implementation |
|
|
757
|
+
|-------------|------------------|----------------|
|
|
758
|
+
| US-1: User Registration | AuthController.register() | auth.controller.js |
|
|
759
|
+
| US-2: User Login | AuthController.login() | auth.controller.js |
|
|
760
|
+
| US-3: Create Task | TaskController.create() | task.controller.js |
|
|
761
|
+
| US-4: List Tasks | TaskController.list() | task.controller.js |
|
|
762
|
+
| US-5: Update Task | TaskController.update() | task.controller.js |
|
|
763
|
+
| US-6: Delete Task | TaskController.delete() | task.controller.js |
|
|
764
|
+
| FR-1: Authentication | AuthService + AuthMiddleware | auth.service.js, auth.middleware.js |
|
|
765
|
+
| FR-2: Task CRUD | TaskService + TaskRepository | task.service.js, task.repository.js |
|
|
766
|
+
| FR-3: Authorization | AuthMiddleware + ownership checks | auth.middleware.js |
|
|
767
|
+
| FR-4: Input Validation | express-validator | auth.validator.js, task.validator.js |
|
|
768
|
+
| FR-5: Error Handling | Error classes + middleware | errors/index.js, error.middleware.js |
|
|
769
|
+
| FR-6: API Versioning | Route prefixes | routes/index.js |
|
|
770
|
+
| NFR-2: Security | bcrypt + JWT + validation | Throughout |
|
|
771
|
+
| NFR-4: Maintainability | Layered architecture | Project structure |
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
## Technology Stack
|
|
776
|
+
|
|
777
|
+
### Core Dependencies
|
|
778
|
+
```json
|
|
779
|
+
{
|
|
780
|
+
"express": "^4.18.0",
|
|
781
|
+
"pg": "^8.11.0",
|
|
782
|
+
"jsonwebtoken": "^9.0.0",
|
|
783
|
+
"bcrypt": "^5.1.0",
|
|
784
|
+
"express-validator": "^7.0.0",
|
|
785
|
+
"dotenv": "^16.0.0",
|
|
786
|
+
"uuid": "^9.0.0"
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Dev Dependencies
|
|
791
|
+
```json
|
|
792
|
+
{
|
|
793
|
+
"jest": "^29.0.0",
|
|
794
|
+
"supertest": "^6.3.0",
|
|
795
|
+
"nodemon": "^3.0.0"
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
## Project Structure
|
|
802
|
+
|
|
803
|
+
```
|
|
804
|
+
src/
|
|
805
|
+
├── controllers/
|
|
806
|
+
│ ├── auth.controller.js
|
|
807
|
+
│ └── task.controller.js
|
|
808
|
+
├── services/
|
|
809
|
+
│ ├── auth.service.js
|
|
810
|
+
│ └── task.service.js
|
|
811
|
+
├── repositories/
|
|
812
|
+
│ ├── user.repository.js
|
|
813
|
+
│ └── task.repository.js
|
|
814
|
+
├── routes/
|
|
815
|
+
│ ├── index.js
|
|
816
|
+
│ ├── auth.routes.js
|
|
817
|
+
│ └── task.routes.js
|
|
818
|
+
├── middleware/
|
|
819
|
+
│ ├── auth.middleware.js
|
|
820
|
+
│ ├── error.middleware.js
|
|
821
|
+
│ └── rate-limit.middleware.js
|
|
822
|
+
├── validators/
|
|
823
|
+
│ ├── auth.validator.js
|
|
824
|
+
│ └── task.validator.js
|
|
825
|
+
├── errors/
|
|
826
|
+
│ └── index.js
|
|
827
|
+
├── config/
|
|
828
|
+
│ ├── database.js
|
|
829
|
+
│ └── jwt.js
|
|
830
|
+
├── utils/
|
|
831
|
+
│ └── logger.js
|
|
832
|
+
└── app.js
|
|
833
|
+
|
|
834
|
+
tests/
|
|
835
|
+
├── unit/
|
|
836
|
+
│ ├── services/
|
|
837
|
+
│ └── repositories/
|
|
838
|
+
└── integration/
|
|
839
|
+
├── auth.test.js
|
|
840
|
+
└── tasks.test.js
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Related Documentation
|
|
846
|
+
|
|
847
|
+
- [Requirements Document](requirements.md) - Feature requirements
|
|
848
|
+
- [Tasks Document](tasks.md) - Implementation plan
|
|
849
|
+
- [Spec Workflow Guide](../../spec-workflow.md) - Understanding Specs
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
**Version**: 1.0.0
|
|
854
|
+
**Last Updated**: 2026-01-23
|
|
855
|
+
**Status**: Example Spec
|