Spaces:
Sleeping
Sleeping
suhail
commited on
Commit
·
9eafd9f
1
Parent(s):
20ee17b
spoecs
Browse files- specs/001-auth-security/TESTING.md +353 -0
- specs/001-auth-security/checklists/requirements.md +44 -0
- specs/001-auth-security/contracts/auth-endpoints.yaml +345 -0
- specs/001-auth-security/contracts/jwt-schema.yaml +321 -0
- specs/001-auth-security/data-model.md +242 -0
- specs/001-auth-security/plan.md +166 -0
- specs/001-auth-security/quickstart.md +489 -0
- specs/001-auth-security/research.md +345 -0
- specs/001-auth-security/spec.md +162 -0
- specs/001-auth-security/tasks.md +237 -0
- specs/001-openai-agent-mcp-tools/checklists/requirements.md +58 -0
- specs/001-openai-agent-mcp-tools/contracts/add_task.json +69 -0
- specs/001-openai-agent-mcp-tools/contracts/complete_task.json +62 -0
- specs/001-openai-agent-mcp-tools/contracts/delete_task.json +40 -0
- specs/001-openai-agent-mcp-tools/contracts/list_tasks.json +62 -0
- specs/001-openai-agent-mcp-tools/contracts/update_task.json +97 -0
- specs/001-openai-agent-mcp-tools/data-model.md +664 -0
- specs/001-openai-agent-mcp-tools/plan.md +747 -0
- specs/001-openai-agent-mcp-tools/quickstart.md +521 -0
- specs/001-openai-agent-mcp-tools/research.md +758 -0
- specs/001-openai-agent-mcp-tools/spec.md +248 -0
- specs/001-openai-agent-mcp-tools/tasks.md +307 -0
- specs/001-task-crud/checklists/requirements.md +53 -0
- specs/001-task-crud/contracts/README.md +355 -0
- specs/001-task-crud/contracts/tasks-api.yaml +476 -0
- specs/001-task-crud/data-model.md +560 -0
- specs/001-task-crud/plan.md +515 -0
- specs/001-task-crud/quickstart.md +460 -0
- specs/001-task-crud/research.md +373 -0
- specs/001-task-crud/spec.md +202 -0
- specs/001-task-crud/tasks.md +275 -0
- specs/001-todo-ai-chatbot/contracts/chat-api.yaml +364 -0
- specs/001-todo-ai-chatbot/data-model.md +476 -0
- specs/001-todo-ai-chatbot/plan.md +386 -0
- specs/001-todo-ai-chatbot/quickstart.md +729 -0
- specs/001-todo-ai-chatbot/research.md +398 -0
- specs/001-todo-ai-chatbot/spec.md +278 -0
- specs/001-todo-ai-chatbot/tasks.md +298 -0
- specs/002-fullstack-ui-integration/checklists/requirements.md +98 -0
- specs/002-fullstack-ui-integration/contracts/existing-api-reference.yaml +611 -0
- specs/002-fullstack-ui-integration/data-model.md +280 -0
- specs/002-fullstack-ui-integration/plan.md +458 -0
- specs/002-fullstack-ui-integration/quickstart.md +458 -0
- specs/002-fullstack-ui-integration/research.md +392 -0
- specs/002-fullstack-ui-integration/spec.md +240 -0
- specs/002-fullstack-ui-integration/tasks.md +286 -0
specs/001-auth-security/TESTING.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authentication & API Security - Testing Guide
|
| 2 |
+
|
| 3 |
+
**Feature**: Authentication & API Security (Spec 001)
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Status**: Ready for Testing
|
| 6 |
+
|
| 7 |
+
## Prerequisites
|
| 8 |
+
|
| 9 |
+
Before testing, ensure:
|
| 10 |
+
|
| 11 |
+
1. **Backend is running**:
|
| 12 |
+
```bash
|
| 13 |
+
cd backend
|
| 14 |
+
python -m uvicorn src.main:app --reload
|
| 15 |
+
# Should be running at http://localhost:8000
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
2. **Database migrations applied**:
|
| 19 |
+
```bash
|
| 20 |
+
cd backend
|
| 21 |
+
python -m alembic upgrade head
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
3. **Frontend is running**:
|
| 25 |
+
```bash
|
| 26 |
+
cd frontend
|
| 27 |
+
npm run dev
|
| 28 |
+
# Should be running at http://localhost:3000
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
4. **Environment variables configured**:
|
| 32 |
+
- `backend/.env` has `BETTER_AUTH_SECRET`
|
| 33 |
+
- `frontend/.env.local` has same `BETTER_AUTH_SECRET`
|
| 34 |
+
- Both secrets match exactly
|
| 35 |
+
|
| 36 |
+
## Test Suite
|
| 37 |
+
|
| 38 |
+
### T048: Test Signup Flow End-to-End
|
| 39 |
+
|
| 40 |
+
**Objective**: Verify new users can create accounts and data is stored correctly in database
|
| 41 |
+
|
| 42 |
+
**Steps**:
|
| 43 |
+
|
| 44 |
+
1. **Navigate to signup page**:
|
| 45 |
+
- Open browser to `http://localhost:3000/auth/signup`
|
| 46 |
+
- Verify signup form is displayed with email, password, and name fields
|
| 47 |
+
|
| 48 |
+
2. **Test validation errors**:
|
| 49 |
+
- Try submitting with empty fields → Should show validation errors
|
| 50 |
+
- Try weak password (e.g., "pass") → Should show "Password must be at least 8 characters"
|
| 51 |
+
- Try invalid email (e.g., "notanemail") → Should show email format error
|
| 52 |
+
|
| 53 |
+
3. **Create valid account**:
|
| 54 |
+
- Email: `test1@example.com`
|
| 55 |
+
- Password: `SecurePass123`
|
| 56 |
+
- Name: `Test User 1`
|
| 57 |
+
- Click "Sign Up"
|
| 58 |
+
- **Expected**: Success message, redirect to signin page
|
| 59 |
+
|
| 60 |
+
4. **Verify in database**:
|
| 61 |
+
```bash
|
| 62 |
+
# Connect to your database and run:
|
| 63 |
+
SELECT id, email, name, password_hash, created_at FROM users WHERE email = 'test1@example.com';
|
| 64 |
+
```
|
| 65 |
+
- **Expected**: User record exists
|
| 66 |
+
- **Expected**: `password_hash` is bcrypt hash (starts with `$2b$`)
|
| 67 |
+
- **Expected**: `created_at` timestamp is recent
|
| 68 |
+
|
| 69 |
+
5. **Test duplicate email**:
|
| 70 |
+
- Try signing up again with `test1@example.com`
|
| 71 |
+
- **Expected**: 409 Conflict error "Email already registered"
|
| 72 |
+
|
| 73 |
+
**Pass Criteria**:
|
| 74 |
+
- ✅ Form validation works correctly
|
| 75 |
+
- ✅ Valid signup creates user in database
|
| 76 |
+
- ✅ Password is hashed (not stored in plain text)
|
| 77 |
+
- ✅ Duplicate email is rejected with 409 error
|
| 78 |
+
- ✅ User is redirected to signin after successful signup
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
### T049: Test Signin Flow End-to-End
|
| 83 |
+
|
| 84 |
+
**Objective**: Verify users can authenticate and receive valid JWT tokens
|
| 85 |
+
|
| 86 |
+
**Steps**:
|
| 87 |
+
|
| 88 |
+
1. **Navigate to signin page**:
|
| 89 |
+
- Open browser to `http://localhost:3000/auth/signin`
|
| 90 |
+
- Verify signin form is displayed
|
| 91 |
+
|
| 92 |
+
2. **Test invalid credentials**:
|
| 93 |
+
- Email: `test1@example.com`
|
| 94 |
+
- Password: `WrongPassword123`
|
| 95 |
+
- Click "Sign In"
|
| 96 |
+
- **Expected**: 401 error "Invalid email or password"
|
| 97 |
+
|
| 98 |
+
3. **Test valid credentials**:
|
| 99 |
+
- Email: `test1@example.com`
|
| 100 |
+
- Password: `SecurePass123`
|
| 101 |
+
- Click "Sign In"
|
| 102 |
+
- **Expected**: Success, redirect to home page (`/`)
|
| 103 |
+
|
| 104 |
+
4. **Verify JWT token**:
|
| 105 |
+
- Open browser DevTools → Application → Local Storage → `http://localhost:3000`
|
| 106 |
+
- Find `auth_session` key
|
| 107 |
+
- **Expected**: JSON object with `token` and `user` fields
|
| 108 |
+
- Copy the token value
|
| 109 |
+
|
| 110 |
+
5. **Decode JWT token** (use jwt.io or command line):
|
| 111 |
+
```bash
|
| 112 |
+
# Using Python
|
| 113 |
+
python -c "import jwt; print(jwt.decode('YOUR_TOKEN_HERE', options={'verify_signature': False}))"
|
| 114 |
+
```
|
| 115 |
+
- **Expected payload**:
|
| 116 |
+
```json
|
| 117 |
+
{
|
| 118 |
+
"sub": "1", // User ID
|
| 119 |
+
"email": "test1@example.com",
|
| 120 |
+
"iat": 1704067200, // Issued at timestamp
|
| 121 |
+
"exp": 1704672000, // Expiration (7 days later)
|
| 122 |
+
"iss": "better-auth"
|
| 123 |
+
}
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
6. **Verify session persistence**:
|
| 127 |
+
- Refresh the page
|
| 128 |
+
- **Expected**: Still logged in (no redirect to signin)
|
| 129 |
+
- **Expected**: User name displayed in header
|
| 130 |
+
|
| 131 |
+
7. **Test signout**:
|
| 132 |
+
- Click "Sign Out" button in header
|
| 133 |
+
- **Expected**: Redirect to signin page
|
| 134 |
+
- **Expected**: localStorage `auth_session` is cleared
|
| 135 |
+
|
| 136 |
+
**Pass Criteria**:
|
| 137 |
+
- ✅ Invalid credentials return 401 error
|
| 138 |
+
- ✅ Valid credentials return JWT token
|
| 139 |
+
- ✅ Token contains correct user_id, email, and expiration
|
| 140 |
+
- ✅ Token expiration is 7 days from issuance
|
| 141 |
+
- ✅ Session persists across page refreshes
|
| 142 |
+
- ✅ Signout clears session and redirects
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
### T050: Test Protected API Access
|
| 147 |
+
|
| 148 |
+
**Objective**: Verify API endpoints require valid JWT tokens and reject invalid tokens
|
| 149 |
+
|
| 150 |
+
**Steps**:
|
| 151 |
+
|
| 152 |
+
1. **Test unauthenticated request**:
|
| 153 |
+
```bash
|
| 154 |
+
# Try to fetch tasks without token
|
| 155 |
+
curl http://localhost:8000/api/tasks
|
| 156 |
+
```
|
| 157 |
+
- **Expected**: 401 Unauthorized
|
| 158 |
+
- **Expected**: Response body: `{"detail": "Not authenticated"}`
|
| 159 |
+
|
| 160 |
+
2. **Test with valid token**:
|
| 161 |
+
- Sign in to get a valid token (from T049)
|
| 162 |
+
- Copy token from localStorage
|
| 163 |
+
```bash
|
| 164 |
+
# Replace YOUR_TOKEN with actual token
|
| 165 |
+
curl http://localhost:8000/api/tasks \
|
| 166 |
+
-H "Authorization: Bearer YOUR_TOKEN"
|
| 167 |
+
```
|
| 168 |
+
- **Expected**: 200 OK
|
| 169 |
+
- **Expected**: Returns task list (may be empty)
|
| 170 |
+
|
| 171 |
+
3. **Test with invalid token**:
|
| 172 |
+
```bash
|
| 173 |
+
curl http://localhost:8000/api/tasks \
|
| 174 |
+
-H "Authorization: Bearer invalid_token_here"
|
| 175 |
+
```
|
| 176 |
+
- **Expected**: 401 Unauthorized
|
| 177 |
+
- **Expected**: Error message about invalid token
|
| 178 |
+
|
| 179 |
+
4. **Test with expired token**:
|
| 180 |
+
- Manually create an expired token or wait 7 days (not practical)
|
| 181 |
+
- Alternative: Temporarily change `JWT_EXPIRATION_DAYS=0` in backend/.env, restart backend, get new token, wait 1 minute
|
| 182 |
+
```bash
|
| 183 |
+
curl http://localhost:8000/api/tasks \
|
| 184 |
+
-H "Authorization: Bearer EXPIRED_TOKEN"
|
| 185 |
+
```
|
| 186 |
+
- **Expected**: 401 Unauthorized
|
| 187 |
+
- **Expected**: Error code `TOKEN_EXPIRED`
|
| 188 |
+
|
| 189 |
+
5. **Test frontend automatic redirect**:
|
| 190 |
+
- In browser, manually edit localStorage to set invalid token
|
| 191 |
+
- Try to access home page (`/`)
|
| 192 |
+
- **Expected**: Automatic redirect to `/auth/signin`
|
| 193 |
+
|
| 194 |
+
6. **Test all protected endpoints**:
|
| 195 |
+
- With valid token, test:
|
| 196 |
+
- `GET /api/tasks` → 200 OK
|
| 197 |
+
- `POST /api/tasks` → 201 Created
|
| 198 |
+
- `GET /api/tasks/{id}` → 200 OK
|
| 199 |
+
- `PATCH /api/tasks/{id}` → 200 OK
|
| 200 |
+
- `DELETE /api/tasks/{id}` → 204 No Content
|
| 201 |
+
- `GET /api/auth/me` → 200 OK
|
| 202 |
+
|
| 203 |
+
**Pass Criteria**:
|
| 204 |
+
- ✅ Requests without token return 401
|
| 205 |
+
- ✅ Requests with valid token succeed
|
| 206 |
+
- ✅ Requests with invalid token return 401
|
| 207 |
+
- ✅ Requests with expired token return 401 with TOKEN_EXPIRED
|
| 208 |
+
- ✅ Frontend automatically redirects on 401
|
| 209 |
+
- ✅ All task endpoints require authentication
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
### T051: Test User Data Isolation
|
| 214 |
+
|
| 215 |
+
**Objective**: Verify users can only access their own tasks, not other users' tasks
|
| 216 |
+
|
| 217 |
+
**Steps**:
|
| 218 |
+
|
| 219 |
+
1. **Create two user accounts**:
|
| 220 |
+
- User A: `usera@example.com` / `PasswordA123`
|
| 221 |
+
- User B: `userb@example.com` / `PasswordB123`
|
| 222 |
+
|
| 223 |
+
2. **Sign in as User A**:
|
| 224 |
+
- Navigate to `/auth/signin`
|
| 225 |
+
- Sign in with User A credentials
|
| 226 |
+
- Copy User A's JWT token from localStorage
|
| 227 |
+
|
| 228 |
+
3. **Create tasks as User A**:
|
| 229 |
+
- Create 2-3 tasks through the UI
|
| 230 |
+
- Note the task IDs (check Network tab or database)
|
| 231 |
+
|
| 232 |
+
4. **Sign out and sign in as User B**:
|
| 233 |
+
- Click "Sign Out"
|
| 234 |
+
- Sign in with User B credentials
|
| 235 |
+
- Copy User B's JWT token
|
| 236 |
+
|
| 237 |
+
5. **Create tasks as User B**:
|
| 238 |
+
- Create 2-3 different tasks through the UI
|
| 239 |
+
|
| 240 |
+
6. **Verify User B cannot see User A's tasks**:
|
| 241 |
+
- Check the task list in UI
|
| 242 |
+
- **Expected**: Only User B's tasks are visible
|
| 243 |
+
- **Expected**: User A's tasks are NOT visible
|
| 244 |
+
|
| 245 |
+
7. **Test API-level isolation**:
|
| 246 |
+
```bash
|
| 247 |
+
# Get User A's task ID from database
|
| 248 |
+
# Try to access it with User B's token
|
| 249 |
+
curl http://localhost:8000/api/tasks/USER_A_TASK_ID \
|
| 250 |
+
-H "Authorization: Bearer USER_B_TOKEN"
|
| 251 |
+
```
|
| 252 |
+
- **Expected**: 404 Not Found (task doesn't exist for User B)
|
| 253 |
+
|
| 254 |
+
8. **Test cross-user modification attempt**:
|
| 255 |
+
```bash
|
| 256 |
+
# Try to update User A's task with User B's token
|
| 257 |
+
curl -X PATCH http://localhost:8000/api/tasks/USER_A_TASK_ID \
|
| 258 |
+
-H "Authorization: Bearer USER_B_TOKEN" \
|
| 259 |
+
-H "Content-Type: application/json" \
|
| 260 |
+
-d '{"completed": true}'
|
| 261 |
+
```
|
| 262 |
+
- **Expected**: 404 Not Found
|
| 263 |
+
|
| 264 |
+
9. **Test cross-user deletion attempt**:
|
| 265 |
+
```bash
|
| 266 |
+
# Try to delete User A's task with User B's token
|
| 267 |
+
curl -X DELETE http://localhost:8000/api/tasks/USER_A_TASK_ID \
|
| 268 |
+
-H "Authorization: Bearer USER_B_TOKEN"
|
| 269 |
+
```
|
| 270 |
+
- **Expected**: 404 Not Found
|
| 271 |
+
|
| 272 |
+
10. **Verify in database**:
|
| 273 |
+
```sql
|
| 274 |
+
-- Check that tasks are correctly associated with users
|
| 275 |
+
SELECT id, user_id, title FROM tasks ORDER BY user_id, id;
|
| 276 |
+
```
|
| 277 |
+
- **Expected**: User A's tasks have `user_id = 1`
|
| 278 |
+
- **Expected**: User B's tasks have `user_id = 2`
|
| 279 |
+
- **Expected**: No cross-contamination
|
| 280 |
+
|
| 281 |
+
**Pass Criteria**:
|
| 282 |
+
- ✅ User A can only see their own tasks
|
| 283 |
+
- ✅ User B can only see their own tasks
|
| 284 |
+
- ✅ User B cannot access User A's tasks via API (404)
|
| 285 |
+
- ✅ User B cannot modify User A's tasks (404)
|
| 286 |
+
- ✅ User B cannot delete User A's tasks (404)
|
| 287 |
+
- ✅ Database correctly associates tasks with user_id
|
| 288 |
+
- ✅ All queries are filtered by authenticated user
|
| 289 |
+
|
| 290 |
+
---
|
| 291 |
+
|
| 292 |
+
## Test Results Summary
|
| 293 |
+
|
| 294 |
+
After completing all tests, fill in the results:
|
| 295 |
+
|
| 296 |
+
| Test | Status | Notes |
|
| 297 |
+
|------|--------|-------|
|
| 298 |
+
| T048: Signup Flow | ⬜ Pass / ⬜ Fail | |
|
| 299 |
+
| T049: Signin Flow | ⬜ Pass / ⬜ Fail | |
|
| 300 |
+
| T050: Protected API | ⬜ Pass / ⬜ Fail | |
|
| 301 |
+
| T051: User Isolation | ⬜ Pass / ⬜ Fail | |
|
| 302 |
+
|
| 303 |
+
## Common Issues & Troubleshooting
|
| 304 |
+
|
| 305 |
+
### Issue: "BETTER_AUTH_SECRET not found"
|
| 306 |
+
- **Cause**: Environment variable not set
|
| 307 |
+
- **Fix**: Ensure both `backend/.env` and `frontend/.env.local` have `BETTER_AUTH_SECRET`
|
| 308 |
+
- **Verify**: Secrets must be identical in both files
|
| 309 |
+
|
| 310 |
+
### Issue: "Token signature verification failed"
|
| 311 |
+
- **Cause**: Frontend and backend have different secrets
|
| 312 |
+
- **Fix**: Copy exact same secret to both .env files
|
| 313 |
+
- **Verify**: Run `grep BETTER_AUTH_SECRET backend/.env frontend/.env.local`
|
| 314 |
+
|
| 315 |
+
### Issue: "401 Unauthorized" on all requests
|
| 316 |
+
- **Cause**: Token not being sent or invalid
|
| 317 |
+
- **Fix**: Check localStorage has valid token, check Authorization header in Network tab
|
| 318 |
+
|
| 319 |
+
### Issue: "User can see other users' tasks"
|
| 320 |
+
- **Cause**: Missing user_id filter in queries
|
| 321 |
+
- **Fix**: Verify `get_current_user` dependency is applied to all task endpoints
|
| 322 |
+
- **Check**: `backend/src/api/routes/tasks.py` should use `current_user_id = Depends(get_current_user)`
|
| 323 |
+
|
| 324 |
+
### Issue: Database migration errors
|
| 325 |
+
- **Cause**: Migration not applied or database out of sync
|
| 326 |
+
- **Fix**: Run `python -m alembic upgrade head` in backend directory
|
| 327 |
+
|
| 328 |
+
## Security Checklist
|
| 329 |
+
|
| 330 |
+
After testing, verify:
|
| 331 |
+
|
| 332 |
+
- [ ] Passwords are hashed (never stored in plain text)
|
| 333 |
+
- [ ] JWT tokens expire after 7 days
|
| 334 |
+
- [ ] Invalid tokens are rejected with 401
|
| 335 |
+
- [ ] Expired tokens are rejected with 401
|
| 336 |
+
- [ ] Users cannot access other users' data
|
| 337 |
+
- [ ] All task endpoints require authentication
|
| 338 |
+
- [ ] BETTER_AUTH_SECRET is not committed to git
|
| 339 |
+
- [ ] Error messages don't leak sensitive information
|
| 340 |
+
- [ ] Token signature is verified on every request
|
| 341 |
+
|
| 342 |
+
## Next Steps
|
| 343 |
+
|
| 344 |
+
Once all tests pass:
|
| 345 |
+
|
| 346 |
+
1. Mark tasks T048-T051 as complete in `tasks.md`
|
| 347 |
+
2. Create git commit with authentication implementation
|
| 348 |
+
3. Consider additional security enhancements:
|
| 349 |
+
- Rate limiting on auth endpoints
|
| 350 |
+
- Account lockout after failed attempts
|
| 351 |
+
- Password reset functionality
|
| 352 |
+
- Refresh token mechanism
|
| 353 |
+
- Multi-factor authentication (MFA)
|
specs/001-auth-security/checklists/requirements.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Specification Quality Checklist: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 4 |
+
**Created**: 2026-01-09
|
| 5 |
+
**Feature**: [spec.md](../spec.md)
|
| 6 |
+
|
| 7 |
+
## Content Quality
|
| 8 |
+
|
| 9 |
+
- [x] No implementation details (languages, frameworks, APIs) - Technologies mentioned are from user-provided constraints
|
| 10 |
+
- [x] Focused on user value and business needs - Emphasizes secure authentication and data isolation
|
| 11 |
+
- [x] Written for non-technical stakeholders - User stories and requirements are clear and accessible
|
| 12 |
+
- [x] All mandatory sections completed - User Scenarios, Requirements, and Success Criteria all present
|
| 13 |
+
|
| 14 |
+
## Requirement Completeness
|
| 15 |
+
|
| 16 |
+
- [x] No [NEEDS CLARIFICATION] markers remain - All requirements are concrete with informed assumptions documented
|
| 17 |
+
- [x] Requirements are testable and unambiguous - Each FR can be verified through testing
|
| 18 |
+
- [x] Success criteria are measurable - All SC items include specific metrics (time, percentage, count)
|
| 19 |
+
- [x] Success criteria are technology-agnostic - Focus on user outcomes and performance, not implementation
|
| 20 |
+
- [x] All acceptance scenarios are defined - Each user story has 2-3 acceptance scenarios
|
| 21 |
+
- [x] Edge cases are identified - 7 edge cases documented covering security and error scenarios
|
| 22 |
+
- [x] Scope is clearly bounded - Out of Scope section explicitly excludes OAuth, MFA, password reset, etc.
|
| 23 |
+
- [x] Dependencies and assumptions identified - Both sections present with specific details
|
| 24 |
+
|
| 25 |
+
## Feature Readiness
|
| 26 |
+
|
| 27 |
+
- [x] All functional requirements have clear acceptance criteria - 20 FRs defined with specific capabilities
|
| 28 |
+
- [x] User scenarios cover primary flows - 4 prioritized user stories from sign-up to token validation
|
| 29 |
+
- [x] Feature meets measurable outcomes defined in Success Criteria - 8 success criteria align with requirements
|
| 30 |
+
- [x] No implementation details leak into specification - Spec focuses on WHAT and WHY, not HOW
|
| 31 |
+
|
| 32 |
+
## Validation Results
|
| 33 |
+
|
| 34 |
+
**Status**: ✅ PASSED
|
| 35 |
+
|
| 36 |
+
All checklist items passed validation. The specification is complete, unambiguous, and ready for the planning phase.
|
| 37 |
+
|
| 38 |
+
## Notes
|
| 39 |
+
|
| 40 |
+
- Technologies mentioned (Better Auth, JWT, FastAPI, Next.js) are from user-provided constraints and are acceptable
|
| 41 |
+
- Assumptions section documents reasonable defaults (1-hour token expiration, HS256 algorithm, password requirements)
|
| 42 |
+
- Success criteria are measurable and technology-agnostic, focusing on user outcomes
|
| 43 |
+
- Edge cases cover critical security scenarios (duplicate emails, expired tokens, missing secrets)
|
| 44 |
+
- Scope is well-defined with clear boundaries in Out of Scope section
|
specs/001-auth-security/contracts/auth-endpoints.yaml
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openapi: 3.0.3
|
| 2 |
+
info:
|
| 3 |
+
title: Authentication API
|
| 4 |
+
description: Authentication endpoints for user signup, signin, and token management
|
| 5 |
+
version: 1.0.0
|
| 6 |
+
contact:
|
| 7 |
+
name: Phase II Todo App
|
| 8 |
+
|
| 9 |
+
servers:
|
| 10 |
+
- url: http://localhost:8000
|
| 11 |
+
description: Local development server
|
| 12 |
+
- url: https://api.production.example.com
|
| 13 |
+
description: Production server
|
| 14 |
+
|
| 15 |
+
paths:
|
| 16 |
+
/api/auth/signup:
|
| 17 |
+
post:
|
| 18 |
+
summary: Register a new user
|
| 19 |
+
description: Create a new user account with email and password
|
| 20 |
+
operationId: signup
|
| 21 |
+
tags:
|
| 22 |
+
- Authentication
|
| 23 |
+
requestBody:
|
| 24 |
+
required: true
|
| 25 |
+
content:
|
| 26 |
+
application/json:
|
| 27 |
+
schema:
|
| 28 |
+
$ref: '#/components/schemas/SignupRequest'
|
| 29 |
+
examples:
|
| 30 |
+
valid:
|
| 31 |
+
summary: Valid signup request
|
| 32 |
+
value:
|
| 33 |
+
email: user@example.com
|
| 34 |
+
password: SecurePass123!
|
| 35 |
+
name: John Doe
|
| 36 |
+
responses:
|
| 37 |
+
'201':
|
| 38 |
+
description: User created successfully
|
| 39 |
+
content:
|
| 40 |
+
application/json:
|
| 41 |
+
schema:
|
| 42 |
+
$ref: '#/components/schemas/SignupResponse'
|
| 43 |
+
examples:
|
| 44 |
+
success:
|
| 45 |
+
summary: Successful signup
|
| 46 |
+
value:
|
| 47 |
+
id: 1
|
| 48 |
+
email: user@example.com
|
| 49 |
+
name: John Doe
|
| 50 |
+
created_at: "2026-01-09T12:00:00Z"
|
| 51 |
+
'400':
|
| 52 |
+
description: Invalid input or validation error
|
| 53 |
+
content:
|
| 54 |
+
application/json:
|
| 55 |
+
schema:
|
| 56 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 57 |
+
examples:
|
| 58 |
+
invalid_email:
|
| 59 |
+
summary: Invalid email format
|
| 60 |
+
value:
|
| 61 |
+
detail: Invalid email format
|
| 62 |
+
error_code: VALIDATION_ERROR
|
| 63 |
+
field_errors:
|
| 64 |
+
email: ["Invalid email format"]
|
| 65 |
+
weak_password:
|
| 66 |
+
summary: Weak password
|
| 67 |
+
value:
|
| 68 |
+
detail: Password does not meet requirements
|
| 69 |
+
error_code: VALIDATION_ERROR
|
| 70 |
+
field_errors:
|
| 71 |
+
password: ["Password must be at least 8 characters", "Password must contain uppercase letter"]
|
| 72 |
+
'409':
|
| 73 |
+
description: Email already registered
|
| 74 |
+
content:
|
| 75 |
+
application/json:
|
| 76 |
+
schema:
|
| 77 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 78 |
+
examples:
|
| 79 |
+
duplicate_email:
|
| 80 |
+
summary: Email already exists
|
| 81 |
+
value:
|
| 82 |
+
detail: Email already registered
|
| 83 |
+
error_code: EMAIL_EXISTS
|
| 84 |
+
|
| 85 |
+
/api/auth/signin:
|
| 86 |
+
post:
|
| 87 |
+
summary: Sign in with email and password
|
| 88 |
+
description: Authenticate user and receive JWT token
|
| 89 |
+
operationId: signin
|
| 90 |
+
tags:
|
| 91 |
+
- Authentication
|
| 92 |
+
requestBody:
|
| 93 |
+
required: true
|
| 94 |
+
content:
|
| 95 |
+
application/json:
|
| 96 |
+
schema:
|
| 97 |
+
$ref: '#/components/schemas/SigninRequest'
|
| 98 |
+
examples:
|
| 99 |
+
valid:
|
| 100 |
+
summary: Valid signin request
|
| 101 |
+
value:
|
| 102 |
+
email: user@example.com
|
| 103 |
+
password: SecurePass123!
|
| 104 |
+
responses:
|
| 105 |
+
'200':
|
| 106 |
+
description: Authentication successful
|
| 107 |
+
content:
|
| 108 |
+
application/json:
|
| 109 |
+
schema:
|
| 110 |
+
$ref: '#/components/schemas/SigninResponse'
|
| 111 |
+
examples:
|
| 112 |
+
success:
|
| 113 |
+
summary: Successful signin
|
| 114 |
+
value:
|
| 115 |
+
access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
| 116 |
+
token_type: bearer
|
| 117 |
+
expires_in: 604800
|
| 118 |
+
user:
|
| 119 |
+
id: 1
|
| 120 |
+
email: user@example.com
|
| 121 |
+
name: John Doe
|
| 122 |
+
'401':
|
| 123 |
+
description: Invalid credentials
|
| 124 |
+
content:
|
| 125 |
+
application/json:
|
| 126 |
+
schema:
|
| 127 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 128 |
+
examples:
|
| 129 |
+
invalid_credentials:
|
| 130 |
+
summary: Invalid email or password
|
| 131 |
+
value:
|
| 132 |
+
detail: Invalid credentials
|
| 133 |
+
error_code: AUTH_FAILED
|
| 134 |
+
'400':
|
| 135 |
+
description: Invalid input
|
| 136 |
+
content:
|
| 137 |
+
application/json:
|
| 138 |
+
schema:
|
| 139 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 140 |
+
|
| 141 |
+
/api/auth/me:
|
| 142 |
+
get:
|
| 143 |
+
summary: Get current user profile
|
| 144 |
+
description: Retrieve authenticated user's profile information
|
| 145 |
+
operationId: getCurrentUser
|
| 146 |
+
tags:
|
| 147 |
+
- Authentication
|
| 148 |
+
security:
|
| 149 |
+
- BearerAuth: []
|
| 150 |
+
responses:
|
| 151 |
+
'200':
|
| 152 |
+
description: User profile retrieved successfully
|
| 153 |
+
content:
|
| 154 |
+
application/json:
|
| 155 |
+
schema:
|
| 156 |
+
$ref: '#/components/schemas/UserProfile'
|
| 157 |
+
examples:
|
| 158 |
+
success:
|
| 159 |
+
summary: User profile
|
| 160 |
+
value:
|
| 161 |
+
id: 1
|
| 162 |
+
email: user@example.com
|
| 163 |
+
name: John Doe
|
| 164 |
+
created_at: "2026-01-09T12:00:00Z"
|
| 165 |
+
'401':
|
| 166 |
+
description: Unauthorized - invalid or missing token
|
| 167 |
+
content:
|
| 168 |
+
application/json:
|
| 169 |
+
schema:
|
| 170 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 171 |
+
examples:
|
| 172 |
+
missing_token:
|
| 173 |
+
summary: No token provided
|
| 174 |
+
value:
|
| 175 |
+
detail: Not authenticated
|
| 176 |
+
error_code: TOKEN_MISSING
|
| 177 |
+
expired_token:
|
| 178 |
+
summary: Token expired
|
| 179 |
+
value:
|
| 180 |
+
detail: Token has expired
|
| 181 |
+
error_code: TOKEN_EXPIRED
|
| 182 |
+
invalid_token:
|
| 183 |
+
summary: Invalid token
|
| 184 |
+
value:
|
| 185 |
+
detail: Invalid token
|
| 186 |
+
error_code: TOKEN_INVALID
|
| 187 |
+
|
| 188 |
+
components:
|
| 189 |
+
securitySchemes:
|
| 190 |
+
BearerAuth:
|
| 191 |
+
type: http
|
| 192 |
+
scheme: bearer
|
| 193 |
+
bearerFormat: JWT
|
| 194 |
+
description: JWT token issued by Better Auth
|
| 195 |
+
|
| 196 |
+
schemas:
|
| 197 |
+
SignupRequest:
|
| 198 |
+
type: object
|
| 199 |
+
required:
|
| 200 |
+
- email
|
| 201 |
+
- password
|
| 202 |
+
- name
|
| 203 |
+
properties:
|
| 204 |
+
email:
|
| 205 |
+
type: string
|
| 206 |
+
format: email
|
| 207 |
+
maxLength: 255
|
| 208 |
+
description: User's email address (must be unique)
|
| 209 |
+
example: user@example.com
|
| 210 |
+
password:
|
| 211 |
+
type: string
|
| 212 |
+
format: password
|
| 213 |
+
minLength: 8
|
| 214 |
+
maxLength: 100
|
| 215 |
+
description: User's password (min 8 chars, must contain uppercase, lowercase, and number)
|
| 216 |
+
example: SecurePass123!
|
| 217 |
+
name:
|
| 218 |
+
type: string
|
| 219 |
+
minLength: 1
|
| 220 |
+
maxLength: 100
|
| 221 |
+
description: User's display name
|
| 222 |
+
example: John Doe
|
| 223 |
+
|
| 224 |
+
SignupResponse:
|
| 225 |
+
type: object
|
| 226 |
+
required:
|
| 227 |
+
- id
|
| 228 |
+
- email
|
| 229 |
+
- name
|
| 230 |
+
- created_at
|
| 231 |
+
properties:
|
| 232 |
+
id:
|
| 233 |
+
type: integer
|
| 234 |
+
description: Unique user identifier
|
| 235 |
+
example: 1
|
| 236 |
+
email:
|
| 237 |
+
type: string
|
| 238 |
+
format: email
|
| 239 |
+
description: User's email address
|
| 240 |
+
example: user@example.com
|
| 241 |
+
name:
|
| 242 |
+
type: string
|
| 243 |
+
description: User's display name
|
| 244 |
+
example: John Doe
|
| 245 |
+
created_at:
|
| 246 |
+
type: string
|
| 247 |
+
format: date-time
|
| 248 |
+
description: Account creation timestamp
|
| 249 |
+
example: "2026-01-09T12:00:00Z"
|
| 250 |
+
|
| 251 |
+
SigninRequest:
|
| 252 |
+
type: object
|
| 253 |
+
required:
|
| 254 |
+
- email
|
| 255 |
+
- password
|
| 256 |
+
properties:
|
| 257 |
+
email:
|
| 258 |
+
type: string
|
| 259 |
+
format: email
|
| 260 |
+
description: User's email address
|
| 261 |
+
example: user@example.com
|
| 262 |
+
password:
|
| 263 |
+
type: string
|
| 264 |
+
format: password
|
| 265 |
+
description: User's password
|
| 266 |
+
example: SecurePass123!
|
| 267 |
+
|
| 268 |
+
SigninResponse:
|
| 269 |
+
type: object
|
| 270 |
+
required:
|
| 271 |
+
- access_token
|
| 272 |
+
- token_type
|
| 273 |
+
- expires_in
|
| 274 |
+
- user
|
| 275 |
+
properties:
|
| 276 |
+
access_token:
|
| 277 |
+
type: string
|
| 278 |
+
description: JWT access token
|
| 279 |
+
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaWF0IjoxNjQwOTk1MjAwLCJleHAiOjE2NDE2MDAwMDB9.signature
|
| 280 |
+
token_type:
|
| 281 |
+
type: string
|
| 282 |
+
enum: [bearer]
|
| 283 |
+
description: Token type (always "bearer")
|
| 284 |
+
example: bearer
|
| 285 |
+
expires_in:
|
| 286 |
+
type: integer
|
| 287 |
+
description: Token expiration time in seconds (7 days = 604800)
|
| 288 |
+
example: 604800
|
| 289 |
+
user:
|
| 290 |
+
$ref: '#/components/schemas/UserProfile'
|
| 291 |
+
|
| 292 |
+
UserProfile:
|
| 293 |
+
type: object
|
| 294 |
+
required:
|
| 295 |
+
- id
|
| 296 |
+
- email
|
| 297 |
+
- name
|
| 298 |
+
- created_at
|
| 299 |
+
properties:
|
| 300 |
+
id:
|
| 301 |
+
type: integer
|
| 302 |
+
description: Unique user identifier
|
| 303 |
+
example: 1
|
| 304 |
+
email:
|
| 305 |
+
type: string
|
| 306 |
+
format: email
|
| 307 |
+
description: User's email address
|
| 308 |
+
example: user@example.com
|
| 309 |
+
name:
|
| 310 |
+
type: string
|
| 311 |
+
description: User's display name
|
| 312 |
+
example: John Doe
|
| 313 |
+
created_at:
|
| 314 |
+
type: string
|
| 315 |
+
format: date-time
|
| 316 |
+
description: Account creation timestamp
|
| 317 |
+
example: "2026-01-09T12:00:00Z"
|
| 318 |
+
|
| 319 |
+
ErrorResponse:
|
| 320 |
+
type: object
|
| 321 |
+
required:
|
| 322 |
+
- detail
|
| 323 |
+
properties:
|
| 324 |
+
detail:
|
| 325 |
+
type: string
|
| 326 |
+
description: Human-readable error message
|
| 327 |
+
example: Invalid credentials
|
| 328 |
+
error_code:
|
| 329 |
+
type: string
|
| 330 |
+
description: Machine-readable error code
|
| 331 |
+
example: AUTH_FAILED
|
| 332 |
+
field_errors:
|
| 333 |
+
type: object
|
| 334 |
+
additionalProperties:
|
| 335 |
+
type: array
|
| 336 |
+
items:
|
| 337 |
+
type: string
|
| 338 |
+
description: Field-specific validation errors
|
| 339 |
+
example:
|
| 340 |
+
email: ["Invalid email format"]
|
| 341 |
+
password: ["Password too short"]
|
| 342 |
+
|
| 343 |
+
tags:
|
| 344 |
+
- name: Authentication
|
| 345 |
+
description: User authentication and authorization endpoints
|
specs/001-auth-security/contracts/jwt-schema.yaml
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JWT Token Schema
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-auth-security
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
|
| 6 |
+
## Overview
|
| 7 |
+
|
| 8 |
+
This document defines the structure and validation rules for JWT tokens used in the authentication system. Tokens are issued by Better Auth on the frontend and verified by the FastAPI backend.
|
| 9 |
+
|
| 10 |
+
## Token Structure
|
| 11 |
+
|
| 12 |
+
### Header
|
| 13 |
+
|
| 14 |
+
```json
|
| 15 |
+
{
|
| 16 |
+
"alg": "HS256",
|
| 17 |
+
"typ": "JWT"
|
| 18 |
+
}
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
| Field | Value | Description |
|
| 22 |
+
|-------|-------|-------------|
|
| 23 |
+
| alg | HS256 | HMAC with SHA-256 algorithm |
|
| 24 |
+
| typ | JWT | Token type |
|
| 25 |
+
|
| 26 |
+
### Payload (Claims)
|
| 27 |
+
|
| 28 |
+
```json
|
| 29 |
+
{
|
| 30 |
+
"sub": "1",
|
| 31 |
+
"email": "user@example.com",
|
| 32 |
+
"iat": 1704801600,
|
| 33 |
+
"exp": 1705406400,
|
| 34 |
+
"iss": "better-auth"
|
| 35 |
+
}
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
| Claim | Type | Required | Description |
|
| 39 |
+
|-------|------|----------|-------------|
|
| 40 |
+
| sub | string | Yes | Subject - User ID (primary key from users table) |
|
| 41 |
+
| email | string | Yes | User's email address |
|
| 42 |
+
| iat | integer | Yes | Issued At - Unix timestamp when token was created |
|
| 43 |
+
| exp | integer | Yes | Expiration - Unix timestamp when token expires (iat + 604800 seconds = 7 days) |
|
| 44 |
+
| iss | string | Yes | Issuer - Always "better-auth" |
|
| 45 |
+
|
| 46 |
+
### Signature
|
| 47 |
+
|
| 48 |
+
The signature is created by:
|
| 49 |
+
1. Encoding the header and payload as Base64URL
|
| 50 |
+
2. Concatenating with a period: `{base64Header}.{base64Payload}`
|
| 51 |
+
3. Signing with HMAC-SHA256 using BETTER_AUTH_SECRET
|
| 52 |
+
4. Encoding the signature as Base64URL
|
| 53 |
+
|
| 54 |
+
**Formula**: `HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), BETTER_AUTH_SECRET)`
|
| 55 |
+
|
| 56 |
+
## Complete Token Format
|
| 57 |
+
|
| 58 |
+
```
|
| 59 |
+
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaWF0IjoxNzA0ODAxNjAwLCJleHAiOjE3MDU0MDY0MDAsImlzcyI6ImJldHRlci1hdXRoIn0.signature_here
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
**Structure**: `{header}.{payload}.{signature}`
|
| 63 |
+
|
| 64 |
+
## Validation Rules
|
| 65 |
+
|
| 66 |
+
### Backend Verification Process
|
| 67 |
+
|
| 68 |
+
1. **Extract Token**: Get token from `Authorization: Bearer {token}` header
|
| 69 |
+
2. **Parse Token**: Split into header, payload, signature
|
| 70 |
+
3. **Verify Signature**:
|
| 71 |
+
- Recompute signature using BETTER_AUTH_SECRET
|
| 72 |
+
- Compare with provided signature
|
| 73 |
+
- Reject if signatures don't match
|
| 74 |
+
4. **Verify Expiration**:
|
| 75 |
+
- Check `exp` claim against current Unix timestamp
|
| 76 |
+
- Reject if `exp < current_time`
|
| 77 |
+
5. **Verify Required Claims**:
|
| 78 |
+
- Ensure `sub`, `email`, `iat`, `exp`, `iss` are present
|
| 79 |
+
- Reject if any required claim is missing
|
| 80 |
+
6. **Extract User ID**:
|
| 81 |
+
- Parse `sub` claim as integer
|
| 82 |
+
- Use as authenticated user ID for data filtering
|
| 83 |
+
|
| 84 |
+
### Validation Checklist
|
| 85 |
+
|
| 86 |
+
- [ ] Token format is valid (3 parts separated by periods)
|
| 87 |
+
- [ ] Header contains correct algorithm (HS256)
|
| 88 |
+
- [ ] Signature is valid (matches recomputed signature)
|
| 89 |
+
- [ ] Token is not expired (exp > current_time)
|
| 90 |
+
- [ ] All required claims are present
|
| 91 |
+
- [ ] User ID (sub) is a valid integer
|
| 92 |
+
- [ ] Email is a valid email format
|
| 93 |
+
|
| 94 |
+
## Error Responses
|
| 95 |
+
|
| 96 |
+
### Missing Token
|
| 97 |
+
|
| 98 |
+
**HTTP Status**: 401 Unauthorized
|
| 99 |
+
|
| 100 |
+
```json
|
| 101 |
+
{
|
| 102 |
+
"detail": "Not authenticated",
|
| 103 |
+
"error_code": "TOKEN_MISSING"
|
| 104 |
+
}
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
### Invalid Signature
|
| 108 |
+
|
| 109 |
+
**HTTP Status**: 401 Unauthorized
|
| 110 |
+
|
| 111 |
+
```json
|
| 112 |
+
{
|
| 113 |
+
"detail": "Invalid token",
|
| 114 |
+
"error_code": "TOKEN_INVALID"
|
| 115 |
+
}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Expired Token
|
| 119 |
+
|
| 120 |
+
**HTTP Status**: 401 Unauthorized
|
| 121 |
+
|
| 122 |
+
```json
|
| 123 |
+
{
|
| 124 |
+
"detail": "Token has expired",
|
| 125 |
+
"error_code": "TOKEN_EXPIRED"
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### Malformed Token
|
| 130 |
+
|
| 131 |
+
**HTTP Status**: 401 Unauthorized
|
| 132 |
+
|
| 133 |
+
```json
|
| 134 |
+
{
|
| 135 |
+
"detail": "Invalid token format",
|
| 136 |
+
"error_code": "TOKEN_MALFORMED"
|
| 137 |
+
}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
### Missing Claims
|
| 141 |
+
|
| 142 |
+
**HTTP Status**: 401 Unauthorized
|
| 143 |
+
|
| 144 |
+
```json
|
| 145 |
+
{
|
| 146 |
+
"detail": "Invalid token payload",
|
| 147 |
+
"error_code": "TOKEN_INVALID_PAYLOAD"
|
| 148 |
+
}
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
## Security Considerations
|
| 152 |
+
|
| 153 |
+
### Secret Management
|
| 154 |
+
|
| 155 |
+
- **BETTER_AUTH_SECRET** must be:
|
| 156 |
+
- At least 32 characters long
|
| 157 |
+
- Cryptographically random
|
| 158 |
+
- Identical in frontend and backend
|
| 159 |
+
- Stored in environment variables (never committed to git)
|
| 160 |
+
- Rotated periodically in production
|
| 161 |
+
|
| 162 |
+
### Token Lifetime
|
| 163 |
+
|
| 164 |
+
- **Expiration**: 7 days (604800 seconds)
|
| 165 |
+
- **Rationale**: Balances security with UX (no refresh tokens in this spec)
|
| 166 |
+
- **Recommendation**: Implement refresh tokens in future iterations for shorter access token lifetime
|
| 167 |
+
|
| 168 |
+
### Transport Security
|
| 169 |
+
|
| 170 |
+
- **HTTPS Required**: Tokens must only be transmitted over HTTPS in production
|
| 171 |
+
- **Header Only**: Tokens should never be in URL query parameters
|
| 172 |
+
- **httpOnly Cookies**: Frontend stores tokens in httpOnly cookies to prevent XSS
|
| 173 |
+
|
| 174 |
+
### Attack Mitigation
|
| 175 |
+
|
| 176 |
+
| Attack | Mitigation |
|
| 177 |
+
|--------|------------|
|
| 178 |
+
| Token Theft | HTTPS only, httpOnly cookies |
|
| 179 |
+
| Token Replay | Short expiration (7 days), HTTPS |
|
| 180 |
+
| Signature Forgery | Strong secret (32+ chars), HS256 algorithm |
|
| 181 |
+
| XSS | httpOnly cookies, CSP headers |
|
| 182 |
+
| CSRF | SameSite cookie attribute, CORS configuration |
|
| 183 |
+
|
| 184 |
+
## Implementation Examples
|
| 185 |
+
|
| 186 |
+
### Backend Verification (Python/FastAPI)
|
| 187 |
+
|
| 188 |
+
```python
|
| 189 |
+
import jwt
|
| 190 |
+
from datetime import datetime
|
| 191 |
+
from fastapi import HTTPException, status
|
| 192 |
+
|
| 193 |
+
def verify_jwt_token(token: str, secret: str) -> dict:
|
| 194 |
+
"""
|
| 195 |
+
Verify JWT token and return payload.
|
| 196 |
+
|
| 197 |
+
Args:
|
| 198 |
+
token: JWT token string
|
| 199 |
+
secret: BETTER_AUTH_SECRET
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
dict: Token payload with claims
|
| 203 |
+
|
| 204 |
+
Raises:
|
| 205 |
+
HTTPException: 401 if token is invalid or expired
|
| 206 |
+
"""
|
| 207 |
+
try:
|
| 208 |
+
# Verify signature and decode
|
| 209 |
+
payload = jwt.decode(
|
| 210 |
+
token,
|
| 211 |
+
secret,
|
| 212 |
+
algorithms=["HS256"],
|
| 213 |
+
options={
|
| 214 |
+
"verify_signature": True,
|
| 215 |
+
"verify_exp": True,
|
| 216 |
+
"require": ["sub", "email", "iat", "exp", "iss"]
|
| 217 |
+
}
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
# Validate issuer
|
| 221 |
+
if payload.get("iss") != "better-auth":
|
| 222 |
+
raise HTTPException(
|
| 223 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 224 |
+
detail="Invalid token issuer"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
return payload
|
| 228 |
+
|
| 229 |
+
except jwt.ExpiredSignatureError:
|
| 230 |
+
raise HTTPException(
|
| 231 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 232 |
+
detail="Token has expired",
|
| 233 |
+
headers={"WWW-Authenticate": "Bearer"}
|
| 234 |
+
)
|
| 235 |
+
except jwt.InvalidTokenError as e:
|
| 236 |
+
raise HTTPException(
|
| 237 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 238 |
+
detail="Invalid token",
|
| 239 |
+
headers={"WWW-Authenticate": "Bearer"}
|
| 240 |
+
)
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
### Frontend Token Inclusion (TypeScript)
|
| 244 |
+
|
| 245 |
+
```typescript
|
| 246 |
+
// Automatically include token in API requests
|
| 247 |
+
async function fetchAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
| 248 |
+
const session = await auth() // Better Auth session
|
| 249 |
+
const token = session?.token
|
| 250 |
+
|
| 251 |
+
if (!token) {
|
| 252 |
+
throw new Error('Not authenticated')
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
| 256 |
+
...options,
|
| 257 |
+
headers: {
|
| 258 |
+
'Content-Type': 'application/json',
|
| 259 |
+
'Authorization': `Bearer ${token}`,
|
| 260 |
+
...options.headers,
|
| 261 |
+
},
|
| 262 |
+
})
|
| 263 |
+
|
| 264 |
+
if (response.status === 401) {
|
| 265 |
+
// Token expired or invalid - redirect to login
|
| 266 |
+
window.location.href = '/auth/signin'
|
| 267 |
+
throw new Error('Authentication required')
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
return response.json()
|
| 271 |
+
}
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
## Testing Checklist
|
| 275 |
+
|
| 276 |
+
- [ ] Valid token with correct signature is accepted
|
| 277 |
+
- [ ] Expired token is rejected with 401
|
| 278 |
+
- [ ] Token with invalid signature is rejected with 401
|
| 279 |
+
- [ ] Token with missing claims is rejected with 401
|
| 280 |
+
- [ ] Token with wrong algorithm is rejected with 401
|
| 281 |
+
- [ ] Request without token is rejected with 401
|
| 282 |
+
- [ ] Malformed token (not 3 parts) is rejected with 401
|
| 283 |
+
- [ ] Token with non-integer user ID is rejected with 401
|
| 284 |
+
|
| 285 |
+
## Token Lifecycle
|
| 286 |
+
|
| 287 |
+
```
|
| 288 |
+
1. User Sign In
|
| 289 |
+
↓
|
| 290 |
+
2. Better Auth validates credentials
|
| 291 |
+
↓
|
| 292 |
+
3. Better Auth creates JWT with user claims
|
| 293 |
+
↓
|
| 294 |
+
4. Better Auth signs JWT with BETTER_AUTH_SECRET
|
| 295 |
+
↓
|
| 296 |
+
5. Frontend receives token
|
| 297 |
+
↓
|
| 298 |
+
6. Frontend stores token in httpOnly cookie
|
| 299 |
+
↓
|
| 300 |
+
7. Frontend includes token in API requests
|
| 301 |
+
↓
|
| 302 |
+
8. Backend extracts token from Authorization header
|
| 303 |
+
↓
|
| 304 |
+
9. Backend verifies signature and expiration
|
| 305 |
+
↓
|
| 306 |
+
10. Backend extracts user_id from 'sub' claim
|
| 307 |
+
↓
|
| 308 |
+
11. Backend filters data by user_id
|
| 309 |
+
↓
|
| 310 |
+
12. Token expires after 7 days
|
| 311 |
+
↓
|
| 312 |
+
13. User must sign in again
|
| 313 |
+
```
|
| 314 |
+
|
| 315 |
+
## Future Enhancements (Out of Scope)
|
| 316 |
+
|
| 317 |
+
- Refresh tokens for shorter access token lifetime
|
| 318 |
+
- Token revocation/blacklist mechanism
|
| 319 |
+
- Multiple device session management
|
| 320 |
+
- Token rotation on refresh
|
| 321 |
+
- Asymmetric signing (RS256) for microservices
|
specs/001-auth-security/data-model.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Model: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-auth-security
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Phase**: 1 - Design
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This document defines the data entities and their relationships for the authentication and API security feature. The primary entity is the User, which will be extended to support password-based authentication.
|
| 10 |
+
|
| 11 |
+
## Entities
|
| 12 |
+
|
| 13 |
+
### User (Modified)
|
| 14 |
+
|
| 15 |
+
**Purpose**: Represents a registered user account with authentication credentials.
|
| 16 |
+
|
| 17 |
+
**Table**: `users`
|
| 18 |
+
|
| 19 |
+
**Fields**:
|
| 20 |
+
|
| 21 |
+
| Field | Type | Constraints | Description |
|
| 22 |
+
|-------|------|-------------|-------------|
|
| 23 |
+
| id | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique user identifier |
|
| 24 |
+
| email | String(255) | UNIQUE, NOT NULL, INDEX | User's email address (used for login) |
|
| 25 |
+
| name | String(100) | NOT NULL | User's display name |
|
| 26 |
+
| password_hash | String(255) | NOT NULL | Bcrypt-hashed password (NEW) |
|
| 27 |
+
| created_at | DateTime | NOT NULL, DEFAULT NOW() | Account creation timestamp |
|
| 28 |
+
| updated_at | DateTime | NOT NULL, DEFAULT NOW() | Last update timestamp |
|
| 29 |
+
|
| 30 |
+
**Indexes**:
|
| 31 |
+
- PRIMARY KEY on `id`
|
| 32 |
+
- UNIQUE INDEX on `email`
|
| 33 |
+
- INDEX on `created_at` (for sorting/filtering)
|
| 34 |
+
|
| 35 |
+
**Relationships**:
|
| 36 |
+
- One-to-Many with Task (one user has many tasks)
|
| 37 |
+
|
| 38 |
+
**Validation Rules**:
|
| 39 |
+
- Email must be valid RFC 5322 format
|
| 40 |
+
- Email must be unique (enforced at database level)
|
| 41 |
+
- Password must be hashed with bcrypt before storage
|
| 42 |
+
- Name must be 1-100 characters
|
| 43 |
+
- password_hash must be exactly 60 characters (bcrypt output length)
|
| 44 |
+
|
| 45 |
+
**State Transitions**: None (users don't have state in this spec)
|
| 46 |
+
|
| 47 |
+
**Security Considerations**:
|
| 48 |
+
- Password is never stored in plain text
|
| 49 |
+
- Password hash uses bcrypt with cost factor 12
|
| 50 |
+
- Email is indexed for fast lookup during authentication
|
| 51 |
+
- created_at and updated_at track account lifecycle
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
### Task (Existing - No Changes)
|
| 56 |
+
|
| 57 |
+
**Purpose**: Represents a to-do item owned by a user.
|
| 58 |
+
|
| 59 |
+
**Table**: `tasks`
|
| 60 |
+
|
| 61 |
+
**Fields**:
|
| 62 |
+
|
| 63 |
+
| Field | Type | Constraints | Description |
|
| 64 |
+
|-------|------|-------------|-------------|
|
| 65 |
+
| id | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique task identifier |
|
| 66 |
+
| user_id | Integer | FOREIGN KEY(users.id), NOT NULL, INDEX | Owner of the task |
|
| 67 |
+
| title | String(200) | NOT NULL | Task title |
|
| 68 |
+
| description | String(1000) | NULLABLE | Task description |
|
| 69 |
+
| completed | Boolean | NOT NULL, DEFAULT FALSE, INDEX | Completion status |
|
| 70 |
+
| created_at | DateTime | NOT NULL, DEFAULT NOW(), INDEX | Creation timestamp |
|
| 71 |
+
| updated_at | DateTime | NOT NULL, DEFAULT NOW() | Last update timestamp |
|
| 72 |
+
|
| 73 |
+
**Relationships**:
|
| 74 |
+
- Many-to-One with User (many tasks belong to one user)
|
| 75 |
+
|
| 76 |
+
**Security Note**: All task queries MUST filter by authenticated user_id to enforce data isolation.
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
### JWT Token (Virtual Entity - Not Stored)
|
| 81 |
+
|
| 82 |
+
**Purpose**: Represents an authentication token issued by Better Auth and verified by the backend.
|
| 83 |
+
|
| 84 |
+
**Storage**: Not persisted in database (stateless authentication)
|
| 85 |
+
|
| 86 |
+
**Structure** (JWT Payload):
|
| 87 |
+
|
| 88 |
+
| Claim | Type | Description |
|
| 89 |
+
|-------|------|-------------|
|
| 90 |
+
| sub | String | User ID (subject) |
|
| 91 |
+
| email | String | User's email address |
|
| 92 |
+
| iat | Integer | Issued at timestamp (Unix epoch) |
|
| 93 |
+
| exp | Integer | Expiration timestamp (Unix epoch, iat + 7 days) |
|
| 94 |
+
| iss | String | Issuer (Better Auth) |
|
| 95 |
+
|
| 96 |
+
**Validation Rules**:
|
| 97 |
+
- Token must be signed with BETTER_AUTH_SECRET using HS256
|
| 98 |
+
- Token must not be expired (exp > current time)
|
| 99 |
+
- Token must contain valid sub (user ID)
|
| 100 |
+
- Token signature must be valid
|
| 101 |
+
|
| 102 |
+
**Lifecycle**:
|
| 103 |
+
1. Issued by Better Auth upon successful authentication
|
| 104 |
+
2. Included in Authorization header for API requests
|
| 105 |
+
3. Verified by backend on every protected endpoint
|
| 106 |
+
4. Expires after 7 days (no refresh in this spec)
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## Database Migrations
|
| 111 |
+
|
| 112 |
+
### Migration 002: Add User Password Field
|
| 113 |
+
|
| 114 |
+
**File**: `backend/alembic/versions/002_add_user_password.py`
|
| 115 |
+
|
| 116 |
+
**Changes**:
|
| 117 |
+
- Add `password_hash` column to `users` table
|
| 118 |
+
- Column is NOT NULL (existing users will need password set)
|
| 119 |
+
|
| 120 |
+
**Upgrade**:
|
| 121 |
+
```sql
|
| 122 |
+
ALTER TABLE users ADD COLUMN password_hash VARCHAR(255) NOT NULL;
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
**Downgrade**:
|
| 126 |
+
```sql
|
| 127 |
+
ALTER TABLE users DROP COLUMN password_hash;
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
**Data Migration Note**: If existing users exist without passwords, they will need to be handled separately (e.g., force password reset on first login, or seed with temporary passwords).
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## Entity Relationships Diagram
|
| 135 |
+
|
| 136 |
+
```
|
| 137 |
+
┌─────────────────────────────────────┐
|
| 138 |
+
│ User │
|
| 139 |
+
├─────────────────────────────────────┤
|
| 140 |
+
│ id (PK) │
|
| 141 |
+
│ email (UNIQUE) │
|
| 142 |
+
│ name │
|
| 143 |
+
│ password_hash (NEW) │
|
| 144 |
+
│ created_at │
|
| 145 |
+
│ updated_at │
|
| 146 |
+
└────────────────────────────────────��┘
|
| 147 |
+
│
|
| 148 |
+
│ 1:N
|
| 149 |
+
│
|
| 150 |
+
▼
|
| 151 |
+
┌─────────────────────────────────────┐
|
| 152 |
+
│ Task │
|
| 153 |
+
├─────────────────────────────────────┤
|
| 154 |
+
│ id (PK) │
|
| 155 |
+
│ user_id (FK → User.id) │
|
| 156 |
+
│ title │
|
| 157 |
+
│ description │
|
| 158 |
+
│ completed │
|
| 159 |
+
│ created_at │
|
| 160 |
+
│ updated_at │
|
| 161 |
+
└─────────────────────────────────────┘
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## Data Access Patterns
|
| 167 |
+
|
| 168 |
+
### Authentication Flow
|
| 169 |
+
1. User submits email + password to Better Auth
|
| 170 |
+
2. Better Auth verifies credentials against users table
|
| 171 |
+
3. Better Auth issues JWT token with user_id in `sub` claim
|
| 172 |
+
4. Frontend stores token in httpOnly cookie
|
| 173 |
+
|
| 174 |
+
### API Request Flow
|
| 175 |
+
1. Frontend includes JWT in Authorization header
|
| 176 |
+
2. Backend extracts token from header
|
| 177 |
+
3. Backend verifies token signature and expiration
|
| 178 |
+
4. Backend extracts user_id from `sub` claim
|
| 179 |
+
5. Backend filters data by user_id
|
| 180 |
+
|
| 181 |
+
### Task Query Pattern
|
| 182 |
+
```sql
|
| 183 |
+
-- All task queries MUST include user_id filter
|
| 184 |
+
SELECT * FROM tasks WHERE user_id = :authenticated_user_id;
|
| 185 |
+
|
| 186 |
+
-- Example: Get user's completed tasks
|
| 187 |
+
SELECT * FROM tasks
|
| 188 |
+
WHERE user_id = :authenticated_user_id
|
| 189 |
+
AND completed = true
|
| 190 |
+
ORDER BY created_at DESC;
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
## Validation Summary
|
| 196 |
+
|
| 197 |
+
### User Entity
|
| 198 |
+
- ✅ Email format validation (RFC 5322)
|
| 199 |
+
- ✅ Email uniqueness (database constraint)
|
| 200 |
+
- ✅ Password strength (minimum 8 chars, complexity rules)
|
| 201 |
+
- ✅ Password hashing (bcrypt, cost 12)
|
| 202 |
+
- ✅ Name length (1-100 characters)
|
| 203 |
+
|
| 204 |
+
### JWT Token
|
| 205 |
+
- ✅ Signature validation (HS256 with shared secret)
|
| 206 |
+
- ✅ Expiration validation (exp claim)
|
| 207 |
+
- ✅ Required claims present (sub, email, iat, exp)
|
| 208 |
+
- ✅ User ID extraction (from sub claim)
|
| 209 |
+
|
| 210 |
+
### Task Entity (Security)
|
| 211 |
+
- ✅ User ownership validation (user_id matches token)
|
| 212 |
+
- ✅ Query filtering (all queries include user_id)
|
| 213 |
+
- ✅ Authorization checks (prevent cross-user access)
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
## Performance Considerations
|
| 218 |
+
|
| 219 |
+
### Indexes
|
| 220 |
+
- `users.email` - UNIQUE INDEX for fast authentication lookups
|
| 221 |
+
- `tasks.user_id` - INDEX for fast user task queries
|
| 222 |
+
- `tasks.completed` - INDEX for filtering by status
|
| 223 |
+
- `tasks.created_at` - INDEX for sorting
|
| 224 |
+
|
| 225 |
+
### Query Optimization
|
| 226 |
+
- JWT verification is stateless (no database lookup)
|
| 227 |
+
- User lookup by email is O(1) with index
|
| 228 |
+
- Task queries filtered by indexed user_id
|
| 229 |
+
- Pagination supported for large task lists
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
## Security Checklist
|
| 234 |
+
|
| 235 |
+
- [x] Passwords never stored in plain text
|
| 236 |
+
- [x] Bcrypt hashing with appropriate cost factor
|
| 237 |
+
- [x] Email uniqueness enforced at database level
|
| 238 |
+
- [x] JWT tokens contain minimal claims (no sensitive data)
|
| 239 |
+
- [x] Token expiration enforced (7 days)
|
| 240 |
+
- [x] User ID extracted from validated token only
|
| 241 |
+
- [x] All task queries filtered by authenticated user
|
| 242 |
+
- [x] Foreign key constraints prevent orphaned tasks
|
specs/001-auth-security/plan.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Branch**: `001-auth-security` | **Date**: 2026-01-09 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
**Input**: Feature specification from `/specs/001-auth-security/spec.md`
|
| 5 |
+
|
| 6 |
+
**Note**: This template is filled in by the `/sp.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
| 7 |
+
|
| 8 |
+
## Summary
|
| 9 |
+
|
| 10 |
+
Implement secure user authentication using Better Auth on the frontend and JWT-based authorization on the backend. The system will enforce stateless authentication where Better Auth issues JWT tokens upon successful login, and the backend verifies these tokens on every API request to ensure users can only access their own data.
|
| 11 |
+
|
| 12 |
+
## Technical Context
|
| 13 |
+
|
| 14 |
+
**Language/Version**: Python 3.11+ (backend), TypeScript 5.3+ (frontend)
|
| 15 |
+
**Primary Dependencies**:
|
| 16 |
+
- Frontend: Next.js 16+, React 18, Better Auth (to be added), Tailwind CSS
|
| 17 |
+
- Backend: FastAPI 0.104+, SQLModel 0.0.14, PyJWT (to be added), Pydantic 2.5+
|
| 18 |
+
|
| 19 |
+
**Storage**: Neon Serverless PostgreSQL (existing users table needs password field)
|
| 20 |
+
**Testing**: pytest (backend), Jest/React Testing Library (frontend - to be configured)
|
| 21 |
+
**Target Platform**: Web application (Linux/Docker backend, browser frontend)
|
| 22 |
+
**Project Type**: Web (monorepo with separate frontend/ and backend/ directories)
|
| 23 |
+
**Performance Goals**:
|
| 24 |
+
- Token verification: <50ms per request
|
| 25 |
+
- Authentication flow: <5 seconds end-to-end
|
| 26 |
+
- Support 100+ concurrent authentication requests
|
| 27 |
+
|
| 28 |
+
**Constraints**:
|
| 29 |
+
- Stateless backend (no server-side session storage)
|
| 30 |
+
- Shared secret (BETTER_AUTH_SECRET) must be identical in frontend and backend
|
| 31 |
+
- JWT tokens must include user_id and email claims
|
| 32 |
+
- All task API endpoints must require valid JWT
|
| 33 |
+
- Token expiry: 7 days (resolved in research.md - balances security with UX without refresh tokens)
|
| 34 |
+
|
| 35 |
+
**Scale/Scope**:
|
| 36 |
+
- Multi-user application (100+ users initially)
|
| 37 |
+
- 5 authentication-related endpoints (signup, signin, token verification)
|
| 38 |
+
- All existing task endpoints (6) require JWT protection
|
| 39 |
+
|
| 40 |
+
## Constitution Check
|
| 41 |
+
|
| 42 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 43 |
+
|
| 44 |
+
### Principle I: User-Centric Functionality
|
| 45 |
+
✅ **PASS** - Authentication directly serves end-users by securing their data and enabling personalized task management. JWT-based authorization ensures each user only accesses their own tasks.
|
| 46 |
+
|
| 47 |
+
### Principle II: Spec-Driven Development
|
| 48 |
+
✅ **PASS** - This plan follows the Spec-Kit Plus workflow. All implementation will reference `/specs/001-auth-security/spec.md` and generated artifacts (data-model.md, contracts/).
|
| 49 |
+
|
| 50 |
+
### Principle III: Security & Data Privacy
|
| 51 |
+
✅ **PASS** - Core focus of this feature:
|
| 52 |
+
- JWT authentication on all task endpoints
|
| 53 |
+
- BETTER_AUTH_SECRET managed via environment variables
|
| 54 |
+
- User data filtered by authenticated user ID
|
| 55 |
+
- 401 responses for unauthorized requests
|
| 56 |
+
- No hardcoded secrets
|
| 57 |
+
|
| 58 |
+
### Principle IV: Scalable Architecture
|
| 59 |
+
✅ **PASS** - Stateless JWT design enables horizontal scaling:
|
| 60 |
+
- No server-side session storage
|
| 61 |
+
- Backend remains stateless
|
| 62 |
+
- Token verification is fast (<50ms target)
|
| 63 |
+
- Database queries use indexed user_id field
|
| 64 |
+
|
| 65 |
+
### Principle V: Maintainable & Consistent Code
|
| 66 |
+
✅ **PASS** - Follows established patterns:
|
| 67 |
+
- FastAPI dependency injection for JWT verification
|
| 68 |
+
- Better Auth integration on frontend
|
| 69 |
+
- Consistent error handling (401 for auth failures)
|
| 70 |
+
- Modular authentication middleware
|
| 71 |
+
|
| 72 |
+
### Key Standards Compliance
|
| 73 |
+
|
| 74 |
+
**API Compliance**: ✅ All authentication endpoints will be documented in `/specs/001-auth-security/contracts/`
|
| 75 |
+
|
| 76 |
+
**Database Integrity**: ✅ Users table already exists; will add password_hash field with proper constraints
|
| 77 |
+
|
| 78 |
+
**Frontend Quality**: ✅ Better Auth integration follows Next.js App Router patterns
|
| 79 |
+
|
| 80 |
+
**Authentication**: ✅ Core requirement - Better Auth + JWT as specified
|
| 81 |
+
|
| 82 |
+
**Spec Adherence**: ✅ All implementation references spec.md
|
| 83 |
+
|
| 84 |
+
### Gate Result: ✅ PASS - Proceed to Phase 0 Research
|
| 85 |
+
|
| 86 |
+
## Project Structure
|
| 87 |
+
|
| 88 |
+
### Documentation (this feature)
|
| 89 |
+
|
| 90 |
+
```text
|
| 91 |
+
specs/001-auth-security/
|
| 92 |
+
├── plan.md # This file (/sp.plan command output)
|
| 93 |
+
├── research.md # Phase 0 output (/sp.plan command)
|
| 94 |
+
├── data-model.md # Phase 1 output (/sp.plan command)
|
| 95 |
+
├── quickstart.md # Phase 1 output (/sp.plan command)
|
| 96 |
+
├── contracts/ # Phase 1 output (/sp.plan command)
|
| 97 |
+
│ ├── auth-endpoints.yaml
|
| 98 |
+
│ └── jwt-schema.yaml
|
| 99 |
+
└── tasks.md # Phase 2 output (/sp.tasks command - NOT created by /sp.plan)
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### Source Code (repository root)
|
| 103 |
+
|
| 104 |
+
```text
|
| 105 |
+
backend/
|
| 106 |
+
├── src/
|
| 107 |
+
│ ├── api/
|
| 108 |
+
│ │ ├── deps.py # JWT verification dependency (modify)
|
| 109 |
+
│ │ └── routes/
|
| 110 |
+
│ │ ├── auth.py # New: signup, signin endpoints
|
| 111 |
+
│ │ └── tasks.py # Existing: already uses get_current_user
|
| 112 |
+
│ ├── core/
|
| 113 |
+
│ │ ├── config.py # Add BETTER_AUTH_SECRET (modify)
|
| 114 |
+
│ │ ├── database.py # Existing
|
| 115 |
+
│ │ └── security.py # New: JWT verification logic
|
| 116 |
+
│ ├── models/
|
| 117 |
+
│ │ ├── user.py # Add password_hash field (modify)
|
| 118 |
+
│ │ └── task.py # Existing
|
| 119 |
+
│ ├── schemas/
|
| 120 |
+
│ │ ├── auth.py # New: signup, signin, token schemas
|
| 121 |
+
│ │ └── task.py # Existing
|
| 122 |
+
│ └── services/
|
| 123 |
+
│ ├── auth_service.py # New: authentication business logic
|
| 124 |
+
│ └── task_service.py # Existing
|
| 125 |
+
├── alembic/
|
| 126 |
+
│ └── versions/
|
| 127 |
+
│ └── 002_add_user_password.py # New migration
|
| 128 |
+
└── tests/
|
| 129 |
+
├── test_auth.py # New: authentication tests
|
| 130 |
+
└── test_tasks.py # Existing: update to test JWT protection
|
| 131 |
+
|
| 132 |
+
frontend/
|
| 133 |
+
├── src/
|
| 134 |
+
│ ├── app/
|
| 135 |
+
│ │ ├── auth/
|
| 136 |
+
│ │ │ ├── signin/
|
| 137 |
+
│ │ │ │ └── page.tsx # New: sign-in page
|
| 138 |
+
│ │ │ └── signup/
|
| 139 |
+
│ │ │ └── page.tsx # New: sign-up page
|
| 140 |
+
│ │ ├── layout.tsx # Modify: add auth provider
|
| 141 |
+
│ │ └── page.tsx # Existing: task list (protect)
|
| 142 |
+
│ ├── components/
|
| 143 |
+
│ │ ├── auth/
|
| 144 |
+
│ │ │ ├── SignInForm.tsx # New: sign-in form
|
| 145 |
+
│ │ │ └── SignUpForm.tsx # New: sign-up form
|
| 146 |
+
│ │ └── tasks/ # Existing components
|
| 147 |
+
│ ├── lib/
|
| 148 |
+
│ │ ├── api.ts # Modify: add JWT to headers
|
| 149 |
+
│ │ ├── auth.ts # New: Better Auth configuration
|
| 150 |
+
│ │ └── types.ts # Existing
|
| 151 |
+
│ └── providers/
|
| 152 |
+
│ └── AuthProvider.tsx # New: auth context provider
|
| 153 |
+
└── tests/
|
| 154 |
+
└── auth/ # New: authentication tests
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
**Structure Decision**: Web application (Option 2) with separate backend/ and frontend/ directories. This is a monorepo structure where:
|
| 158 |
+
- Backend handles JWT verification and API protection
|
| 159 |
+
- Frontend handles Better Auth integration and token management
|
| 160 |
+
- Both share BETTER_AUTH_SECRET via environment variables
|
| 161 |
+
|
| 162 |
+
## Complexity Tracking
|
| 163 |
+
|
| 164 |
+
> **Fill ONLY if Constitution Check has violations that must be justified**
|
| 165 |
+
|
| 166 |
+
No constitutional violations detected. All complexity is justified by security requirements and follows established patterns.
|
specs/001-auth-security/quickstart.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-auth-security
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
|
| 6 |
+
## Overview
|
| 7 |
+
|
| 8 |
+
This guide provides step-by-step instructions for setting up and testing the authentication and API security feature. Follow these steps to configure Better Auth on the frontend and JWT verification on the backend.
|
| 9 |
+
|
| 10 |
+
## Prerequisites
|
| 11 |
+
|
| 12 |
+
- Node.js 18+ and npm installed
|
| 13 |
+
- Python 3.11+ installed
|
| 14 |
+
- PostgreSQL database (Neon Serverless) accessible
|
| 15 |
+
- Git repository cloned
|
| 16 |
+
- Existing task CRUD functionality working (from Spec 001-task-crud)
|
| 17 |
+
|
| 18 |
+
## Setup Instructions
|
| 19 |
+
|
| 20 |
+
### 1. Environment Configuration
|
| 21 |
+
|
| 22 |
+
#### Backend Environment Variables
|
| 23 |
+
|
| 24 |
+
Create or update `backend/.env`:
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
# Database
|
| 28 |
+
DATABASE_URL=postgresql://user:password@host:5432/database
|
| 29 |
+
|
| 30 |
+
# Authentication
|
| 31 |
+
BETTER_AUTH_SECRET=your-secret-key-min-32-characters-long-and-random
|
| 32 |
+
|
| 33 |
+
# Application
|
| 34 |
+
APP_NAME=Task CRUD API
|
| 35 |
+
DEBUG=True
|
| 36 |
+
CORS_ORIGINS=http://localhost:3000
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**Important**: Generate a strong random secret for `BETTER_AUTH_SECRET`:
|
| 40 |
+
```bash
|
| 41 |
+
# Generate a secure random secret (32+ characters)
|
| 42 |
+
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
#### Frontend Environment Variables
|
| 46 |
+
|
| 47 |
+
Create or update `frontend/.env.local`:
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
# API Configuration
|
| 51 |
+
NEXT_PUBLIC_API_URL=http://localhost:8000
|
| 52 |
+
|
| 53 |
+
# Authentication (MUST match backend secret)
|
| 54 |
+
BETTER_AUTH_SECRET=your-secret-key-min-32-characters-long-and-random
|
| 55 |
+
|
| 56 |
+
# Better Auth Database (optional - uses same as backend)
|
| 57 |
+
DATABASE_URL=postgresql://user:password@host:5432/database
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
**Critical**: The `BETTER_AUTH_SECRET` must be **identical** in both frontend and backend.
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
### 2. Install Dependencies
|
| 65 |
+
|
| 66 |
+
#### Backend Dependencies
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
cd backend
|
| 70 |
+
|
| 71 |
+
# Add new dependencies to requirements.txt
|
| 72 |
+
echo "PyJWT==2.8.0" >> requirements.txt
|
| 73 |
+
echo "passlib[bcrypt]==1.7.4" >> requirements.txt
|
| 74 |
+
echo "python-multipart==0.0.6" >> requirements.txt
|
| 75 |
+
|
| 76 |
+
# Install all dependencies
|
| 77 |
+
pip install -r requirements.txt
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
#### Frontend Dependencies
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
cd frontend
|
| 84 |
+
|
| 85 |
+
# Install Better Auth
|
| 86 |
+
npm install better-auth @better-auth/react
|
| 87 |
+
|
| 88 |
+
# Install development dependencies (if not already installed)
|
| 89 |
+
npm install --save-dev @types/node @types/react @types/react-dom
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
### 3. Database Migration
|
| 95 |
+
|
| 96 |
+
#### Run Migration to Add Password Field
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
cd backend
|
| 100 |
+
|
| 101 |
+
# Create migration
|
| 102 |
+
alembic revision --autogenerate -m "Add password_hash to users"
|
| 103 |
+
|
| 104 |
+
# Review the generated migration file in alembic/versions/
|
| 105 |
+
# Ensure it adds password_hash column to users table
|
| 106 |
+
|
| 107 |
+
# Apply migration
|
| 108 |
+
alembic upgrade head
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
**Expected Migration**:
|
| 112 |
+
```python
|
| 113 |
+
def upgrade():
|
| 114 |
+
op.add_column('users', sa.Column('password_hash', sa.String(255), nullable=False))
|
| 115 |
+
|
| 116 |
+
def downgrade():
|
| 117 |
+
op.drop_column('users', 'password_hash')
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
### 4. Backend Implementation
|
| 123 |
+
|
| 124 |
+
#### Create Security Module
|
| 125 |
+
|
| 126 |
+
Create `backend/src/core/security.py`:
|
| 127 |
+
|
| 128 |
+
```python
|
| 129 |
+
import jwt
|
| 130 |
+
from datetime import datetime, timedelta
|
| 131 |
+
from passlib.context import CryptContext
|
| 132 |
+
from fastapi import HTTPException, status
|
| 133 |
+
from src.core.config import settings
|
| 134 |
+
|
| 135 |
+
# Password hashing
|
| 136 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 137 |
+
|
| 138 |
+
def hash_password(password: str) -> str:
|
| 139 |
+
"""Hash a password using bcrypt."""
|
| 140 |
+
return pwd_context.hash(password)
|
| 141 |
+
|
| 142 |
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 143 |
+
"""Verify a password against its hash."""
|
| 144 |
+
return pwd_context.verify(plain_password, hashed_password)
|
| 145 |
+
|
| 146 |
+
def create_jwt_token(user_id: int, email: str) -> str:
|
| 147 |
+
"""Create a JWT token for a user."""
|
| 148 |
+
payload = {
|
| 149 |
+
"sub": str(user_id),
|
| 150 |
+
"email": email,
|
| 151 |
+
"iat": datetime.utcnow(),
|
| 152 |
+
"exp": datetime.utcnow() + timedelta(days=7),
|
| 153 |
+
"iss": "better-auth"
|
| 154 |
+
}
|
| 155 |
+
return jwt.encode(payload, settings.BETTER_AUTH_SECRET, algorithm="HS256")
|
| 156 |
+
|
| 157 |
+
def verify_jwt_token(token: str) -> dict:
|
| 158 |
+
"""Verify and decode a JWT token."""
|
| 159 |
+
try:
|
| 160 |
+
payload = jwt.decode(
|
| 161 |
+
token,
|
| 162 |
+
settings.BETTER_AUTH_SECRET,
|
| 163 |
+
algorithms=["HS256"]
|
| 164 |
+
)
|
| 165 |
+
return payload
|
| 166 |
+
except jwt.ExpiredSignatureError:
|
| 167 |
+
raise HTTPException(
|
| 168 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 169 |
+
detail="Token has expired"
|
| 170 |
+
)
|
| 171 |
+
except jwt.InvalidTokenError:
|
| 172 |
+
raise HTTPException(
|
| 173 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 174 |
+
detail="Invalid token"
|
| 175 |
+
)
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
#### Update Dependencies
|
| 179 |
+
|
| 180 |
+
Modify `backend/src/api/deps.py`:
|
| 181 |
+
|
| 182 |
+
```python
|
| 183 |
+
from fastapi import Depends, HTTPException, status
|
| 184 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 185 |
+
from sqlmodel import Session
|
| 186 |
+
from src.core.database import get_session
|
| 187 |
+
from src.core.security import verify_jwt_token
|
| 188 |
+
|
| 189 |
+
security = HTTPBearer()
|
| 190 |
+
|
| 191 |
+
def get_db() -> Generator[Session, None, None]:
|
| 192 |
+
"""Get database session dependency."""
|
| 193 |
+
yield from get_session()
|
| 194 |
+
|
| 195 |
+
def get_current_user(
|
| 196 |
+
credentials: HTTPAuthorizationCredentials = Depends(security)
|
| 197 |
+
) -> int:
|
| 198 |
+
"""
|
| 199 |
+
Get current user ID from JWT token.
|
| 200 |
+
Extracts and verifies JWT from Authorization header.
|
| 201 |
+
"""
|
| 202 |
+
token = credentials.credentials
|
| 203 |
+
payload = verify_jwt_token(token)
|
| 204 |
+
user_id = payload.get("sub")
|
| 205 |
+
|
| 206 |
+
if not user_id:
|
| 207 |
+
raise HTTPException(
|
| 208 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 209 |
+
detail="Invalid token payload"
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
return int(user_id)
|
| 213 |
+
```
|
| 214 |
+
|
| 215 |
+
#### Update Configuration
|
| 216 |
+
|
| 217 |
+
Modify `backend/src/core/config.py`:
|
| 218 |
+
|
| 219 |
+
```python
|
| 220 |
+
class Settings(BaseSettings):
|
| 221 |
+
# ... existing fields ...
|
| 222 |
+
|
| 223 |
+
# Authentication
|
| 224 |
+
BETTER_AUTH_SECRET: str # Remove Optional, make required
|
| 225 |
+
JWT_ALGORITHM: str = "HS256"
|
| 226 |
+
JWT_EXPIRATION_DAYS: int = 7
|
| 227 |
+
```
|
| 228 |
+
|
| 229 |
+
---
|
| 230 |
+
|
| 231 |
+
### 5. Frontend Implementation
|
| 232 |
+
|
| 233 |
+
#### Configure Better Auth
|
| 234 |
+
|
| 235 |
+
Create `frontend/src/lib/auth.ts`:
|
| 236 |
+
|
| 237 |
+
```typescript
|
| 238 |
+
import { betterAuth } from "better-auth"
|
| 239 |
+
import { jwt } from "better-auth/plugins"
|
| 240 |
+
|
| 241 |
+
export const auth = betterAuth({
|
| 242 |
+
database: {
|
| 243 |
+
provider: "postgres",
|
| 244 |
+
url: process.env.DATABASE_URL!,
|
| 245 |
+
},
|
| 246 |
+
emailAndPassword: {
|
| 247 |
+
enabled: true,
|
| 248 |
+
requireEmailVerification: false,
|
| 249 |
+
},
|
| 250 |
+
plugins: [
|
| 251 |
+
jwt({
|
| 252 |
+
secret: process.env.BETTER_AUTH_SECRET!,
|
| 253 |
+
expiresIn: "7d",
|
| 254 |
+
})
|
| 255 |
+
],
|
| 256 |
+
secret: process.env.BETTER_AUTH_SECRET!,
|
| 257 |
+
})
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
#### Update API Client
|
| 261 |
+
|
| 262 |
+
Modify `frontend/src/lib/api.ts`:
|
| 263 |
+
|
| 264 |
+
```typescript
|
| 265 |
+
import { auth } from './auth'
|
| 266 |
+
|
| 267 |
+
async function fetchAPI<T>(
|
| 268 |
+
endpoint: string,
|
| 269 |
+
options: RequestInit = {}
|
| 270 |
+
): Promise<T> {
|
| 271 |
+
const session = await auth()
|
| 272 |
+
const token = session?.token
|
| 273 |
+
|
| 274 |
+
const url = `${API_BASE_URL}${endpoint}`
|
| 275 |
+
|
| 276 |
+
const response = await fetch(url, {
|
| 277 |
+
...options,
|
| 278 |
+
headers: {
|
| 279 |
+
'Content-Type': 'application/json',
|
| 280 |
+
...(token && { 'Authorization': `Bearer ${token}` }),
|
| 281 |
+
...options.headers,
|
| 282 |
+
},
|
| 283 |
+
})
|
| 284 |
+
|
| 285 |
+
if (response.status === 401) {
|
| 286 |
+
// Redirect to login
|
| 287 |
+
if (typeof window !== 'undefined') {
|
| 288 |
+
window.location.href = '/auth/signin'
|
| 289 |
+
}
|
| 290 |
+
throw new APIError('Authentication required', 401)
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
if (!response.ok) {
|
| 294 |
+
const errorData: ErrorResponse = await response.json().catch(() => ({
|
| 295 |
+
detail: 'An unexpected error occurred',
|
| 296 |
+
}))
|
| 297 |
+
|
| 298 |
+
throw new APIError(
|
| 299 |
+
errorData.detail,
|
| 300 |
+
response.status,
|
| 301 |
+
errorData.error_code,
|
| 302 |
+
errorData.field_errors
|
| 303 |
+
)
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
return response.json()
|
| 307 |
+
}
|
| 308 |
+
```
|
| 309 |
+
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
### 6. Testing
|
| 313 |
+
|
| 314 |
+
#### Backend Tests
|
| 315 |
+
|
| 316 |
+
```bash
|
| 317 |
+
cd backend
|
| 318 |
+
|
| 319 |
+
# Test authentication endpoints
|
| 320 |
+
pytest tests/test_auth.py -v
|
| 321 |
+
|
| 322 |
+
# Test JWT protection on task endpoints
|
| 323 |
+
pytest tests/test_tasks.py -v
|
| 324 |
+
|
| 325 |
+
# Run all tests
|
| 326 |
+
pytest -v
|
| 327 |
+
```
|
| 328 |
+
|
| 329 |
+
#### Manual Testing with curl
|
| 330 |
+
|
| 331 |
+
**Sign Up**:
|
| 332 |
+
```bash
|
| 333 |
+
curl -X POST http://localhost:8000/api/auth/signup \
|
| 334 |
+
-H "Content-Type: application/json" \
|
| 335 |
+
-d '{
|
| 336 |
+
"email": "test@example.com",
|
| 337 |
+
"password": "SecurePass123!",
|
| 338 |
+
"name": "Test User"
|
| 339 |
+
}'
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
**Sign In**:
|
| 343 |
+
```bash
|
| 344 |
+
curl -X POST http://localhost:8000/api/auth/signin \
|
| 345 |
+
-H "Content-Type: application/json" \
|
| 346 |
+
-d '{
|
| 347 |
+
"email": "test@example.com",
|
| 348 |
+
"password": "SecurePass123!"
|
| 349 |
+
}'
|
| 350 |
+
```
|
| 351 |
+
|
| 352 |
+
**Access Protected Endpoint**:
|
| 353 |
+
```bash
|
| 354 |
+
# Save token from signin response
|
| 355 |
+
TOKEN="your-jwt-token-here"
|
| 356 |
+
|
| 357 |
+
curl -X GET http://localhost:8000/api/tasks \
|
| 358 |
+
-H "Authorization: Bearer $TOKEN"
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
**Test Unauthorized Access**:
|
| 362 |
+
```bash
|
| 363 |
+
# Should return 401
|
| 364 |
+
curl -X GET http://localhost:8000/api/tasks
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
---
|
| 368 |
+
|
| 369 |
+
### 7. Running the Application
|
| 370 |
+
|
| 371 |
+
#### Start Backend
|
| 372 |
+
|
| 373 |
+
```bash
|
| 374 |
+
cd backend
|
| 375 |
+
uvicorn src.main:app --reload --port 8000
|
| 376 |
+
```
|
| 377 |
+
|
| 378 |
+
#### Start Frontend
|
| 379 |
+
|
| 380 |
+
```bash
|
| 381 |
+
cd frontend
|
| 382 |
+
npm run dev
|
| 383 |
+
```
|
| 384 |
+
|
| 385 |
+
#### Access Application
|
| 386 |
+
|
| 387 |
+
- Frontend: http://localhost:3000
|
| 388 |
+
- Backend API: http://localhost:8000
|
| 389 |
+
- API Docs: http://localhost:8000/docs
|
| 390 |
+
|
| 391 |
+
---
|
| 392 |
+
|
| 393 |
+
## Verification Checklist
|
| 394 |
+
|
| 395 |
+
### Backend Verification
|
| 396 |
+
|
| 397 |
+
- [ ] `BETTER_AUTH_SECRET` is set in backend/.env
|
| 398 |
+
- [ ] PyJWT, passlib, python-multipart installed
|
| 399 |
+
- [ ] Database migration applied (password_hash column exists)
|
| 400 |
+
- [ ] `src/core/security.py` created with JWT functions
|
| 401 |
+
- [ ] `src/api/deps.py` updated with JWT verification
|
| 402 |
+
- [ ] Backend starts without errors: `uvicorn src.main:app --reload`
|
| 403 |
+
- [ ] API docs accessible at http://localhost:8000/docs
|
| 404 |
+
|
| 405 |
+
### Frontend Verification
|
| 406 |
+
|
| 407 |
+
- [ ] `BETTER_AUTH_SECRET` matches backend (identical value)
|
| 408 |
+
- [ ] better-auth and @better-auth/react installed
|
| 409 |
+
- [ ] `src/lib/auth.ts` created with Better Auth config
|
| 410 |
+
- [ ] `src/lib/api.ts` updated to include JWT in headers
|
| 411 |
+
- [ ] Frontend starts without errors: `npm run dev`
|
| 412 |
+
- [ ] Can access http://localhost:3000
|
| 413 |
+
|
| 414 |
+
### Integration Verification
|
| 415 |
+
|
| 416 |
+
- [ ] User can sign up with email/password
|
| 417 |
+
- [ ] User can sign in and receive JWT token
|
| 418 |
+
- [ ] Authenticated requests to /api/tasks succeed
|
| 419 |
+
- [ ] Unauthenticated requests to /api/tasks return 401
|
| 420 |
+
- [ ] User can only see their own tasks
|
| 421 |
+
- [ ] Token expires after 7 days (test with modified exp claim)
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## Troubleshooting
|
| 426 |
+
|
| 427 |
+
### "Invalid token" errors
|
| 428 |
+
|
| 429 |
+
**Cause**: BETTER_AUTH_SECRET mismatch between frontend and backend
|
| 430 |
+
|
| 431 |
+
**Solution**: Verify both .env files have identical BETTER_AUTH_SECRET values
|
| 432 |
+
|
| 433 |
+
### "Token has expired" immediately
|
| 434 |
+
|
| 435 |
+
**Cause**: System clock skew or incorrect exp claim
|
| 436 |
+
|
| 437 |
+
**Solution**: Check system time, verify token exp claim is 7 days in future
|
| 438 |
+
|
| 439 |
+
### "Not authenticated" on all requests
|
| 440 |
+
|
| 441 |
+
**Cause**: Token not being included in Authorization header
|
| 442 |
+
|
| 443 |
+
**Solution**: Check frontend api.ts includes `Authorization: Bearer ${token}` header
|
| 444 |
+
|
| 445 |
+
### Database connection errors
|
| 446 |
+
|
| 447 |
+
**Cause**: DATABASE_URL incorrect or database not accessible
|
| 448 |
+
|
| 449 |
+
**Solution**: Verify DATABASE_URL format and database is running
|
| 450 |
+
|
| 451 |
+
### Import errors for better-auth
|
| 452 |
+
|
| 453 |
+
**Cause**: Package not installed or wrong version
|
| 454 |
+
|
| 455 |
+
**Solution**: Run `npm install better-auth @better-auth/react` in frontend directory
|
| 456 |
+
|
| 457 |
+
---
|
| 458 |
+
|
| 459 |
+
## Next Steps
|
| 460 |
+
|
| 461 |
+
After completing this setup:
|
| 462 |
+
|
| 463 |
+
1. Run `/sp.tasks` to generate implementation tasks
|
| 464 |
+
2. Implement authentication endpoints (signup, signin)
|
| 465 |
+
3. Implement JWT verification middleware
|
| 466 |
+
4. Update task endpoints to require authentication
|
| 467 |
+
5. Create frontend auth pages (signin, signup)
|
| 468 |
+
6. Test end-to-end authentication flow
|
| 469 |
+
7. Deploy to production with HTTPS enabled
|
| 470 |
+
|
| 471 |
+
---
|
| 472 |
+
|
| 473 |
+
## Security Reminders
|
| 474 |
+
|
| 475 |
+
- ✅ Never commit .env files to git
|
| 476 |
+
- ✅ Use HTTPS in production
|
| 477 |
+
- ✅ Rotate BETTER_AUTH_SECRET periodically
|
| 478 |
+
- ✅ Use strong passwords (min 8 chars, complexity requirements)
|
| 479 |
+
- ✅ Monitor for suspicious authentication attempts
|
| 480 |
+
- ✅ Keep dependencies updated for security patches
|
| 481 |
+
|
| 482 |
+
---
|
| 483 |
+
|
| 484 |
+
## Reference Documentation
|
| 485 |
+
|
| 486 |
+
- Better Auth: https://better-auth.com/docs
|
| 487 |
+
- PyJWT: https://pyjwt.readthedocs.io/
|
| 488 |
+
- FastAPI Security: https://fastapi.tiangolo.com/tutorial/security/
|
| 489 |
+
- JWT.io: https://jwt.io/ (for debugging tokens)
|
specs/001-auth-security/research.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Research: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-auth-security
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Phase**: 0 - Research & Technical Decisions
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This document captures research findings and technical decisions for implementing authentication and API security using Better Auth (frontend) and JWT verification (backend).
|
| 10 |
+
|
| 11 |
+
## Research Questions & Resolutions
|
| 12 |
+
|
| 13 |
+
### 1. Token Expiry Duration
|
| 14 |
+
|
| 15 |
+
**Question**: Spec says 1 hour, user input says 7 days - which should we use?
|
| 16 |
+
|
| 17 |
+
**Decision**: **7 days**
|
| 18 |
+
|
| 19 |
+
**Rationale**:
|
| 20 |
+
- The spec explicitly excludes "Token refresh mechanism and refresh tokens" from scope
|
| 21 |
+
- Without refresh tokens, 1-hour expiry creates poor UX (users logged out every hour)
|
| 22 |
+
- This is a hackathon/MVP project where simplicity is prioritized
|
| 23 |
+
- 7 days balances security with usability for the initial release
|
| 24 |
+
- Industry standard for web apps *with refresh tokens* is 1 hour access + long-lived refresh
|
| 25 |
+
- Industry standard for web apps *without refresh tokens* is 7-30 days
|
| 26 |
+
|
| 27 |
+
**Alternatives Considered**:
|
| 28 |
+
- 1 hour: Too short without refresh mechanism, poor UX
|
| 29 |
+
- 24 hours: Reasonable middle ground, but 7 days is acceptable for MVP
|
| 30 |
+
- 30 days: Too long, increases security risk unnecessarily
|
| 31 |
+
|
| 32 |
+
**Implementation**: Set `exp` claim in JWT to 7 days (604800 seconds) from issuance
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
### 2. Better Auth Integration Pattern
|
| 37 |
+
|
| 38 |
+
**Question**: How should Better Auth be integrated in Next.js 16 App Router?
|
| 39 |
+
|
| 40 |
+
**Decision**: Use Better Auth with email/password provider and JWT plugin
|
| 41 |
+
|
| 42 |
+
**Research Findings**:
|
| 43 |
+
- Better Auth supports Next.js App Router with server-side session management
|
| 44 |
+
- JWT plugin allows issuing tokens that can be verified by external backends
|
| 45 |
+
- Configuration file: `lib/auth.ts` with email provider and JWT plugin
|
| 46 |
+
- Session management via Better Auth's built-in session handling
|
| 47 |
+
- Token accessible via `auth()` helper in server components
|
| 48 |
+
|
| 49 |
+
**Implementation Pattern**:
|
| 50 |
+
```typescript
|
| 51 |
+
// lib/auth.ts
|
| 52 |
+
import { betterAuth } from "better-auth"
|
| 53 |
+
import { jwt } from "better-auth/plugins"
|
| 54 |
+
|
| 55 |
+
export const auth = betterAuth({
|
| 56 |
+
database: {
|
| 57 |
+
// Database connection for Better Auth's session storage
|
| 58 |
+
},
|
| 59 |
+
emailAndPassword: {
|
| 60 |
+
enabled: true,
|
| 61 |
+
},
|
| 62 |
+
plugins: [
|
| 63 |
+
jwt({
|
| 64 |
+
secret: process.env.BETTER_AUTH_SECRET!,
|
| 65 |
+
expiresIn: "7d",
|
| 66 |
+
})
|
| 67 |
+
],
|
| 68 |
+
})
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**Alternatives Considered**:
|
| 72 |
+
- NextAuth.js: More popular but heavier, Better Auth is simpler for JWT use case
|
| 73 |
+
- Custom JWT implementation: Reinventing the wheel, Better Auth handles edge cases
|
| 74 |
+
- Auth0/Clerk: Third-party services, adds external dependency and cost
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
### 3. Backend JWT Verification Strategy
|
| 79 |
+
|
| 80 |
+
**Question**: How should FastAPI verify JWT tokens from Better Auth?
|
| 81 |
+
|
| 82 |
+
**Decision**: Use PyJWT library with FastAPI dependency injection
|
| 83 |
+
|
| 84 |
+
**Research Findings**:
|
| 85 |
+
- PyJWT is the standard Python library for JWT handling
|
| 86 |
+
- FastAPI's dependency injection system is ideal for auth middleware
|
| 87 |
+
- Better Auth uses HS256 (HMAC-SHA256) by default with shared secret
|
| 88 |
+
- Token verification should happen in a reusable dependency
|
| 89 |
+
|
| 90 |
+
**Implementation Pattern**:
|
| 91 |
+
```python
|
| 92 |
+
# src/core/security.py
|
| 93 |
+
import jwt
|
| 94 |
+
from fastapi import HTTPException, status
|
| 95 |
+
from src.core.config import settings
|
| 96 |
+
|
| 97 |
+
def verify_jwt_token(token: str) -> dict:
|
| 98 |
+
try:
|
| 99 |
+
payload = jwt.decode(
|
| 100 |
+
token,
|
| 101 |
+
settings.BETTER_AUTH_SECRET,
|
| 102 |
+
algorithms=["HS256"]
|
| 103 |
+
)
|
| 104 |
+
return payload
|
| 105 |
+
except jwt.ExpiredSignatureError:
|
| 106 |
+
raise HTTPException(
|
| 107 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 108 |
+
detail="Token has expired"
|
| 109 |
+
)
|
| 110 |
+
except jwt.InvalidTokenError:
|
| 111 |
+
raise HTTPException(
|
| 112 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 113 |
+
detail="Invalid token"
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# src/api/deps.py
|
| 117 |
+
from fastapi import Depends, HTTPException, status
|
| 118 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 119 |
+
|
| 120 |
+
security = HTTPBearer()
|
| 121 |
+
|
| 122 |
+
def get_current_user(
|
| 123 |
+
credentials: HTTPAuthorizationCredentials = Depends(security)
|
| 124 |
+
) -> int:
|
| 125 |
+
token = credentials.credentials
|
| 126 |
+
payload = verify_jwt_token(token)
|
| 127 |
+
user_id = payload.get("sub") # Better Auth uses 'sub' for user ID
|
| 128 |
+
if not user_id:
|
| 129 |
+
raise HTTPException(
|
| 130 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 131 |
+
detail="Invalid token payload"
|
| 132 |
+
)
|
| 133 |
+
return int(user_id)
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
**Alternatives Considered**:
|
| 137 |
+
- python-jose: Older library, PyJWT is more actively maintained
|
| 138 |
+
- Middleware approach: Less flexible than dependency injection
|
| 139 |
+
- Manual token parsing: Error-prone, PyJWT handles edge cases
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
### 4. Password Hashing Strategy
|
| 144 |
+
|
| 145 |
+
**Question**: How should passwords be hashed and verified?
|
| 146 |
+
|
| 147 |
+
**Decision**: Use passlib with bcrypt algorithm
|
| 148 |
+
|
| 149 |
+
**Research Findings**:
|
| 150 |
+
- Better Auth handles password hashing on the frontend side
|
| 151 |
+
- Backend needs to verify passwords for custom auth endpoints (if any)
|
| 152 |
+
- passlib is the standard Python library for password hashing
|
| 153 |
+
- bcrypt is industry-standard, resistant to rainbow table attacks
|
| 154 |
+
- Cost factor of 12 provides good security/performance balance
|
| 155 |
+
|
| 156 |
+
**Implementation Pattern**:
|
| 157 |
+
```python
|
| 158 |
+
# src/core/security.py
|
| 159 |
+
from passlib.context import CryptContext
|
| 160 |
+
|
| 161 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 162 |
+
|
| 163 |
+
def hash_password(password: str) -> str:
|
| 164 |
+
return pwd_context.hash(password)
|
| 165 |
+
|
| 166 |
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 167 |
+
return pwd_context.verify(plain_password, hashed_password)
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
**Note**: Since Better Auth handles authentication, backend password hashing may only be needed for:
|
| 171 |
+
- Admin user creation scripts
|
| 172 |
+
- Testing utilities
|
| 173 |
+
- Future direct authentication endpoints
|
| 174 |
+
|
| 175 |
+
**Alternatives Considered**:
|
| 176 |
+
- argon2: More modern but requires C dependencies, complicates deployment
|
| 177 |
+
- scrypt: Good but bcrypt is more widely supported
|
| 178 |
+
- Plain SHA256: Insecure, vulnerable to rainbow tables
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
### 5. Frontend Token Storage
|
| 183 |
+
|
| 184 |
+
**Question**: Where should JWT tokens be stored in the frontend?
|
| 185 |
+
|
| 186 |
+
**Decision**: Use Better Auth's built-in session management (httpOnly cookies)
|
| 187 |
+
|
| 188 |
+
**Research Findings**:
|
| 189 |
+
- Better Auth stores session tokens in httpOnly cookies by default
|
| 190 |
+
- This prevents XSS attacks (JavaScript cannot access the token)
|
| 191 |
+
- Better Auth's `auth()` helper automatically includes token in requests
|
| 192 |
+
- For API calls to backend, extract token from Better Auth session
|
| 193 |
+
|
| 194 |
+
**Implementation Pattern**:
|
| 195 |
+
```typescript
|
| 196 |
+
// lib/api.ts
|
| 197 |
+
import { auth } from './auth'
|
| 198 |
+
|
| 199 |
+
async function fetchAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
| 200 |
+
const session = await auth()
|
| 201 |
+
const token = session?.token // Better Auth provides token in session
|
| 202 |
+
|
| 203 |
+
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
| 204 |
+
...options,
|
| 205 |
+
headers: {
|
| 206 |
+
'Content-Type': 'application/json',
|
| 207 |
+
...(token && { 'Authorization': `Bearer ${token}` }),
|
| 208 |
+
...options.headers,
|
| 209 |
+
},
|
| 210 |
+
})
|
| 211 |
+
|
| 212 |
+
// Handle 401 responses
|
| 213 |
+
if (response.status === 401) {
|
| 214 |
+
// Redirect to login
|
| 215 |
+
window.location.href = '/auth/signin'
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
return response.json()
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
**Alternatives Considered**:
|
| 223 |
+
- localStorage: Vulnerable to XSS attacks
|
| 224 |
+
- sessionStorage: Same XSS vulnerability as localStorage
|
| 225 |
+
- Memory only: Lost on page refresh, poor UX
|
| 226 |
+
|
| 227 |
+
---
|
| 228 |
+
|
| 229 |
+
### 6. Error Handling for Authentication Failures
|
| 230 |
+
|
| 231 |
+
**Question**: How should authentication errors be communicated to users?
|
| 232 |
+
|
| 233 |
+
**Decision**: Use standardized error responses with appropriate HTTP status codes
|
| 234 |
+
|
| 235 |
+
**Research Findings**:
|
| 236 |
+
- 401 Unauthorized: Authentication required or failed
|
| 237 |
+
- 403 Forbidden: Authenticated but not authorized (not used in this spec)
|
| 238 |
+
- Generic error messages prevent information leakage
|
| 239 |
+
- Specific errors only in development mode
|
| 240 |
+
|
| 241 |
+
**Implementation Pattern**:
|
| 242 |
+
```python
|
| 243 |
+
# Backend error responses
|
| 244 |
+
{
|
| 245 |
+
"detail": "Invalid credentials", # Generic, doesn't reveal if email or password wrong
|
| 246 |
+
"error_code": "AUTH_FAILED"
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
{
|
| 250 |
+
"detail": "Token has expired",
|
| 251 |
+
"error_code": "TOKEN_EXPIRED"
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
{
|
| 255 |
+
"detail": "Invalid token",
|
| 256 |
+
"error_code": "TOKEN_INVALID"
|
| 257 |
+
}
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
**Security Considerations**:
|
| 261 |
+
- Never reveal whether email exists in database
|
| 262 |
+
- Never reveal which field (email/password) was incorrect
|
| 263 |
+
- Log detailed errors server-side for debugging
|
| 264 |
+
- Return generic errors to client
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
### 7. Database Schema Changes
|
| 269 |
+
|
| 270 |
+
**Question**: What changes are needed to the existing User model?
|
| 271 |
+
|
| 272 |
+
**Decision**: Add `password_hash` field to users table
|
| 273 |
+
|
| 274 |
+
**Research Findings**:
|
| 275 |
+
- Current User model has: id, email, name, created_at, updated_at
|
| 276 |
+
- Need to add: password_hash (string, nullable=False)
|
| 277 |
+
- Better Auth may also need its own tables for session management
|
| 278 |
+
- Migration should be reversible
|
| 279 |
+
|
| 280 |
+
**Implementation**:
|
| 281 |
+
```python
|
| 282 |
+
# alembic/versions/002_add_user_password.py
|
| 283 |
+
def upgrade():
|
| 284 |
+
op.add_column('users', sa.Column('password_hash', sa.String(255), nullable=False))
|
| 285 |
+
|
| 286 |
+
def downgrade():
|
| 287 |
+
op.drop_column('users', 'password_hash')
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
**Note**: Better Auth may create its own tables (sessions, accounts, etc.) - these should be in a separate migration or handled by Better Auth's migration system.
|
| 291 |
+
|
| 292 |
+
---
|
| 293 |
+
|
| 294 |
+
## Dependencies to Add
|
| 295 |
+
|
| 296 |
+
### Backend
|
| 297 |
+
- `PyJWT==2.8.0` - JWT encoding/decoding
|
| 298 |
+
- `passlib[bcrypt]==1.7.4` - Password hashing
|
| 299 |
+
- `python-multipart==0.0.6` - Form data parsing (for login forms)
|
| 300 |
+
|
| 301 |
+
### Frontend
|
| 302 |
+
- `better-auth` - Authentication library
|
| 303 |
+
- `@better-auth/react` - React hooks for Better Auth
|
| 304 |
+
|
| 305 |
+
---
|
| 306 |
+
|
| 307 |
+
## Environment Variables
|
| 308 |
+
|
| 309 |
+
### Backend (.env)
|
| 310 |
+
```
|
| 311 |
+
BETTER_AUTH_SECRET=<shared-secret-min-32-chars>
|
| 312 |
+
DATABASE_URL=<neon-postgres-url>
|
| 313 |
+
```
|
| 314 |
+
|
| 315 |
+
### Frontend (.env.local)
|
| 316 |
+
```
|
| 317 |
+
BETTER_AUTH_SECRET=<same-shared-secret>
|
| 318 |
+
NEXT_PUBLIC_API_URL=http://localhost:8000
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
**Critical**: BETTER_AUTH_SECRET must be identical in both frontend and backend.
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
## Security Checklist
|
| 326 |
+
|
| 327 |
+
- [x] Passwords hashed with bcrypt (cost factor 12)
|
| 328 |
+
- [x] JWT tokens signed with HS256 and shared secret
|
| 329 |
+
- [x] Tokens expire after 7 days
|
| 330 |
+
- [x] httpOnly cookies prevent XSS attacks
|
| 331 |
+
- [x] Generic error messages prevent information leakage
|
| 332 |
+
- [x] HTTPS required in production (documented in assumptions)
|
| 333 |
+
- [x] User ID extracted from validated token, not request parameters
|
| 334 |
+
- [x] All task endpoints require authentication
|
| 335 |
+
- [x] Database queries filtered by authenticated user ID
|
| 336 |
+
|
| 337 |
+
---
|
| 338 |
+
|
| 339 |
+
## Next Steps
|
| 340 |
+
|
| 341 |
+
Phase 1 will use these research findings to:
|
| 342 |
+
1. Create data-model.md with User entity updates
|
| 343 |
+
2. Generate API contracts for auth endpoints
|
| 344 |
+
3. Create quickstart.md with setup instructions
|
| 345 |
+
4. Update agent context files with new dependencies
|
specs/001-auth-security/spec.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `001-auth-security`
|
| 4 |
+
**Created**: 2026-01-09
|
| 5 |
+
**Status**: Draft
|
| 6 |
+
**Input**: User description: "Authentication & API Security – Phase II Todo Web App"
|
| 7 |
+
|
| 8 |
+
## User Scenarios & Testing *(mandatory)*
|
| 9 |
+
|
| 10 |
+
### User Story 1 - User Sign Up (Priority: P1)
|
| 11 |
+
|
| 12 |
+
A new user visits the application and creates an account to start managing their tasks. The system securely registers the user and establishes their identity for future sessions.
|
| 13 |
+
|
| 14 |
+
**Why this priority**: Without user registration, no one can use the application. This is the entry point for all users and must work reliably.
|
| 15 |
+
|
| 16 |
+
**Independent Test**: Can be fully tested by submitting registration form with valid credentials and verifying account creation. Delivers immediate value by allowing users to establish their identity in the system.
|
| 17 |
+
|
| 18 |
+
**Acceptance Scenarios**:
|
| 19 |
+
|
| 20 |
+
1. **Given** a new user on the sign-up page, **When** they provide valid email and password, **Then** their account is created and they receive confirmation
|
| 21 |
+
2. **Given** a user attempting to sign up, **When** they provide an email that already exists, **Then** they receive a clear error message indicating the email is already registered
|
| 22 |
+
3. **Given** a user on the sign-up page, **When** they provide invalid credentials (weak password, malformed email), **Then** they receive specific validation feedback before submission
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
### User Story 2 - User Sign In (Priority: P2)
|
| 27 |
+
|
| 28 |
+
A registered user returns to the application and signs in with their credentials. The system authenticates them and provides a secure token for accessing their personal data.
|
| 29 |
+
|
| 30 |
+
**Why this priority**: After registration, users need to authenticate to access their tasks. This enables returning users to access the application.
|
| 31 |
+
|
| 32 |
+
**Independent Test**: Can be fully tested by submitting valid credentials and verifying successful authentication with token issuance. Delivers value by allowing registered users to access their accounts.
|
| 33 |
+
|
| 34 |
+
**Acceptance Scenarios**:
|
| 35 |
+
|
| 36 |
+
1. **Given** a registered user on the sign-in page, **When** they provide correct email and password, **Then** they are authenticated and receive a secure token
|
| 37 |
+
2. **Given** a user attempting to sign in, **When** they provide incorrect credentials, **Then** they receive a generic error message without revealing which field was incorrect
|
| 38 |
+
3. **Given** an authenticated user, **When** their session token is issued, **Then** the token contains their user identity and has a defined expiration time
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
### User Story 3 - Protected API Access (Priority: P3)
|
| 43 |
+
|
| 44 |
+
An authenticated user makes requests to the API to manage their tasks. The system verifies their identity on every request and ensures they can only access their own data.
|
| 45 |
+
|
| 46 |
+
**Why this priority**: This enforces the security boundary that prevents users from accessing each other's data. Critical for data privacy and security.
|
| 47 |
+
|
| 48 |
+
**Independent Test**: Can be fully tested by making API requests with valid tokens and verifying that only the authenticated user's data is returned. Delivers value by ensuring data isolation between users.
|
| 49 |
+
|
| 50 |
+
**Acceptance Scenarios**:
|
| 51 |
+
|
| 52 |
+
1. **Given** an authenticated user with a valid token, **When** they request their tasks via the API, **Then** they receive only their own tasks
|
| 53 |
+
2. **Given** an authenticated user, **When** they attempt to access another user's task by ID, **Then** the request is denied with appropriate error
|
| 54 |
+
3. **Given** a user making an API request, **When** the token is included in the Authorization header, **Then** the backend extracts and verifies the token signature
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
### User Story 4 - Invalid Token Handling (Priority: P4)
|
| 59 |
+
|
| 60 |
+
A user attempts to access protected resources without a valid token (expired, malformed, or missing). The system rejects the request and returns a clear unauthorized response.
|
| 61 |
+
|
| 62 |
+
**Why this priority**: This prevents unauthorized access and provides clear feedback when authentication fails. Essential for security but lower priority than the happy path flows.
|
| 63 |
+
|
| 64 |
+
**Independent Test**: Can be fully tested by making API requests with invalid/missing tokens and verifying 401 responses. Delivers value by enforcing authentication requirements.
|
| 65 |
+
|
| 66 |
+
**Acceptance Scenarios**:
|
| 67 |
+
|
| 68 |
+
1. **Given** a user making an API request, **When** no token is provided, **Then** the system returns 401 Unauthorized
|
| 69 |
+
2. **Given** a user with an expired token, **When** they make an API request, **Then** the system returns 401 Unauthorized with indication that token is expired
|
| 70 |
+
3. **Given** a user with a malformed token, **When** they make an API request, **Then** the system returns 401 Unauthorized without exposing internal error details
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
### Edge Cases
|
| 75 |
+
|
| 76 |
+
- What happens when a user tries to sign up with an email that's already registered?
|
| 77 |
+
- How does the system handle concurrent sign-in attempts from the same user?
|
| 78 |
+
- What happens when the shared secret (BETTER_AUTH_SECRET) is missing or misconfigured?
|
| 79 |
+
- How does the system handle tokens that are syntactically valid but signed with the wrong secret?
|
| 80 |
+
- What happens when a user's token expires mid-session while they're actively using the application?
|
| 81 |
+
- How does the system handle extremely long passwords or email addresses?
|
| 82 |
+
- What happens when the backend receives a token with valid signature but for a user that no longer exists?
|
| 83 |
+
|
| 84 |
+
## Requirements *(mandatory)*
|
| 85 |
+
|
| 86 |
+
### Functional Requirements
|
| 87 |
+
|
| 88 |
+
- **FR-001**: System MUST allow new users to create accounts with email and password
|
| 89 |
+
- **FR-002**: System MUST validate email format and password strength during registration
|
| 90 |
+
- **FR-003**: System MUST prevent duplicate account creation with the same email address
|
| 91 |
+
- **FR-004**: System MUST authenticate users by verifying their email and password credentials
|
| 92 |
+
- **FR-005**: System MUST issue JWT tokens upon successful authentication
|
| 93 |
+
- **FR-006**: System MUST include user identity (user ID, email) in the JWT token payload
|
| 94 |
+
- **FR-007**: System MUST sign JWT tokens using the shared secret (BETTER_AUTH_SECRET)
|
| 95 |
+
- **FR-008**: System MUST set token expiration time to prevent indefinite access
|
| 96 |
+
- **FR-009**: System MUST require JWT token in Authorization header for all protected API endpoints
|
| 97 |
+
- **FR-010**: System MUST verify JWT signature on every protected API request
|
| 98 |
+
- **FR-011**: System MUST extract user identity from verified JWT tokens
|
| 99 |
+
- **FR-012**: System MUST filter all task queries by the authenticated user's ID
|
| 100 |
+
- **FR-013**: System MUST return 401 Unauthorized for requests without valid tokens
|
| 101 |
+
- **FR-014**: System MUST return 401 Unauthorized for expired tokens
|
| 102 |
+
- **FR-015**: System MUST return 401 Unauthorized for tokens with invalid signatures
|
| 103 |
+
- **FR-016**: System MUST prevent users from accessing or modifying other users' tasks
|
| 104 |
+
- **FR-017**: System MUST use the same BETTER_AUTH_SECRET value in both frontend and backend
|
| 105 |
+
- **FR-018**: System MUST store passwords securely using industry-standard hashing
|
| 106 |
+
- **FR-019**: System MUST not expose sensitive error details in authentication failure responses
|
| 107 |
+
- **FR-020**: System MUST maintain stateless authentication (no server-side session storage)
|
| 108 |
+
|
| 109 |
+
### Key Entities
|
| 110 |
+
|
| 111 |
+
- **User**: Represents a registered user account with email, hashed password, and unique identifier. Each user owns a collection of tasks and can only access their own data.
|
| 112 |
+
- **JWT Token**: A cryptographically signed token containing user identity claims (user ID, email, expiration time). Used to authenticate API requests without server-side session state.
|
| 113 |
+
- **Authentication Session**: The period during which a user's JWT token is valid, allowing them to make authenticated requests to the API.
|
| 114 |
+
|
| 115 |
+
## Success Criteria *(mandatory)*
|
| 116 |
+
|
| 117 |
+
### Measurable Outcomes
|
| 118 |
+
|
| 119 |
+
- **SC-001**: Users can complete account registration in under 1 minute with clear validation feedback
|
| 120 |
+
- **SC-002**: Users can sign in and receive authentication token in under 5 seconds
|
| 121 |
+
- **SC-003**: 100% of API requests without valid tokens receive 401 Unauthorized responses
|
| 122 |
+
- **SC-004**: 100% of authenticated users can only retrieve and modify their own tasks
|
| 123 |
+
- **SC-005**: System successfully verifies token signatures for 100% of valid tokens
|
| 124 |
+
- **SC-006**: Zero instances of users accessing other users' data in testing
|
| 125 |
+
- **SC-007**: Authentication flow handles 100 concurrent sign-in requests without errors
|
| 126 |
+
- **SC-008**: Token verification adds less than 50ms latency to API requests
|
| 127 |
+
|
| 128 |
+
## Assumptions *(optional)*
|
| 129 |
+
|
| 130 |
+
- Better Auth library is already configured in the frontend application
|
| 131 |
+
- Database schema includes a users table with email and password fields
|
| 132 |
+
- Frontend and backend share the same BETTER_AUTH_SECRET environment variable
|
| 133 |
+
- JWT tokens will use HS256 (HMAC with SHA-256) signing algorithm
|
| 134 |
+
- Access tokens will expire after 1 hour (industry standard for web applications)
|
| 135 |
+
- Password requirements: minimum 8 characters, at least one uppercase, one lowercase, one number
|
| 136 |
+
- Email validation follows RFC 5322 standard format
|
| 137 |
+
- The application uses HTTPS in production to protect tokens in transit
|
| 138 |
+
- Rate limiting for authentication endpoints will be handled separately (not in this spec)
|
| 139 |
+
|
| 140 |
+
## Dependencies *(optional)*
|
| 141 |
+
|
| 142 |
+
- Better Auth library must be installed and configured in the Next.js frontend
|
| 143 |
+
- Backend must have JWT library for token verification (e.g., PyJWT for Python)
|
| 144 |
+
- Database must have users table with appropriate schema
|
| 145 |
+
- Environment configuration must support BETTER_AUTH_SECRET in both frontend and backend
|
| 146 |
+
- Task CRUD API endpoints must be implemented (from Spec 001-task-crud)
|
| 147 |
+
|
| 148 |
+
## Out of Scope *(optional)*
|
| 149 |
+
|
| 150 |
+
- OAuth providers (Google, GitHub, etc.) and social login
|
| 151 |
+
- Password reset and forgot password functionality
|
| 152 |
+
- Email verification and account activation
|
| 153 |
+
- Multi-factor authentication (MFA)
|
| 154 |
+
- Token refresh mechanism and refresh tokens
|
| 155 |
+
- Remember me functionality
|
| 156 |
+
- Session management across multiple devices
|
| 157 |
+
- Account deletion and data export
|
| 158 |
+
- Role-based access control (RBAC) beyond basic user isolation
|
| 159 |
+
- Rate limiting and brute force protection
|
| 160 |
+
- Chatbot or AI-powered features
|
| 161 |
+
- UI/UX polish and advanced form interactions
|
| 162 |
+
- Password strength meter or complexity requirements beyond basic validation
|
specs/001-auth-security/tasks.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tasks: Authentication & API Security
|
| 2 |
+
|
| 3 |
+
**Input**: Design documents from `/specs/001-auth-security/`
|
| 4 |
+
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
|
| 5 |
+
|
| 6 |
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing.
|
| 7 |
+
|
| 8 |
+
## Format: `[ID] [P?] [Story] Description`
|
| 9 |
+
|
| 10 |
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
| 11 |
+
- **[Story]**: Which user story this task belongs to (US1, US2, US3, US4)
|
| 12 |
+
- Include exact file paths in descriptions
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Phase 1: Setup (Shared Infrastructure)
|
| 17 |
+
|
| 18 |
+
**Purpose**: Project initialization and dependency installation
|
| 19 |
+
|
| 20 |
+
- [x] T001 Add PyJWT==2.8.0, passlib[bcrypt]==1.7.4, python-multipart==0.0.6 to backend/requirements.txt
|
| 21 |
+
- [x] T002 Install backend dependencies with pip install -r backend/requirements.txt
|
| 22 |
+
- [x] T003 [P] Add better-auth and @better-auth/react to frontend/package.json
|
| 23 |
+
- [x] T004 [P] Install frontend dependencies with npm install in frontend/
|
| 24 |
+
- [x] T005 [P] Add BETTER_AUTH_SECRET to backend/.env (generate 32+ char random string)
|
| 25 |
+
- [x] T006 [P] Add BETTER_AUTH_SECRET to frontend/.env.local (same value as backend)
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Phase 2: Foundational (Blocking Prerequisites)
|
| 30 |
+
|
| 31 |
+
**Purpose**: Core infrastructure that MUST be complete before ANY user story
|
| 32 |
+
|
| 33 |
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
| 34 |
+
|
| 35 |
+
- [x] T007 Create backend/src/core/security.py with password hashing and JWT functions
|
| 36 |
+
- [x] T008 Update backend/src/core/config.py to add BETTER_AUTH_SECRET (required, not optional)
|
| 37 |
+
- [x] T009 Add password_hash field to User model in backend/src/models/user.py
|
| 38 |
+
- [x] T010 Create database migration backend/alembic/versions/002_add_user_password.py
|
| 39 |
+
- [x] T011 Run alembic upgrade head to apply password_hash migration
|
| 40 |
+
- [x] T012 Create backend/src/schemas/auth.py with SignupRequest, SigninRequest, TokenResponse schemas
|
| 41 |
+
- [x] T013 Create frontend/src/lib/auth.ts with Better Auth configuration (email/password + JWT plugin)
|
| 42 |
+
|
| 43 |
+
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Phase 3: User Story 1 - User Sign Up (Priority: P1) 🎯 MVP
|
| 48 |
+
|
| 49 |
+
**Goal**: New users can create accounts with email and password
|
| 50 |
+
|
| 51 |
+
**Independent Test**: Submit signup form with valid credentials and verify account creation in database
|
| 52 |
+
|
| 53 |
+
### Implementation for User Story 1
|
| 54 |
+
|
| 55 |
+
- [x] T014 [P] [US1] Create backend/src/services/auth_service.py with signup method (hash password, create user)
|
| 56 |
+
- [x] T015 [P] [US1] Create backend/src/api/routes/auth.py with POST /api/auth/signup endpoint
|
| 57 |
+
- [x] T016 [US1] Add email validation (RFC 5322 format) in signup endpoint
|
| 58 |
+
- [x] T017 [US1] Add password validation (min 8 chars, uppercase, lowercase, number) in signup endpoint
|
| 59 |
+
- [x] T018 [US1] Handle duplicate email error (409 Conflict) in signup endpoint
|
| 60 |
+
- [x] T019 [P] [US1] Create frontend/src/components/auth/SignUpForm.tsx with form fields and validation
|
| 61 |
+
- [x] T020 [P] [US1] Create frontend/src/app/auth/signup/page.tsx using SignUpForm component
|
| 62 |
+
- [x] T021 [US1] Connect SignUpForm to Better Auth signup API
|
| 63 |
+
|
| 64 |
+
**Checkpoint**: Users can successfully sign up and create accounts
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## Phase 4: User Story 2 - User Sign In (Priority: P2)
|
| 69 |
+
|
| 70 |
+
**Goal**: Registered users can sign in and receive JWT tokens
|
| 71 |
+
|
| 72 |
+
**Independent Test**: Submit signin form with valid credentials and verify JWT token is issued
|
| 73 |
+
|
| 74 |
+
### Implementation for User Story 2
|
| 75 |
+
|
| 76 |
+
- [x] T022 [US2] Add signin method to backend/src/services/auth_service.py (verify password, create JWT)
|
| 77 |
+
- [x] T023 [US2] Add POST /api/auth/signin endpoint to backend/src/api/routes/auth.py
|
| 78 |
+
- [x] T024 [US2] Return JWT token with 7-day expiration in signin response
|
| 79 |
+
- [x] T025 [US2] Handle invalid credentials with generic error (401 Unauthorized)
|
| 80 |
+
- [x] T026 [P] [US2] Create frontend/src/components/auth/SignInForm.tsx with email/password fields
|
| 81 |
+
- [x] T027 [P] [US2] Create frontend/src/app/auth/signin/page.tsx using SignInForm component
|
| 82 |
+
- [x] T028 [US2] Connect SignInForm to Better Auth signin API
|
| 83 |
+
- [x] T029 [US2] Store JWT token in httpOnly cookie via Better Auth session
|
| 84 |
+
|
| 85 |
+
**Checkpoint**: Users can sign in and receive valid JWT tokens
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## Phase 5: User Story 3 - Protected API Access (Priority: P3)
|
| 90 |
+
|
| 91 |
+
**Goal**: Authenticated users can access API with JWT tokens and only see their own data
|
| 92 |
+
|
| 93 |
+
**Independent Test**: Make API request with valid token and verify only authenticated user's tasks are returned
|
| 94 |
+
|
| 95 |
+
### Implementation for User Story 3
|
| 96 |
+
|
| 97 |
+
- [x] T030 [US3] Update backend/src/api/deps.py get_current_user to extract and verify JWT from Authorization header
|
| 98 |
+
- [x] T031 [US3] Add HTTPBearer security scheme to get_current_user dependency
|
| 99 |
+
- [x] T032 [US3] Extract user_id from JWT 'sub' claim in get_current_user
|
| 100 |
+
- [x] T033 [US3] Return 401 Unauthorized if token is missing in get_current_user
|
| 101 |
+
- [x] T034 [P] [US3] Update frontend/src/lib/api.ts fetchAPI to include Authorization: Bearer header
|
| 102 |
+
- [x] T035 [P] [US3] Get JWT token from Better Auth session in fetchAPI
|
| 103 |
+
- [x] T036 [US3] Verify all task endpoints filter by authenticated user_id (already implemented, just verify)
|
| 104 |
+
- [x] T037 [US3] Add GET /api/auth/me endpoint to return current user profile
|
| 105 |
+
|
| 106 |
+
**Checkpoint**: API requests require valid JWT tokens and enforce user data isolation
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## Phase 6: User Story 4 - Invalid Token Handling (Priority: P4)
|
| 111 |
+
|
| 112 |
+
**Goal**: System rejects invalid, expired, or missing tokens with clear error responses
|
| 113 |
+
|
| 114 |
+
**Independent Test**: Make API requests with invalid/missing tokens and verify 401 responses
|
| 115 |
+
|
| 116 |
+
### Implementation for User Story 4
|
| 117 |
+
|
| 118 |
+
- [x] T038 [P] [US4] Handle expired token error (jwt.ExpiredSignatureError) in backend/src/core/security.py
|
| 119 |
+
- [x] T039 [P] [US4] Handle invalid signature error (jwt.InvalidTokenError) in backend/src/core/security.py
|
| 120 |
+
- [x] T040 [P] [US4] Handle malformed token error in backend/src/api/deps.py get_current_user
|
| 121 |
+
- [x] T041 [US4] Return 401 with error_code TOKEN_EXPIRED for expired tokens
|
| 122 |
+
- [x] T042 [US4] Return 401 with error_code TOKEN_INVALID for invalid tokens
|
| 123 |
+
- [x] T043 [US4] Return 401 with error_code TOKEN_MISSING for missing tokens
|
| 124 |
+
- [x] T044 [US4] Add 401 error handling in frontend/src/lib/api.ts to redirect to /auth/signin
|
| 125 |
+
|
| 126 |
+
**Checkpoint**: All authentication errors are handled gracefully with appropriate responses
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## Phase 7: Polish & Cross-Cutting Concerns
|
| 131 |
+
|
| 132 |
+
**Purpose**: Integration, testing, and documentation
|
| 133 |
+
|
| 134 |
+
- [x] T045 [P] Create frontend/src/providers/AuthProvider.tsx to wrap app with Better Auth context
|
| 135 |
+
- [x] T046 [P] Update frontend/src/app/layout.tsx to include AuthProvider
|
| 136 |
+
- [x] T047 Protect frontend/src/app/page.tsx (task list) to require authentication
|
| 137 |
+
- [x] T047.1 Register auth router in backend/src/main.py (bugfix)
|
| 138 |
+
- [ ] T048 Test signup flow end-to-end (frontend → backend → database)
|
| 139 |
+
- [ ] T049 Test signin flow end-to-end (frontend → backend → JWT issuance)
|
| 140 |
+
- [ ] T050 Test protected API access (valid token → success, invalid → 401)
|
| 141 |
+
- [ ] T051 Test user data isolation (user A cannot access user B's tasks)
|
| 142 |
+
- [x] T052 Verify BETTER_AUTH_SECRET is identical in frontend and backend .env files
|
| 143 |
+
- [x] T053 Update backend/README.md with authentication setup instructions
|
| 144 |
+
- [x] T054 Update frontend/README.md with Better Auth configuration notes
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## Dependencies & Execution Order
|
| 149 |
+
|
| 150 |
+
### Phase Dependencies
|
| 151 |
+
|
| 152 |
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
| 153 |
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
| 154 |
+
- **User Stories (Phase 3-6)**: All depend on Foundational phase completion
|
| 155 |
+
- User stories can proceed in parallel (if staffed)
|
| 156 |
+
- Or sequentially in priority order (P1 → P2 → P3 → P4)
|
| 157 |
+
- **Polish (Phase 7)**: Depends on all user stories being complete
|
| 158 |
+
|
| 159 |
+
### User Story Dependencies
|
| 160 |
+
|
| 161 |
+
- **User Story 1 (P1)**: Can start after Foundational - No dependencies on other stories
|
| 162 |
+
- **User Story 2 (P2)**: Can start after Foundational - Depends on US1 (needs User model with password_hash)
|
| 163 |
+
- **User Story 3 (P3)**: Can start after Foundational - Depends on US2 (needs JWT tokens to be issued)
|
| 164 |
+
- **User Story 4 (P4)**: Can start after US3 - Depends on JWT verification being implemented
|
| 165 |
+
|
| 166 |
+
### Within Each User Story
|
| 167 |
+
|
| 168 |
+
- Backend services before endpoints
|
| 169 |
+
- Backend endpoints before frontend components
|
| 170 |
+
- Frontend components before frontend pages
|
| 171 |
+
- Core implementation before error handling
|
| 172 |
+
|
| 173 |
+
### Parallel Opportunities
|
| 174 |
+
|
| 175 |
+
- **Phase 1**: T003, T004, T005, T006 can run in parallel
|
| 176 |
+
- **Phase 3 (US1)**: T014, T015 (backend) can run parallel with T019, T020 (frontend)
|
| 177 |
+
- **Phase 4 (US2)**: T026, T027 (frontend) can run parallel with backend work
|
| 178 |
+
- **Phase 5 (US3)**: T034, T035 (frontend) can run parallel with backend work
|
| 179 |
+
- **Phase 6 (US4)**: T038, T039, T040 can run in parallel
|
| 180 |
+
- **Phase 7**: T045, T046, T053, T054 can run in parallel
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## Parallel Example: User Story 1
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
# Launch backend and frontend tasks together:
|
| 188 |
+
Task: "Create backend/src/services/auth_service.py with signup method"
|
| 189 |
+
Task: "Create backend/src/api/routes/auth.py with POST /api/auth/signup endpoint"
|
| 190 |
+
Task: "Create frontend/src/components/auth/SignUpForm.tsx"
|
| 191 |
+
Task: "Create frontend/src/app/auth/signup/page.tsx"
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## Implementation Strategy
|
| 197 |
+
|
| 198 |
+
### MVP First (User Story 1 Only)
|
| 199 |
+
|
| 200 |
+
1. Complete Phase 1: Setup (T001-T006)
|
| 201 |
+
2. Complete Phase 2: Foundational (T007-T013) - CRITICAL
|
| 202 |
+
3. Complete Phase 3: User Story 1 (T014-T021)
|
| 203 |
+
4. **STOP and VALIDATE**: Test signup independently
|
| 204 |
+
5. Deploy/demo if ready
|
| 205 |
+
|
| 206 |
+
### Incremental Delivery
|
| 207 |
+
|
| 208 |
+
1. Setup + Foundational → Foundation ready
|
| 209 |
+
2. Add User Story 1 → Test independently → Deploy (MVP!)
|
| 210 |
+
3. Add User Story 2 → Test independently → Deploy
|
| 211 |
+
4. Add User Story 3 → Test independently → Deploy
|
| 212 |
+
5. Add User Story 4 → Test independently → Deploy
|
| 213 |
+
6. Polish → Final deployment
|
| 214 |
+
|
| 215 |
+
### Parallel Team Strategy
|
| 216 |
+
|
| 217 |
+
With multiple developers:
|
| 218 |
+
|
| 219 |
+
1. Team completes Setup + Foundational together
|
| 220 |
+
2. Once Foundational is done:
|
| 221 |
+
- Developer A: User Story 1 (signup)
|
| 222 |
+
- Developer B: User Story 2 (signin) - starts after US1 model is ready
|
| 223 |
+
- Developer C: User Story 3 (API protection) - starts after US2 tokens are ready
|
| 224 |
+
3. Stories integrate independently
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## Notes
|
| 229 |
+
|
| 230 |
+
- Total tasks: 54
|
| 231 |
+
- MVP scope: Phase 1 + Phase 2 + Phase 3 (User Story 1) = 21 tasks
|
| 232 |
+
- [P] tasks = different files, no dependencies
|
| 233 |
+
- [Story] label maps task to specific user story
|
| 234 |
+
- Each user story should be independently testable
|
| 235 |
+
- Commit after each task or logical group
|
| 236 |
+
- Verify BETTER_AUTH_SECRET matches in both .env files
|
| 237 |
+
- Test authentication flow end-to-end before moving to next story
|
specs/001-openai-agent-mcp-tools/checklists/requirements.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Specification Quality Checklist: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 4 |
+
**Created**: 2026-01-14
|
| 5 |
+
**Feature**: [spec.md](../spec.md)
|
| 6 |
+
|
| 7 |
+
## Content Quality
|
| 8 |
+
|
| 9 |
+
- [x] No implementation details (languages, frameworks, APIs)
|
| 10 |
+
- [x] Focused on user value and business needs
|
| 11 |
+
- [x] Written for non-technical stakeholders
|
| 12 |
+
- [x] All mandatory sections completed
|
| 13 |
+
|
| 14 |
+
## Requirement Completeness
|
| 15 |
+
|
| 16 |
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
| 17 |
+
- [x] Requirements are testable and unambiguous
|
| 18 |
+
- [x] Success criteria are measurable
|
| 19 |
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
| 20 |
+
- [x] All acceptance scenarios are defined
|
| 21 |
+
- [x] Edge cases are identified
|
| 22 |
+
- [x] Scope is clearly bounded
|
| 23 |
+
- [x] Dependencies and assumptions identified
|
| 24 |
+
|
| 25 |
+
## Feature Readiness
|
| 26 |
+
|
| 27 |
+
- [x] All functional requirements have clear acceptance criteria
|
| 28 |
+
- [x] User scenarios cover primary flows
|
| 29 |
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
| 30 |
+
- [x] No implementation details leak into specification
|
| 31 |
+
|
| 32 |
+
## Validation Results
|
| 33 |
+
|
| 34 |
+
**Status**: ✅ PASSED
|
| 35 |
+
|
| 36 |
+
**Issues Found**: None
|
| 37 |
+
|
| 38 |
+
**Analysis**:
|
| 39 |
+
|
| 40 |
+
1. **Content Quality**: The specification is written from a user and business perspective. While it mentions specific technologies (OpenAI Agents SDK, MCP SDK, Cohere), these are part of the explicit requirements provided by the user in the feature description. The spec focuses on what the system must do, not how to implement it at a code level.
|
| 41 |
+
|
| 42 |
+
2. **Requirement Completeness**: All 44 functional requirements are testable and unambiguous. No [NEEDS CLARIFICATION] markers remain because the user provided a comprehensive feature description with explicit technical constraints.
|
| 43 |
+
|
| 44 |
+
3. **Success Criteria**: All 10 success criteria are measurable and technology-agnostic from a user perspective (e.g., "95% success rate", "within 5 seconds", "50 concurrent users").
|
| 45 |
+
|
| 46 |
+
4. **User Scenarios**: 5 prioritized user stories (P1-P5) cover the complete CRUD workflow for task management via natural language, each with independent test criteria and acceptance scenarios.
|
| 47 |
+
|
| 48 |
+
5. **Edge Cases**: 7 edge cases identified covering API failures, ambiguous requests, concurrent access, and context window limits.
|
| 49 |
+
|
| 50 |
+
6. **Scope**: Clear boundaries defined with explicit "Out of Scope" section listing 15 excluded items.
|
| 51 |
+
|
| 52 |
+
7. **Dependencies**: All dependencies and assumptions clearly documented.
|
| 53 |
+
|
| 54 |
+
## Notes
|
| 55 |
+
|
| 56 |
+
- The specification is ready for `/sp.plan` execution
|
| 57 |
+
- No clarifications needed from the user
|
| 58 |
+
- All mandatory sections are complete and meet quality standards
|
specs/001-openai-agent-mcp-tools/contracts/add_task.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "add_task",
|
| 3 |
+
"description": "Add a new task to the user's todo list. Creates a task with a title and optional description, due date, and priority.",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"type": "object",
|
| 6 |
+
"properties": {
|
| 7 |
+
"title": {
|
| 8 |
+
"type": "string",
|
| 9 |
+
"description": "The title of the task (required, max 200 characters)",
|
| 10 |
+
"maxLength": 200
|
| 11 |
+
},
|
| 12 |
+
"description": {
|
| 13 |
+
"type": "string",
|
| 14 |
+
"description": "Optional detailed description of the task (max 1000 characters)",
|
| 15 |
+
"maxLength": 1000
|
| 16 |
+
},
|
| 17 |
+
"due_date": {
|
| 18 |
+
"type": "string",
|
| 19 |
+
"description": "Optional due date in ISO 8601 format (YYYY-MM-DD)",
|
| 20 |
+
"format": "date"
|
| 21 |
+
},
|
| 22 |
+
"priority": {
|
| 23 |
+
"type": "string",
|
| 24 |
+
"description": "Optional priority level",
|
| 25 |
+
"enum": ["low", "medium", "high"]
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"required": ["title"]
|
| 29 |
+
},
|
| 30 |
+
"returns": {
|
| 31 |
+
"type": "object",
|
| 32 |
+
"properties": {
|
| 33 |
+
"success": {
|
| 34 |
+
"type": "boolean",
|
| 35 |
+
"description": "Whether the task was created successfully"
|
| 36 |
+
},
|
| 37 |
+
"task": {
|
| 38 |
+
"type": "object",
|
| 39 |
+
"description": "The created task object",
|
| 40 |
+
"properties": {
|
| 41 |
+
"id": {
|
| 42 |
+
"type": "integer",
|
| 43 |
+
"description": "Unique task ID"
|
| 44 |
+
},
|
| 45 |
+
"title": {
|
| 46 |
+
"type": "string",
|
| 47 |
+
"description": "Task title"
|
| 48 |
+
},
|
| 49 |
+
"description": {
|
| 50 |
+
"type": "string",
|
| 51 |
+
"description": "Task description"
|
| 52 |
+
},
|
| 53 |
+
"completed": {
|
| 54 |
+
"type": "boolean",
|
| 55 |
+
"description": "Task completion status"
|
| 56 |
+
},
|
| 57 |
+
"created_at": {
|
| 58 |
+
"type": "string",
|
| 59 |
+
"description": "Task creation timestamp"
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"message": {
|
| 64 |
+
"type": "string",
|
| 65 |
+
"description": "User-friendly confirmation message"
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
}
|
specs/001-openai-agent-mcp-tools/contracts/complete_task.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "complete_task",
|
| 3 |
+
"description": "Mark a task as completed. Accepts either a task ID (integer) or task title (string) to identify the task.",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"type": "object",
|
| 6 |
+
"properties": {
|
| 7 |
+
"task_identifier": {
|
| 8 |
+
"oneOf": [
|
| 9 |
+
{
|
| 10 |
+
"type": "integer",
|
| 11 |
+
"description": "Task ID"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"type": "string",
|
| 15 |
+
"description": "Task title (exact match)"
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"description": "Task ID or title to identify which task to complete"
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"required": ["task_identifier"]
|
| 22 |
+
},
|
| 23 |
+
"returns": {
|
| 24 |
+
"type": "object",
|
| 25 |
+
"properties": {
|
| 26 |
+
"success": {
|
| 27 |
+
"type": "boolean",
|
| 28 |
+
"description": "Whether the task was marked as completed"
|
| 29 |
+
},
|
| 30 |
+
"task": {
|
| 31 |
+
"type": "object",
|
| 32 |
+
"description": "The updated task object",
|
| 33 |
+
"properties": {
|
| 34 |
+
"id": {
|
| 35 |
+
"type": "integer",
|
| 36 |
+
"description": "Unique task ID"
|
| 37 |
+
},
|
| 38 |
+
"title": {
|
| 39 |
+
"type": "string",
|
| 40 |
+
"description": "Task title"
|
| 41 |
+
},
|
| 42 |
+
"completed": {
|
| 43 |
+
"type": "boolean",
|
| 44 |
+
"description": "Task completion status (should be true)"
|
| 45 |
+
},
|
| 46 |
+
"updated_at": {
|
| 47 |
+
"type": "string",
|
| 48 |
+
"description": "Task update timestamp"
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"message": {
|
| 53 |
+
"type": "string",
|
| 54 |
+
"description": "User-friendly confirmation message"
|
| 55 |
+
},
|
| 56 |
+
"error": {
|
| 57 |
+
"type": "string",
|
| 58 |
+
"description": "Error message if task not found or operation failed"
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
}
|
specs/001-openai-agent-mcp-tools/contracts/delete_task.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "delete_task",
|
| 3 |
+
"description": "Delete a task permanently. Accepts either a task ID (integer) or task title (string) to identify the task.",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"type": "object",
|
| 6 |
+
"properties": {
|
| 7 |
+
"task_identifier": {
|
| 8 |
+
"oneOf": [
|
| 9 |
+
{
|
| 10 |
+
"type": "integer",
|
| 11 |
+
"description": "Task ID"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"type": "string",
|
| 15 |
+
"description": "Task title (exact match)"
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"description": "Task ID or title to identify which task to delete"
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"required": ["task_identifier"]
|
| 22 |
+
},
|
| 23 |
+
"returns": {
|
| 24 |
+
"type": "object",
|
| 25 |
+
"properties": {
|
| 26 |
+
"success": {
|
| 27 |
+
"type": "boolean",
|
| 28 |
+
"description": "Whether the task was deleted successfully"
|
| 29 |
+
},
|
| 30 |
+
"message": {
|
| 31 |
+
"type": "string",
|
| 32 |
+
"description": "User-friendly confirmation message"
|
| 33 |
+
},
|
| 34 |
+
"error": {
|
| 35 |
+
"type": "string",
|
| 36 |
+
"description": "Error message if task not found or operation failed"
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
specs/001-openai-agent-mcp-tools/contracts/list_tasks.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "list_tasks",
|
| 3 |
+
"description": "List all tasks for the authenticated user. Supports filtering by completion status.",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"type": "object",
|
| 6 |
+
"properties": {
|
| 7 |
+
"filter": {
|
| 8 |
+
"type": "string",
|
| 9 |
+
"description": "Filter tasks by completion status",
|
| 10 |
+
"enum": ["all", "completed", "incomplete"],
|
| 11 |
+
"default": "all"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"required": []
|
| 15 |
+
},
|
| 16 |
+
"returns": {
|
| 17 |
+
"type": "object",
|
| 18 |
+
"properties": {
|
| 19 |
+
"success": {
|
| 20 |
+
"type": "boolean",
|
| 21 |
+
"description": "Whether the operation succeeded"
|
| 22 |
+
},
|
| 23 |
+
"tasks": {
|
| 24 |
+
"type": "array",
|
| 25 |
+
"description": "List of tasks matching the filter",
|
| 26 |
+
"items": {
|
| 27 |
+
"type": "object",
|
| 28 |
+
"properties": {
|
| 29 |
+
"id": {
|
| 30 |
+
"type": "integer",
|
| 31 |
+
"description": "Unique task ID"
|
| 32 |
+
},
|
| 33 |
+
"title": {
|
| 34 |
+
"type": "string",
|
| 35 |
+
"description": "Task title"
|
| 36 |
+
},
|
| 37 |
+
"description": {
|
| 38 |
+
"type": "string",
|
| 39 |
+
"description": "Task description"
|
| 40 |
+
},
|
| 41 |
+
"completed": {
|
| 42 |
+
"type": "boolean",
|
| 43 |
+
"description": "Task completion status"
|
| 44 |
+
},
|
| 45 |
+
"created_at": {
|
| 46 |
+
"type": "string",
|
| 47 |
+
"description": "Task creation timestamp"
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"count": {
|
| 53 |
+
"type": "integer",
|
| 54 |
+
"description": "Total number of tasks returned"
|
| 55 |
+
},
|
| 56 |
+
"message": {
|
| 57 |
+
"type": "string",
|
| 58 |
+
"description": "User-friendly message describing the results"
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
}
|
specs/001-openai-agent-mcp-tools/contracts/update_task.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "update_task",
|
| 3 |
+
"description": "Update an existing task's properties. Accepts either a task ID (integer) or task title (string) to identify the task, and a dictionary of fields to update.",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"type": "object",
|
| 6 |
+
"properties": {
|
| 7 |
+
"task_identifier": {
|
| 8 |
+
"oneOf": [
|
| 9 |
+
{
|
| 10 |
+
"type": "integer",
|
| 11 |
+
"description": "Task ID"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"type": "string",
|
| 15 |
+
"description": "Task title (exact match)"
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"description": "Task ID or title to identify which task to update"
|
| 19 |
+
},
|
| 20 |
+
"updates": {
|
| 21 |
+
"type": "object",
|
| 22 |
+
"description": "Dictionary of fields to update",
|
| 23 |
+
"properties": {
|
| 24 |
+
"title": {
|
| 25 |
+
"type": "string",
|
| 26 |
+
"description": "New task title (max 200 characters)",
|
| 27 |
+
"maxLength": 200
|
| 28 |
+
},
|
| 29 |
+
"description": {
|
| 30 |
+
"type": "string",
|
| 31 |
+
"description": "New task description (max 1000 characters)",
|
| 32 |
+
"maxLength": 1000
|
| 33 |
+
},
|
| 34 |
+
"due_date": {
|
| 35 |
+
"type": "string",
|
| 36 |
+
"description": "New due date in ISO 8601 format (YYYY-MM-DD)",
|
| 37 |
+
"format": "date"
|
| 38 |
+
},
|
| 39 |
+
"priority": {
|
| 40 |
+
"type": "string",
|
| 41 |
+
"description": "New priority level",
|
| 42 |
+
"enum": ["low", "medium", "high"]
|
| 43 |
+
},
|
| 44 |
+
"completed": {
|
| 45 |
+
"type": "boolean",
|
| 46 |
+
"description": "New completion status"
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"minProperties": 1
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"required": ["task_identifier", "updates"]
|
| 53 |
+
},
|
| 54 |
+
"returns": {
|
| 55 |
+
"type": "object",
|
| 56 |
+
"properties": {
|
| 57 |
+
"success": {
|
| 58 |
+
"type": "boolean",
|
| 59 |
+
"description": "Whether the task was updated successfully"
|
| 60 |
+
},
|
| 61 |
+
"task": {
|
| 62 |
+
"type": "object",
|
| 63 |
+
"description": "The updated task object",
|
| 64 |
+
"properties": {
|
| 65 |
+
"id": {
|
| 66 |
+
"type": "integer",
|
| 67 |
+
"description": "Unique task ID"
|
| 68 |
+
},
|
| 69 |
+
"title": {
|
| 70 |
+
"type": "string",
|
| 71 |
+
"description": "Task title"
|
| 72 |
+
},
|
| 73 |
+
"description": {
|
| 74 |
+
"type": "string",
|
| 75 |
+
"description": "Task description"
|
| 76 |
+
},
|
| 77 |
+
"completed": {
|
| 78 |
+
"type": "boolean",
|
| 79 |
+
"description": "Task completion status"
|
| 80 |
+
},
|
| 81 |
+
"updated_at": {
|
| 82 |
+
"type": "string",
|
| 83 |
+
"description": "Task update timestamp"
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
"message": {
|
| 88 |
+
"type": "string",
|
| 89 |
+
"description": "User-friendly confirmation message"
|
| 90 |
+
},
|
| 91 |
+
"error": {
|
| 92 |
+
"type": "string",
|
| 93 |
+
"description": "Error message if task not found or operation failed"
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
specs/001-openai-agent-mcp-tools/data-model.md
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Model: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-openai-agent-mcp-tools
|
| 4 |
+
**Date**: 2026-01-14
|
| 5 |
+
**Phase**: Phase 1 - Design & Contracts
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This document defines the runtime entities and data flow for the AI agent with MCP tools implementation. Note that these are primarily runtime entities, not new database tables. Existing database models (Task, Conversation, Message) remain unchanged.
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## Runtime Entities
|
| 14 |
+
|
| 15 |
+
### 1. AgentConfiguration
|
| 16 |
+
|
| 17 |
+
**Purpose**: Runtime configuration for agent initialization with provider selection.
|
| 18 |
+
|
| 19 |
+
**Type**: Runtime configuration object (not persisted to database)
|
| 20 |
+
|
| 21 |
+
**Attributes**:
|
| 22 |
+
|
| 23 |
+
| Attribute | Type | Description | Source |
|
| 24 |
+
|-----------|------|-------------|--------|
|
| 25 |
+
| `provider_type` | `str` | Provider identifier: "gemini", "openrouter", "cohere" | Environment variable `LLM_PROVIDER` |
|
| 26 |
+
| `model_name` | `str` | Model identifier (e.g., "gemini-1.5-flash") | Provider-specific default or env var |
|
| 27 |
+
| `api_key` | `str` | API key for the provider | Environment variable (provider-specific) |
|
| 28 |
+
| `context_window_size` | `int` | Maximum context window in tokens | Provider-specific constant |
|
| 29 |
+
| `max_tokens` | `int` | Maximum tokens per response | Provider-specific constant |
|
| 30 |
+
| `temperature` | `float` | Sampling temperature (0.0-1.0) | Default: 0.7 |
|
| 31 |
+
| `fallback_provider` | `Optional[str]` | Fallback provider if primary fails | Environment variable `FALLBACK_PROVIDER` |
|
| 32 |
+
|
| 33 |
+
**Example**:
|
| 34 |
+
|
| 35 |
+
```python
|
| 36 |
+
@dataclass
|
| 37 |
+
class AgentConfiguration:
|
| 38 |
+
provider_type: str
|
| 39 |
+
model_name: str
|
| 40 |
+
api_key: str
|
| 41 |
+
context_window_size: int
|
| 42 |
+
max_tokens: int
|
| 43 |
+
temperature: float = 0.7
|
| 44 |
+
fallback_provider: Optional[str] = None
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def from_environment(cls) -> "AgentConfiguration":
|
| 48 |
+
"""Load configuration from environment variables."""
|
| 49 |
+
provider_type = os.getenv("LLM_PROVIDER", "gemini")
|
| 50 |
+
|
| 51 |
+
if provider_type == "gemini":
|
| 52 |
+
return cls(
|
| 53 |
+
provider_type="gemini",
|
| 54 |
+
model_name="gemini-1.5-flash",
|
| 55 |
+
api_key=os.getenv("GEMINI_API_KEY"),
|
| 56 |
+
context_window_size=1_000_000,
|
| 57 |
+
max_tokens=8192,
|
| 58 |
+
fallback_provider=os.getenv("FALLBACK_PROVIDER")
|
| 59 |
+
)
|
| 60 |
+
# ... other providers
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
### 2. ToolExecutionResult
|
| 66 |
+
|
| 67 |
+
**Purpose**: Represents the outcome of an MCP tool invocation.
|
| 68 |
+
|
| 69 |
+
**Type**: Runtime result object (not persisted separately, stored in Message.metadata)
|
| 70 |
+
|
| 71 |
+
**Attributes**:
|
| 72 |
+
|
| 73 |
+
| Attribute | Type | Description |
|
| 74 |
+
|-----------|------|-------------|
|
| 75 |
+
| `tool_name` | `str` | Name of the executed tool |
|
| 76 |
+
| `success` | `bool` | Whether the tool execution succeeded |
|
| 77 |
+
| `data` | `dict` | Tool-specific result data (task object or list of tasks) |
|
| 78 |
+
| `error_message` | `Optional[str]` | Error message if execution failed |
|
| 79 |
+
| `execution_timestamp` | `datetime` | When the tool was executed |
|
| 80 |
+
|
| 81 |
+
**Example**:
|
| 82 |
+
|
| 83 |
+
```python
|
| 84 |
+
@dataclass
|
| 85 |
+
class ToolExecutionResult:
|
| 86 |
+
tool_name: str
|
| 87 |
+
success: bool
|
| 88 |
+
data: dict
|
| 89 |
+
error_message: Optional[str] = None
|
| 90 |
+
execution_timestamp: datetime = field(default_factory=datetime.utcnow)
|
| 91 |
+
|
| 92 |
+
def to_dict(self) -> dict:
|
| 93 |
+
"""Convert to dictionary for storage in Message.metadata."""
|
| 94 |
+
return {
|
| 95 |
+
"tool_name": self.tool_name,
|
| 96 |
+
"success": self.success,
|
| 97 |
+
"data": self.data,
|
| 98 |
+
"error_message": self.error_message,
|
| 99 |
+
"execution_timestamp": self.execution_timestamp.isoformat()
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
### 3. AgentRequestContext
|
| 106 |
+
|
| 107 |
+
**Purpose**: Context needed for agent execution, assembled per request.
|
| 108 |
+
|
| 109 |
+
**Type**: Runtime context object (not persisted)
|
| 110 |
+
|
| 111 |
+
**Attributes**:
|
| 112 |
+
|
| 113 |
+
| Attribute | Type | Description |
|
| 114 |
+
|-----------|------|-------------|
|
| 115 |
+
| `user_id` | `int` | Authenticated user ID from JWT token |
|
| 116 |
+
| `conversation_id` | `int` | Conversation ID for this chat session |
|
| 117 |
+
| `message_history` | `List[dict]` | Formatted message history for agent |
|
| 118 |
+
| `jwt_token` | `str` | JWT token for authentication (not passed to agent) |
|
| 119 |
+
| `system_prompt` | `str` | System prompt for agent behavior |
|
| 120 |
+
|
| 121 |
+
**Example**:
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
@dataclass
|
| 125 |
+
class AgentRequestContext:
|
| 126 |
+
user_id: int
|
| 127 |
+
conversation_id: int
|
| 128 |
+
message_history: List[dict]
|
| 129 |
+
jwt_token: str
|
| 130 |
+
system_prompt: str
|
| 131 |
+
|
| 132 |
+
@classmethod
|
| 133 |
+
async def from_request(
|
| 134 |
+
cls,
|
| 135 |
+
user_id: int,
|
| 136 |
+
conversation_id: Optional[int],
|
| 137 |
+
jwt_token: str,
|
| 138 |
+
db: Session
|
| 139 |
+
) -> "AgentRequestContext":
|
| 140 |
+
"""Build context from request parameters."""
|
| 141 |
+
conversation_service = ConversationService(db)
|
| 142 |
+
|
| 143 |
+
# Get or create conversation
|
| 144 |
+
conversation = await conversation_service.get_or_create_conversation(
|
| 145 |
+
user_id=user_id,
|
| 146 |
+
conversation_id=conversation_id
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
# Load and format message history
|
| 150 |
+
messages = await conversation_service.get_messages(conversation.id)
|
| 151 |
+
message_history = await conversation_service.format_messages_for_agent(
|
| 152 |
+
messages=messages,
|
| 153 |
+
max_messages=20,
|
| 154 |
+
max_tokens=8000
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
return cls(
|
| 158 |
+
user_id=user_id,
|
| 159 |
+
conversation_id=conversation.id,
|
| 160 |
+
message_history=message_history,
|
| 161 |
+
jwt_token=jwt_token,
|
| 162 |
+
system_prompt=get_default_system_prompt()
|
| 163 |
+
)
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
---
|
| 167 |
+
|
| 168 |
+
## Existing Database Models (No Changes)
|
| 169 |
+
|
| 170 |
+
### Task Model
|
| 171 |
+
|
| 172 |
+
**Table**: `tasks`
|
| 173 |
+
|
| 174 |
+
**Attributes** (existing, no changes):
|
| 175 |
+
- `id`: Primary key
|
| 176 |
+
- `user_id`: Foreign key to users table (indexed)
|
| 177 |
+
- `title`: Task title (max 200 chars)
|
| 178 |
+
- `description`: Optional description (max 1000 chars)
|
| 179 |
+
- `completed`: Boolean flag (indexed)
|
| 180 |
+
- `created_at`: Timestamp (indexed)
|
| 181 |
+
- `updated_at`: Timestamp
|
| 182 |
+
|
| 183 |
+
**Note**: No changes to Task model. MCP tools interact with existing schema.
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
### Conversation Model
|
| 188 |
+
|
| 189 |
+
**Table**: `conversation`
|
| 190 |
+
|
| 191 |
+
**Attributes** (existing, no changes):
|
| 192 |
+
- `id`: Primary key
|
| 193 |
+
- `user_id`: Foreign key to users table (indexed)
|
| 194 |
+
- `title`: Optional conversation title
|
| 195 |
+
- `created_at`: Timestamp (indexed)
|
| 196 |
+
- `updated_at`: Timestamp
|
| 197 |
+
|
| 198 |
+
**Note**: No changes to Conversation model.
|
| 199 |
+
|
| 200 |
+
---
|
| 201 |
+
|
| 202 |
+
### Message Model
|
| 203 |
+
|
| 204 |
+
**Table**: `message`
|
| 205 |
+
|
| 206 |
+
**Attributes** (existing, with metadata usage):
|
| 207 |
+
- `id`: Primary key
|
| 208 |
+
- `conversation_id`: Foreign key to conversation table
|
| 209 |
+
- `role`: Message role ("user" or "assistant")
|
| 210 |
+
- `content`: Message content (text)
|
| 211 |
+
- `metadata`: JSON field for storing tool calls and results (existing field, new usage)
|
| 212 |
+
- `created_at`: Timestamp
|
| 213 |
+
|
| 214 |
+
**Metadata Structure** (new usage of existing field):
|
| 215 |
+
|
| 216 |
+
```json
|
| 217 |
+
{
|
| 218 |
+
"tool_calls": [
|
| 219 |
+
{
|
| 220 |
+
"name": "add_task",
|
| 221 |
+
"arguments": {
|
| 222 |
+
"title": "Buy groceries",
|
| 223 |
+
"description": "Milk, eggs, bread"
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
],
|
| 227 |
+
"tool_results": [
|
| 228 |
+
{
|
| 229 |
+
"tool_name": "add_task",
|
| 230 |
+
"success": true,
|
| 231 |
+
"data": {
|
| 232 |
+
"id": 123,
|
| 233 |
+
"title": "Buy groceries",
|
| 234 |
+
"completed": false
|
| 235 |
+
},
|
| 236 |
+
"execution_timestamp": "2026-01-14T12:00:00Z"
|
| 237 |
+
}
|
| 238 |
+
]
|
| 239 |
+
}
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
## Stateless Request Cycle Flow
|
| 245 |
+
|
| 246 |
+
### Flow Diagram
|
| 247 |
+
|
| 248 |
+
```
|
| 249 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 250 |
+
│ 1. Receive Chat Request │
|
| 251 |
+
│ POST /api/{user_id}/chat │
|
| 252 |
+
│ - Validate JWT token │
|
| 253 |
+
│ - Extract user_id, conversation_id │
|
| 254 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 255 |
+
│
|
| 256 |
+
▼
|
| 257 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 258 |
+
│ 2. Load Conversation History (Database) │
|
| 259 |
+
│ ConversationService.get_or_create_conversation() │
|
| 260 |
+
│ ConversationService.get_messages() │
|
| 261 |
+
│ ConversationService.format_messages_for_agent() │
|
| 262 |
+
│ - Query: SELECT * FROM message WHERE conversation_id = ? │
|
| 263 |
+
│ - Trim to last 20 messages, max 8000 tokens │
|
| 264 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 265 |
+
│
|
| 266 |
+
▼
|
| 267 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 268 |
+
│ 3. Store User Message (Database) │
|
| 269 |
+
│ ConversationService.add_message() │
|
| 270 |
+
│ - INSERT INTO message (conversation_id, role, content) │
|
| 271 |
+
│ - role = "user" │
|
| 272 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 273 |
+
│
|
| 274 |
+
▼
|
| 275 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 276 |
+
│ 4. Execute Agent (Stateless) │
|
| 277 |
+
│ AgentRunner.execute() │
|
| 278 |
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
| 279 |
+
│ │ 4a. Get tool definitions from MCPToolRegistry │ │
|
| 280 |
+
│ │ 4b. Call LLM with tools (Gemini API) │ │
|
| 281 |
+
│ │ 4c. If tool_calls present: │ │
|
| 282 |
+
│ │ - Execute each tool via MCPToolRegistry │ │
|
| 283 |
+
│ │ - Inject user_id for security │ │
|
| 284 |
+
│ │ - Collect tool results │ │
|
| 285 |
+
│ │ 4d. Call LLM with tool results │ │
|
| 286 |
+
│ │ 4e. Return final response │ │
|
| 287 |
+
│ └──────────────────────────────────────────────────────────┘ │
|
| 288 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 289 |
+
│
|
| 290 |
+
▼
|
| 291 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 292 |
+
│ 5. Persist Agent Response (Database) │
|
| 293 |
+
│ ConversationService.add_message() │
|
| 294 |
+
│ - INSERT INTO message (conversation_id, role, content, metadata)│
|
| 295 |
+
│ - role = "assistant" │
|
| 296 |
+
│ - metadata = {tool_calls, tool_results} │
|
| 297 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 298 |
+
│
|
| 299 |
+
▼
|
| 300 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 301 |
+
│ 6. Return Response │
|
| 302 |
+
│ ChatResponse(message, conversation_id) │
|
| 303 |
+
└─────────────────────────────────────────────────────────────────┘
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
### Detailed Flow Steps
|
| 307 |
+
|
| 308 |
+
#### Step 1: Receive Chat Request
|
| 309 |
+
|
| 310 |
+
**Endpoint**: `POST /api/{user_id}/chat`
|
| 311 |
+
|
| 312 |
+
**Input**: `ChatRequest`
|
| 313 |
+
```python
|
| 314 |
+
class ChatRequest(BaseModel):
|
| 315 |
+
message: str
|
| 316 |
+
conversation_id: Optional[int] = None
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
**Authentication**: JWT token validated via `get_current_user` dependency
|
| 320 |
+
|
| 321 |
+
**Authorization**: Verify `current_user["id"] == user_id`
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
#### Step 2: Load Conversation History
|
| 326 |
+
|
| 327 |
+
**Service**: `ConversationService`
|
| 328 |
+
|
| 329 |
+
**Operations**:
|
| 330 |
+
1. Get or create conversation:
|
| 331 |
+
```python
|
| 332 |
+
conversation = await conversation_service.get_or_create_conversation(
|
| 333 |
+
user_id=user_id,
|
| 334 |
+
conversation_id=request.conversation_id
|
| 335 |
+
)
|
| 336 |
+
```
|
| 337 |
+
|
| 338 |
+
2. Load messages:
|
| 339 |
+
```python
|
| 340 |
+
messages = await conversation_service.get_messages(conversation.id)
|
| 341 |
+
```
|
| 342 |
+
|
| 343 |
+
3. Format and trim for agent:
|
| 344 |
+
```python
|
| 345 |
+
message_history = await conversation_service.format_messages_for_agent(
|
| 346 |
+
messages=messages,
|
| 347 |
+
max_messages=20,
|
| 348 |
+
max_tokens=8000
|
| 349 |
+
)
|
| 350 |
+
```
|
| 351 |
+
|
| 352 |
+
**Database Queries**:
|
| 353 |
+
- `SELECT * FROM conversation WHERE id = ? AND user_id = ?`
|
| 354 |
+
- `INSERT INTO conversation (user_id, created_at, updated_at)` (if new)
|
| 355 |
+
- `SELECT * FROM message WHERE conversation_id = ? ORDER BY created_at ASC`
|
| 356 |
+
|
| 357 |
+
---
|
| 358 |
+
|
| 359 |
+
#### Step 3: Store User Message
|
| 360 |
+
|
| 361 |
+
**Service**: `ConversationService`
|
| 362 |
+
|
| 363 |
+
**Operation**:
|
| 364 |
+
```python
|
| 365 |
+
await conversation_service.add_message(
|
| 366 |
+
conversation_id=conversation.id,
|
| 367 |
+
role="user",
|
| 368 |
+
content=request.message
|
| 369 |
+
)
|
| 370 |
+
```
|
| 371 |
+
|
| 372 |
+
**Database Query**:
|
| 373 |
+
- `INSERT INTO message (conversation_id, role, content, created_at) VALUES (?, 'user', ?, ?)`
|
| 374 |
+
|
| 375 |
+
---
|
| 376 |
+
|
| 377 |
+
#### Step 4: Execute Agent
|
| 378 |
+
|
| 379 |
+
**Service**: `AgentRunner`
|
| 380 |
+
|
| 381 |
+
**Sub-steps**:
|
| 382 |
+
|
| 383 |
+
**4a. Get Tool Definitions**:
|
| 384 |
+
```python
|
| 385 |
+
tool_definitions = tool_registry.get_tool_definitions()
|
| 386 |
+
# Returns: [{"type": "function", "function": {...}}, ...]
|
| 387 |
+
```
|
| 388 |
+
|
| 389 |
+
**4b. First LLM Call**:
|
| 390 |
+
```python
|
| 391 |
+
response = await provider.generate_response_with_tools(
|
| 392 |
+
messages=message_history + [{"role": "user", "content": request.message}],
|
| 393 |
+
system_prompt=system_prompt,
|
| 394 |
+
tools=tool_definitions
|
| 395 |
+
)
|
| 396 |
+
# Returns: {"content": str, "tool_calls": [...]} or {"content": str, "tool_calls": None}
|
| 397 |
+
```
|
| 398 |
+
|
| 399 |
+
**4c. Execute Tools** (if tool_calls present):
|
| 400 |
+
```python
|
| 401 |
+
tool_results = []
|
| 402 |
+
for tool_call in response["tool_calls"]:
|
| 403 |
+
result = await tool_registry.execute_tool(
|
| 404 |
+
tool_name=tool_call["name"],
|
| 405 |
+
arguments=tool_call["arguments"],
|
| 406 |
+
user_id=user_id # SECURITY: Injected by backend
|
| 407 |
+
)
|
| 408 |
+
tool_results.append(result)
|
| 409 |
+
```
|
| 410 |
+
|
| 411 |
+
**4d. Second LLM Call** (with tool results):
|
| 412 |
+
```python
|
| 413 |
+
final_response = await provider.generate_response_with_tool_results(
|
| 414 |
+
messages=message_history,
|
| 415 |
+
tool_calls=response["tool_calls"],
|
| 416 |
+
tool_results=tool_results
|
| 417 |
+
)
|
| 418 |
+
# Returns: {"content": str, "tool_calls": [...], "tool_results": [...]}
|
| 419 |
+
```
|
| 420 |
+
|
| 421 |
+
**4e. Return Final Response**:
|
| 422 |
+
```python
|
| 423 |
+
return {
|
| 424 |
+
"content": final_response["content"],
|
| 425 |
+
"tool_calls": response["tool_calls"],
|
| 426 |
+
"tool_results": tool_results
|
| 427 |
+
}
|
| 428 |
+
```
|
| 429 |
+
|
| 430 |
+
---
|
| 431 |
+
|
| 432 |
+
#### Step 5: Persist Agent Response
|
| 433 |
+
|
| 434 |
+
**Service**: `ConversationService`
|
| 435 |
+
|
| 436 |
+
**Operation**:
|
| 437 |
+
```python
|
| 438 |
+
await conversation_service.add_message(
|
| 439 |
+
conversation_id=conversation.id,
|
| 440 |
+
role="assistant",
|
| 441 |
+
content=agent_response["content"],
|
| 442 |
+
metadata={
|
| 443 |
+
"tool_calls": agent_response.get("tool_calls"),
|
| 444 |
+
"tool_results": agent_response.get("tool_results")
|
| 445 |
+
}
|
| 446 |
+
)
|
| 447 |
+
```
|
| 448 |
+
|
| 449 |
+
**Database Query**:
|
| 450 |
+
- `INSERT INTO message (conversation_id, role, content, metadata, created_at) VALUES (?, 'assistant', ?, ?, ?)`
|
| 451 |
+
|
| 452 |
+
---
|
| 453 |
+
|
| 454 |
+
#### Step 6: Return Response
|
| 455 |
+
|
| 456 |
+
**Output**: `ChatResponse`
|
| 457 |
+
```python
|
| 458 |
+
class ChatResponse(BaseModel):
|
| 459 |
+
message: str
|
| 460 |
+
conversation_id: int
|
| 461 |
+
```
|
| 462 |
+
|
| 463 |
+
**HTTP Response**: `200 OK` with JSON body
|
| 464 |
+
|
| 465 |
+
---
|
| 466 |
+
|
| 467 |
+
## MCP Tool Execution Flow
|
| 468 |
+
|
| 469 |
+
### Tool Invocation Sequence
|
| 470 |
+
|
| 471 |
+
```
|
| 472 |
+
Agent → MCPToolRegistry.execute_tool()
|
| 473 |
+
│
|
| 474 |
+
├─ Validate tool exists
|
| 475 |
+
├─ Inject user_id (SECURITY)
|
| 476 |
+
├─ Call tool function
|
| 477 |
+
│ │
|
| 478 |
+
│ └─ Tool Implementation
|
| 479 |
+
│ ├─ Validate inputs
|
| 480 |
+
│ ├─ Query database (with user_id filter)
|
| 481 |
+
│ ├─ Perform operation
|
| 482 |
+
│ └─ Return structured result
|
| 483 |
+
│
|
| 484 |
+
└─ Return result to agent
|
| 485 |
+
```
|
| 486 |
+
|
| 487 |
+
### Tool Result Format (Standard)
|
| 488 |
+
|
| 489 |
+
All MCP tools MUST return results in this format:
|
| 490 |
+
|
| 491 |
+
```python
|
| 492 |
+
{
|
| 493 |
+
"success": bool, # True if operation succeeded
|
| 494 |
+
"data": dict, # Tool-specific result data
|
| 495 |
+
"message": str, # User-friendly message
|
| 496 |
+
"error": Optional[str] # Error message if success=False
|
| 497 |
+
}
|
| 498 |
+
```
|
| 499 |
+
|
| 500 |
+
**Success Example**:
|
| 501 |
+
```python
|
| 502 |
+
{
|
| 503 |
+
"success": True,
|
| 504 |
+
"data": {
|
| 505 |
+
"id": 123,
|
| 506 |
+
"title": "Buy groceries",
|
| 507 |
+
"completed": False,
|
| 508 |
+
"created_at": "2026-01-14T12:00:00Z"
|
| 509 |
+
},
|
| 510 |
+
"message": "Task 'Buy groceries' created successfully"
|
| 511 |
+
}
|
| 512 |
+
```
|
| 513 |
+
|
| 514 |
+
**Error Example**:
|
| 515 |
+
```python
|
| 516 |
+
{
|
| 517 |
+
"success": False,
|
| 518 |
+
"data": {},
|
| 519 |
+
"message": "Task not found",
|
| 520 |
+
"error": "No task found with ID 999 for user 42"
|
| 521 |
+
}
|
| 522 |
+
```
|
| 523 |
+
|
| 524 |
+
---
|
| 525 |
+
|
| 526 |
+
## Security Model
|
| 527 |
+
|
| 528 |
+
### User Context Injection
|
| 529 |
+
|
| 530 |
+
**Critical Security Pattern**: User context (`user_id`) is ALWAYS injected by the backend, NEVER trusted from LLM output.
|
| 531 |
+
|
| 532 |
+
**Implementation**:
|
| 533 |
+
|
| 534 |
+
```python
|
| 535 |
+
async def execute_tool(
|
| 536 |
+
self,
|
| 537 |
+
tool_name: str,
|
| 538 |
+
arguments: Dict[str, Any],
|
| 539 |
+
user_id: int # From JWT token, not LLM
|
| 540 |
+
) -> Dict[str, Any]:
|
| 541 |
+
"""Execute tool with user context injection."""
|
| 542 |
+
|
| 543 |
+
# SECURITY: Inject user_id, overwrite if present in arguments
|
| 544 |
+
arguments["user_id"] = user_id
|
| 545 |
+
|
| 546 |
+
# Execute tool
|
| 547 |
+
result = await self.tools[tool_name](**arguments)
|
| 548 |
+
return result
|
| 549 |
+
```
|
| 550 |
+
|
| 551 |
+
**Why This Matters**:
|
| 552 |
+
- Prevents cross-user data access
|
| 553 |
+
- LLM cannot manipulate user_id
|
| 554 |
+
- All database queries filtered by authenticated user_id
|
| 555 |
+
|
| 556 |
+
---
|
| 557 |
+
|
| 558 |
+
## Performance Considerations
|
| 559 |
+
|
| 560 |
+
### Conversation History Trimming
|
| 561 |
+
|
| 562 |
+
**Strategy**: Keep last 20 messages, max 8000 tokens
|
| 563 |
+
|
| 564 |
+
**Rationale**:
|
| 565 |
+
- Free-tier context window limits (Gemini: 1M tokens, but trimming for efficiency)
|
| 566 |
+
- Faster LLM responses with shorter context
|
| 567 |
+
- Reduced API costs
|
| 568 |
+
|
| 569 |
+
**Implementation**:
|
| 570 |
+
```python
|
| 571 |
+
async def format_messages_for_agent(
|
| 572 |
+
self,
|
| 573 |
+
messages: List[Message],
|
| 574 |
+
max_messages: int = 20,
|
| 575 |
+
max_tokens: int = 8000
|
| 576 |
+
) -> List[Dict[str, str]]:
|
| 577 |
+
"""Format and trim messages for agent context."""
|
| 578 |
+
|
| 579 |
+
# Keep last N messages
|
| 580 |
+
recent_messages = messages[-max_messages:]
|
| 581 |
+
|
| 582 |
+
# Format for agent
|
| 583 |
+
formatted = [
|
| 584 |
+
{"role": msg.role, "content": msg.content}
|
| 585 |
+
for msg in recent_messages
|
| 586 |
+
]
|
| 587 |
+
|
| 588 |
+
# Estimate tokens (rough: 1 token ≈ 4 characters)
|
| 589 |
+
total_tokens = sum(len(msg["content"]) // 4 for msg in formatted)
|
| 590 |
+
|
| 591 |
+
# Trim oldest messages if over limit
|
| 592 |
+
while total_tokens > max_tokens and len(formatted) > 1:
|
| 593 |
+
formatted.pop(0)
|
| 594 |
+
total_tokens = sum(len(msg["content"]) // 4 for msg in formatted)
|
| 595 |
+
|
| 596 |
+
return formatted
|
| 597 |
+
```
|
| 598 |
+
|
| 599 |
+
---
|
| 600 |
+
|
| 601 |
+
## Error Handling
|
| 602 |
+
|
| 603 |
+
### Tool Execution Errors
|
| 604 |
+
|
| 605 |
+
**Pattern**: Return structured errors, don't throw exceptions
|
| 606 |
+
|
| 607 |
+
**Example**:
|
| 608 |
+
```python
|
| 609 |
+
try:
|
| 610 |
+
result = await tool_function(**arguments)
|
| 611 |
+
return result
|
| 612 |
+
except ValueError as e:
|
| 613 |
+
return {
|
| 614 |
+
"success": False,
|
| 615 |
+
"data": {},
|
| 616 |
+
"message": "Invalid input",
|
| 617 |
+
"error": str(e)
|
| 618 |
+
}
|
| 619 |
+
except Exception as e:
|
| 620 |
+
logger.error(f"Tool execution error: {tool_name}", exc_info=True)
|
| 621 |
+
return {
|
| 622 |
+
"success": False,
|
| 623 |
+
"data": {},
|
| 624 |
+
"message": "Tool execution failed",
|
| 625 |
+
"error": "An unexpected error occurred"
|
| 626 |
+
}
|
| 627 |
+
```
|
| 628 |
+
|
| 629 |
+
### Provider Errors
|
| 630 |
+
|
| 631 |
+
**Pattern**: Fallback to secondary provider or return user-friendly error
|
| 632 |
+
|
| 633 |
+
**Example**:
|
| 634 |
+
```python
|
| 635 |
+
try:
|
| 636 |
+
response = await primary_provider.generate_response_with_tools(...)
|
| 637 |
+
except RateLimitError:
|
| 638 |
+
if fallback_provider:
|
| 639 |
+
response = await fallback_provider.generate_response_with_tools(...)
|
| 640 |
+
else:
|
| 641 |
+
raise HTTPException(
|
| 642 |
+
status_code=429,
|
| 643 |
+
detail="Rate limit exceeded. Please try again in a few minutes."
|
| 644 |
+
)
|
| 645 |
+
```
|
| 646 |
+
|
| 647 |
+
---
|
| 648 |
+
|
| 649 |
+
## Summary
|
| 650 |
+
|
| 651 |
+
This data model defines:
|
| 652 |
+
- ✅ Runtime entities for agent configuration and execution
|
| 653 |
+
- ✅ Stateless request cycle flow with database persistence
|
| 654 |
+
- ✅ MCP tool execution flow with user context injection
|
| 655 |
+
- ✅ Security model preventing cross-user data access
|
| 656 |
+
- ✅ Performance optimizations for free-tier constraints
|
| 657 |
+
- ✅ Error handling patterns for reliability
|
| 658 |
+
|
| 659 |
+
**Key Principles**:
|
| 660 |
+
1. **Stateless**: No in-memory state, all state in database
|
| 661 |
+
2. **Secure**: User context injected by backend, not LLM
|
| 662 |
+
3. **Restart-safe**: Server restarts don't affect conversations
|
| 663 |
+
4. **Free-tier compatible**: Conversation history trimming
|
| 664 |
+
5. **Structured**: All tools return consistent format
|
specs/001-openai-agent-mcp-tools/plan.md
ADDED
|
@@ -0,0 +1,747 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Branch**: `001-openai-agent-mcp-tools` | **Date**: 2026-01-14 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
**Input**: Feature specification from `/specs/001-openai-agent-mcp-tools/spec.md`
|
| 5 |
+
|
| 6 |
+
## Summary
|
| 7 |
+
|
| 8 |
+
This plan implements an AI-powered Todo agent using the OpenAI Agents SDK with external client configuration to support free-tier API providers (Gemini, OpenRouter, Cohere). The agent will execute natural language task management operations through stateless MCP tools that persist all state in the database. The implementation maintains a fully stateless backend architecture where every chat request loads conversation history from the database, executes the agent with MCP tools, persists results, and returns responses.
|
| 9 |
+
|
| 10 |
+
**Primary Requirement**: Enable users to manage tasks via natural language by implementing an OpenAI Agent that maps user intents to MCP tool invocations (add_task, list_tasks, complete_task, delete_task, update_task).
|
| 11 |
+
|
| 12 |
+
**Technical Approach**:
|
| 13 |
+
1. Configure OpenAI Agents SDK with external client abstraction for free-tier providers
|
| 14 |
+
2. Implement MCP server using Official MCP SDK with 5 stateless task tools
|
| 15 |
+
3. Integrate agent execution into existing stateless chat endpoint
|
| 16 |
+
4. Ensure all state persists in Neon PostgreSQL database
|
| 17 |
+
|
| 18 |
+
## Technical Context
|
| 19 |
+
|
| 20 |
+
**Language/Version**: Python 3.11+
|
| 21 |
+
**Primary Dependencies**:
|
| 22 |
+
- OpenAI Agents SDK (agent reasoning and orchestration)
|
| 23 |
+
- Official MCP SDK (tool server implementation)
|
| 24 |
+
- FastAPI 0.104.1 (existing backend framework)
|
| 25 |
+
- SQLModel 0.0.14 (existing ORM)
|
| 26 |
+
- google-generativeai 0.3.2 (Gemini provider - already installed)
|
| 27 |
+
- Cohere SDK (to be added for Cohere provider support)
|
| 28 |
+
|
| 29 |
+
**Storage**: Neon Serverless PostgreSQL (existing: tasks, conversations, messages tables)
|
| 30 |
+
**Testing**: pytest 7.4.3 (existing)
|
| 31 |
+
**Target Platform**: Linux server (FastAPI backend)
|
| 32 |
+
**Project Type**: Web application (backend-only changes for this spec)
|
| 33 |
+
**Performance Goals**:
|
| 34 |
+
- Agent response within 5 seconds (excluding external API latency)
|
| 35 |
+
- MCP tool invocations <100ms (database operations)
|
| 36 |
+
- Support 50 concurrent users
|
| 37 |
+
|
| 38 |
+
**Constraints**:
|
| 39 |
+
- Free-tier API constraints (short context windows, rate limits, token caps)
|
| 40 |
+
- Stateless architecture (no in-memory state)
|
| 41 |
+
- No frontend changes permitted
|
| 42 |
+
- All backend code inside backend/ directory
|
| 43 |
+
|
| 44 |
+
**Scale/Scope**:
|
| 45 |
+
- 5 MCP tools (add_task, list_tasks, complete_task, delete_task, update_task)
|
| 46 |
+
- 3 external LLM providers (Gemini, OpenRouter, Cohere)
|
| 47 |
+
- Multi-user support with JWT-based user scoping
|
| 48 |
+
|
| 49 |
+
## Constitution Check
|
| 50 |
+
|
| 51 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 52 |
+
|
| 53 |
+
### Phase II Constitutional Compliance
|
| 54 |
+
|
| 55 |
+
✅ **User-Centric Functionality**: Agent enables natural language task management, improving UX
|
| 56 |
+
✅ **Spec-Driven Development**: All implementation follows approved spec in `/specs/001-openai-agent-mcp-tools/`
|
| 57 |
+
✅ **Security & Data Privacy**: JWT authentication enforced; MCP tools validate user scoping
|
| 58 |
+
✅ **Scalable Architecture**: Stateless design with database-backed persistence
|
| 59 |
+
✅ **Maintainable & Consistent Code**: Follows existing FastAPI + SQLModel patterns
|
| 60 |
+
|
| 61 |
+
### Phase III Constitutional Compliance
|
| 62 |
+
|
| 63 |
+
✅ **Mandatory Development Framework**: Using Spec-Kit Plus workflow with Claude Code
|
| 64 |
+
✅ **Stateless FastAPI Backend**: No in-memory state; all state persists in database
|
| 65 |
+
✅ **MCP Server Implementation**: Using Official MCP SDK for all tool implementations
|
| 66 |
+
✅ **OpenAI Agents SDK**: Required for agent reasoning and orchestration
|
| 67 |
+
✅ **Database-Persisted State**: All conversations, messages, and tasks in Neon PostgreSQL
|
| 68 |
+
|
| 69 |
+
### Agent & Skill Governance
|
| 70 |
+
|
| 71 |
+
✅ **Conversational AI Architect Agent**: Required for agent design, reasoning workflows, intent detection
|
| 72 |
+
- **Mandatory Skill**: `agent-behavior-reasoning`
|
| 73 |
+
- **Domain**: AI agent design, tool selection logic, response quality optimization
|
| 74 |
+
|
| 75 |
+
✅ **Backend Systems Agent**: Required for MCP tool design, API implementation, database operations
|
| 76 |
+
- **Mandatory Skill**: `backend-mcp-tools`
|
| 77 |
+
- **Domain**: Server-side architecture, MCP tool contracts, stateless backend logic
|
| 78 |
+
|
| 79 |
+
### MCP Tool Constitutional Rules
|
| 80 |
+
|
| 81 |
+
✅ **Tool Implementation Requirements**: All 5 required tools defined (add_task, list_tasks, complete_task, delete_task, update_task)
|
| 82 |
+
✅ **Statelessness**: Tools operate statelessly with explicit inputs
|
| 83 |
+
✅ **State Persistence**: All modifications persist to Neon PostgreSQL
|
| 84 |
+
✅ **Agent Access Control**: AI agents ONLY modify tasks through MCP tools
|
| 85 |
+
✅ **Tool Contracts**: Each tool defines clear contracts with structured responses
|
| 86 |
+
|
| 87 |
+
### Chat & Conversation Rules
|
| 88 |
+
|
| 89 |
+
✅ **Stateless Request Cycle**: Load history → Execute agent → Invoke tools → Store results → Return response
|
| 90 |
+
✅ **Server Restart Resilience**: All state recoverable from database
|
| 91 |
+
✅ **Conversation Continuity Mandate**: Conversation context maintained across turns
|
| 92 |
+
|
| 93 |
+
### Error Handling & Confirmation Law
|
| 94 |
+
|
| 95 |
+
✅ **User-Facing Confirmations**: Agent returns friendly confirmations for all actions
|
| 96 |
+
✅ **Graceful Error Handling**: Task not found, invalid requests, system errors handled gracefully
|
| 97 |
+
✅ **Silent Failure Prohibition**: All errors logged and communicated to users
|
| 98 |
+
|
| 99 |
+
**GATE STATUS**: ✅ PASSED - All constitutional requirements satisfied
|
| 100 |
+
|
| 101 |
+
## Project Structure
|
| 102 |
+
|
| 103 |
+
### Documentation (this feature)
|
| 104 |
+
|
| 105 |
+
```text
|
| 106 |
+
specs/001-openai-agent-mcp-tools/
|
| 107 |
+
├── plan.md # This file (/sp.plan command output)
|
| 108 |
+
├── research.md # Phase 0 output (research findings)
|
| 109 |
+
├── data-model.md # Phase 1 output (entity definitions)
|
| 110 |
+
├── quickstart.md # Phase 1 output (setup instructions)
|
| 111 |
+
├── contracts/ # Phase 1 output (MCP tool contracts)
|
| 112 |
+
│ ├── add_task.json
|
| 113 |
+
│ ├── list_tasks.json
|
| 114 |
+
│ ├── complete_task.json
|
| 115 |
+
│ ├── delete_task.json
|
| 116 |
+
│ └── update_task.json
|
| 117 |
+
└── tasks.md # Phase 2 output (/sp.tasks command - NOT created by /sp.plan)
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### Source Code (repository root)
|
| 121 |
+
|
| 122 |
+
```text
|
| 123 |
+
backend/
|
| 124 |
+
├── src/
|
| 125 |
+
│ ├── agent/ # NEW: Agent configuration and execution
|
| 126 |
+
│ │ ├── __init__.py
|
| 127 |
+
│ │ ├── agent_config.py # Agent setup with external client
|
| 128 |
+
│ │ ├── agent_runner.py # Agent execution logic
|
| 129 |
+
│ │ └── providers/ # External LLM provider configurations
|
| 130 |
+
│ │ ├── __init__.py
|
| 131 |
+
│ │ ├── gemini.py # Gemini provider config
|
| 132 |
+
│ │ ├── openrouter.py # OpenRouter provider config
|
| 133 |
+
│ │ └── cohere.py # Cohere provider config
|
| 134 |
+
│ │
|
| 135 |
+
│ ├── mcp/ # NEW: MCP server and tools
|
| 136 |
+
│ │ ├── __init__.py
|
| 137 |
+
│ │ ├── server.py # MCP server setup
|
| 138 |
+
│ │ └── tools/ # MCP tool implementations
|
| 139 |
+
│ │ ├── __init__.py
|
| 140 |
+
│ │ ├── add_task.py # add_task tool
|
| 141 |
+
│ │ ├── list_tasks.py # list_tasks tool
|
| 142 |
+
│ │ ├── complete_task.py # complete_task tool
|
| 143 |
+
│ │ ├── delete_task.py # delete_task tool
|
| 144 |
+
│ │ └── update_task.py # update_task tool
|
| 145 |
+
│ │
|
| 146 |
+
│ ├── api/
|
| 147 |
+
│ │ └── routes/
|
| 148 |
+
│ │ └── chat.py # MODIFIED: Integrate agent execution
|
| 149 |
+
│ │
|
| 150 |
+
│ ├── services/
|
| 151 |
+
│ │ ├── llm_service.py # MODIFIED: Delegate to agent_runner
|
| 152 |
+
│ │ └── conversation_service.py # EXISTING: Conversation persistence
|
| 153 |
+
│ │
|
| 154 |
+
│ ├── models/ # EXISTING: No changes
|
| 155 |
+
│ │ ├── task.py
|
| 156 |
+
│ │ ├── conversation.py
|
| 157 |
+
│ │ └── message.py
|
| 158 |
+
│ │
|
| 159 |
+
│ ├── schemas/ # EXISTING: No changes
|
| 160 |
+
│ │ ├── chat_request.py
|
| 161 |
+
│ │ └── chat_response.py
|
| 162 |
+
│ │
|
| 163 |
+
│ └── core/ # EXISTING: No changes
|
| 164 |
+
│ ├── config.py
|
| 165 |
+
│ ├── database.py
|
| 166 |
+
│ └── security.py
|
| 167 |
+
│
|
| 168 |
+
└── requirements.txt # MODIFIED: Add OpenAI Agents SDK, MCP SDK, Cohere SDK
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
**Structure Decision**: Web application structure (backend-only changes). All new code resides in `backend/src/agent/` and `backend/src/mcp/` directories. Existing chat endpoint (`backend/src/api/routes/chat.py`) is modified to integrate agent execution. No frontend changes per spec requirements.
|
| 172 |
+
|
| 173 |
+
## Complexity Tracking
|
| 174 |
+
|
| 175 |
+
> **No constitutional violations requiring justification**
|
| 176 |
+
|
| 177 |
+
All complexity introduced is justified by constitutional requirements:
|
| 178 |
+
- OpenAI Agents SDK: Required by Phase III constitution for agent reasoning
|
| 179 |
+
- MCP Server: Required by Phase III constitution for tool implementation
|
| 180 |
+
- External client abstraction: Required by spec to support free-tier providers
|
| 181 |
+
- Stateless architecture: Required by Phase III constitution for scalability
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## Phase 0: Research & Technology Validation
|
| 186 |
+
|
| 187 |
+
### Research Objectives
|
| 188 |
+
|
| 189 |
+
The following unknowns must be resolved before design:
|
| 190 |
+
|
| 191 |
+
1. **OpenAI Agents SDK External Client Configuration**
|
| 192 |
+
- How to configure OpenAI Agents SDK with non-OpenAI providers
|
| 193 |
+
- External client abstraction patterns
|
| 194 |
+
- Compatibility with Gemini, OpenRouter, Cohere APIs
|
| 195 |
+
|
| 196 |
+
2. **Official MCP SDK Integration**
|
| 197 |
+
- MCP SDK installation and setup for Python
|
| 198 |
+
- Tool registration patterns
|
| 199 |
+
- Stateless tool implementation best practices
|
| 200 |
+
|
| 201 |
+
3. **Free-Tier Provider Capabilities**
|
| 202 |
+
- Function calling support in Gemini free tier
|
| 203 |
+
- Function calling support in OpenRouter free tier
|
| 204 |
+
- Function calling support in Cohere free tier
|
| 205 |
+
- Context window limits and token caps
|
| 206 |
+
|
| 207 |
+
4. **Agent-MCP Integration Pattern**
|
| 208 |
+
- How OpenAI Agents SDK invokes MCP tools
|
| 209 |
+
- Tool result handling and response formatting
|
| 210 |
+
- Error propagation from tools to agent
|
| 211 |
+
|
| 212 |
+
5. **Stateless Request Cycle Implementation**
|
| 213 |
+
- Loading conversation history for agent context
|
| 214 |
+
- Persisting tool calls and results
|
| 215 |
+
- Maintaining conversation continuity
|
| 216 |
+
|
| 217 |
+
### Research Tasks
|
| 218 |
+
|
| 219 |
+
**Agent**: Conversational AI Architect Agent
|
| 220 |
+
**Skill**: `agent-behavior-reasoning`
|
| 221 |
+
|
| 222 |
+
#### Task 1: Research OpenAI Agents SDK External Client Configuration
|
| 223 |
+
|
| 224 |
+
**Objective**: Determine how to configure OpenAI Agents SDK to use external LLM providers (Gemini, OpenRouter, Cohere) instead of OpenAI API.
|
| 225 |
+
|
| 226 |
+
**Research Questions**:
|
| 227 |
+
- Does OpenAI Agents SDK support external client configuration?
|
| 228 |
+
- What is the abstraction layer for provider switching?
|
| 229 |
+
- How to implement custom client adapters for non-OpenAI providers?
|
| 230 |
+
- Are there existing examples or libraries for this pattern?
|
| 231 |
+
|
| 232 |
+
**Deliverable**: Document external client configuration approach with code examples
|
| 233 |
+
|
| 234 |
+
---
|
| 235 |
+
|
| 236 |
+
#### Task 2: Research Official MCP SDK for Python
|
| 237 |
+
|
| 238 |
+
**Objective**: Understand how to implement MCP server and tools using the Official MCP SDK in Python.
|
| 239 |
+
|
| 240 |
+
**Research Questions**:
|
| 241 |
+
- What is the Official MCP SDK package name and installation method?
|
| 242 |
+
- How to define MCP tools with input/output schemas?
|
| 243 |
+
- How to register tools with the MCP server?
|
| 244 |
+
- How to handle tool invocation and return structured responses?
|
| 245 |
+
- Best practices for stateless tool implementation?
|
| 246 |
+
|
| 247 |
+
**Deliverable**: Document MCP SDK setup, tool definition patterns, and server configuration
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
#### Task 3: Research Free-Tier Provider Function Calling Support
|
| 252 |
+
|
| 253 |
+
**Objective**: Validate that Gemini, OpenRouter, and Cohere free tiers support function calling (required for MCP tool invocation).
|
| 254 |
+
|
| 255 |
+
**Research Questions**:
|
| 256 |
+
- Does Gemini free tier support function calling?
|
| 257 |
+
- Does OpenRouter free tier support function calling?
|
| 258 |
+
- Does Cohere free tier support function calling?
|
| 259 |
+
- What are the context window limits for each provider?
|
| 260 |
+
- What are the rate limits and token caps?
|
| 261 |
+
- How to handle rate limit errors gracefully?
|
| 262 |
+
|
| 263 |
+
**Deliverable**: Provider capability matrix with function calling support, limits, and constraints
|
| 264 |
+
|
| 265 |
+
---
|
| 266 |
+
|
| 267 |
+
#### Task 4: Research Agent-MCP Integration Pattern
|
| 268 |
+
|
| 269 |
+
**Objective**: Understand how OpenAI Agents SDK integrates with MCP tools for function calling.
|
| 270 |
+
|
| 271 |
+
**Research Questions**:
|
| 272 |
+
- How does OpenAI Agents SDK invoke external tools?
|
| 273 |
+
- What is the tool invocation protocol?
|
| 274 |
+
- How to map MCP tool schemas to agent tool definitions?
|
| 275 |
+
- How to handle tool results and format responses?
|
| 276 |
+
- How to propagate errors from tools to agent?
|
| 277 |
+
|
| 278 |
+
**Deliverable**: Document agent-MCP integration pattern with code examples
|
| 279 |
+
|
| 280 |
+
---
|
| 281 |
+
|
| 282 |
+
#### Task 5: Research Stateless Request Cycle Implementation
|
| 283 |
+
|
| 284 |
+
**Objective**: Design the stateless request cycle for loading conversation history, executing agent, and persisting results.
|
| 285 |
+
|
| 286 |
+
**Research Questions**:
|
| 287 |
+
- How to format conversation history for agent context?
|
| 288 |
+
- How to persist tool calls and results in the database?
|
| 289 |
+
- How to maintain conversation continuity across requests?
|
| 290 |
+
- How to handle concurrent requests from the same user?
|
| 291 |
+
|
| 292 |
+
**Deliverable**: Document stateless request cycle flow with database interaction patterns
|
| 293 |
+
|
| 294 |
+
---
|
| 295 |
+
|
| 296 |
+
### Research Output
|
| 297 |
+
|
| 298 |
+
**File**: `specs/001-openai-agent-mcp-tools/research.md`
|
| 299 |
+
|
| 300 |
+
**Format**:
|
| 301 |
+
```markdown
|
| 302 |
+
# Research Findings: OpenAI Agent MCP Tools
|
| 303 |
+
|
| 304 |
+
## 1. OpenAI Agents SDK External Client Configuration
|
| 305 |
+
|
| 306 |
+
**Decision**: [Chosen approach]
|
| 307 |
+
**Rationale**: [Why chosen]
|
| 308 |
+
**Alternatives Considered**: [Other options evaluated]
|
| 309 |
+
**Implementation Notes**: [Key details]
|
| 310 |
+
|
| 311 |
+
## 2. Official MCP SDK Integration
|
| 312 |
+
|
| 313 |
+
**Decision**: [Chosen approach]
|
| 314 |
+
**Rationale**: [Why chosen]
|
| 315 |
+
**Alternatives Considered**: [Other options evaluated]
|
| 316 |
+
**Implementation Notes**: [Key details]
|
| 317 |
+
|
| 318 |
+
## 3. Free-Tier Provider Capabilities
|
| 319 |
+
|
| 320 |
+
**Provider Capability Matrix**:
|
| 321 |
+
|
| 322 |
+
| Provider | Function Calling | Context Window | Rate Limits | Token Caps | Recommended Use |
|
| 323 |
+
|----------|------------------|----------------|-------------|------------|-----------------|
|
| 324 |
+
| Gemini | [Yes/No] | [Size] | [Limits] | [Caps] | [Primary/Fallback] |
|
| 325 |
+
| OpenRouter | [Yes/No] | [Size] | [Limits] | [Caps] | [Primary/Fallback] |
|
| 326 |
+
| Cohere | [Yes/No] | [Size] | [Limits] | [Caps] | [Primary/Fallback] |
|
| 327 |
+
|
| 328 |
+
**Decision**: [Primary provider choice]
|
| 329 |
+
**Rationale**: [Why chosen]
|
| 330 |
+
|
| 331 |
+
## 4. Agent-MCP Integration Pattern
|
| 332 |
+
|
| 333 |
+
**Decision**: [Chosen integration approach]
|
| 334 |
+
**Rationale**: [Why chosen]
|
| 335 |
+
**Implementation Notes**: [Key details]
|
| 336 |
+
|
| 337 |
+
## 5. Stateless Request Cycle Implementation
|
| 338 |
+
|
| 339 |
+
**Decision**: [Chosen request cycle design]
|
| 340 |
+
**Rationale**: [Why chosen]
|
| 341 |
+
**Implementation Notes**: [Key details]
|
| 342 |
+
```
|
| 343 |
+
|
| 344 |
+
---
|
| 345 |
+
|
| 346 |
+
## Phase 1: Design & Contracts
|
| 347 |
+
|
| 348 |
+
**Prerequisites**: `research.md` complete with all decisions documented
|
| 349 |
+
|
| 350 |
+
### Design Objectives
|
| 351 |
+
|
| 352 |
+
1. Define data models for agent configuration and tool execution results
|
| 353 |
+
2. Generate MCP tool contracts (input/output schemas)
|
| 354 |
+
3. Design agent configuration and provider selection logic
|
| 355 |
+
4. Design stateless request cycle flow
|
| 356 |
+
5. Create quickstart guide for local development
|
| 357 |
+
|
| 358 |
+
### Design Tasks
|
| 359 |
+
|
| 360 |
+
**Agent**: Backend Systems Agent
|
| 361 |
+
**Skill**: `backend-mcp-tools`
|
| 362 |
+
|
| 363 |
+
#### Task 1: Generate Data Model
|
| 364 |
+
|
| 365 |
+
**Objective**: Define entities for agent configuration, tool execution, and provider management.
|
| 366 |
+
|
| 367 |
+
**Entities to Define**:
|
| 368 |
+
|
| 369 |
+
1. **AgentConfiguration** (runtime configuration, not persisted)
|
| 370 |
+
- provider_type: str (gemini, openrouter, cohere)
|
| 371 |
+
- model_name: str
|
| 372 |
+
- api_key: str (from environment)
|
| 373 |
+
- context_window_size: int
|
| 374 |
+
- max_tokens: int
|
| 375 |
+
- temperature: float
|
| 376 |
+
|
| 377 |
+
2. **ToolExecutionResult** (runtime result, not persisted separately)
|
| 378 |
+
- tool_name: str
|
| 379 |
+
- success: bool
|
| 380 |
+
- data: dict (task object or list of tasks)
|
| 381 |
+
- error_message: Optional[str]
|
| 382 |
+
- execution_timestamp: datetime
|
| 383 |
+
|
| 384 |
+
3. **AgentRequestContext** (runtime context, not persisted)
|
| 385 |
+
- user_id: int
|
| 386 |
+
- conversation_id: int
|
| 387 |
+
- message_history: List[dict]
|
| 388 |
+
- jwt_token: str
|
| 389 |
+
|
| 390 |
+
**Note**: These are runtime entities, not database tables. Existing database models (Task, Conversation, Message) remain unchanged.
|
| 391 |
+
|
| 392 |
+
**Deliverable**: `specs/001-openai-agent-mcp-tools/data-model.md`
|
| 393 |
+
|
| 394 |
+
---
|
| 395 |
+
|
| 396 |
+
#### Task 2: Generate MCP Tool Contracts
|
| 397 |
+
|
| 398 |
+
**Objective**: Define input/output schemas for all 5 MCP tools.
|
| 399 |
+
|
| 400 |
+
**Tools to Define**:
|
| 401 |
+
|
| 402 |
+
1. **add_task**
|
| 403 |
+
- Input: title (required), description (optional), due_date (optional), priority (optional), user_id (required)
|
| 404 |
+
- Output: success (bool), task (Task object), message (str)
|
| 405 |
+
|
| 406 |
+
2. **list_tasks**
|
| 407 |
+
- Input: user_id (required), filter (optional: "all", "completed", "incomplete")
|
| 408 |
+
- Output: success (bool), tasks (List[Task]), count (int), message (str)
|
| 409 |
+
|
| 410 |
+
3. **complete_task**
|
| 411 |
+
- Input: user_id (required), task_identifier (int or str - ID or title)
|
| 412 |
+
- Output: success (bool), task (Task object), message (str)
|
| 413 |
+
|
| 414 |
+
4. **delete_task**
|
| 415 |
+
- Input: user_id (required), task_identifier (int or str - ID or title)
|
| 416 |
+
- Output: success (bool), message (str)
|
| 417 |
+
|
| 418 |
+
5. **update_task**
|
| 419 |
+
- Input: user_id (required), task_identifier (int or str), updates (dict with title, description, due_date, priority, completed)
|
| 420 |
+
- Output: success (bool), task (Task object), message (str)
|
| 421 |
+
|
| 422 |
+
**Deliverable**: `specs/001-openai-agent-mcp-tools/contracts/` directory with 5 JSON schema files
|
| 423 |
+
|
| 424 |
+
---
|
| 425 |
+
|
| 426 |
+
#### Task 3: Design Agent Configuration Logic
|
| 427 |
+
|
| 428 |
+
**Objective**: Design provider selection and agent initialization logic.
|
| 429 |
+
|
| 430 |
+
**Design Elements**:
|
| 431 |
+
|
| 432 |
+
1. **Environment Variables**:
|
| 433 |
+
- `LLM_PROVIDER`: Primary provider (gemini, openrouter, cohere)
|
| 434 |
+
- `GEMINI_API_KEY`: Gemini API key
|
| 435 |
+
- `OPENROUTER_API_KEY`: OpenRouter API key
|
| 436 |
+
- `COHERE_API_KEY`: Cohere API key
|
| 437 |
+
- `FALLBACK_PROVIDER`: Fallback provider (optional)
|
| 438 |
+
|
| 439 |
+
2. **Provider Configuration**:
|
| 440 |
+
- Each provider has a configuration class (GeminiProvider, OpenRouterProvider, CohereProvider)
|
| 441 |
+
- Configuration includes model name, context window, token limits
|
| 442 |
+
- Provider classes implement a common interface for agent initialization
|
| 443 |
+
|
| 444 |
+
3. **Agent Initialization**:
|
| 445 |
+
- Load provider configuration from environment
|
| 446 |
+
- Initialize external client for selected provider
|
| 447 |
+
- Register MCP tools with agent
|
| 448 |
+
- Return configured agent instance
|
| 449 |
+
|
| 450 |
+
**Deliverable**: Design documented in `research.md` or `data-model.md`
|
| 451 |
+
|
| 452 |
+
---
|
| 453 |
+
|
| 454 |
+
#### Task 4: Design Stateless Request Cycle Flow
|
| 455 |
+
|
| 456 |
+
**Objective**: Design the complete request cycle from chat endpoint to agent execution to database persistence.
|
| 457 |
+
|
| 458 |
+
**Flow Steps**:
|
| 459 |
+
|
| 460 |
+
1. **Receive Chat Request** (chat.py endpoint)
|
| 461 |
+
- Validate JWT token
|
| 462 |
+
- Extract user_id and conversation_id
|
| 463 |
+
- Validate user authorization
|
| 464 |
+
|
| 465 |
+
2. **Load Conversation History** (conversation_service.py)
|
| 466 |
+
- Query database for conversation and messages
|
| 467 |
+
- Format messages for agent context
|
| 468 |
+
- Return message history
|
| 469 |
+
|
| 470 |
+
3. **Store User Message** (conversation_service.py)
|
| 471 |
+
- Create new Message record with role="user"
|
| 472 |
+
- Persist to database
|
| 473 |
+
- Return message ID
|
| 474 |
+
|
| 475 |
+
4. **Execute Agent** (agent_runner.py)
|
| 476 |
+
- Initialize agent with provider configuration
|
| 477 |
+
- Load conversation history into agent context
|
| 478 |
+
- Execute agent reasoning with user message
|
| 479 |
+
- Agent selects and invokes MCP tools
|
| 480 |
+
- Collect tool results
|
| 481 |
+
- Generate final response
|
| 482 |
+
|
| 483 |
+
5. **Persist Agent Response** (conversation_service.py)
|
| 484 |
+
- Create new Message record with role="assistant"
|
| 485 |
+
- Store tool calls and results in message metadata
|
| 486 |
+
- Persist to database
|
| 487 |
+
- Return message ID
|
| 488 |
+
|
| 489 |
+
6. **Return Response** (chat.py endpoint)
|
| 490 |
+
- Format ChatResponse with agent message
|
| 491 |
+
- Return to client
|
| 492 |
+
|
| 493 |
+
**Deliverable**: Flow diagram and implementation notes in `data-model.md`
|
| 494 |
+
|
| 495 |
+
---
|
| 496 |
+
|
| 497 |
+
#### Task 5: Create Quickstart Guide
|
| 498 |
+
|
| 499 |
+
**Objective**: Document local development setup for testing agent and MCP tools.
|
| 500 |
+
|
| 501 |
+
**Quickstart Sections**:
|
| 502 |
+
|
| 503 |
+
1. **Prerequisites**:
|
| 504 |
+
- Python 3.11+
|
| 505 |
+
- Neon PostgreSQL database
|
| 506 |
+
- API keys for Gemini/OpenRouter/Cohere
|
| 507 |
+
|
| 508 |
+
2. **Installation**:
|
| 509 |
+
- Install dependencies: `pip install -r backend/requirements.txt`
|
| 510 |
+
- Set environment variables in `.env`
|
| 511 |
+
- Run database migrations: `alembic upgrade head`
|
| 512 |
+
|
| 513 |
+
3. **Configuration**:
|
| 514 |
+
- Configure LLM provider in `.env`
|
| 515 |
+
- Set API keys
|
| 516 |
+
- Configure database connection
|
| 517 |
+
|
| 518 |
+
4. **Running the Server**:
|
| 519 |
+
- Start FastAPI server: `uvicorn src.main:app --reload`
|
| 520 |
+
- Test chat endpoint: `curl -X POST http://localhost:8000/api/{user_id}/chat`
|
| 521 |
+
|
| 522 |
+
5. **Testing MCP Tools**:
|
| 523 |
+
- Test add_task tool
|
| 524 |
+
- Test list_tasks tool
|
| 525 |
+
- Test complete_task tool
|
| 526 |
+
- Test delete_task tool
|
| 527 |
+
- Test update_task tool
|
| 528 |
+
|
| 529 |
+
**Deliverable**: `specs/001-openai-agent-mcp-tools/quickstart.md`
|
| 530 |
+
|
| 531 |
+
---
|
| 532 |
+
|
| 533 |
+
#### Task 6: Update Agent Context
|
| 534 |
+
|
| 535 |
+
**Objective**: Update Claude Code agent context with new technologies from this plan.
|
| 536 |
+
|
| 537 |
+
**Command**: Run `.specify/scripts/powershell/update-agent-context.ps1 -AgentType claude`
|
| 538 |
+
|
| 539 |
+
**Technologies to Add**:
|
| 540 |
+
- OpenAI Agents SDK
|
| 541 |
+
- Official MCP SDK
|
| 542 |
+
- Cohere SDK
|
| 543 |
+
- External client configuration patterns
|
| 544 |
+
- MCP tool implementation patterns
|
| 545 |
+
|
| 546 |
+
**Deliverable**: Updated agent context file
|
| 547 |
+
|
| 548 |
+
---
|
| 549 |
+
|
| 550 |
+
### Phase 1 Outputs
|
| 551 |
+
|
| 552 |
+
**Files Created**:
|
| 553 |
+
1. `specs/001-openai-agent-mcp-tools/research.md` - Research findings and decisions
|
| 554 |
+
2. `specs/001-openai-agent-mcp-tools/data-model.md` - Entity definitions and flow diagrams
|
| 555 |
+
3. `specs/001-openai-agent-mcp-tools/contracts/` - MCP tool JSON schemas (5 files)
|
| 556 |
+
4. `specs/001-openai-agent-mcp-tools/quickstart.md` - Local development guide
|
| 557 |
+
5. Updated agent context file
|
| 558 |
+
|
| 559 |
+
---
|
| 560 |
+
|
| 561 |
+
## Phase 2: Implementation Planning (Not Executed by /sp.plan)
|
| 562 |
+
|
| 563 |
+
**Note**: Phase 2 (task generation) is executed by the `/sp.tasks` command, NOT by `/sp.plan`. This section provides guidance for task generation.
|
| 564 |
+
|
| 565 |
+
### Implementation Phases
|
| 566 |
+
|
| 567 |
+
#### Phase 2.1: MCP Server & Tools Implementation
|
| 568 |
+
|
| 569 |
+
**Agent**: Backend Systems Agent
|
| 570 |
+
**Skill**: `backend-mcp-tools`
|
| 571 |
+
|
| 572 |
+
**Tasks**:
|
| 573 |
+
1. Install Official MCP SDK and Cohere SDK
|
| 574 |
+
2. Implement MCP server setup (`backend/src/mcp/server.py`)
|
| 575 |
+
3. Implement add_task tool (`backend/src/mcp/tools/add_task.py`)
|
| 576 |
+
4. Implement list_tasks tool (`backend/src/mcp/tools/list_tasks.py`)
|
| 577 |
+
5. Implement complete_task tool (`backend/src/mcp/tools/complete_task.py`)
|
| 578 |
+
6. Implement delete_task tool (`backend/src/mcp/tools/delete_task.py`)
|
| 579 |
+
7. Implement update_task tool (`backend/src/mcp/tools/update_task.py`)
|
| 580 |
+
8. Test MCP tools in isolation
|
| 581 |
+
|
| 582 |
+
#### Phase 2.2: Agent Configuration & Provider Setup
|
| 583 |
+
|
| 584 |
+
**Agent**: Conversational AI Architect Agent
|
| 585 |
+
**Skill**: `agent-behavior-reasoning`
|
| 586 |
+
|
| 587 |
+
**Tasks**:
|
| 588 |
+
1. Install OpenAI Agents SDK
|
| 589 |
+
2. Implement provider configuration classes (`backend/src/agent/providers/`)
|
| 590 |
+
3. Implement agent configuration logic (`backend/src/agent/agent_config.py`)
|
| 591 |
+
4. Implement agent runner (`backend/src/agent/agent_runner.py`)
|
| 592 |
+
5. Test agent initialization with each provider
|
| 593 |
+
|
| 594 |
+
#### Phase 2.3: Agent-MCP Integration
|
| 595 |
+
|
| 596 |
+
**Agent**: Backend Systems Agent
|
| 597 |
+
**Skill**: `backend-mcp-tools`
|
| 598 |
+
|
| 599 |
+
**Tasks**:
|
| 600 |
+
1. Register MCP tools with agent
|
| 601 |
+
2. Implement tool invocation handling
|
| 602 |
+
3. Implement tool result processing
|
| 603 |
+
4. Test agent-MCP integration
|
| 604 |
+
|
| 605 |
+
#### Phase 2.4: Chat Endpoint Integration
|
| 606 |
+
|
| 607 |
+
**Agent**: Backend Systems Agent
|
| 608 |
+
**Skill**: `backend-mcp-tools`
|
| 609 |
+
|
| 610 |
+
**Tasks**:
|
| 611 |
+
1. Modify chat endpoint to use agent_runner
|
| 612 |
+
2. Implement stateless request cycle
|
| 613 |
+
3. Persist tool calls and results
|
| 614 |
+
4. Test end-to-end chat flow
|
| 615 |
+
|
| 616 |
+
#### Phase 2.5: Error Handling & Edge Cases
|
| 617 |
+
|
| 618 |
+
**Agent**: Backend Systems Agent
|
| 619 |
+
**Skill**: `backend-mcp-tools`
|
| 620 |
+
|
| 621 |
+
**Tasks**:
|
| 622 |
+
1. Implement provider error handling
|
| 623 |
+
2. Implement rate limit handling
|
| 624 |
+
3. Implement tool error handling
|
| 625 |
+
4. Test edge cases (task not found, invalid input, concurrent requests)
|
| 626 |
+
|
| 627 |
+
#### Phase 2.6: Testing & Validation
|
| 628 |
+
|
| 629 |
+
**Agent**: Backend Systems Agent
|
| 630 |
+
**Skill**: `backend-mcp-tools`
|
| 631 |
+
|
| 632 |
+
**Tasks**:
|
| 633 |
+
1. Write unit tests for MCP tools
|
| 634 |
+
2. Write integration tests for agent execution
|
| 635 |
+
3. Write end-to-end tests for chat flow
|
| 636 |
+
4. Validate all acceptance criteria from spec
|
| 637 |
+
|
| 638 |
+
---
|
| 639 |
+
|
| 640 |
+
## Acceptance Criteria
|
| 641 |
+
|
| 642 |
+
### Phase 0 Acceptance
|
| 643 |
+
|
| 644 |
+
- [ ] All research questions answered
|
| 645 |
+
- [ ] Provider capability matrix complete
|
| 646 |
+
- [ ] External client configuration approach documented
|
| 647 |
+
- [ ] MCP SDK integration approach documented
|
| 648 |
+
- [ ] Stateless request cycle design documented
|
| 649 |
+
|
| 650 |
+
### Phase 1 Acceptance
|
| 651 |
+
|
| 652 |
+
- [ ] Data model entities defined
|
| 653 |
+
- [ ] All 5 MCP tool contracts defined with JSON schemas
|
| 654 |
+
- [ ] Agent configuration logic designed
|
| 655 |
+
- [ ] Stateless request cycle flow documented
|
| 656 |
+
- [ ] Quickstart guide created
|
| 657 |
+
- [ ] Agent context updated
|
| 658 |
+
|
| 659 |
+
### Phase 2 Acceptance (Guidance for /sp.tasks)
|
| 660 |
+
|
| 661 |
+
- [ ] MCP server implemented and running
|
| 662 |
+
- [ ] All 5 MCP tools implemented and tested
|
| 663 |
+
- [ ] Agent configured with external client
|
| 664 |
+
- [ ] Agent-MCP integration working
|
| 665 |
+
- [ ] Chat endpoint integrated with agent
|
| 666 |
+
- [ ] Stateless request cycle functional
|
| 667 |
+
- [ ] All error handling implemented
|
| 668 |
+
- [ ] All tests passing
|
| 669 |
+
- [ ] All spec acceptance criteria met
|
| 670 |
+
|
| 671 |
+
---
|
| 672 |
+
|
| 673 |
+
## Risk Analysis
|
| 674 |
+
|
| 675 |
+
### Technical Risks
|
| 676 |
+
|
| 677 |
+
1. **OpenAI Agents SDK External Client Compatibility**
|
| 678 |
+
- **Risk**: OpenAI Agents SDK may not support external clients
|
| 679 |
+
- **Mitigation**: Research alternative agent frameworks if needed
|
| 680 |
+
- **Fallback**: Implement custom agent logic without SDK
|
| 681 |
+
|
| 682 |
+
2. **Free-Tier Function Calling Support**
|
| 683 |
+
- **Risk**: Free-tier providers may not support function calling
|
| 684 |
+
- **Mitigation**: Validate provider capabilities in Phase 0
|
| 685 |
+
- **Fallback**: Use prompt-based tool selection if function calling unavailable
|
| 686 |
+
|
| 687 |
+
3. **MCP SDK Python Availability**
|
| 688 |
+
- **Risk**: Official MCP SDK may not have Python implementation
|
| 689 |
+
- **Mitigation**: Research MCP SDK availability in Phase 0
|
| 690 |
+
- **Fallback**: Implement custom MCP server if SDK unavailable
|
| 691 |
+
|
| 692 |
+
4. **Rate Limit Handling**
|
| 693 |
+
- **Risk**: Free-tier rate limits may impact user experience
|
| 694 |
+
- **Mitigation**: Implement graceful degradation and retry logic
|
| 695 |
+
- **Fallback**: Queue requests or display rate limit messages
|
| 696 |
+
|
| 697 |
+
### Architectural Risks
|
| 698 |
+
|
| 699 |
+
1. **Stateless Architecture Complexity**
|
| 700 |
+
- **Risk**: Loading conversation history on every request may impact performance
|
| 701 |
+
- **Mitigation**: Optimize database queries with proper indexing
|
| 702 |
+
- **Fallback**: Implement conversation history pagination if needed
|
| 703 |
+
|
| 704 |
+
2. **Concurrent Request Handling**
|
| 705 |
+
- **Risk**: Concurrent requests from same user may cause race conditions
|
| 706 |
+
- **Mitigation**: Use database transactions and optimistic locking
|
| 707 |
+
- **Fallback**: Implement request queuing per user
|
| 708 |
+
|
| 709 |
+
---
|
| 710 |
+
|
| 711 |
+
## Dependencies
|
| 712 |
+
|
| 713 |
+
### External Dependencies
|
| 714 |
+
|
| 715 |
+
1. **OpenAI Agents SDK**: Required for agent reasoning and orchestration
|
| 716 |
+
2. **Official MCP SDK**: Required for MCP server and tool implementation
|
| 717 |
+
3. **Cohere SDK**: Required for Cohere provider support
|
| 718 |
+
4. **External API Accounts**: Gemini, OpenRouter, Cohere accounts with API keys
|
| 719 |
+
|
| 720 |
+
### Internal Dependencies
|
| 721 |
+
|
| 722 |
+
1. **Spec-1 Completion**: Chat UI and basic chat endpoint must be functional
|
| 723 |
+
2. **Database Schema**: Conversations and messages tables must exist
|
| 724 |
+
3. **Better Auth**: JWT authentication must be functional
|
| 725 |
+
4. **Existing Models**: Task, Conversation, Message models must be available
|
| 726 |
+
|
| 727 |
+
---
|
| 728 |
+
|
| 729 |
+
## Next Steps
|
| 730 |
+
|
| 731 |
+
1. **Execute Phase 0**: Run research tasks to resolve all unknowns
|
| 732 |
+
2. **Execute Phase 1**: Generate design artifacts (data-model.md, contracts/, quickstart.md)
|
| 733 |
+
3. **Re-evaluate Constitution Check**: Verify all constitutional requirements still satisfied
|
| 734 |
+
4. **Execute /sp.tasks**: Generate implementation tasks based on this plan
|
| 735 |
+
5. **Execute /sp.implement**: Implement tasks in dependency order
|
| 736 |
+
|
| 737 |
+
---
|
| 738 |
+
|
| 739 |
+
## Notes
|
| 740 |
+
|
| 741 |
+
- This plan focuses exclusively on backend implementation; no frontend changes
|
| 742 |
+
- All code must reside in `backend/` directory per constitutional requirements
|
| 743 |
+
- Agent behavior must follow Agent Behavior Specification (to be referenced in tasks)
|
| 744 |
+
- MCP tools must be stateless and database-backed per constitutional requirements
|
| 745 |
+
- Error handling must prioritize user experience with friendly messages
|
| 746 |
+
- Provider selection must be configurable via environment variables
|
| 747 |
+
- Fallback provider support is optional but recommended for reliability
|
specs/001-openai-agent-mcp-tools/quickstart.md
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart Guide: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-openai-agent-mcp-tools
|
| 4 |
+
**Date**: 2026-01-14
|
| 5 |
+
**Purpose**: Local development setup for testing AI agent with MCP tools
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Prerequisites
|
| 10 |
+
|
| 11 |
+
Before starting, ensure you have:
|
| 12 |
+
|
| 13 |
+
- **Python 3.11+** installed
|
| 14 |
+
- **Neon PostgreSQL database** accessible (connection string ready)
|
| 15 |
+
- **API keys** for at least one LLM provider:
|
| 16 |
+
- Google Gemini API key (recommended, free tier)
|
| 17 |
+
- OpenRouter API key (optional, fallback)
|
| 18 |
+
- Cohere API key (optional, not recommended)
|
| 19 |
+
- **Git** installed
|
| 20 |
+
- **Node.js 18+** (for frontend, if testing end-to-end)
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## Installation
|
| 25 |
+
|
| 26 |
+
### 1. Clone Repository (if not already done)
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
git clone <repository-url>
|
| 30 |
+
cd evolution-of-todo/phase-2-full-stack-web-app
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
### 2. Checkout Feature Branch
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
git checkout 001-openai-agent-mcp-tools
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### 3. Install Backend Dependencies
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
cd backend
|
| 43 |
+
pip install -r requirements.txt
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
**Expected new dependencies** (added by this feature):
|
| 47 |
+
- `mcp` - Official MCP SDK
|
| 48 |
+
- `cohere` - Cohere SDK (if using Cohere provider)
|
| 49 |
+
- `openai` - OpenAI SDK (for agent compatibility, even if not using OpenAI)
|
| 50 |
+
|
| 51 |
+
### 4. Set Up Environment Variables
|
| 52 |
+
|
| 53 |
+
Create a `.env` file in the `backend/` directory:
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
cd backend
|
| 57 |
+
touch .env
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Add the following configuration to `.env`:
|
| 61 |
+
|
| 62 |
+
```env
|
| 63 |
+
# Database Configuration
|
| 64 |
+
DATABASE_URL=postgresql://user:password@host:5432/database
|
| 65 |
+
|
| 66 |
+
# Authentication
|
| 67 |
+
BETTER_AUTH_SECRET=your-secret-key-here
|
| 68 |
+
|
| 69 |
+
# LLM Provider Configuration
|
| 70 |
+
LLM_PROVIDER=gemini # Options: gemini, openrouter, cohere
|
| 71 |
+
FALLBACK_PROVIDER=openrouter # Optional fallback provider
|
| 72 |
+
|
| 73 |
+
# API Keys (provide at least one)
|
| 74 |
+
GEMINI_API_KEY=your-gemini-api-key-here
|
| 75 |
+
OPENROUTER_API_KEY=your-openrouter-key-here # Optional
|
| 76 |
+
COHERE_API_KEY=your-cohere-key-here # Optional
|
| 77 |
+
|
| 78 |
+
# Agent Configuration (optional, defaults provided)
|
| 79 |
+
AGENT_TEMPERATURE=0.7
|
| 80 |
+
AGENT_MAX_TOKENS=8192
|
| 81 |
+
CONVERSATION_MAX_MESSAGES=20
|
| 82 |
+
CONVERSATION_MAX_TOKENS=8000
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
**How to get API keys**:
|
| 86 |
+
|
| 87 |
+
- **Gemini**: Visit [Google AI Studio](https://makersuite.google.com/app/apikey) (free, no credit card required)
|
| 88 |
+
- **OpenRouter**: Visit [OpenRouter](https://openrouter.ai/) (free models available)
|
| 89 |
+
- **Cohere**: Visit [Cohere](https://cohere.com/) (trial only, not recommended)
|
| 90 |
+
|
| 91 |
+
### 5. Run Database Migrations
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
cd backend
|
| 95 |
+
alembic upgrade head
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
**Expected output**:
|
| 99 |
+
```
|
| 100 |
+
INFO [alembic.runtime.migration] Running upgrade -> 20260114_1044, add conversation and message tables
|
| 101 |
+
INFO [alembic.runtime.migration] Running upgrade 20260114_1044 -> 20260114_1115, add metadata to message
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## Running the Server
|
| 107 |
+
|
| 108 |
+
### Start Backend Server
|
| 109 |
+
|
| 110 |
+
```bash
|
| 111 |
+
cd backend
|
| 112 |
+
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
**Expected output**:
|
| 116 |
+
```
|
| 117 |
+
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
| 118 |
+
INFO: Started reloader process [12345] using StatReload
|
| 119 |
+
INFO: Started server process [12346]
|
| 120 |
+
INFO: Waiting for application startup.
|
| 121 |
+
INFO: Application startup complete.
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
**Verify server is running**:
|
| 125 |
+
```bash
|
| 126 |
+
curl http://localhost:8000/health
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
Expected response: `{"status": "healthy"}`
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Testing the Agent
|
| 134 |
+
|
| 135 |
+
### 1. Create a Test User
|
| 136 |
+
|
| 137 |
+
First, create a test user via the auth endpoint:
|
| 138 |
+
|
| 139 |
+
```bash
|
| 140 |
+
curl -X POST http://localhost:8000/api/auth/signup \
|
| 141 |
+
-H "Content-Type: application/json" \
|
| 142 |
+
-d '{
|
| 143 |
+
"email": "test@example.com",
|
| 144 |
+
"password": "testpassword123"
|
| 145 |
+
}'
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
**Expected response**:
|
| 149 |
+
```json
|
| 150 |
+
{
|
| 151 |
+
"user": {
|
| 152 |
+
"id": 1,
|
| 153 |
+
"email": "test@example.com"
|
| 154 |
+
},
|
| 155 |
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
| 156 |
+
}
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
**Save the token** - you'll need it for authenticated requests.
|
| 160 |
+
|
| 161 |
+
### 2. Test Chat Endpoint (Without Agent)
|
| 162 |
+
|
| 163 |
+
Test basic chat functionality:
|
| 164 |
+
|
| 165 |
+
```bash
|
| 166 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 167 |
+
-H "Content-Type: application/json" \
|
| 168 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 169 |
+
-d '{
|
| 170 |
+
"message": "Hello, can you help me manage my tasks?"
|
| 171 |
+
}'
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
**Expected response**:
|
| 175 |
+
```json
|
| 176 |
+
{
|
| 177 |
+
"message": "Hello! I'm your AI task assistant. I can help you create, view, complete, and manage your tasks. What would you like to do?",
|
| 178 |
+
"conversation_id": 1
|
| 179 |
+
}
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
### 3. Test Task Creation via Natural Language
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 186 |
+
-H "Content-Type: application/json" \
|
| 187 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 188 |
+
-d '{
|
| 189 |
+
"message": "Add a task to buy groceries",
|
| 190 |
+
"conversation_id": 1
|
| 191 |
+
}'
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
**Expected response**:
|
| 195 |
+
```json
|
| 196 |
+
{
|
| 197 |
+
"message": "I've created a new task: 'Buy groceries'. Your task has been added to your list!",
|
| 198 |
+
"conversation_id": 1
|
| 199 |
+
}
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### 4. Test Task Listing
|
| 203 |
+
|
| 204 |
+
```bash
|
| 205 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 206 |
+
-H "Content-Type: application/json" \
|
| 207 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 208 |
+
-d '{
|
| 209 |
+
"message": "Show me my tasks",
|
| 210 |
+
"conversation_id": 1
|
| 211 |
+
}'
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
**Expected response**:
|
| 215 |
+
```json
|
| 216 |
+
{
|
| 217 |
+
"message": "You have 1 task:\n\n1. Buy groceries (incomplete)\n\nWould you like to complete any of these tasks?",
|
| 218 |
+
"conversation_id": 1
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
### 5. Test Task Completion
|
| 223 |
+
|
| 224 |
+
```bash
|
| 225 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 226 |
+
-H "Content-Type: application/json" \
|
| 227 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 228 |
+
-d '{
|
| 229 |
+
"message": "Mark the groceries task as complete",
|
| 230 |
+
"conversation_id": 1
|
| 231 |
+
}'
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
**Expected response**:
|
| 235 |
+
```json
|
| 236 |
+
{
|
| 237 |
+
"message": "Great! I've marked 'Buy groceries' as completed. Well done!",
|
| 238 |
+
"conversation_id": 1
|
| 239 |
+
}
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
## Testing MCP Tools Directly
|
| 245 |
+
|
| 246 |
+
### Test add_task Tool
|
| 247 |
+
|
| 248 |
+
```bash
|
| 249 |
+
# This requires accessing the MCP server directly (advanced)
|
| 250 |
+
# For now, test via the agent as shown above
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
### Test list_tasks Tool
|
| 254 |
+
|
| 255 |
+
```bash
|
| 256 |
+
# Access via agent chat interface
|
| 257 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 258 |
+
-H "Content-Type: application/json" \
|
| 259 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 260 |
+
-d '{
|
| 261 |
+
"message": "List all my incomplete tasks"
|
| 262 |
+
}'
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
### Test complete_task Tool
|
| 266 |
+
|
| 267 |
+
```bash
|
| 268 |
+
# Access via agent chat interface
|
| 269 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 270 |
+
-H "Content-Type: application/json" \
|
| 271 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 272 |
+
-d '{
|
| 273 |
+
"message": "Complete task 1"
|
| 274 |
+
}'
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
### Test delete_task Tool
|
| 278 |
+
|
| 279 |
+
```bash
|
| 280 |
+
# Access via agent chat interface
|
| 281 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 282 |
+
-H "Content-Type: application/json" \
|
| 283 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 284 |
+
-d '{
|
| 285 |
+
"message": "Delete the groceries task"
|
| 286 |
+
}'
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
### Test update_task Tool
|
| 290 |
+
|
| 291 |
+
```bash
|
| 292 |
+
# Access via agent chat interface
|
| 293 |
+
curl -X POST http://localhost:8000/api/1/chat \
|
| 294 |
+
-H "Content-Type: application/json" \
|
| 295 |
+
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
| 296 |
+
-d '{
|
| 297 |
+
"message": "Change the groceries task to buy groceries and milk"
|
| 298 |
+
}'
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
---
|
| 302 |
+
|
| 303 |
+
## Troubleshooting
|
| 304 |
+
|
| 305 |
+
### Issue: "Rate limit exceeded"
|
| 306 |
+
|
| 307 |
+
**Cause**: Gemini free tier has 15 requests/minute limit
|
| 308 |
+
|
| 309 |
+
**Solution**:
|
| 310 |
+
1. Wait 1 minute before retrying
|
| 311 |
+
2. Configure fallback provider in `.env`:
|
| 312 |
+
```env
|
| 313 |
+
FALLBACK_PROVIDER=openrouter
|
| 314 |
+
OPENROUTER_API_KEY=your-key-here
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
### Issue: "Tool not found"
|
| 318 |
+
|
| 319 |
+
**Cause**: MCP tools not registered properly
|
| 320 |
+
|
| 321 |
+
**Solution**:
|
| 322 |
+
1. Check MCP server logs for errors
|
| 323 |
+
2. Verify tool registration in `backend/src/mcp/server.py`
|
| 324 |
+
3. Restart server: `uvicorn src.main:app --reload`
|
| 325 |
+
|
| 326 |
+
### Issue: "Unauthorized" (401 error)
|
| 327 |
+
|
| 328 |
+
**Cause**: Invalid or expired JWT token
|
| 329 |
+
|
| 330 |
+
**Solution**:
|
| 331 |
+
1. Get a new token via `/api/auth/signin`
|
| 332 |
+
2. Ensure token is included in `Authorization: Bearer <token>` header
|
| 333 |
+
3. Check `BETTER_AUTH_SECRET` matches between frontend and backend
|
| 334 |
+
|
| 335 |
+
### Issue: "Task not found"
|
| 336 |
+
|
| 337 |
+
**Cause**: Task doesn't exist or belongs to different user
|
| 338 |
+
|
| 339 |
+
**Solution**:
|
| 340 |
+
1. List tasks first: "Show me my tasks"
|
| 341 |
+
2. Use exact task ID or title
|
| 342 |
+
3. Verify user_id in request matches authenticated user
|
| 343 |
+
|
| 344 |
+
### Issue: "Database connection failed"
|
| 345 |
+
|
| 346 |
+
**Cause**: Invalid `DATABASE_URL` or database not accessible
|
| 347 |
+
|
| 348 |
+
**Solution**:
|
| 349 |
+
1. Verify `DATABASE_URL` in `.env`
|
| 350 |
+
2. Test connection: `psql $DATABASE_URL`
|
| 351 |
+
3. Check Neon dashboard for database status
|
| 352 |
+
4. Ensure IP is whitelisted in Neon settings
|
| 353 |
+
|
| 354 |
+
### Issue: "Agent returns generic response, doesn't use tools"
|
| 355 |
+
|
| 356 |
+
**Cause**: LLM provider doesn't support function calling or tools not registered
|
| 357 |
+
|
| 358 |
+
**Solution**:
|
| 359 |
+
1. Verify provider supports function calling (Gemini does)
|
| 360 |
+
2. Check tool definitions in agent logs
|
| 361 |
+
3. Test with explicit tool request: "Use the add_task tool to create a task"
|
| 362 |
+
|
| 363 |
+
---
|
| 364 |
+
|
| 365 |
+
## Viewing Logs
|
| 366 |
+
|
| 367 |
+
### Backend Logs
|
| 368 |
+
|
| 369 |
+
```bash
|
| 370 |
+
# View real-time logs
|
| 371 |
+
tail -f backend/logs/app.log
|
| 372 |
+
|
| 373 |
+
# View last 100 lines
|
| 374 |
+
tail -n 100 backend/logs/app.log
|
| 375 |
+
|
| 376 |
+
# Search for errors
|
| 377 |
+
grep ERROR backend/logs/app.log
|
| 378 |
+
```
|
| 379 |
+
|
| 380 |
+
### Database Queries
|
| 381 |
+
|
| 382 |
+
```bash
|
| 383 |
+
# Connect to database
|
| 384 |
+
psql $DATABASE_URL
|
| 385 |
+
|
| 386 |
+
# View conversations
|
| 387 |
+
SELECT * FROM conversation WHERE user_id = 1;
|
| 388 |
+
|
| 389 |
+
# View messages
|
| 390 |
+
SELECT * FROM message WHERE conversation_id = 1 ORDER BY created_at;
|
| 391 |
+
|
| 392 |
+
# View tasks
|
| 393 |
+
SELECT * FROM tasks WHERE user_id = 1;
|
| 394 |
+
```
|
| 395 |
+
|
| 396 |
+
---
|
| 397 |
+
|
| 398 |
+
## Testing with Frontend (Optional)
|
| 399 |
+
|
| 400 |
+
If you want to test the full stack with the frontend UI:
|
| 401 |
+
|
| 402 |
+
### 1. Start Frontend
|
| 403 |
+
|
| 404 |
+
```bash
|
| 405 |
+
cd frontend
|
| 406 |
+
npm install
|
| 407 |
+
npm run dev
|
| 408 |
+
```
|
| 409 |
+
|
| 410 |
+
### 2. Access UI
|
| 411 |
+
|
| 412 |
+
Open browser to `http://localhost:3000`
|
| 413 |
+
|
| 414 |
+
### 3. Sign In
|
| 415 |
+
|
| 416 |
+
Use the test user credentials:
|
| 417 |
+
- Email: `test@example.com`
|
| 418 |
+
- Password: `testpassword123`
|
| 419 |
+
|
| 420 |
+
### 4. Navigate to Chat
|
| 421 |
+
|
| 422 |
+
Click "Chat" in the navigation menu
|
| 423 |
+
|
| 424 |
+
### 5. Test Natural Language Commands
|
| 425 |
+
|
| 426 |
+
Try these commands:
|
| 427 |
+
- "Add a task to buy groceries"
|
| 428 |
+
- "Show me my tasks"
|
| 429 |
+
- "Mark task 1 as complete"
|
| 430 |
+
- "Delete the groceries task"
|
| 431 |
+
|
| 432 |
+
---
|
| 433 |
+
|
| 434 |
+
## Next Steps
|
| 435 |
+
|
| 436 |
+
After verifying the agent works locally:
|
| 437 |
+
|
| 438 |
+
1. **Run Tests**: `pytest backend/tests/`
|
| 439 |
+
2. **Check Coverage**: `pytest --cov=src backend/tests/`
|
| 440 |
+
3. **Review Logs**: Check for any errors or warnings
|
| 441 |
+
4. **Test Edge Cases**: Try ambiguous requests, invalid inputs
|
| 442 |
+
5. **Performance Testing**: Test with multiple concurrent requests
|
| 443 |
+
|
| 444 |
+
---
|
| 445 |
+
|
| 446 |
+
## Development Workflow
|
| 447 |
+
|
| 448 |
+
### Making Changes
|
| 449 |
+
|
| 450 |
+
1. **Modify Code**: Edit files in `backend/src/`
|
| 451 |
+
2. **Server Auto-Reloads**: Uvicorn detects changes and reloads
|
| 452 |
+
3. **Test Changes**: Use curl or frontend to test
|
| 453 |
+
4. **Check Logs**: Monitor logs for errors
|
| 454 |
+
5. **Commit Changes**: `git add . && git commit -m "description"`
|
| 455 |
+
|
| 456 |
+
### Adding New MCP Tools
|
| 457 |
+
|
| 458 |
+
1. Create tool file: `backend/src/mcp/tools/new_tool.py`
|
| 459 |
+
2. Define tool function with decorator: `@mcp_server.tool()`
|
| 460 |
+
3. Register tool in `backend/src/mcp/tool_registry.py`
|
| 461 |
+
4. Test tool via agent chat interface
|
| 462 |
+
5. Add tests: `backend/tests/mcp/test_new_tool.py`
|
| 463 |
+
|
| 464 |
+
### Debugging Agent Behavior
|
| 465 |
+
|
| 466 |
+
1. **Enable Debug Logging**: Set `LOG_LEVEL=DEBUG` in `.env`
|
| 467 |
+
2. **View Tool Calls**: Check message metadata in database
|
| 468 |
+
3. **Test Tool Directly**: Call tool function in Python shell
|
| 469 |
+
4. **Check Provider Logs**: Review Gemini API logs
|
| 470 |
+
5. **Validate Tool Schemas**: Ensure JSON schemas are correct
|
| 471 |
+
|
| 472 |
+
---
|
| 473 |
+
|
| 474 |
+
## Useful Commands
|
| 475 |
+
|
| 476 |
+
```bash
|
| 477 |
+
# Start backend with debug logging
|
| 478 |
+
LOG_LEVEL=DEBUG uvicorn src.main:app --reload
|
| 479 |
+
|
| 480 |
+
# Run specific test
|
| 481 |
+
pytest backend/tests/mcp/test_add_task.py -v
|
| 482 |
+
|
| 483 |
+
# Check database schema
|
| 484 |
+
psql $DATABASE_URL -c "\d tasks"
|
| 485 |
+
|
| 486 |
+
# View recent messages
|
| 487 |
+
psql $DATABASE_URL -c "SELECT role, content FROM message ORDER BY created_at DESC LIMIT 10;"
|
| 488 |
+
|
| 489 |
+
# Clear conversation history (for testing)
|
| 490 |
+
psql $DATABASE_URL -c "DELETE FROM message WHERE conversation_id = 1;"
|
| 491 |
+
|
| 492 |
+
# Reset database (CAUTION: deletes all data)
|
| 493 |
+
alembic downgrade base
|
| 494 |
+
alembic upgrade head
|
| 495 |
+
```
|
| 496 |
+
|
| 497 |
+
---
|
| 498 |
+
|
| 499 |
+
## Support
|
| 500 |
+
|
| 501 |
+
If you encounter issues not covered in this guide:
|
| 502 |
+
|
| 503 |
+
1. Check the [research.md](./research.md) for implementation details
|
| 504 |
+
2. Review the [data-model.md](./data-model.md) for architecture
|
| 505 |
+
3. Inspect the [plan.md](./plan.md) for design decisions
|
| 506 |
+
4. Check backend logs for error messages
|
| 507 |
+
5. Verify environment variables are set correctly
|
| 508 |
+
|
| 509 |
+
---
|
| 510 |
+
|
| 511 |
+
## Summary
|
| 512 |
+
|
| 513 |
+
You should now have:
|
| 514 |
+
- ✅ Backend server running on `http://localhost:8000`
|
| 515 |
+
- ✅ Database migrations applied
|
| 516 |
+
- ✅ Test user created with JWT token
|
| 517 |
+
- ✅ Agent responding to natural language commands
|
| 518 |
+
- ✅ MCP tools executing task operations
|
| 519 |
+
- ✅ Conversation history persisting in database
|
| 520 |
+
|
| 521 |
+
**Ready for implementation!** Proceed to `/sp.tasks` to generate implementation tasks.
|
specs/001-openai-agent-mcp-tools/research.md
ADDED
|
@@ -0,0 +1,758 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Research Findings: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Date**: 2026-01-14
|
| 4 |
+
**Feature**: 001-openai-agent-mcp-tools
|
| 5 |
+
**Research Phase**: Phase 0 - Technology Validation
|
| 6 |
+
|
| 7 |
+
## Executive Summary
|
| 8 |
+
|
| 9 |
+
This research validates the technical approach for implementing an AI-powered Todo agent with MCP tools using free-tier API providers. Key findings:
|
| 10 |
+
|
| 11 |
+
1. **OpenAI Agents SDK is NOT suitable** - Has compatibility issues and doesn't support external providers
|
| 12 |
+
2. **Custom agent implementation is RECOMMENDED** - Provides full control and works with any provider
|
| 13 |
+
3. **Google Gemini is the PRIMARY provider** - Best free-tier offering with full function calling support
|
| 14 |
+
4. **MCP SDK (FastMCP) is production-ready** - Already installed, provides clean decorator-based API
|
| 15 |
+
5. **Stateless architecture is feasible** - Conversation history trimming handles free-tier constraints
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## 1. OpenAI Agents SDK External Client Configuration
|
| 20 |
+
|
| 21 |
+
### Decision: Use Custom Agent Implementation (NOT OpenAI Agents SDK)
|
| 22 |
+
|
| 23 |
+
**Rationale**:
|
| 24 |
+
- OpenAI Agents SDK (v0.4.2) has compatibility issues with current OpenAI SDK
|
| 25 |
+
- SDK is NOT designed for external providers (Gemini, OpenRouter, Cohere)
|
| 26 |
+
- Custom implementation provides full control over agent logic
|
| 27 |
+
- Simpler debugging and maintenance
|
| 28 |
+
- Works with any provider supporting function calling
|
| 29 |
+
|
| 30 |
+
**Alternatives Considered**:
|
| 31 |
+
1. **OpenAI Agents SDK with external client** - REJECTED: Not supported by SDK design
|
| 32 |
+
2. **LangChain Agents** - REJECTED: Too heavy, unnecessary complexity for our use case
|
| 33 |
+
3. **Custom agent orchestration** - SELECTED: Best fit for requirements
|
| 34 |
+
|
| 35 |
+
**Implementation Approach**:
|
| 36 |
+
|
| 37 |
+
```python
|
| 38 |
+
class AgentRunner:
|
| 39 |
+
"""Custom agent orchestration without OpenAI Agents SDK dependency."""
|
| 40 |
+
|
| 41 |
+
def __init__(self, provider: LLMProvider, tools: MCPToolRegistry):
|
| 42 |
+
self.provider = provider
|
| 43 |
+
self.tools = tools
|
| 44 |
+
|
| 45 |
+
async def execute(self, messages: List[Dict], system_prompt: str, user_id: int) -> Dict:
|
| 46 |
+
"""Execute agent reasoning with tool invocation."""
|
| 47 |
+
|
| 48 |
+
# 1. Get tool definitions for LLM
|
| 49 |
+
tool_definitions = self.tools.get_tool_definitions()
|
| 50 |
+
|
| 51 |
+
# 2. Call LLM with tools
|
| 52 |
+
response = await self.provider.generate_response_with_tools(
|
| 53 |
+
messages=messages,
|
| 54 |
+
system_prompt=system_prompt,
|
| 55 |
+
tools=tool_definitions
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# 3. Execute tool calls if present
|
| 59 |
+
if response.get("tool_calls"):
|
| 60 |
+
tool_results = []
|
| 61 |
+
for tool_call in response["tool_calls"]:
|
| 62 |
+
result = await self.tools.execute_tool(
|
| 63 |
+
tool_name=tool_call["name"],
|
| 64 |
+
arguments=tool_call["arguments"],
|
| 65 |
+
user_id=user_id # Inject user context for security
|
| 66 |
+
)
|
| 67 |
+
tool_results.append(result)
|
| 68 |
+
|
| 69 |
+
# 4. Send results back to LLM for final response
|
| 70 |
+
final_response = await self.provider.generate_response_with_tool_results(
|
| 71 |
+
messages=messages,
|
| 72 |
+
tool_calls=response["tool_calls"],
|
| 73 |
+
tool_results=tool_results
|
| 74 |
+
)
|
| 75 |
+
return final_response
|
| 76 |
+
|
| 77 |
+
return response
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
**Key Benefits**:
|
| 81 |
+
- No dependency on broken SDK
|
| 82 |
+
- Full control over agent logic
|
| 83 |
+
- Works with any provider supporting function calling
|
| 84 |
+
- Simpler debugging and maintenance
|
| 85 |
+
- Follows existing codebase patterns (FastAPI, async/await)
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## 2. Official MCP SDK Integration
|
| 90 |
+
|
| 91 |
+
### Decision: Use MCP SDK with FastMCP Server
|
| 92 |
+
|
| 93 |
+
**Package**: `mcp` version 1.20.0 (already installed in environment)
|
| 94 |
+
|
| 95 |
+
**Installation**: `pip install mcp`
|
| 96 |
+
|
| 97 |
+
**Rationale**:
|
| 98 |
+
- Official MCP SDK provides production-ready server implementation
|
| 99 |
+
- FastMCP offers clean decorator-based API (similar to FastAPI)
|
| 100 |
+
- Already installed in the environment
|
| 101 |
+
- Well-documented with examples
|
| 102 |
+
- Supports stateless tool implementation
|
| 103 |
+
|
| 104 |
+
**Alternatives Considered**:
|
| 105 |
+
1. **Custom MCP server implementation** - REJECTED: Unnecessary complexity, SDK is production-ready
|
| 106 |
+
2. **Low-level MCP Server class** - REJECTED: FastMCP provides better developer experience
|
| 107 |
+
3. **FastMCP (high-level)** - SELECTED: Best fit for requirements
|
| 108 |
+
|
| 109 |
+
**Tool Definition Pattern**:
|
| 110 |
+
|
| 111 |
+
```python
|
| 112 |
+
from mcp.server import FastMCP
|
| 113 |
+
from typing import Optional
|
| 114 |
+
|
| 115 |
+
mcp_server = FastMCP("todo-mcp-server")
|
| 116 |
+
|
| 117 |
+
@mcp_server.tool()
|
| 118 |
+
async def add_task(
|
| 119 |
+
user_id: int,
|
| 120 |
+
title: str,
|
| 121 |
+
description: Optional[str] = None,
|
| 122 |
+
due_date: Optional[str] = None,
|
| 123 |
+
priority: Optional[str] = None
|
| 124 |
+
) -> dict:
|
| 125 |
+
"""Add a new task to the user's todo list.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
user_id: ID of the user creating the task (injected by backend)
|
| 129 |
+
title: Task title (required)
|
| 130 |
+
description: Optional task description
|
| 131 |
+
due_date: Optional due date in ISO format
|
| 132 |
+
priority: Optional priority (low, medium, high)
|
| 133 |
+
|
| 134 |
+
Returns:
|
| 135 |
+
dict: {success: bool, task: Task, message: str}
|
| 136 |
+
"""
|
| 137 |
+
# Implementation with database access
|
| 138 |
+
async with get_db_session() as db:
|
| 139 |
+
task = Task(
|
| 140 |
+
user_id=user_id,
|
| 141 |
+
title=title,
|
| 142 |
+
description=description,
|
| 143 |
+
# ... other fields
|
| 144 |
+
)
|
| 145 |
+
db.add(task)
|
| 146 |
+
await db.commit()
|
| 147 |
+
await db.refresh(task)
|
| 148 |
+
|
| 149 |
+
return {
|
| 150 |
+
"success": True,
|
| 151 |
+
"task": task.dict(),
|
| 152 |
+
"message": f"Task '{title}' created successfully"
|
| 153 |
+
}
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
**Stateless Implementation Best Practices**:
|
| 157 |
+
1. **No in-memory state** - All state persists in database
|
| 158 |
+
2. **Explicit user context** - `user_id` passed to every tool (injected by backend, not LLM)
|
| 159 |
+
3. **Database transactions** - Use transactions for consistency
|
| 160 |
+
4. **Structured responses** - Always return `{success, data, message}` format
|
| 161 |
+
5. **Error handling** - Return structured errors, don't throw exceptions
|
| 162 |
+
|
| 163 |
+
**Tool Registration**:
|
| 164 |
+
|
| 165 |
+
```python
|
| 166 |
+
class MCPToolRegistry:
|
| 167 |
+
"""Registry for MCP tools with user context injection."""
|
| 168 |
+
|
| 169 |
+
def __init__(self):
|
| 170 |
+
self.tools: Dict[str, Callable] = {}
|
| 171 |
+
self.tool_schemas: Dict[str, Dict] = {}
|
| 172 |
+
|
| 173 |
+
def register_tool(self, name: str, func: Callable, schema: Dict):
|
| 174 |
+
"""Register a tool with its schema."""
|
| 175 |
+
self.tools[name] = func
|
| 176 |
+
self.tool_schemas[name] = schema
|
| 177 |
+
|
| 178 |
+
def get_tool_definitions(self) -> List[Dict]:
|
| 179 |
+
"""Get tool definitions for LLM in OpenAI function format."""
|
| 180 |
+
return [
|
| 181 |
+
{
|
| 182 |
+
"type": "function",
|
| 183 |
+
"function": {
|
| 184 |
+
"name": name,
|
| 185 |
+
"description": schema["description"],
|
| 186 |
+
"parameters": schema["parameters"]
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
for name, schema in self.tool_schemas.items()
|
| 190 |
+
]
|
| 191 |
+
|
| 192 |
+
async def execute_tool(
|
| 193 |
+
self,
|
| 194 |
+
tool_name: str,
|
| 195 |
+
arguments: Dict[str, Any],
|
| 196 |
+
user_id: int
|
| 197 |
+
) -> Dict[str, Any]:
|
| 198 |
+
"""Execute a tool with user context injection."""
|
| 199 |
+
if tool_name not in self.tools:
|
| 200 |
+
return {"success": False, "error": f"Tool '{tool_name}' not found"}
|
| 201 |
+
|
| 202 |
+
try:
|
| 203 |
+
# SECURITY: Inject user_id, don't trust LLM output
|
| 204 |
+
arguments["user_id"] = user_id
|
| 205 |
+
result = await self.tools[tool_name](**arguments)
|
| 206 |
+
return result
|
| 207 |
+
except Exception as e:
|
| 208 |
+
logger.error(f"Tool execution error: {tool_name}", exc_info=True)
|
| 209 |
+
return {"success": False, "error": str(e)}
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
---
|
| 213 |
+
|
| 214 |
+
## 3. Free-Tier Provider Capabilities
|
| 215 |
+
|
| 216 |
+
### Provider Capability Matrix
|
| 217 |
+
|
| 218 |
+
| Provider | Function Calling | Context Window | Rate Limits | Token Caps | Cost | Recommendation |
|
| 219 |
+
|----------|------------------|----------------|-------------|------------|------|----------------|
|
| 220 |
+
| **Google Gemini** | ✅ Full support | 1M-2M tokens | 15 RPM, 1500 RPD | 1M tokens/min | Free | **PRIMARY** |
|
| 221 |
+
| **OpenRouter** | ✅ Select models | 4k-200k tokens | Varies by model | Varies | Free models available | **FALLBACK** |
|
| 222 |
+
| **Cohere** | ✅ Yes | 4k-128k tokens | 100/min (trial) | Limited | Trial only | **NOT RECOMMENDED** |
|
| 223 |
+
|
| 224 |
+
**Legend**:
|
| 225 |
+
- RPM: Requests Per Minute
|
| 226 |
+
- RPD: Requests Per Day
|
| 227 |
+
|
| 228 |
+
### Decision: Google Gemini as Primary Provider
|
| 229 |
+
|
| 230 |
+
**Primary Provider**: Google Gemini (`gemini-1.5-flash`)
|
| 231 |
+
|
| 232 |
+
**Rationale**:
|
| 233 |
+
- **Best free-tier offering**: No credit card required, true free tier
|
| 234 |
+
- **Full function calling support**: Native support for tool invocation
|
| 235 |
+
- **Large context window**: 1M tokens (handles long conversations)
|
| 236 |
+
- **Generous rate limits**: 15 RPM, 1500 RPD sufficient for development and small-scale production
|
| 237 |
+
- **Already integrated**: `google-generativeai` SDK already installed in backend
|
| 238 |
+
|
| 239 |
+
**Fallback Provider**: OpenRouter (free models)
|
| 240 |
+
|
| 241 |
+
**Rationale**:
|
| 242 |
+
- **Good backup**: When Gemini hits rate limits
|
| 243 |
+
- **Free models available**: `google/gemini-flash-1.5:free`, `meta-llama/llama-3.2-3b-instruct:free`
|
| 244 |
+
- **No additional cost**: Maintains free-tier requirement
|
| 245 |
+
|
| 246 |
+
**Cohere NOT Recommended**:
|
| 247 |
+
- **Trial only**: Not a true free tier
|
| 248 |
+
- **Limited availability**: Trial expires
|
| 249 |
+
- **Smaller context window**: 4k-128k tokens insufficient for long conversations
|
| 250 |
+
|
| 251 |
+
### Implementation: Gemini Provider with Function Calling
|
| 252 |
+
|
| 253 |
+
```python
|
| 254 |
+
import google.generativeai as genai
|
| 255 |
+
from typing import List, Dict, Any, Optional
|
| 256 |
+
|
| 257 |
+
class GeminiProvider(LLMProvider):
|
| 258 |
+
"""Google Gemini provider with function calling support."""
|
| 259 |
+
|
| 260 |
+
def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
|
| 261 |
+
genai.configure(api_key=api_key)
|
| 262 |
+
self.model_name = model_name
|
| 263 |
+
|
| 264 |
+
async def generate_response_with_tools(
|
| 265 |
+
self,
|
| 266 |
+
messages: List[Dict[str, str]],
|
| 267 |
+
system_prompt: str,
|
| 268 |
+
tools: List[Dict[str, Any]]
|
| 269 |
+
) -> Dict[str, Any]:
|
| 270 |
+
"""Generate response with function calling support."""
|
| 271 |
+
|
| 272 |
+
# Convert tools to Gemini format
|
| 273 |
+
gemini_tools = self._convert_tools_to_gemini_format(tools)
|
| 274 |
+
|
| 275 |
+
# Initialize model with tools
|
| 276 |
+
model = genai.GenerativeModel(
|
| 277 |
+
model_name=self.model_name,
|
| 278 |
+
tools=gemini_tools,
|
| 279 |
+
system_instruction=system_prompt
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
# Start chat with history
|
| 283 |
+
chat = model.start_chat(history=self._format_history(messages[:-1]))
|
| 284 |
+
|
| 285 |
+
# Send latest message
|
| 286 |
+
response = chat.send_message(messages[-1]["content"])
|
| 287 |
+
|
| 288 |
+
# Check for function calls
|
| 289 |
+
if response.candidates[0].content.parts[0].function_call:
|
| 290 |
+
function_call = response.candidates[0].content.parts[0].function_call
|
| 291 |
+
return {
|
| 292 |
+
"content": None,
|
| 293 |
+
"tool_calls": [{
|
| 294 |
+
"name": function_call.name,
|
| 295 |
+
"arguments": dict(function_call.args)
|
| 296 |
+
}]
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
# Regular text response
|
| 300 |
+
return {
|
| 301 |
+
"content": response.text,
|
| 302 |
+
"tool_calls": None
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
async def generate_response_with_tool_results(
|
| 306 |
+
self,
|
| 307 |
+
messages: List[Dict[str, str]],
|
| 308 |
+
tool_calls: List[Dict],
|
| 309 |
+
tool_results: List[Dict]
|
| 310 |
+
) -> Dict[str, Any]:
|
| 311 |
+
"""Generate final response after tool execution."""
|
| 312 |
+
|
| 313 |
+
# Format tool results for Gemini
|
| 314 |
+
function_responses = [
|
| 315 |
+
genai.protos.FunctionResponse(
|
| 316 |
+
name=tool_call["name"],
|
| 317 |
+
response={"result": tool_result}
|
| 318 |
+
)
|
| 319 |
+
for tool_call, tool_result in zip(tool_calls, tool_results)
|
| 320 |
+
]
|
| 321 |
+
|
| 322 |
+
# Send tool results back to model
|
| 323 |
+
model = genai.GenerativeModel(model_name=self.model_name)
|
| 324 |
+
chat = model.start_chat(history=self._format_history(messages))
|
| 325 |
+
|
| 326 |
+
response = chat.send_message(
|
| 327 |
+
genai.protos.Content(parts=[
|
| 328 |
+
genai.protos.Part(function_response=fr)
|
| 329 |
+
for fr in function_responses
|
| 330 |
+
])
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
return {
|
| 334 |
+
"content": response.text,
|
| 335 |
+
"tool_calls": tool_calls,
|
| 336 |
+
"tool_results": tool_results
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
def _convert_tools_to_gemini_format(self, tools: List[Dict]) -> List:
|
| 340 |
+
"""Convert OpenAI function format to Gemini format."""
|
| 341 |
+
gemini_tools = []
|
| 342 |
+
for tool in tools:
|
| 343 |
+
func = tool["function"]
|
| 344 |
+
gemini_tools.append(
|
| 345 |
+
genai.protos.Tool(
|
| 346 |
+
function_declarations=[
|
| 347 |
+
genai.protos.FunctionDeclaration(
|
| 348 |
+
name=func["name"],
|
| 349 |
+
description=func["description"],
|
| 350 |
+
parameters=func["parameters"]
|
| 351 |
+
)
|
| 352 |
+
]
|
| 353 |
+
)
|
| 354 |
+
)
|
| 355 |
+
return gemini_tools
|
| 356 |
+
|
| 357 |
+
def _format_history(self, messages: List[Dict]) -> List:
|
| 358 |
+
"""Format messages for Gemini chat history."""
|
| 359 |
+
return [
|
| 360 |
+
genai.protos.Content(
|
| 361 |
+
role="user" if msg["role"] == "user" else "model",
|
| 362 |
+
parts=[genai.protos.Part(text=msg["content"])]
|
| 363 |
+
)
|
| 364 |
+
for msg in messages
|
| 365 |
+
]
|
| 366 |
+
```
|
| 367 |
+
|
| 368 |
+
### Rate Limit Handling
|
| 369 |
+
|
| 370 |
+
```python
|
| 371 |
+
class RateLimitHandler:
|
| 372 |
+
"""Handle rate limits with fallback provider."""
|
| 373 |
+
|
| 374 |
+
def __init__(self, primary_provider: LLMProvider, fallback_provider: Optional[LLMProvider] = None):
|
| 375 |
+
self.primary = primary_provider
|
| 376 |
+
self.fallback = fallback_provider
|
| 377 |
+
self.rate_limit_count = 0
|
| 378 |
+
|
| 379 |
+
async def generate_response(self, *args, **kwargs):
|
| 380 |
+
"""Generate response with automatic fallback."""
|
| 381 |
+
try:
|
| 382 |
+
return await self.primary.generate_response_with_tools(*args, **kwargs)
|
| 383 |
+
except Exception as e:
|
| 384 |
+
if "rate limit" in str(e).lower() or "429" in str(e):
|
| 385 |
+
self.rate_limit_count += 1
|
| 386 |
+
logger.warning(f"Rate limit hit on primary provider, using fallback")
|
| 387 |
+
|
| 388 |
+
if self.fallback:
|
| 389 |
+
return await self.fallback.generate_response_with_tools(*args, **kwargs)
|
| 390 |
+
else:
|
| 391 |
+
raise HTTPException(
|
| 392 |
+
status_code=429,
|
| 393 |
+
detail="Rate limit exceeded. Please try again in a few minutes."
|
| 394 |
+
)
|
| 395 |
+
raise
|
| 396 |
+
```
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
## 4. Agent-MCP Integration Pattern
|
| 401 |
+
|
| 402 |
+
### Decision: Tool Registry with User Context Injection
|
| 403 |
+
|
| 404 |
+
**Rationale**:
|
| 405 |
+
- **Security**: User context (`user_id`) injected by backend, never trusted from LLM
|
| 406 |
+
- **Stateless**: Tools receive all context explicitly
|
| 407 |
+
- **Testable**: Tools can be tested independently
|
| 408 |
+
- **Maintainable**: Clear separation between agent logic and tool execution
|
| 409 |
+
|
| 410 |
+
**Tool Invocation Flow**:
|
| 411 |
+
|
| 412 |
+
```
|
| 413 |
+
User Message → LLM (with tools) → Tool Calls → Execute MCP Tools →
|
| 414 |
+
Tool Results → LLM (with results) → Final Response
|
| 415 |
+
```
|
| 416 |
+
|
| 417 |
+
**Implementation Pattern**:
|
| 418 |
+
|
| 419 |
+
```python
|
| 420 |
+
class AgentRunner:
|
| 421 |
+
"""Agent orchestration with MCP tool integration."""
|
| 422 |
+
|
| 423 |
+
async def execute(
|
| 424 |
+
self,
|
| 425 |
+
messages: List[Dict],
|
| 426 |
+
system_prompt: str,
|
| 427 |
+
user_id: int
|
| 428 |
+
) -> Dict[str, Any]:
|
| 429 |
+
"""Execute agent with tool invocation."""
|
| 430 |
+
|
| 431 |
+
# 1. Get tool definitions
|
| 432 |
+
tool_definitions = self.tools.get_tool_definitions()
|
| 433 |
+
|
| 434 |
+
# 2. First LLM call with tools
|
| 435 |
+
response = await self.provider.generate_response_with_tools(
|
| 436 |
+
messages=messages,
|
| 437 |
+
system_prompt=system_prompt,
|
| 438 |
+
tools=tool_definitions
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
# 3. If no tool calls, return response
|
| 442 |
+
if not response.get("tool_calls"):
|
| 443 |
+
return {
|
| 444 |
+
"content": response["content"],
|
| 445 |
+
"tool_calls": None,
|
| 446 |
+
"tool_results": None
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
# 4. Execute tool calls
|
| 450 |
+
tool_results = []
|
| 451 |
+
for tool_call in response["tool_calls"]:
|
| 452 |
+
result = await self.tools.execute_tool(
|
| 453 |
+
tool_name=tool_call["name"],
|
| 454 |
+
arguments=tool_call["arguments"],
|
| 455 |
+
user_id=user_id # SECURITY: Inject user context
|
| 456 |
+
)
|
| 457 |
+
tool_results.append(result)
|
| 458 |
+
|
| 459 |
+
# 5. Second LLM call with tool results
|
| 460 |
+
final_response = await self.provider.generate_response_with_tool_results(
|
| 461 |
+
messages=messages,
|
| 462 |
+
tool_calls=response["tool_calls"],
|
| 463 |
+
tool_results=tool_results
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
return {
|
| 467 |
+
"content": final_response["content"],
|
| 468 |
+
"tool_calls": response["tool_calls"],
|
| 469 |
+
"tool_results": tool_results
|
| 470 |
+
}
|
| 471 |
+
```
|
| 472 |
+
|
| 473 |
+
**Error Propagation**:
|
| 474 |
+
|
| 475 |
+
```python
|
| 476 |
+
async def execute_tool(self, tool_name: str, arguments: Dict, user_id: int) -> Dict:
|
| 477 |
+
"""Execute tool with error handling."""
|
| 478 |
+
try:
|
| 479 |
+
# Inject user_id
|
| 480 |
+
arguments["user_id"] = user_id
|
| 481 |
+
|
| 482 |
+
# Execute tool
|
| 483 |
+
result = await self.tools[tool_name](**arguments)
|
| 484 |
+
|
| 485 |
+
# Validate result format
|
| 486 |
+
if not isinstance(result, dict) or "success" not in result:
|
| 487 |
+
return {
|
| 488 |
+
"success": False,
|
| 489 |
+
"error": "Tool returned invalid response format"
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
return result
|
| 493 |
+
|
| 494 |
+
except KeyError:
|
| 495 |
+
return {
|
| 496 |
+
"success": False,
|
| 497 |
+
"error": f"Tool '{tool_name}' not found"
|
| 498 |
+
}
|
| 499 |
+
except TypeError as e:
|
| 500 |
+
return {
|
| 501 |
+
"success": False,
|
| 502 |
+
"error": f"Invalid arguments for tool '{tool_name}': {str(e)}"
|
| 503 |
+
}
|
| 504 |
+
except Exception as e:
|
| 505 |
+
logger.error(f"Tool execution error: {tool_name}", exc_info=True)
|
| 506 |
+
return {
|
| 507 |
+
"success": False,
|
| 508 |
+
"error": f"Tool execution failed: {str(e)}"
|
| 509 |
+
}
|
| 510 |
+
```
|
| 511 |
+
|
| 512 |
+
---
|
| 513 |
+
|
| 514 |
+
## 5. Stateless Request Cycle Implementation
|
| 515 |
+
|
| 516 |
+
### Decision: Database-Backed Conversation History with Trimming
|
| 517 |
+
|
| 518 |
+
**Rationale**:
|
| 519 |
+
- **Stateless**: Every request loads conversation history from database
|
| 520 |
+
- **Scalable**: No in-memory state, supports horizontal scaling
|
| 521 |
+
- **Restart-safe**: Server restarts don't affect conversation continuity
|
| 522 |
+
- **Free-tier compatible**: Conversation history trimming handles token limits
|
| 523 |
+
|
| 524 |
+
**Complete Request Flow**:
|
| 525 |
+
|
| 526 |
+
```python
|
| 527 |
+
@router.post("/api/{user_id}/chat", response_model=ChatResponse)
|
| 528 |
+
async def chat(
|
| 529 |
+
user_id: int,
|
| 530 |
+
request: ChatRequest,
|
| 531 |
+
db: Session = Depends(get_session),
|
| 532 |
+
current_user: Dict = Depends(get_current_user)
|
| 533 |
+
) -> ChatResponse:
|
| 534 |
+
"""Stateless chat endpoint with agent execution."""
|
| 535 |
+
|
| 536 |
+
# 1. Validate user authorization
|
| 537 |
+
if current_user["id"] != user_id:
|
| 538 |
+
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 539 |
+
|
| 540 |
+
# 2. Load or create conversation
|
| 541 |
+
conversation_service = ConversationService(db)
|
| 542 |
+
conversation = await conversation_service.get_or_create_conversation(
|
| 543 |
+
user_id=user_id,
|
| 544 |
+
conversation_id=request.conversation_id
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
# 3. Load message history from database
|
| 548 |
+
messages = await conversation_service.get_messages(conversation.id)
|
| 549 |
+
|
| 550 |
+
# 4. Format and trim history for agent
|
| 551 |
+
message_history = await conversation_service.format_messages_for_agent(
|
| 552 |
+
messages=messages,
|
| 553 |
+
max_messages=20, # Keep last 20 messages
|
| 554 |
+
max_tokens=8000 # Trim to fit free-tier context window
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
# 5. Store user message
|
| 558 |
+
await conversation_service.add_message(
|
| 559 |
+
conversation_id=conversation.id,
|
| 560 |
+
role="user",
|
| 561 |
+
content=request.message
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
# 6. Execute agent with tools
|
| 565 |
+
llm_service = LLMService()
|
| 566 |
+
tool_registry = MCPToolRegistry()
|
| 567 |
+
agent = AgentRunner(provider=llm_service.provider, tools=tool_registry)
|
| 568 |
+
|
| 569 |
+
agent_response = await agent.execute(
|
| 570 |
+
messages=message_history + [{"role": "user", "content": request.message}],
|
| 571 |
+
system_prompt=llm_service.get_default_system_prompt(),
|
| 572 |
+
user_id=user_id
|
| 573 |
+
)
|
| 574 |
+
|
| 575 |
+
# 7. Store assistant message with tool metadata
|
| 576 |
+
await conversation_service.add_message(
|
| 577 |
+
conversation_id=conversation.id,
|
| 578 |
+
role="assistant",
|
| 579 |
+
content=agent_response["content"],
|
| 580 |
+
metadata={
|
| 581 |
+
"tool_calls": agent_response.get("tool_calls"),
|
| 582 |
+
"tool_results": agent_response.get("tool_results")
|
| 583 |
+
}
|
| 584 |
+
)
|
| 585 |
+
|
| 586 |
+
# 8. Return response
|
| 587 |
+
return ChatResponse(
|
| 588 |
+
message=agent_response["content"],
|
| 589 |
+
conversation_id=conversation.id
|
| 590 |
+
)
|
| 591 |
+
```
|
| 592 |
+
|
| 593 |
+
**Conversation History Trimming**:
|
| 594 |
+
|
| 595 |
+
```python
|
| 596 |
+
async def format_messages_for_agent(
|
| 597 |
+
self,
|
| 598 |
+
messages: List[Message],
|
| 599 |
+
max_messages: int = 20,
|
| 600 |
+
max_tokens: int = 8000
|
| 601 |
+
) -> List[Dict[str, str]]:
|
| 602 |
+
"""Format messages with trimming for free-tier constraints."""
|
| 603 |
+
|
| 604 |
+
# Keep last N messages
|
| 605 |
+
recent_messages = messages[-max_messages:]
|
| 606 |
+
|
| 607 |
+
# Format for agent
|
| 608 |
+
formatted = [
|
| 609 |
+
{"role": msg.role, "content": msg.content}
|
| 610 |
+
for msg in recent_messages
|
| 611 |
+
]
|
| 612 |
+
|
| 613 |
+
# Estimate tokens (rough: 1 token ≈ 4 characters)
|
| 614 |
+
total_tokens = sum(len(msg["content"]) // 4 for msg in formatted)
|
| 615 |
+
|
| 616 |
+
# Trim oldest messages if over limit
|
| 617 |
+
while total_tokens > max_tokens and len(formatted) > 1:
|
| 618 |
+
formatted.pop(0) # Remove oldest
|
| 619 |
+
total_tokens = sum(len(msg["content"]) // 4 for msg in formatted)
|
| 620 |
+
|
| 621 |
+
return formatted
|
| 622 |
+
```
|
| 623 |
+
|
| 624 |
+
**Concurrent Request Handling**:
|
| 625 |
+
|
| 626 |
+
```python
|
| 627 |
+
# Use database transactions for consistency
|
| 628 |
+
async def add_message(
|
| 629 |
+
self,
|
| 630 |
+
conversation_id: int,
|
| 631 |
+
role: str,
|
| 632 |
+
content: str,
|
| 633 |
+
metadata: Optional[Dict] = None
|
| 634 |
+
) -> Message:
|
| 635 |
+
"""Add message with transaction for concurrent safety."""
|
| 636 |
+
|
| 637 |
+
async with self.db.begin(): # Transaction
|
| 638 |
+
message = Message(
|
| 639 |
+
conversation_id=conversation_id,
|
| 640 |
+
role=role,
|
| 641 |
+
content=content,
|
| 642 |
+
metadata=metadata,
|
| 643 |
+
created_at=datetime.utcnow()
|
| 644 |
+
)
|
| 645 |
+
self.db.add(message)
|
| 646 |
+
await self.db.flush() # Get ID before commit
|
| 647 |
+
await self.db.refresh(message)
|
| 648 |
+
return message
|
| 649 |
+
```
|
| 650 |
+
|
| 651 |
+
---
|
| 652 |
+
|
| 653 |
+
## Implementation Recommendations
|
| 654 |
+
|
| 655 |
+
### Phase 1: MCP Tools (Priority 1)
|
| 656 |
+
|
| 657 |
+
**Files to Create**:
|
| 658 |
+
- `backend/src/mcp/server.py` - MCP server setup
|
| 659 |
+
- `backend/src/mcp/tools/add_task.py` - add_task tool
|
| 660 |
+
- `backend/src/mcp/tools/list_tasks.py` - list_tasks tool
|
| 661 |
+
- `backend/src/mcp/tools/complete_task.py` - complete_task tool
|
| 662 |
+
- `backend/src/mcp/tools/delete_task.py` - delete_task tool
|
| 663 |
+
- `backend/src/mcp/tools/update_task.py` - update_task tool
|
| 664 |
+
- `backend/src/mcp/tool_registry.py` - MCPToolRegistry class
|
| 665 |
+
|
| 666 |
+
**Testing Strategy**:
|
| 667 |
+
- Unit tests for each tool in isolation
|
| 668 |
+
- Test with mock database
|
| 669 |
+
- Validate user scoping (users can only access their own tasks)
|
| 670 |
+
|
| 671 |
+
### Phase 2: Provider Enhancement (Priority 2)
|
| 672 |
+
|
| 673 |
+
**Files to Modify**:
|
| 674 |
+
- `backend/src/services/llm_service.py` - Add function calling support to GeminiProvider
|
| 675 |
+
- `backend/src/services/providers/gemini.py` - Implement tool invocation methods
|
| 676 |
+
- `backend/src/services/providers/openrouter.py` - Create OpenRouter fallback provider
|
| 677 |
+
|
| 678 |
+
**Testing Strategy**:
|
| 679 |
+
- Test function calling with Gemini API
|
| 680 |
+
- Test tool definition conversion
|
| 681 |
+
- Test rate limit handling with fallback
|
| 682 |
+
|
| 683 |
+
### Phase 3: Agent Integration (Priority 3)
|
| 684 |
+
|
| 685 |
+
**Files to Create**:
|
| 686 |
+
- `backend/src/agent/agent_runner.py` - AgentRunner class
|
| 687 |
+
- `backend/src/agent/agent_config.py` - Agent configuration
|
| 688 |
+
|
| 689 |
+
**Files to Modify**:
|
| 690 |
+
- `backend/src/services/llm_service.py` - Integrate AgentRunner
|
| 691 |
+
|
| 692 |
+
**Testing Strategy**:
|
| 693 |
+
- Test agent-tool integration
|
| 694 |
+
- Test tool invocation flow
|
| 695 |
+
- Test error handling
|
| 696 |
+
|
| 697 |
+
### Phase 4: Chat Endpoint Integration (Priority 4)
|
| 698 |
+
|
| 699 |
+
**Files to Modify**:
|
| 700 |
+
- `backend/src/api/routes/chat.py` - Integrate AgentRunner
|
| 701 |
+
- `backend/src/services/conversation_service.py` - Add message formatting and trimming
|
| 702 |
+
|
| 703 |
+
**Testing Strategy**:
|
| 704 |
+
- End-to-end tests for chat flow
|
| 705 |
+
- Test conversation history loading
|
| 706 |
+
- Test tool metadata persistence
|
| 707 |
+
- Test concurrent requests
|
| 708 |
+
|
| 709 |
+
---
|
| 710 |
+
|
| 711 |
+
## Risk Mitigation
|
| 712 |
+
|
| 713 |
+
### Technical Risks
|
| 714 |
+
|
| 715 |
+
1. **Rate Limit Exhaustion**
|
| 716 |
+
- **Mitigation**: Implement fallback to OpenRouter
|
| 717 |
+
- **Monitoring**: Track rate limit hits
|
| 718 |
+
- **User Communication**: Display friendly error messages
|
| 719 |
+
|
| 720 |
+
2. **Context Window Overflow**
|
| 721 |
+
- **Mitigation**: Conversation history trimming
|
| 722 |
+
- **Strategy**: Keep last 20 messages, max 8000 tokens
|
| 723 |
+
- **Fallback**: Summarize old messages if needed
|
| 724 |
+
|
| 725 |
+
3. **Tool Execution Failures**
|
| 726 |
+
- **Mitigation**: Structured error responses
|
| 727 |
+
- **Logging**: Comprehensive error logging
|
| 728 |
+
- **User Experience**: Friendly error messages
|
| 729 |
+
|
| 730 |
+
### Architectural Risks
|
| 731 |
+
|
| 732 |
+
1. **Database Performance**
|
| 733 |
+
- **Mitigation**: Proper indexing on conversation_id, user_id
|
| 734 |
+
- **Optimization**: Limit message history queries
|
| 735 |
+
- **Monitoring**: Track query performance
|
| 736 |
+
|
| 737 |
+
2. **Concurrent Requests**
|
| 738 |
+
- **Mitigation**: Database transactions
|
| 739 |
+
- **Testing**: Concurrent request tests
|
| 740 |
+
- **Validation**: Ensure no race conditions
|
| 741 |
+
|
| 742 |
+
---
|
| 743 |
+
|
| 744 |
+
## Conclusion
|
| 745 |
+
|
| 746 |
+
All research objectives have been met. The technical approach is validated and ready for implementation:
|
| 747 |
+
|
| 748 |
+
✅ **Custom agent implementation** (not OpenAI Agents SDK)
|
| 749 |
+
✅ **MCP SDK with FastMCP** (production-ready)
|
| 750 |
+
✅ **Google Gemini as primary provider** (best free-tier offering)
|
| 751 |
+
✅ **Tool registry with user context injection** (secure and stateless)
|
| 752 |
+
✅ **Database-backed conversation history** (stateless and restart-safe)
|
| 753 |
+
|
| 754 |
+
**Next Steps**:
|
| 755 |
+
1. Update `plan.md` with research decisions
|
| 756 |
+
2. Generate Phase 1 design artifacts (data-model.md, contracts/, quickstart.md)
|
| 757 |
+
3. Execute `/sp.tasks` to generate implementation tasks
|
| 758 |
+
4. Begin implementation starting with MCP tools
|
specs/001-openai-agent-mcp-tools/spec.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `001-openai-agent-mcp-tools`
|
| 4 |
+
**Created**: 2026-01-14
|
| 5 |
+
**Status**: Draft
|
| 6 |
+
**Input**: User description: "Spec-2: OpenAI Agent MCP Tools - AI execution layer with MCP server and task management tools"
|
| 7 |
+
|
| 8 |
+
## Context
|
| 9 |
+
|
| 10 |
+
This is Spec-2 of Phase III: Todo AI Chatbot. This specification builds on top of Spec-1 (chat UI + basic agent wiring) and introduces the AI execution layer, MCP server, and task management tools. Spec-1 must already be complete before implementing this specification.
|
| 11 |
+
|
| 12 |
+
This specification explicitly focuses on:
|
| 13 |
+
- Building an AI agent using the OpenAI Agents SDK
|
| 14 |
+
- Configuring the agent to run using free-tier API keys via external client configuration
|
| 15 |
+
- Integrating Cohere as a supported provider
|
| 16 |
+
- Implementing MCP tools for task operations
|
| 17 |
+
- Keeping the backend fully stateless with database-backed persistence
|
| 18 |
+
|
| 19 |
+
## User Scenarios & Testing *(mandatory)*
|
| 20 |
+
|
| 21 |
+
### User Story 1 - Create Task via Natural Language (Priority: P1)
|
| 22 |
+
|
| 23 |
+
A user wants to create a new task by typing a natural language request to the AI agent, such as "Add a task to buy groceries" or "Remind me to call mom tomorrow."
|
| 24 |
+
|
| 25 |
+
**Why this priority**: This is the core value proposition of the AI-powered todo system. Without the ability to create tasks via natural language, the AI agent provides no functional value. This is the minimum viable feature that demonstrates the agent's capability.
|
| 26 |
+
|
| 27 |
+
**Independent Test**: Can be fully tested by sending a chat message with a task creation intent and verifying that a new task appears in the user's task list with the correct title and details.
|
| 28 |
+
|
| 29 |
+
**Acceptance Scenarios**:
|
| 30 |
+
|
| 31 |
+
1. **Given** a logged-in user with an active conversation, **When** the user sends "Add a task to buy groceries", **Then** the agent creates a new task with title "Buy groceries" and confirms the creation in natural language
|
| 32 |
+
2. **Given** a logged-in user, **When** the user sends "Create a task: finish project report by Friday", **Then** the agent creates a task with appropriate title and due date, and responds with confirmation
|
| 33 |
+
3. **Given** a logged-in user, **When** the user sends an ambiguous request like "todo something", **Then** the agent asks for clarification about what task to create
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
### User Story 2 - List Tasks via Natural Language (Priority: P2)
|
| 38 |
+
|
| 39 |
+
A user wants to view their tasks by asking the AI agent in natural language, such as "Show me my tasks" or "What do I need to do today?"
|
| 40 |
+
|
| 41 |
+
**Why this priority**: After creating tasks, users need to view them. This is the second most critical feature for a functional todo system. It validates that the agent can retrieve and present information.
|
| 42 |
+
|
| 43 |
+
**Independent Test**: Can be fully tested by creating several tasks, then asking the agent to list them, and verifying that all tasks are returned in a readable format.
|
| 44 |
+
|
| 45 |
+
**Acceptance Scenarios**:
|
| 46 |
+
|
| 47 |
+
1. **Given** a user with 3 existing tasks, **When** the user asks "Show me my tasks", **Then** the agent lists all 3 tasks with their titles and status
|
| 48 |
+
2. **Given** a user with no tasks, **When** the user asks "What are my tasks?", **Then** the agent responds that there are no tasks currently
|
| 49 |
+
3. **Given** a user with completed and incomplete tasks, **When** the user asks "Show me my incomplete tasks", **Then** the agent filters and shows only incomplete tasks
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
### User Story 3 - Complete Task via Natural Language (Priority: P3)
|
| 54 |
+
|
| 55 |
+
A user wants to mark a task as complete by telling the AI agent, such as "Mark 'buy groceries' as done" or "I finished the project report."
|
| 56 |
+
|
| 57 |
+
**Why this priority**: Completing tasks is a core workflow in any todo system. This feature demonstrates the agent's ability to modify existing data based on user intent.
|
| 58 |
+
|
| 59 |
+
**Independent Test**: Can be fully tested by creating a task, asking the agent to mark it complete, and verifying the task's status changes to completed.
|
| 60 |
+
|
| 61 |
+
**Acceptance Scenarios**:
|
| 62 |
+
|
| 63 |
+
1. **Given** a user with an incomplete task "Buy groceries", **When** the user says "Mark 'buy groceries' as complete", **Then** the agent marks the task as complete and confirms the action
|
| 64 |
+
2. **Given** a user with multiple tasks, **When** the user says "I finished task 2", **Then** the agent identifies the correct task by ID and marks it complete
|
| 65 |
+
3. **Given** a user referencing a non-existent task, **When** the user says "Complete task 'xyz'", **Then** the agent responds that the task was not found and asks for clarification
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
### User Story 4 - Delete Task via Natural Language (Priority: P4)
|
| 70 |
+
|
| 71 |
+
A user wants to remove a task by asking the AI agent, such as "Delete the groceries task" or "Remove task 3."
|
| 72 |
+
|
| 73 |
+
**Why this priority**: Users need the ability to remove tasks that are no longer relevant. This is less critical than creation, viewing, and completion, but still important for task management.
|
| 74 |
+
|
| 75 |
+
**Independent Test**: Can be fully tested by creating a task, asking the agent to delete it, and verifying the task no longer appears in the task list.
|
| 76 |
+
|
| 77 |
+
**Acceptance Scenarios**:
|
| 78 |
+
|
| 79 |
+
1. **Given** a user with a task "Buy groceries", **When** the user says "Delete the groceries task", **Then** the agent removes the task and confirms deletion
|
| 80 |
+
2. **Given** a user with multiple tasks, **When** the user says "Remove task 2", **Then** the agent deletes the correct task by ID
|
| 81 |
+
3. **Given** a user referencing a non-existent task, **When** the user says "Delete task 'xyz'", **Then** the agent responds that the task was not found
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
### User Story 5 - Update Task via Natural Language (Priority: P5)
|
| 86 |
+
|
| 87 |
+
A user wants to modify an existing task by telling the AI agent, such as "Change the groceries task to 'buy groceries and milk'" or "Update task 1 title to 'finish report by Monday'."
|
| 88 |
+
|
| 89 |
+
**Why this priority**: Task updates are useful but less critical than the core CRUD operations. Users can work around this by deleting and recreating tasks if needed.
|
| 90 |
+
|
| 91 |
+
**Independent Test**: Can be fully tested by creating a task, asking the agent to update it, and verifying the task's details have changed.
|
| 92 |
+
|
| 93 |
+
**Acceptance Scenarios**:
|
| 94 |
+
|
| 95 |
+
1. **Given** a user with a task "Buy groceries", **When** the user says "Change the groceries task to 'buy groceries and milk'", **Then** the agent updates the task title and confirms the change
|
| 96 |
+
2. **Given** a user with a task, **When** the user says "Update task 1 description to 'urgent'", **Then** the agent updates the task description field
|
| 97 |
+
3. **Given** a user referencing a non-existent task, **When** the user says "Update task 'xyz'", **Then** the agent responds that the task was not found
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
### Edge Cases
|
| 102 |
+
|
| 103 |
+
- What happens when the agent receives a request while the external API provider (Gemini/OpenRouter/Cohere) is rate-limited or unavailable?
|
| 104 |
+
- How does the system handle ambiguous natural language requests that could map to multiple operations (e.g., "do something with task 1")?
|
| 105 |
+
- What happens when a user tries to complete or delete a task that was already completed or deleted by another session?
|
| 106 |
+
- How does the agent behave when the conversation history becomes very long and approaches the context window limit of free-tier models?
|
| 107 |
+
- What happens when the MCP server is unavailable or a tool call fails due to database connectivity issues?
|
| 108 |
+
- How does the system handle concurrent requests from the same user in multiple browser tabs?
|
| 109 |
+
- What happens when a user references a task by title but multiple tasks have similar titles?
|
| 110 |
+
|
| 111 |
+
## Requirements *(mandatory)*
|
| 112 |
+
|
| 113 |
+
### Functional Requirements
|
| 114 |
+
|
| 115 |
+
#### Agent Configuration
|
| 116 |
+
|
| 117 |
+
- **FR-001**: System MUST use the OpenAI Agents SDK to define the AI agent, including Agent, Runner, and Tool interfaces
|
| 118 |
+
- **FR-002**: System MUST configure the agent using an external client abstraction that supports free-tier API providers (Gemini, OpenRouter, Cohere)
|
| 119 |
+
- **FR-003**: System MUST allow switching between API providers via environment variables without code changes
|
| 120 |
+
- **FR-004**: System MUST support Cohere as either a primary provider or fallback provider
|
| 121 |
+
- **FR-005**: System MUST load all API keys from environment variables only (no hardcoded secrets)
|
| 122 |
+
- **FR-006**: System MUST handle free-tier constraints including short context windows, rate limits, and token caps
|
| 123 |
+
- **FR-007**: System MUST degrade gracefully when API provider errors occur or rate limits are hit
|
| 124 |
+
|
| 125 |
+
#### Agent Behavior
|
| 126 |
+
|
| 127 |
+
- **FR-008**: Agent MUST correctly map natural language task creation requests to the add_task MCP tool
|
| 128 |
+
- **FR-009**: Agent MUST correctly map natural language task listing requests to the list_tasks MCP tool
|
| 129 |
+
- **FR-010**: Agent MUST correctly map natural language task completion requests to the complete_task MCP tool
|
| 130 |
+
- **FR-011**: Agent MUST correctly map natural language task deletion requests to the delete_task MCP tool
|
| 131 |
+
- **FR-012**: Agent MUST correctly map natural language task update requests to the update_task MCP tool
|
| 132 |
+
- **FR-013**: Agent MUST confirm actions in friendly, natural language after executing MCP tools
|
| 133 |
+
- **FR-014**: Agent MUST handle errors (task not found, invalid input) gracefully and provide helpful error messages to users
|
| 134 |
+
- **FR-015**: Agent MUST ask clarifying questions when user intent is ambiguous
|
| 135 |
+
- **FR-016**: Agent MUST follow the Agent Behavior Specification defined in Phase III
|
| 136 |
+
|
| 137 |
+
#### MCP Server & Tools
|
| 138 |
+
|
| 139 |
+
- **FR-017**: System MUST implement an MCP server using the Official MCP SDK
|
| 140 |
+
- **FR-018**: MCP server MUST expose exactly 5 tools: add_task, list_tasks, complete_task, delete_task, update_task
|
| 141 |
+
- **FR-019**: Each MCP tool MUST validate all inputs before processing
|
| 142 |
+
- **FR-020**: Each MCP tool MUST enforce user scoping (users can only access their own tasks)
|
| 143 |
+
- **FR-021**: Each MCP tool MUST return structured responses that the agent can interpret
|
| 144 |
+
- **FR-022**: MCP tools MUST be stateless and persist all state in the database
|
| 145 |
+
- **FR-023**: add_task tool MUST accept task title and optional description, due date, and priority
|
| 146 |
+
- **FR-024**: list_tasks tool MUST return all tasks for the authenticated user with filtering options (completed/incomplete)
|
| 147 |
+
- **FR-025**: complete_task tool MUST accept a task identifier (ID or title) and mark the task as completed
|
| 148 |
+
- **FR-026**: delete_task tool MUST accept a task identifier (ID or title) and remove the task
|
| 149 |
+
- **FR-027**: update_task tool MUST accept a task identifier and fields to update (title, description, due date, priority, status)
|
| 150 |
+
|
| 151 |
+
#### Stateless Architecture
|
| 152 |
+
|
| 153 |
+
- **FR-028**: Backend MUST store NO in-memory state related to conversations or agent execution
|
| 154 |
+
- **FR-029**: Every chat request MUST load conversation and messages from the database
|
| 155 |
+
- **FR-030**: Every chat request MUST execute the agent with loaded context
|
| 156 |
+
- **FR-031**: Every chat request MUST persist agent responses and tool results to the database
|
| 157 |
+
- **FR-032**: Every chat request MUST return the response to the client
|
| 158 |
+
- **FR-033**: System MUST maintain conversation continuity across server restarts
|
| 159 |
+
- **FR-034**: System MUST support concurrent requests from multiple users without state conflicts
|
| 160 |
+
|
| 161 |
+
#### Security & Configuration
|
| 162 |
+
|
| 163 |
+
- **FR-035**: System MUST authenticate users using the existing Better Auth setup before allowing agent access
|
| 164 |
+
- **FR-036**: System MUST load external LLM provider API keys from environment variables
|
| 165 |
+
- **FR-037**: System MUST load Cohere API key from environment variables
|
| 166 |
+
- **FR-038**: System MUST support separate configuration for different API providers
|
| 167 |
+
- **FR-039**: System MUST NOT expose API keys in logs, error messages, or API responses
|
| 168 |
+
- **FR-040**: System MUST validate JWT tokens before processing any agent requests
|
| 169 |
+
|
| 170 |
+
#### Project Structure
|
| 171 |
+
|
| 172 |
+
- **FR-041**: All backend logic MUST remain inside the backend/ directory
|
| 173 |
+
- **FR-042**: MCP server code MUST be located inside the backend/ directory
|
| 174 |
+
- **FR-043**: No frontend changes are permitted in this specification
|
| 175 |
+
- **FR-044**: No file relocations or renames are permitted unless explicitly required and justified
|
| 176 |
+
|
| 177 |
+
### Key Entities
|
| 178 |
+
|
| 179 |
+
- **Agent Configuration**: Represents the AI agent setup including provider selection, API keys, model parameters, and tool registrations. Attributes include provider type (Gemini/OpenRouter/Cohere), model name, context window size, and tool list.
|
| 180 |
+
|
| 181 |
+
- **MCP Tool**: Represents a callable function that the agent can invoke to perform task operations. Attributes include tool name, input schema, output schema, and validation rules.
|
| 182 |
+
|
| 183 |
+
- **Tool Execution Result**: Represents the outcome of an MCP tool invocation. Attributes include success status, data payload (task object or list of tasks), error message (if failed), and execution timestamp.
|
| 184 |
+
|
| 185 |
+
- **Agent Request Context**: Represents the context needed for agent execution. Attributes include user ID, conversation ID, message history, and authentication token.
|
| 186 |
+
|
| 187 |
+
## Success Criteria *(mandatory)*
|
| 188 |
+
|
| 189 |
+
### Measurable Outcomes
|
| 190 |
+
|
| 191 |
+
- **SC-001**: Users can create tasks using natural language with 95% success rate for clear, unambiguous requests
|
| 192 |
+
- **SC-002**: Users can list, complete, delete, and update tasks using natural language with 90% success rate
|
| 193 |
+
- **SC-003**: System operates successfully using free-tier API keys without requiring paid subscriptions
|
| 194 |
+
- **SC-004**: Conversations persist correctly across server restarts with 100% continuity
|
| 195 |
+
- **SC-005**: Agent responds to user requests within 5 seconds under normal conditions (excluding API provider delays)
|
| 196 |
+
- **SC-006**: System handles at least 50 concurrent users without degradation
|
| 197 |
+
- **SC-007**: MCP tool invocations succeed 99% of the time when inputs are valid
|
| 198 |
+
- **SC-008**: Agent correctly interprets user intent and selects the appropriate MCP tool 90% of the time
|
| 199 |
+
- **SC-009**: System gracefully handles API provider rate limits and errors without crashing
|
| 200 |
+
- **SC-010**: All task operations enforce user scoping with 100% accuracy (no cross-user data leaks)
|
| 201 |
+
|
| 202 |
+
## Assumptions
|
| 203 |
+
|
| 204 |
+
- Spec-1 (chat UI + basic agent wiring) is already complete and functional
|
| 205 |
+
- Database schema for conversations and messages already exists from Spec-1
|
| 206 |
+
- Database schema for tasks already exists from Phase II
|
| 207 |
+
- Better Auth is already configured and issuing JWT tokens
|
| 208 |
+
- Frontend already has a chat interface that can send messages and display responses
|
| 209 |
+
- Users are already authenticated before accessing the chat interface
|
| 210 |
+
- The OpenAI Agents SDK is compatible with external client configurations for non-OpenAI providers
|
| 211 |
+
- Free-tier API providers (Gemini, OpenRouter, Cohere) support the necessary features for agent execution (function calling, structured outputs)
|
| 212 |
+
- The Official MCP SDK is available and compatible with the backend technology stack
|
| 213 |
+
|
| 214 |
+
## Dependencies
|
| 215 |
+
|
| 216 |
+
- Spec-1 (chat UI + basic agent wiring) must be complete
|
| 217 |
+
- OpenAI Agents SDK must be installed and configured
|
| 218 |
+
- Official MCP SDK must be installed and configured
|
| 219 |
+
- External API provider accounts (Gemini, OpenRouter, Cohere) must be created and API keys obtained
|
| 220 |
+
- Database must be accessible and contain the necessary tables for conversations, messages, and tasks
|
| 221 |
+
- Better Auth must be functional and issuing valid JWT tokens
|
| 222 |
+
|
| 223 |
+
## Out of Scope
|
| 224 |
+
|
| 225 |
+
The following items are explicitly excluded from this specification:
|
| 226 |
+
|
| 227 |
+
- UI/UX changes to the chat interface
|
| 228 |
+
- Advanced memory optimization or conversation summarization
|
| 229 |
+
- Multi-agent orchestration or agent-to-agent communication
|
| 230 |
+
- Paid OpenAI API usage or GPT-4 integration
|
| 231 |
+
- Voice input or speech-to-text capabilities
|
| 232 |
+
- Task sharing or collaboration features
|
| 233 |
+
- Task reminders or notifications
|
| 234 |
+
- Task categories or tags
|
| 235 |
+
- Task search or filtering beyond basic completed/incomplete status
|
| 236 |
+
- Performance optimization beyond basic functionality
|
| 237 |
+
- Advanced error recovery or retry mechanisms
|
| 238 |
+
- Monitoring, logging, or observability infrastructure
|
| 239 |
+
- Load testing or stress testing
|
| 240 |
+
- Deployment or infrastructure changes
|
| 241 |
+
|
| 242 |
+
## Notes
|
| 243 |
+
|
| 244 |
+
- The agent behavior must strictly follow the Agent Behavior Specification defined in Phase III (reference to be provided during planning)
|
| 245 |
+
- The choice between using Cohere as primary or fallback provider should be configurable via environment variables
|
| 246 |
+
- The MCP server should be designed to allow easy addition of new tools in future specifications
|
| 247 |
+
- Error handling should prioritize user experience over technical accuracy (friendly messages, not stack traces)
|
| 248 |
+
- The stateless architecture is critical for scalability and must not be compromised
|
specs/001-openai-agent-mcp-tools/tasks.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tasks: OpenAI Agent MCP Tools
|
| 2 |
+
|
| 3 |
+
**Input**: Design documents from `/specs/001-openai-agent-mcp-tools/`
|
| 4 |
+
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
|
| 5 |
+
|
| 6 |
+
**Tests**: Tests are NOT explicitly requested in the specification, so test tasks are omitted per template guidelines.
|
| 7 |
+
|
| 8 |
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
| 9 |
+
|
| 10 |
+
## Format: `[ID] [P?] [Story] Description`
|
| 11 |
+
|
| 12 |
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
| 13 |
+
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
| 14 |
+
- Include exact file paths in descriptions
|
| 15 |
+
|
| 16 |
+
## Path Conventions
|
| 17 |
+
|
| 18 |
+
- **Web app**: `backend/src/`, `frontend/src/`
|
| 19 |
+
- All tasks are backend-only per plan.md
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Phase 1: Setup (Shared Infrastructure)
|
| 24 |
+
|
| 25 |
+
**Purpose**: Project initialization and dependency installation
|
| 26 |
+
|
| 27 |
+
- [ ] T001 Install MCP SDK and Cohere SDK in backend/requirements.txt
|
| 28 |
+
- [ ] T002 [P] Create backend/src/agent/ directory structure with __init__.py
|
| 29 |
+
- [ ] T003 [P] Create backend/src/mcp/ directory structure with __init__.py
|
| 30 |
+
- [ ] T004 [P] Create backend/src/agent/providers/ directory with __init__.py
|
| 31 |
+
- [ ] T005 [P] Create backend/src/mcp/tools/ directory with __init__.py
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## Phase 2: Foundational (Blocking Prerequisites)
|
| 36 |
+
|
| 37 |
+
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
| 38 |
+
|
| 39 |
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
| 40 |
+
|
| 41 |
+
- [ ] T006 Create MCPToolRegistry class in backend/src/mcp/tool_registry.py with user context injection
|
| 42 |
+
- [ ] T007 [P] Create LLMProvider base class in backend/src/agent/providers/base.py
|
| 43 |
+
- [ ] T008 [P] Implement GeminiProvider with function calling in backend/src/agent/providers/gemini.py
|
| 44 |
+
- [ ] T009 [P] Implement OpenRouterProvider as fallback in backend/src/agent/providers/openrouter.py
|
| 45 |
+
- [ ] T010 [P] Implement CohereProvider (optional) in backend/src/agent/providers/cohere.py
|
| 46 |
+
- [ ] T011 Create AgentConfiguration dataclass in backend/src/agent/agent_config.py
|
| 47 |
+
- [ ] T012 Create AgentRunner class with tool invocation in backend/src/agent/agent_runner.py
|
| 48 |
+
- [ ] T013 Update ConversationService with format_messages_for_agent method in backend/src/services/conversation_service.py
|
| 49 |
+
- [ ] T014 Add environment variable loading for LLM_PROVIDER, GEMINI_API_KEY, OPENROUTER_API_KEY in backend/src/core/config.py
|
| 50 |
+
|
| 51 |
+
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## Phase 3: User Story 1 - Create Task via Natural Language (Priority: P1) 🎯 MVP
|
| 56 |
+
|
| 57 |
+
**Goal**: Enable users to create tasks by sending natural language requests like "Add a task to buy groceries"
|
| 58 |
+
|
| 59 |
+
**Independent Test**: Send chat message "Add a task to buy groceries" and verify new task appears in database with correct title
|
| 60 |
+
|
| 61 |
+
**Agent**: Backend Systems Agent
|
| 62 |
+
**Skill**: backend-mcp-tools
|
| 63 |
+
|
| 64 |
+
### Implementation for User Story 1
|
| 65 |
+
|
| 66 |
+
- [ ] T015 [P] [US1] Implement add_task MCP tool in backend/src/mcp/tools/add_task.py with user_id injection and validation
|
| 67 |
+
- [ ] T016 [US1] Register add_task tool with MCPToolRegistry in backend/src/mcp/tool_registry.py
|
| 68 |
+
- [ ] T017 [US1] Update AgentRunner to support add_task tool invocation in backend/src/agent/agent_runner.py
|
| 69 |
+
- [ ] T018 [US1] Modify chat endpoint to use AgentRunner for task creation in backend/src/api/routes/chat.py
|
| 70 |
+
- [ ] T019 [US1] Add error handling for task creation failures in backend/src/mcp/tools/add_task.py
|
| 71 |
+
- [ ] T020 [US1] Test end-to-end: "Add a task to buy groceries" creates task and returns confirmation
|
| 72 |
+
|
| 73 |
+
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Phase 4: User Story 2 - List Tasks via Natural Language (Priority: P2)
|
| 78 |
+
|
| 79 |
+
**Goal**: Enable users to view their tasks by asking "Show me my tasks" or "What do I need to do today?"
|
| 80 |
+
|
| 81 |
+
**Independent Test**: Create 3 tasks, send "Show me my tasks", verify all 3 tasks are listed in response
|
| 82 |
+
|
| 83 |
+
**Agent**: Backend Systems Agent
|
| 84 |
+
**Skill**: backend-mcp-tools
|
| 85 |
+
|
| 86 |
+
### Implementation for User Story 2
|
| 87 |
+
|
| 88 |
+
- [ ] T021 [P] [US2] Implement list_tasks MCP tool with filtering in backend/src/mcp/tools/list_tasks.py
|
| 89 |
+
- [ ] T022 [US2] Register list_tasks tool with MCPToolRegistry in backend/src/mcp/tool_registry.py
|
| 90 |
+
- [ ] T023 [US2] Update AgentRunner to support list_tasks tool invocation in backend/src/agent/agent_runner.py
|
| 91 |
+
- [ ] T024 [US2] Add filtering logic for completed/incomplete tasks in backend/src/mcp/tools/list_tasks.py
|
| 92 |
+
- [ ] T025 [US2] Test end-to-end: "Show me my tasks" returns all user tasks with correct formatting
|
| 93 |
+
|
| 94 |
+
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## Phase 5: User Story 3 - Complete Task via Natural Language (Priority: P3)
|
| 99 |
+
|
| 100 |
+
**Goal**: Enable users to mark tasks complete by saying "Mark 'buy groceries' as done" or "I finished task 2"
|
| 101 |
+
|
| 102 |
+
**Independent Test**: Create task, send "Mark task 1 as complete", verify task status changes to completed in database
|
| 103 |
+
|
| 104 |
+
**Agent**: Backend Systems Agent
|
| 105 |
+
**Skill**: backend-mcp-tools
|
| 106 |
+
|
| 107 |
+
### Implementation for User Story 3
|
| 108 |
+
|
| 109 |
+
- [ ] T026 [P] [US3] Implement complete_task MCP tool with ID/title lookup in backend/src/mcp/tools/complete_task.py
|
| 110 |
+
- [ ] T027 [US3] Register complete_task tool with MCPToolRegistry in backend/src/mcp/tool_registry.py
|
| 111 |
+
- [ ] T028 [US3] Update AgentRunner to support complete_task tool invocation in backend/src/agent/agent_runner.py
|
| 112 |
+
- [ ] T029 [US3] Add task identifier resolution (ID or title) in backend/src/mcp/tools/complete_task.py
|
| 113 |
+
- [ ] T030 [US3] Test end-to-end: "Mark task 1 as complete" updates task status and returns confirmation
|
| 114 |
+
|
| 115 |
+
**Checkpoint**: At this point, User Stories 1, 2, AND 3 should all work independently
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Phase 6: User Story 4 - Delete Task via Natural Language (Priority: P4)
|
| 120 |
+
|
| 121 |
+
**Goal**: Enable users to remove tasks by saying "Delete the groceries task" or "Remove task 3"
|
| 122 |
+
|
| 123 |
+
**Independent Test**: Create task, send "Delete task 1", verify task no longer exists in database
|
| 124 |
+
|
| 125 |
+
**Agent**: Backend Systems Agent
|
| 126 |
+
**Skill**: backend-mcp-tools
|
| 127 |
+
|
| 128 |
+
### Implementation for User Story 4
|
| 129 |
+
|
| 130 |
+
- [ ] T031 [P] [US4] Implement delete_task MCP tool with ID/title lookup in backend/src/mcp/tools/delete_task.py
|
| 131 |
+
- [ ] T032 [US4] Register delete_task tool with MCPToolRegistry in backend/src/mcp/tool_registry.py
|
| 132 |
+
- [ ] T033 [US4] Update AgentRunner to support delete_task tool invocation in backend/src/agent/agent_runner.py
|
| 133 |
+
- [ ] T034 [US4] Add task identifier resolution (ID or title) in backend/src/mcp/tools/delete_task.py
|
| 134 |
+
- [ ] T035 [US4] Test end-to-end: "Delete task 1" removes task and returns confirmation
|
| 135 |
+
|
| 136 |
+
**Checkpoint**: At this point, User Stories 1-4 should all work independently
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## Phase 7: User Story 5 - Update Task via Natural Language (Priority: P5)
|
| 141 |
+
|
| 142 |
+
**Goal**: Enable users to modify tasks by saying "Change the groceries task to 'buy groceries and milk'"
|
| 143 |
+
|
| 144 |
+
**Independent Test**: Create task, send "Update task 1 title to 'new title'", verify task title changes in database
|
| 145 |
+
|
| 146 |
+
**Agent**: Backend Systems Agent
|
| 147 |
+
**Skill**: backend-mcp-tools
|
| 148 |
+
|
| 149 |
+
### Implementation for User Story 5
|
| 150 |
+
|
| 151 |
+
- [ ] T036 [P] [US5] Implement update_task MCP tool with field updates in backend/src/mcp/tools/update_task.py
|
| 152 |
+
- [ ] T037 [US5] Register update_task tool with MCPToolRegistry in backend/src/mcp/tool_registry.py
|
| 153 |
+
- [ ] T038 [US5] Update AgentRunner to support update_task tool invocation in backend/src/agent/agent_runner.py
|
| 154 |
+
- [ ] T039 [US5] Add task identifier resolution and field validation in backend/src/mcp/tools/update_task.py
|
| 155 |
+
- [ ] T040 [US5] Test end-to-end: "Update task 1 title to 'new title'" modifies task and returns confirmation
|
| 156 |
+
|
| 157 |
+
**Checkpoint**: All user stories should now be independently functional
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## Phase 8: Polish & Cross-Cutting Concerns
|
| 162 |
+
|
| 163 |
+
**Purpose**: Improvements that affect multiple user stories
|
| 164 |
+
|
| 165 |
+
- [ ] T041 [P] Add rate limit handling with fallback provider in backend/src/agent/agent_runner.py
|
| 166 |
+
- [ ] T042 [P] Add comprehensive error logging for all MCP tools in backend/src/mcp/tools/
|
| 167 |
+
- [ ] T043 [P] Add conversation history trimming (20 messages, 8000 tokens) in backend/src/services/conversation_service.py
|
| 168 |
+
- [ ] T044 [P] Update LLMService to delegate to AgentRunner in backend/src/services/llm_service.py
|
| 169 |
+
- [ ] T045 [P] Add tool call metadata persistence in Message.metadata in backend/src/services/conversation_service.py
|
| 170 |
+
- [ ] T046 Validate quickstart.md instructions by running all test scenarios
|
| 171 |
+
- [ ] T047 [P] Add system prompt configuration for agent behavior in backend/src/agent/agent_config.py
|
| 172 |
+
- [ ] T048 [P] Document environment variables in backend/.env.example
|
| 173 |
+
|
| 174 |
+
---
|
| 175 |
+
|
| 176 |
+
## Dependencies & Execution Order
|
| 177 |
+
|
| 178 |
+
### Phase Dependencies
|
| 179 |
+
|
| 180 |
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
| 181 |
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
| 182 |
+
- **User Stories (Phase 3-7)**: All depend on Foundational phase completion
|
| 183 |
+
- User stories can then proceed in parallel (if staffed)
|
| 184 |
+
- Or sequentially in priority order (P1 → P2 → P3 → P4 → P5)
|
| 185 |
+
- **Polish (Phase 8)**: Depends on all desired user stories being complete
|
| 186 |
+
|
| 187 |
+
### User Story Dependencies
|
| 188 |
+
|
| 189 |
+
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
| 190 |
+
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - Independent of US1
|
| 191 |
+
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - Independent of US1/US2
|
| 192 |
+
- **User Story 4 (P4)**: Can start after Foundational (Phase 2) - Independent of US1/US2/US3
|
| 193 |
+
- **User Story 5 (P5)**: Can start after Foundational (Phase 2) - Independent of US1/US2/US3/US4
|
| 194 |
+
|
| 195 |
+
### Within Each User Story
|
| 196 |
+
|
| 197 |
+
- MCP tool implementation before registration
|
| 198 |
+
- Tool registration before AgentRunner integration
|
| 199 |
+
- AgentRunner integration before chat endpoint modification
|
| 200 |
+
- Core implementation before end-to-end testing
|
| 201 |
+
|
| 202 |
+
### Parallel Opportunities
|
| 203 |
+
|
| 204 |
+
- All Setup tasks (T002-T005) marked [P] can run in parallel
|
| 205 |
+
- All Foundational provider tasks (T007-T010) marked [P] can run in parallel
|
| 206 |
+
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
|
| 207 |
+
- All MCP tool implementations (T015, T021, T026, T031, T036) marked [P] can run in parallel after Foundational
|
| 208 |
+
- All Polish tasks marked [P] can run in parallel
|
| 209 |
+
|
| 210 |
+
---
|
| 211 |
+
|
| 212 |
+
## Parallel Example: After Foundational Phase
|
| 213 |
+
|
| 214 |
+
```bash
|
| 215 |
+
# Launch all MCP tool implementations together:
|
| 216 |
+
Task: "Implement add_task MCP tool in backend/src/mcp/tools/add_task.py"
|
| 217 |
+
Task: "Implement list_tasks MCP tool in backend/src/mcp/tools/list_tasks.py"
|
| 218 |
+
Task: "Implement complete_task MCP tool in backend/src/mcp/tools/complete_task.py"
|
| 219 |
+
Task: "Implement delete_task MCP tool in backend/src/mcp/tools/delete_task.py"
|
| 220 |
+
Task: "Implement update_task MCP tool in backend/src/mcp/tools/update_task.py"
|
| 221 |
+
|
| 222 |
+
# Then register all tools together:
|
| 223 |
+
Task: "Register add_task tool with MCPToolRegistry"
|
| 224 |
+
Task: "Register list_tasks tool with MCPToolRegistry"
|
| 225 |
+
Task: "Register complete_task tool with MCPToolRegistry"
|
| 226 |
+
Task: "Register delete_task tool with MCPToolRegistry"
|
| 227 |
+
Task: "Register update_task tool with MCPToolRegistry"
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
## Implementation Strategy
|
| 233 |
+
|
| 234 |
+
### MVP First (User Story 1 Only)
|
| 235 |
+
|
| 236 |
+
1. Complete Phase 1: Setup (T001-T005)
|
| 237 |
+
2. Complete Phase 2: Foundational (T006-T014) - CRITICAL - blocks all stories
|
| 238 |
+
3. Complete Phase 3: User Story 1 (T015-T020)
|
| 239 |
+
4. **STOP and VALIDATE**: Test User Story 1 independently
|
| 240 |
+
- Send "Add a task to buy groceries"
|
| 241 |
+
- Verify task created in database
|
| 242 |
+
- Verify agent returns confirmation
|
| 243 |
+
5. Deploy/demo if ready
|
| 244 |
+
|
| 245 |
+
### Incremental Delivery
|
| 246 |
+
|
| 247 |
+
1. Complete Setup + Foundational → Foundation ready
|
| 248 |
+
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
|
| 249 |
+
3. Add User Story 2 → Test independently → Deploy/Demo
|
| 250 |
+
4. Add User Story 3 → Test independently → Deploy/Demo
|
| 251 |
+
5. Add User Story 4 → Test independently → Deploy/Demo
|
| 252 |
+
6. Add User Story 5 → Test independently → Deploy/Demo
|
| 253 |
+
7. Each story adds value without breaking previous stories
|
| 254 |
+
|
| 255 |
+
### Parallel Team Strategy
|
| 256 |
+
|
| 257 |
+
With multiple developers:
|
| 258 |
+
|
| 259 |
+
1. Team completes Setup + Foundational together (T001-T014)
|
| 260 |
+
2. Once Foundational is done:
|
| 261 |
+
- Developer A: User Story 1 (T015-T020)
|
| 262 |
+
- Developer B: User Story 2 (T021-T025)
|
| 263 |
+
- Developer C: User Story 3 (T026-T030)
|
| 264 |
+
- Developer D: User Story 4 (T031-T035)
|
| 265 |
+
- Developer E: User Story 5 (T036-T040)
|
| 266 |
+
3. Stories complete and integrate independently
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
## Task Summary
|
| 271 |
+
|
| 272 |
+
**Total Tasks**: 48 tasks
|
| 273 |
+
|
| 274 |
+
**Tasks per Phase**:
|
| 275 |
+
- Phase 1 (Setup): 5 tasks
|
| 276 |
+
- Phase 2 (Foundational): 9 tasks (BLOCKING)
|
| 277 |
+
- Phase 3 (US1 - Create Task): 6 tasks
|
| 278 |
+
- Phase 4 (US2 - List Tasks): 5 tasks
|
| 279 |
+
- Phase 5 (US3 - Complete Task): 5 tasks
|
| 280 |
+
- Phase 6 (US4 - Delete Task): 5 tasks
|
| 281 |
+
- Phase 7 (US5 - Update Task): 5 tasks
|
| 282 |
+
- Phase 8 (Polish): 8 tasks
|
| 283 |
+
|
| 284 |
+
**Parallel Opportunities**: 23 tasks marked [P] can run in parallel within their phase
|
| 285 |
+
|
| 286 |
+
**Independent Test Criteria**:
|
| 287 |
+
- US1: Send "Add a task to buy groceries" → Task created in DB
|
| 288 |
+
- US2: Send "Show me my tasks" → All tasks listed
|
| 289 |
+
- US3: Send "Mark task 1 as complete" → Task status updated
|
| 290 |
+
- US4: Send "Delete task 1" → Task removed from DB
|
| 291 |
+
- US5: Send "Update task 1 title to 'new title'" → Task title changed
|
| 292 |
+
|
| 293 |
+
**Suggested MVP Scope**: Phase 1 + Phase 2 + Phase 3 (User Story 1 only) = 20 tasks
|
| 294 |
+
|
| 295 |
+
---
|
| 296 |
+
|
| 297 |
+
## Notes
|
| 298 |
+
|
| 299 |
+
- [P] tasks = different files, no dependencies
|
| 300 |
+
- [Story] label maps task to specific user story for traceability
|
| 301 |
+
- Each user story should be independently completable and testable
|
| 302 |
+
- Commit after each task or logical group
|
| 303 |
+
- Stop at any checkpoint to validate story independently
|
| 304 |
+
- Research.md indicates custom agent implementation (NOT OpenAI Agents SDK)
|
| 305 |
+
- All MCP tools must inject user_id for security (never trust LLM output)
|
| 306 |
+
- Stateless architecture: load conversation history from DB on every request
|
| 307 |
+
- Free-tier constraints: trim history to 20 messages, 8000 tokens
|
specs/001-task-crud/checklists/requirements.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Specification Quality Checklist: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 4 |
+
**Created**: 2026-01-08
|
| 5 |
+
**Feature**: [Task CRUD Operations](../spec.md)
|
| 6 |
+
|
| 7 |
+
## Content Quality
|
| 8 |
+
|
| 9 |
+
- [x] No implementation details (languages, frameworks, APIs)
|
| 10 |
+
- [x] Focused on user value and business needs
|
| 11 |
+
- [x] Written for non-technical stakeholders
|
| 12 |
+
- [x] All mandatory sections completed
|
| 13 |
+
|
| 14 |
+
**Notes**: Spec successfully avoids implementation details. Technical constraints are properly separated in their own section. User stories focus on user value and business outcomes.
|
| 15 |
+
|
| 16 |
+
## Requirement Completeness
|
| 17 |
+
|
| 18 |
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
| 19 |
+
- [x] Requirements are testable and unambiguous
|
| 20 |
+
- [x] Success criteria are measurable
|
| 21 |
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
| 22 |
+
- [x] All acceptance scenarios are defined
|
| 23 |
+
- [x] Edge cases are identified
|
| 24 |
+
- [x] Scope is clearly bounded
|
| 25 |
+
- [x] Dependencies and assumptions identified
|
| 26 |
+
|
| 27 |
+
**Notes**: All 15 functional requirements are specific and testable. Success criteria include both quantitative metrics (time, percentage) and qualitative measures (user understanding, visual feedback). Edge cases cover validation, concurrency, error handling, and security. Out of Scope section clearly defines boundaries.
|
| 28 |
+
|
| 29 |
+
## Feature Readiness
|
| 30 |
+
|
| 31 |
+
- [x] All functional requirements have clear acceptance criteria
|
| 32 |
+
- [x] User scenarios cover primary flows
|
| 33 |
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
| 34 |
+
- [x] No implementation details leak into specification
|
| 35 |
+
|
| 36 |
+
**Notes**: 4 user stories with priorities P1-P4 cover the complete task management lifecycle. Each story has independent test criteria and acceptance scenarios. Success criteria align with functional requirements.
|
| 37 |
+
|
| 38 |
+
## Validation Summary
|
| 39 |
+
|
| 40 |
+
**Status**: ✅ PASSED - Specification is complete and ready for planning phase
|
| 41 |
+
|
| 42 |
+
**Strengths**:
|
| 43 |
+
- Clear prioritization of user stories (P1-P4) enables incremental delivery
|
| 44 |
+
- Comprehensive functional requirements (FR-001 through FR-015)
|
| 45 |
+
- Measurable success criteria with specific metrics
|
| 46 |
+
- Well-defined data isolation and security requirements
|
| 47 |
+
- Explicit assumptions about authentication dependency
|
| 48 |
+
|
| 49 |
+
**Ready for**: `/sp.plan` (implementation planning)
|
| 50 |
+
|
| 51 |
+
## Notes
|
| 52 |
+
|
| 53 |
+
All checklist items passed on first validation. No clarifications needed. The specification provides sufficient detail for architectural planning while remaining technology-agnostic in the requirements and success criteria sections.
|
specs/001-task-crud/contracts/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Contracts: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Feature**: Task CRUD Operations
|
| 4 |
+
**Date**: 2026-01-08
|
| 5 |
+
**Status**: Complete
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This directory contains the API contract specifications for the Task CRUD feature. The contracts define the REST API endpoints, request/response formats, validation rules, and error handling.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
- **tasks-api.yaml**: OpenAPI 3.1.0 specification for all task endpoints
|
| 14 |
+
|
| 15 |
+
## API Endpoints Summary
|
| 16 |
+
|
| 17 |
+
| Method | Endpoint | Description | Auth Required |
|
| 18 |
+
|--------|----------|-------------|---------------|
|
| 19 |
+
| GET | `/api/tasks` | List all tasks for authenticated user | Yes (JWT) |
|
| 20 |
+
| POST | `/api/tasks` | Create a new task | Yes (JWT) |
|
| 21 |
+
| GET | `/api/tasks/{task_id}` | Get a specific task | Yes (JWT) |
|
| 22 |
+
| PUT | `/api/tasks/{task_id}` | Update a task (full replacement) | Yes (JWT) |
|
| 23 |
+
| PATCH | `/api/tasks/{task_id}` | Partially update a task | Yes (JWT) |
|
| 24 |
+
| DELETE | `/api/tasks/{task_id}` | Delete a task | Yes (JWT) |
|
| 25 |
+
|
| 26 |
+
## Authentication
|
| 27 |
+
|
| 28 |
+
All endpoints require JWT authentication via the `Authorization` header:
|
| 29 |
+
|
| 30 |
+
```
|
| 31 |
+
Authorization: Bearer <jwt_token>
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
**Note**: JWT token generation and validation will be implemented in Spec 2 (Authentication feature). For Spec 1 implementation, endpoints will accept a placeholder user_id parameter.
|
| 35 |
+
|
| 36 |
+
## Request/Response Formats
|
| 37 |
+
|
| 38 |
+
### TaskCreate (POST /api/tasks)
|
| 39 |
+
|
| 40 |
+
**Request Body**:
|
| 41 |
+
```json
|
| 42 |
+
{
|
| 43 |
+
"title": "Buy groceries",
|
| 44 |
+
"description": "Milk, eggs, bread"
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**Response (201 Created)**:
|
| 49 |
+
```json
|
| 50 |
+
{
|
| 51 |
+
"id": 1,
|
| 52 |
+
"user_id": 42,
|
| 53 |
+
"title": "Buy groceries",
|
| 54 |
+
"description": "Milk, eggs, bread",
|
| 55 |
+
"completed": false,
|
| 56 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 57 |
+
"updated_at": "2026-01-08T10:00:00Z"
|
| 58 |
+
}
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### TaskUpdate (PUT /api/tasks/{task_id})
|
| 62 |
+
|
| 63 |
+
**Request Body**:
|
| 64 |
+
```json
|
| 65 |
+
{
|
| 66 |
+
"title": "Buy groceries and milk",
|
| 67 |
+
"description": "Updated description",
|
| 68 |
+
"completed": false
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**Response (200 OK)**:
|
| 73 |
+
```json
|
| 74 |
+
{
|
| 75 |
+
"id": 1,
|
| 76 |
+
"user_id": 42,
|
| 77 |
+
"title": "Buy groceries and milk",
|
| 78 |
+
"description": "Updated description",
|
| 79 |
+
"completed": false,
|
| 80 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 81 |
+
"updated_at": "2026-01-08T10:15:00Z"
|
| 82 |
+
}
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### TaskPatch (PATCH /api/tasks/{task_id})
|
| 86 |
+
|
| 87 |
+
**Request Body** (partial update):
|
| 88 |
+
```json
|
| 89 |
+
{
|
| 90 |
+
"completed": true
|
| 91 |
+
}
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
**Response (200 OK)**:
|
| 95 |
+
```json
|
| 96 |
+
{
|
| 97 |
+
"id": 1,
|
| 98 |
+
"user_id": 42,
|
| 99 |
+
"title": "Buy groceries",
|
| 100 |
+
"description": "Milk, eggs, bread",
|
| 101 |
+
"completed": true,
|
| 102 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 103 |
+
"updated_at": "2026-01-08T10:20:00Z"
|
| 104 |
+
}
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
### TaskListResponse (GET /api/tasks)
|
| 108 |
+
|
| 109 |
+
**Query Parameters**:
|
| 110 |
+
- `completed` (boolean, optional): Filter by completion status
|
| 111 |
+
- `sort` (string, optional): Sort order (created_at_desc, created_at_asc)
|
| 112 |
+
- `limit` (integer, optional): Maximum number of tasks (default: 50, max: 100)
|
| 113 |
+
- `offset` (integer, optional): Number of tasks to skip (default: 0)
|
| 114 |
+
|
| 115 |
+
**Response (200 OK)**:
|
| 116 |
+
```json
|
| 117 |
+
{
|
| 118 |
+
"tasks": [
|
| 119 |
+
{
|
| 120 |
+
"id": 1,
|
| 121 |
+
"user_id": 42,
|
| 122 |
+
"title": "Buy groceries",
|
| 123 |
+
"description": "Milk, eggs, bread",
|
| 124 |
+
"completed": false,
|
| 125 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 126 |
+
"updated_at": "2026-01-08T10:00:00Z"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"id": 2,
|
| 130 |
+
"user_id": 42,
|
| 131 |
+
"title": "Finish project report",
|
| 132 |
+
"description": null,
|
| 133 |
+
"completed": true,
|
| 134 |
+
"created_at": "2026-01-07T15:30:00Z",
|
| 135 |
+
"updated_at": "2026-01-08T09:00:00Z"
|
| 136 |
+
}
|
| 137 |
+
],
|
| 138 |
+
"total": 2
|
| 139 |
+
}
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
## Error Responses
|
| 143 |
+
|
| 144 |
+
### 400 Bad Request (Validation Error)
|
| 145 |
+
|
| 146 |
+
```json
|
| 147 |
+
{
|
| 148 |
+
"detail": "Validation error",
|
| 149 |
+
"error_code": "VALIDATION_ERROR",
|
| 150 |
+
"field_errors": {
|
| 151 |
+
"title": [
|
| 152 |
+
"Title must be between 1 and 200 characters"
|
| 153 |
+
]
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
### 401 Unauthorized
|
| 159 |
+
|
| 160 |
+
```json
|
| 161 |
+
{
|
| 162 |
+
"detail": "Missing or invalid authentication token",
|
| 163 |
+
"error_code": "UNAUTHORIZED"
|
| 164 |
+
}
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
### 404 Not Found
|
| 168 |
+
|
| 169 |
+
```json
|
| 170 |
+
{
|
| 171 |
+
"detail": "Task not found",
|
| 172 |
+
"error_code": "TASK_NOT_FOUND"
|
| 173 |
+
}
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
### 500 Internal Server Error
|
| 177 |
+
|
| 178 |
+
```json
|
| 179 |
+
{
|
| 180 |
+
"detail": "An unexpected error occurred",
|
| 181 |
+
"error_code": "INTERNAL_SERVER_ERROR"
|
| 182 |
+
}
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
## Validation Rules
|
| 186 |
+
|
| 187 |
+
### Title
|
| 188 |
+
- **Required**: Yes
|
| 189 |
+
- **Min Length**: 1 character
|
| 190 |
+
- **Max Length**: 200 characters
|
| 191 |
+
- **Type**: String
|
| 192 |
+
|
| 193 |
+
### Description
|
| 194 |
+
- **Required**: No
|
| 195 |
+
- **Max Length**: 1000 characters
|
| 196 |
+
- **Type**: String or null
|
| 197 |
+
|
| 198 |
+
### Completed
|
| 199 |
+
- **Required**: Yes (for PUT), No (for PATCH)
|
| 200 |
+
- **Type**: Boolean
|
| 201 |
+
- **Default**: false (on creation)
|
| 202 |
+
|
| 203 |
+
## Data Isolation
|
| 204 |
+
|
| 205 |
+
All endpoints enforce user data isolation:
|
| 206 |
+
- Tasks are filtered by authenticated user ID
|
| 207 |
+
- Users can only access their own tasks
|
| 208 |
+
- Attempting to access another user's task returns 404 (not 403, to avoid information leakage)
|
| 209 |
+
|
| 210 |
+
## Filtering and Sorting
|
| 211 |
+
|
| 212 |
+
### Filter by Completion Status
|
| 213 |
+
|
| 214 |
+
**Get active tasks**:
|
| 215 |
+
```
|
| 216 |
+
GET /api/tasks?completed=false
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
**Get completed tasks**:
|
| 220 |
+
```
|
| 221 |
+
GET /api/tasks?completed=true
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
**Get all tasks** (no filter):
|
| 225 |
+
```
|
| 226 |
+
GET /api/tasks
|
| 227 |
+
```
|
| 228 |
+
|
| 229 |
+
### Sort by Creation Date
|
| 230 |
+
|
| 231 |
+
**Newest first** (default):
|
| 232 |
+
```
|
| 233 |
+
GET /api/tasks?sort=created_at_desc
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
**Oldest first**:
|
| 237 |
+
```
|
| 238 |
+
GET /api/tasks?sort=created_at_asc
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
### Pagination
|
| 242 |
+
|
| 243 |
+
**First page** (50 tasks):
|
| 244 |
+
```
|
| 245 |
+
GET /api/tasks?limit=50&offset=0
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
**Second page**:
|
| 249 |
+
```
|
| 250 |
+
GET /api/tasks?limit=50&offset=50
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
## Testing the API
|
| 254 |
+
|
| 255 |
+
### Using cURL
|
| 256 |
+
|
| 257 |
+
**Create a task**:
|
| 258 |
+
```bash
|
| 259 |
+
curl -X POST http://localhost:8000/api/tasks \
|
| 260 |
+
-H "Content-Type: application/json" \
|
| 261 |
+
-H "Authorization: Bearer <jwt_token>" \
|
| 262 |
+
-d '{"title": "Buy groceries", "description": "Milk, eggs, bread"}'
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
**List tasks**:
|
| 266 |
+
```bash
|
| 267 |
+
curl -X GET http://localhost:8000/api/tasks \
|
| 268 |
+
-H "Authorization: Bearer <jwt_token>"
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
**Update a task**:
|
| 272 |
+
```bash
|
| 273 |
+
curl -X PUT http://localhost:8000/api/tasks/1 \
|
| 274 |
+
-H "Content-Type: application/json" \
|
| 275 |
+
-H "Authorization: Bearer <jwt_token>" \
|
| 276 |
+
-d '{"title": "Buy groceries and milk", "description": "Updated", "completed": false}'
|
| 277 |
+
```
|
| 278 |
+
|
| 279 |
+
**Toggle completion**:
|
| 280 |
+
```bash
|
| 281 |
+
curl -X PATCH http://localhost:8000/api/tasks/1 \
|
| 282 |
+
-H "Content-Type: application/json" \
|
| 283 |
+
-H "Authorization: Bearer <jwt_token>" \
|
| 284 |
+
-d '{"completed": true}'
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
**Delete a task**:
|
| 288 |
+
```bash
|
| 289 |
+
curl -X DELETE http://localhost:8000/api/tasks/1 \
|
| 290 |
+
-H "Authorization: Bearer <jwt_token>"
|
| 291 |
+
```
|
| 292 |
+
|
| 293 |
+
### Using Swagger UI
|
| 294 |
+
|
| 295 |
+
FastAPI automatically generates interactive API documentation:
|
| 296 |
+
|
| 297 |
+
1. Start the backend server: `uvicorn main:app --reload`
|
| 298 |
+
2. Open browser: `http://localhost:8000/docs`
|
| 299 |
+
3. Use the interactive interface to test endpoints
|
| 300 |
+
|
| 301 |
+
## Implementation Notes
|
| 302 |
+
|
| 303 |
+
### Backend (FastAPI)
|
| 304 |
+
|
| 305 |
+
The OpenAPI specification in `tasks-api.yaml` should be used to:
|
| 306 |
+
1. Validate implementation matches contract
|
| 307 |
+
2. Generate API documentation
|
| 308 |
+
3. Guide Pydantic schema creation
|
| 309 |
+
4. Define route handlers
|
| 310 |
+
|
| 311 |
+
### Frontend (Next.js)
|
| 312 |
+
|
| 313 |
+
The API contracts should be used to:
|
| 314 |
+
1. Create TypeScript interfaces for API responses
|
| 315 |
+
2. Implement API client functions in `lib/api.ts`
|
| 316 |
+
3. Handle error responses consistently
|
| 317 |
+
4. Validate request data before sending
|
| 318 |
+
|
| 319 |
+
### Testing
|
| 320 |
+
|
| 321 |
+
The contracts should be used to:
|
| 322 |
+
1. Write contract tests (verify API matches specification)
|
| 323 |
+
2. Generate test fixtures
|
| 324 |
+
3. Validate request/response formats
|
| 325 |
+
4. Test error handling
|
| 326 |
+
|
| 327 |
+
## Contract Validation
|
| 328 |
+
|
| 329 |
+
To validate the OpenAPI specification:
|
| 330 |
+
|
| 331 |
+
```bash
|
| 332 |
+
# Install validator
|
| 333 |
+
npm install -g @apidevtools/swagger-cli
|
| 334 |
+
|
| 335 |
+
# Validate specification
|
| 336 |
+
swagger-cli validate tasks-api.yaml
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
## Version History
|
| 340 |
+
|
| 341 |
+
- **v1.0.0** (2026-01-08): Initial API contract for Task CRUD operations
|
| 342 |
+
|
| 343 |
+
## References
|
| 344 |
+
|
| 345 |
+
- OpenAPI Specification: https://spec.openapis.org/oas/v3.1.0
|
| 346 |
+
- FastAPI OpenAPI Support: https://fastapi.tiangolo.com/tutorial/metadata/
|
| 347 |
+
- Pydantic Validation: https://docs.pydantic.dev/latest/
|
| 348 |
+
|
| 349 |
+
## Next Steps
|
| 350 |
+
|
| 351 |
+
1. Implement backend API routes following this contract
|
| 352 |
+
2. Create Pydantic schemas matching request/response formats
|
| 353 |
+
3. Implement frontend API client using TypeScript interfaces
|
| 354 |
+
4. Write contract tests to validate implementation
|
| 355 |
+
5. Generate API documentation from OpenAPI spec
|
specs/001-task-crud/contracts/tasks-api.yaml
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openapi: 3.1.0
|
| 2 |
+
info:
|
| 3 |
+
title: Task CRUD API
|
| 4 |
+
description: REST API for managing tasks in the Phase II Todo Web Application
|
| 5 |
+
version: 1.0.0
|
| 6 |
+
contact:
|
| 7 |
+
name: API Support
|
| 8 |
+
email: support@example.com
|
| 9 |
+
|
| 10 |
+
servers:
|
| 11 |
+
- url: http://localhost:8000
|
| 12 |
+
description: Local development server
|
| 13 |
+
- url: https://api.example.com
|
| 14 |
+
description: Production server
|
| 15 |
+
|
| 16 |
+
tags:
|
| 17 |
+
- name: tasks
|
| 18 |
+
description: Task management operations
|
| 19 |
+
|
| 20 |
+
paths:
|
| 21 |
+
/api/tasks:
|
| 22 |
+
get:
|
| 23 |
+
tags:
|
| 24 |
+
- tasks
|
| 25 |
+
summary: List all tasks for authenticated user
|
| 26 |
+
description: Retrieves all tasks belonging to the authenticated user with optional filtering and sorting
|
| 27 |
+
operationId: listTasks
|
| 28 |
+
parameters:
|
| 29 |
+
- name: completed
|
| 30 |
+
in: query
|
| 31 |
+
description: Filter by completion status
|
| 32 |
+
required: false
|
| 33 |
+
schema:
|
| 34 |
+
type: boolean
|
| 35 |
+
example: false
|
| 36 |
+
- name: sort
|
| 37 |
+
in: query
|
| 38 |
+
description: Sort order (created_at_desc, created_at_asc)
|
| 39 |
+
required: false
|
| 40 |
+
schema:
|
| 41 |
+
type: string
|
| 42 |
+
enum: [created_at_desc, created_at_asc]
|
| 43 |
+
default: created_at_desc
|
| 44 |
+
- name: limit
|
| 45 |
+
in: query
|
| 46 |
+
description: Maximum number of tasks to return
|
| 47 |
+
required: false
|
| 48 |
+
schema:
|
| 49 |
+
type: integer
|
| 50 |
+
minimum: 1
|
| 51 |
+
maximum: 100
|
| 52 |
+
default: 50
|
| 53 |
+
- name: offset
|
| 54 |
+
in: query
|
| 55 |
+
description: Number of tasks to skip (for pagination)
|
| 56 |
+
required: false
|
| 57 |
+
schema:
|
| 58 |
+
type: integer
|
| 59 |
+
minimum: 0
|
| 60 |
+
default: 0
|
| 61 |
+
responses:
|
| 62 |
+
'200':
|
| 63 |
+
description: Successful response with task list
|
| 64 |
+
content:
|
| 65 |
+
application/json:
|
| 66 |
+
schema:
|
| 67 |
+
$ref: '#/components/schemas/TaskListResponse'
|
| 68 |
+
'401':
|
| 69 |
+
description: Unauthorized - missing or invalid JWT token
|
| 70 |
+
content:
|
| 71 |
+
application/json:
|
| 72 |
+
schema:
|
| 73 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 74 |
+
'500':
|
| 75 |
+
description: Internal server error
|
| 76 |
+
content:
|
| 77 |
+
application/json:
|
| 78 |
+
schema:
|
| 79 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 80 |
+
security:
|
| 81 |
+
- bearerAuth: []
|
| 82 |
+
|
| 83 |
+
post:
|
| 84 |
+
tags:
|
| 85 |
+
- tasks
|
| 86 |
+
summary: Create a new task
|
| 87 |
+
description: Creates a new task for the authenticated user
|
| 88 |
+
operationId: createTask
|
| 89 |
+
requestBody:
|
| 90 |
+
required: true
|
| 91 |
+
content:
|
| 92 |
+
application/json:
|
| 93 |
+
schema:
|
| 94 |
+
$ref: '#/components/schemas/TaskCreate'
|
| 95 |
+
responses:
|
| 96 |
+
'201':
|
| 97 |
+
description: Task created successfully
|
| 98 |
+
content:
|
| 99 |
+
application/json:
|
| 100 |
+
schema:
|
| 101 |
+
$ref: '#/components/schemas/TaskResponse'
|
| 102 |
+
'400':
|
| 103 |
+
description: Bad request - validation error
|
| 104 |
+
content:
|
| 105 |
+
application/json:
|
| 106 |
+
schema:
|
| 107 |
+
$ref: '#/components/schemas/ValidationErrorResponse'
|
| 108 |
+
'401':
|
| 109 |
+
description: Unauthorized - missing or invalid JWT token
|
| 110 |
+
content:
|
| 111 |
+
application/json:
|
| 112 |
+
schema:
|
| 113 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 114 |
+
'500':
|
| 115 |
+
description: Internal server error
|
| 116 |
+
content:
|
| 117 |
+
application/json:
|
| 118 |
+
schema:
|
| 119 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 120 |
+
security:
|
| 121 |
+
- bearerAuth: []
|
| 122 |
+
|
| 123 |
+
/api/tasks/{task_id}:
|
| 124 |
+
get:
|
| 125 |
+
tags:
|
| 126 |
+
- tasks
|
| 127 |
+
summary: Get a specific task
|
| 128 |
+
description: Retrieves a single task by ID (must belong to authenticated user)
|
| 129 |
+
operationId: getTask
|
| 130 |
+
parameters:
|
| 131 |
+
- name: task_id
|
| 132 |
+
in: path
|
| 133 |
+
description: Task ID
|
| 134 |
+
required: true
|
| 135 |
+
schema:
|
| 136 |
+
type: integer
|
| 137 |
+
example: 1
|
| 138 |
+
responses:
|
| 139 |
+
'200':
|
| 140 |
+
description: Successful response with task details
|
| 141 |
+
content:
|
| 142 |
+
application/json:
|
| 143 |
+
schema:
|
| 144 |
+
$ref: '#/components/schemas/TaskResponse'
|
| 145 |
+
'401':
|
| 146 |
+
description: Unauthorized - missing or invalid JWT token
|
| 147 |
+
content:
|
| 148 |
+
application/json:
|
| 149 |
+
schema:
|
| 150 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 151 |
+
'404':
|
| 152 |
+
description: Task not found or does not belong to user
|
| 153 |
+
content:
|
| 154 |
+
application/json:
|
| 155 |
+
schema:
|
| 156 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 157 |
+
'500':
|
| 158 |
+
description: Internal server error
|
| 159 |
+
content:
|
| 160 |
+
application/json:
|
| 161 |
+
schema:
|
| 162 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 163 |
+
security:
|
| 164 |
+
- bearerAuth: []
|
| 165 |
+
|
| 166 |
+
put:
|
| 167 |
+
tags:
|
| 168 |
+
- tasks
|
| 169 |
+
summary: Update a task (full replacement)
|
| 170 |
+
description: Updates all fields of an existing task (must belong to authenticated user)
|
| 171 |
+
operationId: updateTask
|
| 172 |
+
parameters:
|
| 173 |
+
- name: task_id
|
| 174 |
+
in: path
|
| 175 |
+
description: Task ID
|
| 176 |
+
required: true
|
| 177 |
+
schema:
|
| 178 |
+
type: integer
|
| 179 |
+
example: 1
|
| 180 |
+
requestBody:
|
| 181 |
+
required: true
|
| 182 |
+
content:
|
| 183 |
+
application/json:
|
| 184 |
+
schema:
|
| 185 |
+
$ref: '#/components/schemas/TaskUpdate'
|
| 186 |
+
responses:
|
| 187 |
+
'200':
|
| 188 |
+
description: Task updated successfully
|
| 189 |
+
content:
|
| 190 |
+
application/json:
|
| 191 |
+
schema:
|
| 192 |
+
$ref: '#/components/schemas/TaskResponse'
|
| 193 |
+
'400':
|
| 194 |
+
description: Bad request - validation error
|
| 195 |
+
content:
|
| 196 |
+
application/json:
|
| 197 |
+
schema:
|
| 198 |
+
$ref: '#/components/schemas/ValidationErrorResponse'
|
| 199 |
+
'401':
|
| 200 |
+
description: Unauthorized - missing or invalid JWT token
|
| 201 |
+
content:
|
| 202 |
+
application/json:
|
| 203 |
+
schema:
|
| 204 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 205 |
+
'404':
|
| 206 |
+
description: Task not found or does not belong to user
|
| 207 |
+
content:
|
| 208 |
+
application/json:
|
| 209 |
+
schema:
|
| 210 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 211 |
+
'500':
|
| 212 |
+
description: Internal server error
|
| 213 |
+
content:
|
| 214 |
+
application/json:
|
| 215 |
+
schema:
|
| 216 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 217 |
+
security:
|
| 218 |
+
- bearerAuth: []
|
| 219 |
+
|
| 220 |
+
patch:
|
| 221 |
+
tags:
|
| 222 |
+
- tasks
|
| 223 |
+
summary: Partially update a task
|
| 224 |
+
description: Updates specific fields of an existing task (must belong to authenticated user)
|
| 225 |
+
operationId: patchTask
|
| 226 |
+
parameters:
|
| 227 |
+
- name: task_id
|
| 228 |
+
in: path
|
| 229 |
+
description: Task ID
|
| 230 |
+
required: true
|
| 231 |
+
schema:
|
| 232 |
+
type: integer
|
| 233 |
+
example: 1
|
| 234 |
+
requestBody:
|
| 235 |
+
required: true
|
| 236 |
+
content:
|
| 237 |
+
application/json:
|
| 238 |
+
schema:
|
| 239 |
+
$ref: '#/components/schemas/TaskPatch'
|
| 240 |
+
responses:
|
| 241 |
+
'200':
|
| 242 |
+
description: Task updated successfully
|
| 243 |
+
content:
|
| 244 |
+
application/json:
|
| 245 |
+
schema:
|
| 246 |
+
$ref: '#/components/schemas/TaskResponse'
|
| 247 |
+
'400':
|
| 248 |
+
description: Bad request - validation error
|
| 249 |
+
content:
|
| 250 |
+
application/json:
|
| 251 |
+
schema:
|
| 252 |
+
$ref: '#/components/schemas/ValidationErrorResponse'
|
| 253 |
+
'401':
|
| 254 |
+
description: Unauthorized - missing or invalid JWT token
|
| 255 |
+
content:
|
| 256 |
+
application/json:
|
| 257 |
+
schema:
|
| 258 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 259 |
+
'404':
|
| 260 |
+
description: Task not found or does not belong to user
|
| 261 |
+
content:
|
| 262 |
+
application/json:
|
| 263 |
+
schema:
|
| 264 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 265 |
+
'500':
|
| 266 |
+
description: Internal server error
|
| 267 |
+
content:
|
| 268 |
+
application/json:
|
| 269 |
+
schema:
|
| 270 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 271 |
+
security:
|
| 272 |
+
- bearerAuth: []
|
| 273 |
+
|
| 274 |
+
delete:
|
| 275 |
+
tags:
|
| 276 |
+
- tasks
|
| 277 |
+
summary: Delete a task
|
| 278 |
+
description: Permanently deletes a task (must belong to authenticated user)
|
| 279 |
+
operationId: deleteTask
|
| 280 |
+
parameters:
|
| 281 |
+
- name: task_id
|
| 282 |
+
in: path
|
| 283 |
+
description: Task ID
|
| 284 |
+
required: true
|
| 285 |
+
schema:
|
| 286 |
+
type: integer
|
| 287 |
+
example: 1
|
| 288 |
+
responses:
|
| 289 |
+
'204':
|
| 290 |
+
description: Task deleted successfully (no content)
|
| 291 |
+
'401':
|
| 292 |
+
description: Unauthorized - missing or invalid JWT token
|
| 293 |
+
content:
|
| 294 |
+
application/json:
|
| 295 |
+
schema:
|
| 296 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 297 |
+
'404':
|
| 298 |
+
description: Task not found or does not belong to user
|
| 299 |
+
content:
|
| 300 |
+
application/json:
|
| 301 |
+
schema:
|
| 302 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 303 |
+
'500':
|
| 304 |
+
description: Internal server error
|
| 305 |
+
content:
|
| 306 |
+
application/json:
|
| 307 |
+
schema:
|
| 308 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 309 |
+
security:
|
| 310 |
+
- bearerAuth: []
|
| 311 |
+
|
| 312 |
+
components:
|
| 313 |
+
securitySchemes:
|
| 314 |
+
bearerAuth:
|
| 315 |
+
type: http
|
| 316 |
+
scheme: bearer
|
| 317 |
+
bearerFormat: JWT
|
| 318 |
+
description: JWT token obtained from authentication endpoint (Spec 2)
|
| 319 |
+
|
| 320 |
+
schemas:
|
| 321 |
+
TaskCreate:
|
| 322 |
+
type: object
|
| 323 |
+
required:
|
| 324 |
+
- title
|
| 325 |
+
properties:
|
| 326 |
+
title:
|
| 327 |
+
type: string
|
| 328 |
+
minLength: 1
|
| 329 |
+
maxLength: 200
|
| 330 |
+
description: Task title (1-200 characters)
|
| 331 |
+
example: "Buy groceries"
|
| 332 |
+
description:
|
| 333 |
+
type: string
|
| 334 |
+
maxLength: 1000
|
| 335 |
+
nullable: true
|
| 336 |
+
description: Optional task description (0-1000 characters)
|
| 337 |
+
example: "Milk, eggs, bread"
|
| 338 |
+
|
| 339 |
+
TaskUpdate:
|
| 340 |
+
type: object
|
| 341 |
+
required:
|
| 342 |
+
- title
|
| 343 |
+
- completed
|
| 344 |
+
properties:
|
| 345 |
+
title:
|
| 346 |
+
type: string
|
| 347 |
+
minLength: 1
|
| 348 |
+
maxLength: 200
|
| 349 |
+
description: Task title (1-200 characters)
|
| 350 |
+
example: "Buy groceries and milk"
|
| 351 |
+
description:
|
| 352 |
+
type: string
|
| 353 |
+
maxLength: 1000
|
| 354 |
+
nullable: true
|
| 355 |
+
description: Optional task description (0-1000 characters)
|
| 356 |
+
example: "Updated description"
|
| 357 |
+
completed:
|
| 358 |
+
type: boolean
|
| 359 |
+
description: Task completion status
|
| 360 |
+
example: false
|
| 361 |
+
|
| 362 |
+
TaskPatch:
|
| 363 |
+
type: object
|
| 364 |
+
properties:
|
| 365 |
+
title:
|
| 366 |
+
type: string
|
| 367 |
+
minLength: 1
|
| 368 |
+
maxLength: 200
|
| 369 |
+
description: Task title (1-200 characters)
|
| 370 |
+
example: "Buy groceries and milk"
|
| 371 |
+
description:
|
| 372 |
+
type: string
|
| 373 |
+
maxLength: 1000
|
| 374 |
+
nullable: true
|
| 375 |
+
description: Optional task description (0-1000 characters)
|
| 376 |
+
example: "Updated description"
|
| 377 |
+
completed:
|
| 378 |
+
type: boolean
|
| 379 |
+
description: Task completion status
|
| 380 |
+
example: true
|
| 381 |
+
|
| 382 |
+
TaskResponse:
|
| 383 |
+
type: object
|
| 384 |
+
required:
|
| 385 |
+
- id
|
| 386 |
+
- user_id
|
| 387 |
+
- title
|
| 388 |
+
- completed
|
| 389 |
+
- created_at
|
| 390 |
+
- updated_at
|
| 391 |
+
properties:
|
| 392 |
+
id:
|
| 393 |
+
type: integer
|
| 394 |
+
description: Unique task identifier
|
| 395 |
+
example: 1
|
| 396 |
+
user_id:
|
| 397 |
+
type: integer
|
| 398 |
+
description: ID of the user who owns this task
|
| 399 |
+
example: 42
|
| 400 |
+
title:
|
| 401 |
+
type: string
|
| 402 |
+
description: Task title
|
| 403 |
+
example: "Buy groceries"
|
| 404 |
+
description:
|
| 405 |
+
type: string
|
| 406 |
+
nullable: true
|
| 407 |
+
description: Task description
|
| 408 |
+
example: "Milk, eggs, bread"
|
| 409 |
+
completed:
|
| 410 |
+
type: boolean
|
| 411 |
+
description: Task completion status
|
| 412 |
+
example: false
|
| 413 |
+
created_at:
|
| 414 |
+
type: string
|
| 415 |
+
format: date-time
|
| 416 |
+
description: Timestamp when task was created
|
| 417 |
+
example: "2026-01-08T10:00:00Z"
|
| 418 |
+
updated_at:
|
| 419 |
+
type: string
|
| 420 |
+
format: date-time
|
| 421 |
+
description: Timestamp when task was last updated
|
| 422 |
+
example: "2026-01-08T10:00:00Z"
|
| 423 |
+
|
| 424 |
+
TaskListResponse:
|
| 425 |
+
type: object
|
| 426 |
+
required:
|
| 427 |
+
- tasks
|
| 428 |
+
- total
|
| 429 |
+
properties:
|
| 430 |
+
tasks:
|
| 431 |
+
type: array
|
| 432 |
+
items:
|
| 433 |
+
$ref: '#/components/schemas/TaskResponse'
|
| 434 |
+
description: Array of tasks
|
| 435 |
+
total:
|
| 436 |
+
type: integer
|
| 437 |
+
description: Total number of tasks (before pagination)
|
| 438 |
+
example: 1
|
| 439 |
+
|
| 440 |
+
ErrorResponse:
|
| 441 |
+
type: object
|
| 442 |
+
required:
|
| 443 |
+
- detail
|
| 444 |
+
properties:
|
| 445 |
+
detail:
|
| 446 |
+
type: string
|
| 447 |
+
description: Human-readable error message
|
| 448 |
+
example: "Task not found"
|
| 449 |
+
error_code:
|
| 450 |
+
type: string
|
| 451 |
+
description: Machine-readable error code
|
| 452 |
+
example: "TASK_NOT_FOUND"
|
| 453 |
+
|
| 454 |
+
ValidationErrorResponse:
|
| 455 |
+
type: object
|
| 456 |
+
required:
|
| 457 |
+
- detail
|
| 458 |
+
properties:
|
| 459 |
+
detail:
|
| 460 |
+
type: string
|
| 461 |
+
description: Human-readable error message
|
| 462 |
+
example: "Validation error"
|
| 463 |
+
error_code:
|
| 464 |
+
type: string
|
| 465 |
+
description: Machine-readable error code
|
| 466 |
+
example: "VALIDATION_ERROR"
|
| 467 |
+
field_errors:
|
| 468 |
+
type: object
|
| 469 |
+
additionalProperties:
|
| 470 |
+
type: array
|
| 471 |
+
items:
|
| 472 |
+
type: string
|
| 473 |
+
description: Field-specific validation errors
|
| 474 |
+
example:
|
| 475 |
+
title:
|
| 476 |
+
- "Title must be between 1 and 200 characters"
|
specs/001-task-crud/data-model.md
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Model: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Feature**: Task CRUD Operations
|
| 4 |
+
**Date**: 2026-01-08
|
| 5 |
+
**Status**: Complete
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This document defines the database schema, entity relationships, and data validation rules for the Task CRUD feature. The data model supports multi-user task management with user data isolation.
|
| 10 |
+
|
| 11 |
+
## Entity Relationship Diagram
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
┌─────────────────┐ ┌─────────────────┐
|
| 15 |
+
│ User │ │ Task │
|
| 16 |
+
├─────────────────┤ ├─────────────────┤
|
| 17 |
+
│ id (PK) │◄────────│ id (PK) │
|
| 18 |
+
│ email │ 1:N │ user_id (FK) │
|
| 19 |
+
│ name │ │ title │
|
| 20 |
+
│ created_at │ │ description │
|
| 21 |
+
│ updated_at │ │ completed │
|
| 22 |
+
└─────────────────┘ │ created_at │
|
| 23 |
+
│ updated_at │
|
| 24 |
+
└─────────────────┘
|
| 25 |
+
|
| 26 |
+
Relationship: One User has many Tasks
|
| 27 |
+
One Task belongs to one User
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
## Entities
|
| 31 |
+
|
| 32 |
+
### Task Entity
|
| 33 |
+
|
| 34 |
+
**Purpose**: Represents a to-do item belonging to a specific user.
|
| 35 |
+
|
| 36 |
+
**Table Name**: `tasks`
|
| 37 |
+
|
| 38 |
+
**Columns**:
|
| 39 |
+
|
| 40 |
+
| Column Name | Type | Constraints | Description |
|
| 41 |
+
|--------------|--------------|--------------------------------|------------------------------------------------|
|
| 42 |
+
| id | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique task identifier |
|
| 43 |
+
| user_id | Integer | FOREIGN KEY (users.id), NOT NULL, INDEX | Owner of the task |
|
| 44 |
+
| title | String(200) | NOT NULL, LENGTH(1-200) | Task title (required) |
|
| 45 |
+
| description | String(1000) | NULLABLE, LENGTH(0-1000) | Optional task description |
|
| 46 |
+
| completed | Boolean | NOT NULL, DEFAULT FALSE, INDEX | Completion status |
|
| 47 |
+
| created_at | DateTime | NOT NULL, DEFAULT NOW() | Timestamp when task was created |
|
| 48 |
+
| updated_at | DateTime | NOT NULL, DEFAULT NOW(), ON UPDATE NOW() | Timestamp of last update |
|
| 49 |
+
|
| 50 |
+
**Indexes**:
|
| 51 |
+
- PRIMARY KEY on `id`
|
| 52 |
+
- INDEX on `user_id` (for filtering tasks by user)
|
| 53 |
+
- INDEX on `completed` (for filtering active/completed tasks)
|
| 54 |
+
- COMPOSITE INDEX on `(user_id, completed)` (for combined filtering)
|
| 55 |
+
- INDEX on `created_at` (for sorting by date)
|
| 56 |
+
|
| 57 |
+
**Constraints**:
|
| 58 |
+
- `user_id` FOREIGN KEY references `users(id)` ON DELETE CASCADE
|
| 59 |
+
- `title` must be between 1 and 200 characters
|
| 60 |
+
- `description` must be between 0 and 1000 characters (NULL allowed)
|
| 61 |
+
- `completed` must be boolean (true/false)
|
| 62 |
+
|
| 63 |
+
**SQLModel Definition**:
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
from sqlmodel import SQLModel, Field, Relationship
|
| 67 |
+
from datetime import datetime
|
| 68 |
+
from typing import Optional
|
| 69 |
+
|
| 70 |
+
class Task(SQLModel, table=True):
|
| 71 |
+
"""Task entity representing a to-do item."""
|
| 72 |
+
|
| 73 |
+
__tablename__ = "tasks"
|
| 74 |
+
|
| 75 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 76 |
+
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
| 77 |
+
title: str = Field(max_length=200, nullable=False)
|
| 78 |
+
description: Optional[str] = Field(default=None, max_length=1000)
|
| 79 |
+
completed: bool = Field(default=False, nullable=False, index=True)
|
| 80 |
+
created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 81 |
+
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 82 |
+
|
| 83 |
+
# Relationship (will be fully implemented in Spec 2)
|
| 84 |
+
# user: Optional["User"] = Relationship(back_populates="tasks")
|
| 85 |
+
|
| 86 |
+
class Config:
|
| 87 |
+
json_schema_extra = {
|
| 88 |
+
"example": {
|
| 89 |
+
"id": 1,
|
| 90 |
+
"user_id": 42,
|
| 91 |
+
"title": "Buy groceries",
|
| 92 |
+
"description": "Milk, eggs, bread",
|
| 93 |
+
"completed": False,
|
| 94 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 95 |
+
"updated_at": "2026-01-08T10:00:00Z"
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### User Entity (Stub)
|
| 101 |
+
|
| 102 |
+
**Purpose**: Represents an authenticated user (full implementation in Spec 2).
|
| 103 |
+
|
| 104 |
+
**Table Name**: `users`
|
| 105 |
+
|
| 106 |
+
**Columns** (minimal for Spec 1):
|
| 107 |
+
|
| 108 |
+
| Column Name | Type | Constraints | Description |
|
| 109 |
+
|--------------|--------------|--------------------------------|------------------------------------------------|
|
| 110 |
+
| id | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique user identifier |
|
| 111 |
+
| email | String(255) | UNIQUE, NOT NULL | User email address |
|
| 112 |
+
| name | String(100) | NOT NULL | User display name |
|
| 113 |
+
| created_at | DateTime | NOT NULL, DEFAULT NOW() | Timestamp when user was created |
|
| 114 |
+
| updated_at | DateTime | NOT NULL, DEFAULT NOW() | Timestamp of last update |
|
| 115 |
+
|
| 116 |
+
**SQLModel Definition** (stub for Spec 1):
|
| 117 |
+
|
| 118 |
+
```python
|
| 119 |
+
from sqlmodel import SQLModel, Field
|
| 120 |
+
from datetime import datetime
|
| 121 |
+
from typing import Optional
|
| 122 |
+
|
| 123 |
+
class User(SQLModel, table=True):
|
| 124 |
+
"""User entity (stub for authentication spec)."""
|
| 125 |
+
|
| 126 |
+
__tablename__ = "users"
|
| 127 |
+
|
| 128 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 129 |
+
email: str = Field(max_length=255, unique=True, nullable=False)
|
| 130 |
+
name: str = Field(max_length=100, nullable=False)
|
| 131 |
+
created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 132 |
+
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 133 |
+
|
| 134 |
+
# Relationship (will be fully implemented in Spec 2)
|
| 135 |
+
# tasks: List["Task"] = Relationship(back_populates="user")
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
## Pydantic Schemas (Request/Response)
|
| 139 |
+
|
| 140 |
+
### TaskCreate (Request)
|
| 141 |
+
|
| 142 |
+
**Purpose**: Validate task creation requests.
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
from pydantic import BaseModel, Field
|
| 146 |
+
from typing import Optional
|
| 147 |
+
|
| 148 |
+
class TaskCreate(BaseModel):
|
| 149 |
+
"""Schema for creating a new task."""
|
| 150 |
+
|
| 151 |
+
title: str = Field(
|
| 152 |
+
min_length=1,
|
| 153 |
+
max_length=200,
|
| 154 |
+
description="Task title (1-200 characters)"
|
| 155 |
+
)
|
| 156 |
+
description: Optional[str] = Field(
|
| 157 |
+
default=None,
|
| 158 |
+
max_length=1000,
|
| 159 |
+
description="Optional task description (0-1000 characters)"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
class Config:
|
| 163 |
+
json_schema_extra = {
|
| 164 |
+
"example": {
|
| 165 |
+
"title": "Buy groceries",
|
| 166 |
+
"description": "Milk, eggs, bread"
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
### TaskUpdate (Request)
|
| 172 |
+
|
| 173 |
+
**Purpose**: Validate task update requests (full replacement).
|
| 174 |
+
|
| 175 |
+
```python
|
| 176 |
+
class TaskUpdate(BaseModel):
|
| 177 |
+
"""Schema for updating an existing task."""
|
| 178 |
+
|
| 179 |
+
title: str = Field(
|
| 180 |
+
min_length=1,
|
| 181 |
+
max_length=200,
|
| 182 |
+
description="Task title (1-200 characters)"
|
| 183 |
+
)
|
| 184 |
+
description: Optional[str] = Field(
|
| 185 |
+
default=None,
|
| 186 |
+
max_length=1000,
|
| 187 |
+
description="Optional task description (0-1000 characters)"
|
| 188 |
+
)
|
| 189 |
+
completed: bool = Field(
|
| 190 |
+
description="Task completion status"
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
class Config:
|
| 194 |
+
json_schema_extra = {
|
| 195 |
+
"example": {
|
| 196 |
+
"title": "Buy groceries and milk",
|
| 197 |
+
"description": "Updated description",
|
| 198 |
+
"completed": False
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
### TaskPatch (Request)
|
| 204 |
+
|
| 205 |
+
**Purpose**: Validate partial task updates (e.g., toggle completion).
|
| 206 |
+
|
| 207 |
+
```python
|
| 208 |
+
class TaskPatch(BaseModel):
|
| 209 |
+
"""Schema for partially updating a task."""
|
| 210 |
+
|
| 211 |
+
title: Optional[str] = Field(
|
| 212 |
+
default=None,
|
| 213 |
+
min_length=1,
|
| 214 |
+
max_length=200,
|
| 215 |
+
description="Task title (1-200 characters)"
|
| 216 |
+
)
|
| 217 |
+
description: Optional[str] = Field(
|
| 218 |
+
default=None,
|
| 219 |
+
max_length=1000,
|
| 220 |
+
description="Optional task description (0-1000 characters)"
|
| 221 |
+
)
|
| 222 |
+
completed: Optional[bool] = Field(
|
| 223 |
+
default=None,
|
| 224 |
+
description="Task completion status"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
class Config:
|
| 228 |
+
json_schema_extra = {
|
| 229 |
+
"example": {
|
| 230 |
+
"completed": True
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
### TaskResponse (Response)
|
| 236 |
+
|
| 237 |
+
**Purpose**: Standardized task response format.
|
| 238 |
+
|
| 239 |
+
```python
|
| 240 |
+
from datetime import datetime
|
| 241 |
+
|
| 242 |
+
class TaskResponse(BaseModel):
|
| 243 |
+
"""Schema for task responses."""
|
| 244 |
+
|
| 245 |
+
id: int
|
| 246 |
+
user_id: int
|
| 247 |
+
title: str
|
| 248 |
+
description: Optional[str]
|
| 249 |
+
completed: bool
|
| 250 |
+
created_at: datetime
|
| 251 |
+
updated_at: datetime
|
| 252 |
+
|
| 253 |
+
class Config:
|
| 254 |
+
from_attributes = True # Enable ORM mode
|
| 255 |
+
json_schema_extra = {
|
| 256 |
+
"example": {
|
| 257 |
+
"id": 1,
|
| 258 |
+
"user_id": 42,
|
| 259 |
+
"title": "Buy groceries",
|
| 260 |
+
"description": "Milk, eggs, bread",
|
| 261 |
+
"completed": False,
|
| 262 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 263 |
+
"updated_at": "2026-01-08T10:00:00Z"
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
### TaskListResponse (Response)
|
| 269 |
+
|
| 270 |
+
**Purpose**: Response format for listing multiple tasks.
|
| 271 |
+
|
| 272 |
+
```python
|
| 273 |
+
from typing import List
|
| 274 |
+
|
| 275 |
+
class TaskListResponse(BaseModel):
|
| 276 |
+
"""Schema for task list responses."""
|
| 277 |
+
|
| 278 |
+
tasks: List[TaskResponse]
|
| 279 |
+
total: int
|
| 280 |
+
|
| 281 |
+
class Config:
|
| 282 |
+
json_schema_extra = {
|
| 283 |
+
"example": {
|
| 284 |
+
"tasks": [
|
| 285 |
+
{
|
| 286 |
+
"id": 1,
|
| 287 |
+
"user_id": 42,
|
| 288 |
+
"title": "Buy groceries",
|
| 289 |
+
"description": "Milk, eggs, bread",
|
| 290 |
+
"completed": False,
|
| 291 |
+
"created_at": "2026-01-08T10:00:00Z",
|
| 292 |
+
"updated_at": "2026-01-08T10:00:00Z"
|
| 293 |
+
}
|
| 294 |
+
],
|
| 295 |
+
"total": 1
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
## Data Validation Rules
|
| 301 |
+
|
| 302 |
+
### Title Validation
|
| 303 |
+
- **Required**: Yes
|
| 304 |
+
- **Min Length**: 1 character
|
| 305 |
+
- **Max Length**: 200 characters
|
| 306 |
+
- **Allowed Characters**: Any Unicode characters
|
| 307 |
+
- **Trimming**: Leading/trailing whitespace should be trimmed
|
| 308 |
+
- **Error Message**: "Title must be between 1 and 200 characters"
|
| 309 |
+
|
| 310 |
+
### Description Validation
|
| 311 |
+
- **Required**: No (optional)
|
| 312 |
+
- **Min Length**: 0 characters (empty string or NULL)
|
| 313 |
+
- **Max Length**: 1000 characters
|
| 314 |
+
- **Allowed Characters**: Any Unicode characters
|
| 315 |
+
- **Trimming**: Leading/trailing whitespace should be trimmed
|
| 316 |
+
- **Error Message**: "Description must be 1000 characters or less"
|
| 317 |
+
|
| 318 |
+
### Completed Validation
|
| 319 |
+
- **Required**: Yes (defaults to False on creation)
|
| 320 |
+
- **Type**: Boolean (true/false)
|
| 321 |
+
- **Error Message**: "Completed must be a boolean value"
|
| 322 |
+
|
| 323 |
+
### User ID Validation
|
| 324 |
+
- **Required**: Yes
|
| 325 |
+
- **Type**: Integer
|
| 326 |
+
- **Validation**: Must reference existing user in users table
|
| 327 |
+
- **Error Message**: "Invalid user ID"
|
| 328 |
+
|
| 329 |
+
## State Transitions
|
| 330 |
+
|
| 331 |
+
### Task Lifecycle
|
| 332 |
+
|
| 333 |
+
```
|
| 334 |
+
┌────────────┐
|
| 335 |
+
│ Created │ (completed = false)
|
| 336 |
+
│ (Initial) │
|
| 337 |
+
└──────┬──────┘
|
| 338 |
+
│
|
| 339 |
+
│ User marks complete
|
| 340 |
+
▼
|
| 341 |
+
┌─────────────┐
|
| 342 |
+
│ Completed │ (completed = true)
|
| 343 |
+
└──────┬──────┘
|
| 344 |
+
│
|
| 345 |
+
│ User marks incomplete
|
| 346 |
+
▼
|
| 347 |
+
┌─────────────┐
|
| 348 |
+
│ Active │ (completed = false)
|
| 349 |
+
└──────┬──────┘
|
| 350 |
+
│
|
| 351 |
+
│ User deletes
|
| 352 |
+
▼
|
| 353 |
+
┌─────────────┐
|
| 354 |
+
│ Deleted │ (removed from database)
|
| 355 |
+
└─────────────┘
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
**Valid Transitions**:
|
| 359 |
+
- Created → Completed (mark as done)
|
| 360 |
+
- Completed → Active (mark as not done)
|
| 361 |
+
- Any state → Deleted (remove task)
|
| 362 |
+
- Active → Updated (edit title/description)
|
| 363 |
+
- Completed → Updated (edit title/description)
|
| 364 |
+
|
| 365 |
+
## Database Migration
|
| 366 |
+
|
| 367 |
+
### Initial Migration (Alembic)
|
| 368 |
+
|
| 369 |
+
```python
|
| 370 |
+
"""Create tasks table
|
| 371 |
+
|
| 372 |
+
Revision ID: 001_create_tasks
|
| 373 |
+
Revises:
|
| 374 |
+
Create Date: 2026-01-08
|
| 375 |
+
|
| 376 |
+
"""
|
| 377 |
+
from alembic import op
|
| 378 |
+
import sqlalchemy as sa
|
| 379 |
+
from sqlalchemy.dialects import postgresql
|
| 380 |
+
|
| 381 |
+
# revision identifiers
|
| 382 |
+
revision = '001_create_tasks'
|
| 383 |
+
down_revision = None
|
| 384 |
+
branch_labels = None
|
| 385 |
+
depends_on = None
|
| 386 |
+
|
| 387 |
+
def upgrade():
|
| 388 |
+
# Create users table (stub for Spec 2)
|
| 389 |
+
op.create_table(
|
| 390 |
+
'users',
|
| 391 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
| 392 |
+
sa.Column('email', sa.String(length=255), nullable=False),
|
| 393 |
+
sa.Column('name', sa.String(length=100), nullable=False),
|
| 394 |
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
| 395 |
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
| 396 |
+
sa.PrimaryKeyConstraint('id'),
|
| 397 |
+
sa.UniqueConstraint('email')
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
# Create tasks table
|
| 401 |
+
op.create_table(
|
| 402 |
+
'tasks',
|
| 403 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
| 404 |
+
sa.Column('user_id', sa.Integer(), nullable=False),
|
| 405 |
+
sa.Column('title', sa.String(length=200), nullable=False),
|
| 406 |
+
sa.Column('description', sa.String(length=1000), nullable=True),
|
| 407 |
+
sa.Column('completed', sa.Boolean(), nullable=False, server_default='false'),
|
| 408 |
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
| 409 |
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
| 410 |
+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
| 411 |
+
sa.PrimaryKeyConstraint('id')
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
# Create indexes
|
| 415 |
+
op.create_index('ix_tasks_user_id', 'tasks', ['user_id'])
|
| 416 |
+
op.create_index('ix_tasks_completed', 'tasks', ['completed'])
|
| 417 |
+
op.create_index('ix_tasks_user_id_completed', 'tasks', ['user_id', 'completed'])
|
| 418 |
+
op.create_index('ix_tasks_created_at', 'tasks', ['created_at'])
|
| 419 |
+
|
| 420 |
+
def downgrade():
|
| 421 |
+
op.drop_index('ix_tasks_created_at', table_name='tasks')
|
| 422 |
+
op.drop_index('ix_tasks_user_id_completed', table_name='tasks')
|
| 423 |
+
op.drop_index('ix_tasks_completed', table_name='tasks')
|
| 424 |
+
op.drop_index('ix_tasks_user_id', table_name='tasks')
|
| 425 |
+
op.drop_table('tasks')
|
| 426 |
+
op.drop_table('users')
|
| 427 |
+
```
|
| 428 |
+
|
| 429 |
+
## Data Integrity Rules
|
| 430 |
+
|
| 431 |
+
### Foreign Key Constraints
|
| 432 |
+
- `tasks.user_id` MUST reference valid `users.id`
|
| 433 |
+
- ON DELETE CASCADE: Deleting a user deletes all their tasks
|
| 434 |
+
- Prevents orphaned tasks in database
|
| 435 |
+
|
| 436 |
+
### Uniqueness Constraints
|
| 437 |
+
- No uniqueness constraint on task titles (users can have duplicate titles)
|
| 438 |
+
- `users.email` must be unique (enforced in users table)
|
| 439 |
+
|
| 440 |
+
### NOT NULL Constraints
|
| 441 |
+
- `tasks.id`: Always required (auto-generated)
|
| 442 |
+
- `tasks.user_id`: Always required (task must belong to user)
|
| 443 |
+
- `tasks.title`: Always required (empty tasks not allowed)
|
| 444 |
+
- `tasks.completed`: Always required (defaults to false)
|
| 445 |
+
- `tasks.created_at`: Always required (auto-generated)
|
| 446 |
+
- `tasks.updated_at`: Always required (auto-updated)
|
| 447 |
+
|
| 448 |
+
### Check Constraints (Optional)
|
| 449 |
+
```sql
|
| 450 |
+
-- Ensure title is not empty after trimming
|
| 451 |
+
ALTER TABLE tasks ADD CONSTRAINT check_title_not_empty
|
| 452 |
+
CHECK (LENGTH(TRIM(title)) > 0);
|
| 453 |
+
|
| 454 |
+
-- Ensure description length if provided
|
| 455 |
+
ALTER TABLE tasks ADD CONSTRAINT check_description_length
|
| 456 |
+
CHECK (description IS NULL OR LENGTH(description) <= 1000);
|
| 457 |
+
```
|
| 458 |
+
|
| 459 |
+
## Query Patterns
|
| 460 |
+
|
| 461 |
+
### Common Queries
|
| 462 |
+
|
| 463 |
+
**Get all tasks for a user**:
|
| 464 |
+
```sql
|
| 465 |
+
SELECT * FROM tasks
|
| 466 |
+
WHERE user_id = ?
|
| 467 |
+
ORDER BY created_at DESC;
|
| 468 |
+
```
|
| 469 |
+
|
| 470 |
+
**Get active tasks for a user**:
|
| 471 |
+
```sql
|
| 472 |
+
SELECT * FROM tasks
|
| 473 |
+
WHERE user_id = ? AND completed = false
|
| 474 |
+
ORDER BY created_at DESC;
|
| 475 |
+
```
|
| 476 |
+
|
| 477 |
+
**Get completed tasks for a user**:
|
| 478 |
+
```sql
|
| 479 |
+
SELECT * FROM tasks
|
| 480 |
+
WHERE user_id = ? AND completed = true
|
| 481 |
+
ORDER BY created_at DESC;
|
| 482 |
+
```
|
| 483 |
+
|
| 484 |
+
**Get specific task with ownership check**:
|
| 485 |
+
```sql
|
| 486 |
+
SELECT * FROM tasks
|
| 487 |
+
WHERE id = ? AND user_id = ?;
|
| 488 |
+
```
|
| 489 |
+
|
| 490 |
+
**Update task with timestamp**:
|
| 491 |
+
```sql
|
| 492 |
+
UPDATE tasks
|
| 493 |
+
SET title = ?, description = ?, completed = ?, updated_at = NOW()
|
| 494 |
+
WHERE id = ? AND user_id = ?;
|
| 495 |
+
```
|
| 496 |
+
|
| 497 |
+
**Delete task with ownership check**:
|
| 498 |
+
```sql
|
| 499 |
+
DELETE FROM tasks
|
| 500 |
+
WHERE id = ? AND user_id = ?;
|
| 501 |
+
```
|
| 502 |
+
|
| 503 |
+
## Performance Considerations
|
| 504 |
+
|
| 505 |
+
### Index Usage
|
| 506 |
+
- `user_id` index: Used in all queries (data isolation)
|
| 507 |
+
- `completed` index: Used for filtering active/completed
|
| 508 |
+
- Composite `(user_id, completed)` index: Optimizes filtered queries
|
| 509 |
+
- `created_at` index: Used for sorting by date
|
| 510 |
+
|
| 511 |
+
### Query Optimization
|
| 512 |
+
- Always include `user_id` in WHERE clause (uses index)
|
| 513 |
+
- Limit result sets for large task lists (pagination)
|
| 514 |
+
- Use `SELECT *` sparingly in production (specify columns)
|
| 515 |
+
- Avoid N+1 queries (use joins if fetching related data)
|
| 516 |
+
|
| 517 |
+
### Connection Pooling
|
| 518 |
+
- Use Neon's built-in connection pooling
|
| 519 |
+
- Configure pool size based on expected concurrent users
|
| 520 |
+
- Reuse database sessions across requests
|
| 521 |
+
|
| 522 |
+
## Data Seeding (Development)
|
| 523 |
+
|
| 524 |
+
### Sample Data for Testing
|
| 525 |
+
|
| 526 |
+
```python
|
| 527 |
+
# Sample users
|
| 528 |
+
users = [
|
| 529 |
+
{"id": 1, "email": "alice@example.com", "name": "Alice"},
|
| 530 |
+
{"id": 2, "email": "bob@example.com", "name": "Bob"}
|
| 531 |
+
]
|
| 532 |
+
|
| 533 |
+
# Sample tasks
|
| 534 |
+
tasks = [
|
| 535 |
+
{
|
| 536 |
+
"user_id": 1,
|
| 537 |
+
"title": "Buy groceries",
|
| 538 |
+
"description": "Milk, eggs, bread",
|
| 539 |
+
"completed": False
|
| 540 |
+
},
|
| 541 |
+
{
|
| 542 |
+
"user_id": 1,
|
| 543 |
+
"title": "Finish project report",
|
| 544 |
+
"description": None,
|
| 545 |
+
"completed": True
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
"user_id": 2,
|
| 549 |
+
"title": "Call dentist",
|
| 550 |
+
"description": "Schedule appointment",
|
| 551 |
+
"completed": False
|
| 552 |
+
}
|
| 553 |
+
]
|
| 554 |
+
```
|
| 555 |
+
|
| 556 |
+
## Summary
|
| 557 |
+
|
| 558 |
+
The data model defines two entities: Task (full implementation) and User (stub for Spec 2). Tasks have a many-to-one relationship with Users, enforced via foreign key constraint. Validation rules ensure data integrity at both API and database layers. Indexes optimize query performance for filtering and sorting. The schema supports all functional requirements from the specification while maintaining user data isolation.
|
| 559 |
+
|
| 560 |
+
**Ready for**: API contract generation (contracts/).
|
specs/001-task-crud/plan.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Branch**: `001-task-crud` | **Date**: 2026-01-08 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
**Input**: Feature specification from `/specs/001-task-crud/spec.md`
|
| 5 |
+
|
| 6 |
+
**Note**: This template is filled in by the `/sp.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
| 7 |
+
|
| 8 |
+
## Summary
|
| 9 |
+
|
| 10 |
+
Implement core task management functionality enabling authenticated users to create, view, update, delete, and mark tasks as complete. The feature provides full CRUD operations with user data isolation, responsive UI, and REST API backend. Tasks include title (1-200 chars), description (0-1000 chars), completion status, and timestamps. Implementation follows a three-layer architecture: Neon PostgreSQL database with SQLModel ORM, FastAPI REST API with Pydantic validation, and Next.js 16+ frontend with Tailwind CSS. Authentication integration deferred to Spec 2.
|
| 11 |
+
|
| 12 |
+
## Technical Context
|
| 13 |
+
|
| 14 |
+
**Language/Version**: Python 3.11+ (backend), TypeScript 5.x (frontend), Node.js 18+ (frontend runtime)
|
| 15 |
+
**Primary Dependencies**: FastAPI 0.104+, SQLModel 0.0.14+, Pydantic 2.x, Next.js 16+, React 18+, Tailwind CSS 3.x
|
| 16 |
+
**Storage**: Neon Serverless PostgreSQL (cloud-hosted, connection pooling enabled)
|
| 17 |
+
**Testing**: pytest (backend unit/integration), Jest + React Testing Library (frontend), Playwright (E2E - optional)
|
| 18 |
+
**Target Platform**: Web application (Linux/Windows server for backend, modern browsers for frontend)
|
| 19 |
+
**Project Type**: Web application (monorepo with separate frontend/ and backend/ directories)
|
| 20 |
+
**Performance Goals**: Task list load <2s, task creation <10s, updates <1s, completion toggle <500ms, 100 concurrent users
|
| 21 |
+
**Constraints**: Stateless API design (JWT-based), responsive design (mobile/tablet/desktop), user data isolation (100%), 95% operation success rate
|
| 22 |
+
**Scale/Scope**: Initial target 100 concurrent users, 4 user stories (P1-P4), 15 functional requirements, 6 REST endpoints, 3 main frontend components
|
| 23 |
+
|
| 24 |
+
## Constitution Check
|
| 25 |
+
|
| 26 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 27 |
+
|
| 28 |
+
### ✅ I. User-Centric Functionality
|
| 29 |
+
- **Status**: PASS
|
| 30 |
+
- **Validation**: All 4 user stories directly serve end-users with clear task management value. Security enforced through user data isolation (FR-007). UX prioritized with responsive design (FR-015) and error handling (FR-014).
|
| 31 |
+
|
| 32 |
+
### ✅ II. Spec-Driven Development
|
| 33 |
+
- **Status**: PASS
|
| 34 |
+
- **Validation**: Implementation references `/specs/001-task-crud/spec.md`. All code generation via Claude Code. No manual coding permitted. Plan, data model, and contracts will be generated before implementation.
|
| 35 |
+
|
| 36 |
+
### ✅ III. Security & Data Privacy
|
| 37 |
+
- **Status**: PASS (with noted dependency)
|
| 38 |
+
- **Validation**: User data isolation enforced (FR-007). JWT authentication required (noted in Technical Constraints). User ID filtering on all queries.
|
| 39 |
+
- **Note**: JWT verification middleware implementation deferred to Spec 2 (authentication feature). Current spec assumes JWT token available and user ID extractable.
|
| 40 |
+
|
| 41 |
+
### ✅ IV. Scalable Architecture
|
| 42 |
+
- **Status**: PASS
|
| 43 |
+
- **Validation**: Stateless API design (JWT-based, no server sessions). Database indexes planned for user_id and completed fields. Frontend components designed as reusable (Task List, Task Form, Task Item). Clear client/server separation (Next.js App Router with server/client components).
|
| 44 |
+
|
| 45 |
+
### ✅ V. Maintainable & Consistent Code
|
| 46 |
+
- **Status**: PASS
|
| 47 |
+
- **Validation**: Standardized patterns: FastAPI + SQLModel (backend), Next.js 16+ App Router + Tailwind CSS (frontend). Modular architecture with clear layer boundaries (database/API/UI). Consistent naming conventions planned.
|
| 48 |
+
|
| 49 |
+
### ✅ API Compliance Standard
|
| 50 |
+
- **Status**: PASS
|
| 51 |
+
- **Validation**: REST endpoints follow spec. JSON request/response format. Pydantic validation for inputs. Standardized error responses with HTTP status codes. Contracts to be documented in `/specs/001-task-crud/contracts/`.
|
| 52 |
+
|
| 53 |
+
### ✅ Database Integrity Standard
|
| 54 |
+
- **Status**: PASS
|
| 55 |
+
- **Validation**: Neon PostgreSQL with SQLModel ORM. Foreign key relationship (Task belongs to User). Indexes for filtering. Timestamps auto-managed. Migrations to be tracked.
|
| 56 |
+
|
| 57 |
+
### ✅ Frontend Quality Standard
|
| 58 |
+
- **Status**: PASS
|
| 59 |
+
- **Validation**: Next.js 16+ App Router patterns. Server components by default, client components for interactivity. Responsive design (mobile/tablet/desktop). Tailwind CSS for all styling.
|
| 60 |
+
|
| 61 |
+
### ⚠️ Authentication Standard
|
| 62 |
+
- **Status**: DEFERRED
|
| 63 |
+
- **Validation**: Better Auth integration and JWT verification deferred to Spec 2. Current implementation assumes JWT token available in requests.
|
| 64 |
+
- **Mitigation**: Endpoints designed with JWT authorization in mind. User ID parameter in API routes prepared for token extraction.
|
| 65 |
+
|
| 66 |
+
### ✅ Spec Adherence Standard
|
| 67 |
+
- **Status**: PASS
|
| 68 |
+
- **Validation**: All implementation references `@specs/001-task-crud/`. Plan, data model, contracts, and tasks will be generated before code. No implementation without spec.
|
| 69 |
+
|
| 70 |
+
### Constitution Check Summary
|
| 71 |
+
|
| 72 |
+
**Overall Status**: ✅ PASS (1 deferred dependency noted)
|
| 73 |
+
|
| 74 |
+
**Violations**: None
|
| 75 |
+
|
| 76 |
+
**Deferred Items**:
|
| 77 |
+
- JWT authentication implementation (Spec 2 dependency - explicitly documented in spec.md Out of Scope section)
|
| 78 |
+
|
| 79 |
+
**Justification**: Authentication deferral is intentional and documented. Task CRUD feature can be implemented with placeholder user_id parameter, then integrated with JWT middleware in Spec 2.
|
| 80 |
+
|
| 81 |
+
## Project Structure
|
| 82 |
+
|
| 83 |
+
### Documentation (this feature)
|
| 84 |
+
|
| 85 |
+
```text
|
| 86 |
+
specs/001-task-crud/
|
| 87 |
+
├── spec.md # Feature specification (completed)
|
| 88 |
+
├── plan.md # This file (/sp.plan command output)
|
| 89 |
+
├── research.md # Phase 0 output (technology decisions)
|
| 90 |
+
├── data-model.md # Phase 1 output (database schema)
|
| 91 |
+
├── quickstart.md # Phase 1 output (setup instructions)
|
| 92 |
+
├── contracts/ # Phase 1 output (API contracts)
|
| 93 |
+
│ ├── tasks-api.yaml # OpenAPI specification for task endpoints
|
| 94 |
+
│ └── README.md # Contract documentation
|
| 95 |
+
├── checklists/ # Quality validation
|
| 96 |
+
│ └── requirements.md # Spec quality checklist (completed)
|
| 97 |
+
└── tasks.md # Phase 2 output (/sp.tasks command - NOT created by /sp.plan)
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### Source Code (repository root)
|
| 101 |
+
|
| 102 |
+
```text
|
| 103 |
+
backend/
|
| 104 |
+
├── src/
|
| 105 |
+
│ ├── models/
|
| 106 |
+
│ │ ├── __init__.py
|
| 107 |
+
│ │ ├── task.py # Task SQLModel definition
|
| 108 |
+
│ │ └── user.py # User model (stub for Spec 2)
|
| 109 |
+
│ ├── schemas/
|
| 110 |
+
│ │ ├── __init__.py
|
| 111 |
+
│ │ └── task.py # Pydantic request/response schemas
|
| 112 |
+
│ ├── api/
|
| 113 |
+
│ │ ├── __init__.py
|
| 114 |
+
│ │ ├── deps.py # Dependencies (DB session, auth stub)
|
| 115 |
+
│ │ └── routes/
|
| 116 |
+
│ │ ├── __init__.py
|
| 117 |
+
│ │ └── tasks.py # Task CRUD endpoints
|
| 118 |
+
│ ├── services/
|
| 119 |
+
│ │ ├── __init__.py
|
| 120 |
+
│ │ └── task_service.py # Business logic layer
|
| 121 |
+
│ ├── core/
|
| 122 |
+
│ │ ├── __init__.py
|
| 123 |
+
│ │ ├── config.py # Settings (database URL, etc.)
|
| 124 |
+
│ │ └── database.py # Database connection setup
|
| 125 |
+
│ └── main.py # FastAPI application entry point
|
| 126 |
+
├── tests/
|
| 127 |
+
│ ├── __init__.py
|
| 128 |
+
│ ├── conftest.py # Pytest fixtures
|
| 129 |
+
│ ├── test_task_api.py # API endpoint tests
|
| 130 |
+
│ └── test_task_service.py # Service layer tests
|
| 131 |
+
├── alembic/ # Database migrations
|
| 132 |
+
│ ├── versions/
|
| 133 |
+
│ └── env.py
|
| 134 |
+
├── requirements.txt # Python dependencies
|
| 135 |
+
├── .env.example # Environment variables template
|
| 136 |
+
└── README.md # Backend setup instructions
|
| 137 |
+
|
| 138 |
+
frontend/
|
| 139 |
+
├── src/
|
| 140 |
+
│ ├── app/
|
| 141 |
+
│ │ ├── layout.tsx # Root layout
|
| 142 |
+
│ │ ├── page.tsx # Home page (task list)
|
| 143 |
+
│ │ └── tasks/
|
| 144 |
+
│ │ └── [id]/
|
| 145 |
+
│ │ └── page.tsx # Task detail page (optional)
|
| 146 |
+
│ ├── components/
|
| 147 |
+
│ │ ├── tasks/
|
| 148 |
+
│ │ │ ├── TaskList.tsx # Server component - displays tasks
|
| 149 |
+
│ │ │ ├── TaskItem.tsx # Client component - interactive task
|
| 150 |
+
│ │ │ ├── TaskForm.tsx # Client component - create/edit form
|
| 151 |
+
│ │ │ └── TaskFilters.tsx # Client component - filter/sort controls
|
| 152 |
+
│ │ └── ui/
|
| 153 |
+
│ │ ├── Button.tsx # Reusable button component
|
| 154 |
+
│ │ ├── Input.tsx # Reusable input component
|
| 155 |
+
│ │ └── Checkbox.tsx # Reusable checkbox component
|
| 156 |
+
│ ├── lib/
|
| 157 |
+
│ │ ├── api.ts # API client functions
|
| 158 |
+
│ │ ├── types.ts # TypeScript type definitions
|
| 159 |
+
│ │ └── utils.ts # Utility functions
|
| 160 |
+
│ └── styles/
|
| 161 |
+
│ └── globals.css # Global styles (Tailwind imports)
|
| 162 |
+
├── public/
|
| 163 |
+
│ └── assets/ # Static assets
|
| 164 |
+
├── tests/
|
| 165 |
+
│ └── components/ # Component tests
|
| 166 |
+
├── package.json # Node dependencies
|
| 167 |
+
├── tsconfig.json # TypeScript configuration
|
| 168 |
+
├── tailwind.config.ts # Tailwind CSS configuration
|
| 169 |
+
├── next.config.js # Next.js configuration
|
| 170 |
+
├── .env.local.example # Environment variables template
|
| 171 |
+
└── README.md # Frontend setup instructions
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
**Structure Decision**: Web application monorepo structure selected based on:
|
| 175 |
+
- Feature requires both frontend UI and backend API
|
| 176 |
+
- Next.js 16+ App Router for frontend (server/client component separation)
|
| 177 |
+
- FastAPI for backend REST API
|
| 178 |
+
- Clear separation of concerns: database models, API routes, business logic, UI components
|
| 179 |
+
- Modular organization enables independent development and testing of layers
|
| 180 |
+
- Aligns with constitution's Maintainable & Consistent Code principle
|
| 181 |
+
|
| 182 |
+
## Complexity Tracking
|
| 183 |
+
|
| 184 |
+
> **Fill ONLY if Constitution Check has violations that must be justified**
|
| 185 |
+
|
| 186 |
+
No violations detected. Complexity tracking not required.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## Phase 0: Research & Technology Decisions
|
| 191 |
+
|
| 192 |
+
**Status**: ✅ Complete
|
| 193 |
+
|
| 194 |
+
**Objective**: Resolve all technical unknowns and establish architectural patterns.
|
| 195 |
+
|
| 196 |
+
**Output**: [research.md](./research.md)
|
| 197 |
+
|
| 198 |
+
### Key Decisions Made
|
| 199 |
+
|
| 200 |
+
1. **Backend Framework**: FastAPI 0.104+ with SQLModel ORM
|
| 201 |
+
- Rationale: Automatic OpenAPI docs, async support, Pydantic v2 integration
|
| 202 |
+
- Alternatives considered: Django REST Framework, Flask
|
| 203 |
+
|
| 204 |
+
2. **Database**: Neon Serverless PostgreSQL
|
| 205 |
+
- Rationale: Serverless scaling, built-in connection pooling, ACID compliance
|
| 206 |
+
- Alternatives considered: Traditional PostgreSQL, MySQL, MongoDB
|
| 207 |
+
|
| 208 |
+
3. **Frontend Framework**: Next.js 16+ (App Router) with TypeScript and Tailwind CSS
|
| 209 |
+
- Rationale: Server/client component separation, built-in optimization, type safety
|
| 210 |
+
- Alternatives considered: Next.js Pages Router, Create React App, Vue.js
|
| 211 |
+
|
| 212 |
+
4. **Architecture Pattern**: Three-layer architecture (Database → API → UI)
|
| 213 |
+
- Clear separation of concerns enables independent testing and development
|
| 214 |
+
- Service layer encapsulates business logic
|
| 215 |
+
|
| 216 |
+
5. **RESTful API Design**: Resource-based URLs with standard HTTP methods
|
| 217 |
+
- GET /api/tasks, POST /api/tasks, PUT /api/tasks/{id}, etc.
|
| 218 |
+
- Aligns with API Compliance standard
|
| 219 |
+
|
| 220 |
+
6. **Data Validation**: Multi-layer validation (Pydantic → SQLModel → Frontend)
|
| 221 |
+
- Defense in depth prevents bad data at multiple levels
|
| 222 |
+
|
| 223 |
+
7. **User Data Isolation**: Filter all queries by authenticated user ID
|
| 224 |
+
- Enforces 100% data isolation success criterion
|
| 225 |
+
|
| 226 |
+
8. **Performance Optimization**: Database indexing + Server Components
|
| 227 |
+
- Indexes on user_id, completed, created_at
|
| 228 |
+
- Server Components reduce JavaScript bundle size
|
| 229 |
+
|
| 230 |
+
9. **Error Handling**: Consistent error response format across all layers
|
| 231 |
+
- Standard HTTP status codes (200, 201, 400, 401, 404, 500)
|
| 232 |
+
|
| 233 |
+
10. **Testing Strategy**: Unit tests (services) + Integration tests (API) + Component tests (UI)
|
| 234 |
+
|
| 235 |
+
### Dependencies and Versions
|
| 236 |
+
|
| 237 |
+
**Backend (Python 3.11+)**:
|
| 238 |
+
- fastapi==0.104.1
|
| 239 |
+
- sqlmodel==0.0.14
|
| 240 |
+
- pydantic==2.5.0
|
| 241 |
+
- uvicorn[standard]==0.24.0
|
| 242 |
+
- alembic==1.13.0
|
| 243 |
+
- psycopg2-binary==2.9.9
|
| 244 |
+
|
| 245 |
+
**Frontend (Node.js 18+)**:
|
| 246 |
+
- next: ^16.0.0
|
| 247 |
+
- react: ^18.2.0
|
| 248 |
+
- typescript: ^5.3.0
|
| 249 |
+
- tailwindcss: ^3.4.0
|
| 250 |
+
|
| 251 |
+
### Deferred to Spec 2
|
| 252 |
+
|
| 253 |
+
- JWT token generation and validation
|
| 254 |
+
- Better Auth integration
|
| 255 |
+
- User registration and login flows
|
| 256 |
+
- Token refresh mechanism
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## Phase 1: Design & Contracts
|
| 261 |
+
|
| 262 |
+
**Status**: ✅ Complete
|
| 263 |
+
|
| 264 |
+
**Objective**: Define data model, API contracts, and setup instructions.
|
| 265 |
+
|
| 266 |
+
**Outputs**:
|
| 267 |
+
- [data-model.md](./data-model.md) - Database schema and entity definitions
|
| 268 |
+
- [contracts/tasks-api.yaml](./contracts/tasks-api.yaml) - OpenAPI 3.1.0 specification
|
| 269 |
+
- [contracts/README.md](./contracts/README.md) - API contract documentation
|
| 270 |
+
- [quickstart.md](./quickstart.md) - Setup and development guide
|
| 271 |
+
|
| 272 |
+
### Data Model Summary
|
| 273 |
+
|
| 274 |
+
**Task Entity**:
|
| 275 |
+
- Table: `tasks`
|
| 276 |
+
- Columns: id, user_id (FK), title, description, completed, created_at, updated_at
|
| 277 |
+
- Indexes: user_id, completed, (user_id, completed), created_at
|
| 278 |
+
- Constraints: Foreign key to users.id, title length 1-200, description length 0-1000
|
| 279 |
+
|
| 280 |
+
**User Entity** (stub for Spec 2):
|
| 281 |
+
- Table: `users`
|
| 282 |
+
- Columns: id, email, name, created_at, updated_at
|
| 283 |
+
- Minimal implementation for Task foreign key relationship
|
| 284 |
+
|
| 285 |
+
**Pydantic Schemas**:
|
| 286 |
+
- TaskCreate: Request schema for creating tasks
|
| 287 |
+
- TaskUpdate: Request schema for full task updates
|
| 288 |
+
- TaskPatch: Request schema for partial updates
|
| 289 |
+
- TaskResponse: Response schema for single task
|
| 290 |
+
- TaskListResponse: Response schema for task lists
|
| 291 |
+
|
| 292 |
+
**Validation Rules**:
|
| 293 |
+
- Title: Required, 1-200 characters
|
| 294 |
+
- Description: Optional, 0-1000 characters
|
| 295 |
+
- Completed: Boolean, defaults to false
|
| 296 |
+
|
| 297 |
+
### API Contracts Summary
|
| 298 |
+
|
| 299 |
+
**6 REST Endpoints**:
|
| 300 |
+
1. GET /api/tasks - List tasks (with filtering and sorting)
|
| 301 |
+
2. POST /api/tasks - Create task
|
| 302 |
+
3. GET /api/tasks/{task_id} - Get specific task
|
| 303 |
+
4. PUT /api/tasks/{task_id} - Update task (full replacement)
|
| 304 |
+
5. PATCH /api/tasks/{task_id} - Partial update (e.g., toggle completion)
|
| 305 |
+
6. DELETE /api/tasks/{task_id} - Delete task
|
| 306 |
+
|
| 307 |
+
**Authentication**: All endpoints require JWT Bearer token (Spec 2)
|
| 308 |
+
|
| 309 |
+
**Query Parameters**:
|
| 310 |
+
- completed (boolean): Filter by completion status
|
| 311 |
+
- sort (string): Sort order (created_at_desc, created_at_asc)
|
| 312 |
+
- limit (integer): Pagination limit (default 50, max 100)
|
| 313 |
+
- offset (integer): Pagination offset (default 0)
|
| 314 |
+
|
| 315 |
+
**Error Responses**:
|
| 316 |
+
- 400: Validation error with field-specific messages
|
| 317 |
+
- 401: Unauthorized (missing/invalid JWT)
|
| 318 |
+
- 404: Task not found or doesn't belong to user
|
| 319 |
+
- 500: Internal server error
|
| 320 |
+
|
| 321 |
+
### Quickstart Guide Summary
|
| 322 |
+
|
| 323 |
+
**Backend Setup**:
|
| 324 |
+
1. Create Python virtual environment
|
| 325 |
+
2. Install dependencies (requirements.txt)
|
| 326 |
+
3. Configure .env with DATABASE_URL
|
| 327 |
+
4. Run Alembic migrations
|
| 328 |
+
5. Start uvicorn server on port 8000
|
| 329 |
+
|
| 330 |
+
**Frontend Setup**:
|
| 331 |
+
1. Install Node dependencies (npm install)
|
| 332 |
+
2. Configure .env.local with NEXT_PUBLIC_API_URL
|
| 333 |
+
3. Configure Tailwind CSS
|
| 334 |
+
4. Start Next.js dev server on port 3000
|
| 335 |
+
|
| 336 |
+
**Development Workflow**:
|
| 337 |
+
- Run backend and frontend concurrently in separate terminals
|
| 338 |
+
- Backend auto-reloads on file changes
|
| 339 |
+
- Frontend hot-reloads on file changes
|
| 340 |
+
- Use Swagger UI at http://localhost:8000/docs for API testing
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
## Phase 2: Task Generation
|
| 345 |
+
|
| 346 |
+
**Status**: ⏳ Pending
|
| 347 |
+
|
| 348 |
+
**Objective**: Generate actionable task list organized by user story.
|
| 349 |
+
|
| 350 |
+
**Command**: `/sp.tasks` (to be run after this plan is complete)
|
| 351 |
+
|
| 352 |
+
**Expected Output**: [tasks.md](./tasks.md)
|
| 353 |
+
|
| 354 |
+
**Task Organization**:
|
| 355 |
+
- Phase 1: Setup (project initialization)
|
| 356 |
+
- Phase 2: Foundational (database, core infrastructure)
|
| 357 |
+
- Phase 3: User Story 1 - View and Create Tasks (P1 - MVP)
|
| 358 |
+
- Phase 4: User Story 2 - Update and Complete Tasks (P2)
|
| 359 |
+
- Phase 5: User Story 3 - Delete Tasks (P3)
|
| 360 |
+
- Phase 6: User Story 4 - Filter and Sort Tasks (P4)
|
| 361 |
+
- Phase N: Polish & Cross-Cutting Concerns
|
| 362 |
+
|
| 363 |
+
**Task Format**: `[ID] [P?] [Story] Description`
|
| 364 |
+
- [P] indicates tasks that can run in parallel
|
| 365 |
+
- [Story] indicates which user story the task belongs to (US1, US2, US3, US4)
|
| 366 |
+
|
| 367 |
+
---
|
| 368 |
+
|
| 369 |
+
## Implementation Readiness
|
| 370 |
+
|
| 371 |
+
### Pre-Implementation Checklist
|
| 372 |
+
|
| 373 |
+
- [x] Feature specification complete (spec.md)
|
| 374 |
+
- [x] Constitution check passed
|
| 375 |
+
- [x] Technical context defined
|
| 376 |
+
- [x] Research complete (technology decisions made)
|
| 377 |
+
- [x] Data model designed (database schema)
|
| 378 |
+
- [x] API contracts defined (OpenAPI specification)
|
| 379 |
+
- [x] Quickstart guide created (setup instructions)
|
| 380 |
+
- [ ] Task list generated (run `/sp.tasks`)
|
| 381 |
+
- [ ] Implementation executed (run `/sp.implement`)
|
| 382 |
+
|
| 383 |
+
### Architecture Decision Records (ADRs)
|
| 384 |
+
|
| 385 |
+
**Significant Decisions Requiring ADR Documentation**:
|
| 386 |
+
|
| 387 |
+
1. **Three-Layer Architecture** (Database → API → UI)
|
| 388 |
+
- Impact: Long-term maintainability and testability
|
| 389 |
+
- Alternatives: Monolithic architecture, microservices
|
| 390 |
+
- Scope: Cross-cutting, influences all development
|
| 391 |
+
|
| 392 |
+
2. **Next.js App Router vs Pages Router**
|
| 393 |
+
- Impact: Frontend performance and development patterns
|
| 394 |
+
- Alternatives: Pages Router, other frameworks
|
| 395 |
+
- Scope: All frontend development
|
| 396 |
+
|
| 397 |
+
3. **SQLModel vs Pure SQLAlchemy**
|
| 398 |
+
- Impact: Backend code structure and type safety
|
| 399 |
+
- Alternatives: Pure SQLAlchemy, Django ORM
|
| 400 |
+
- Scope: All database interactions
|
| 401 |
+
|
| 402 |
+
**Recommendation**: Run `/sp.adr` after implementation to document these decisions.
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
## Risk Analysis
|
| 407 |
+
|
| 408 |
+
### Technical Risks
|
| 409 |
+
|
| 410 |
+
| Risk | Likelihood | Impact | Mitigation |
|
| 411 |
+
|------|------------|--------|------------|
|
| 412 |
+
| Database connection pooling issues with Neon | Medium | High | Use Neon's built-in pooling, monitor connections |
|
| 413 |
+
| JWT authentication integration complexity | Low | Medium | Well-documented in Spec 2, standard patterns |
|
| 414 |
+
| Next.js 16+ App Router learning curve | Medium | Low | Extensive documentation, clear server/client separation |
|
| 415 |
+
| Data isolation bugs (user accessing others' tasks) | Low | Critical | Comprehensive testing, query-level filtering |
|
| 416 |
+
| Performance degradation with large task lists | Medium | Medium | Implement pagination, database indexes |
|
| 417 |
+
|
| 418 |
+
### Mitigation Strategies
|
| 419 |
+
|
| 420 |
+
1. **Database Connection Issues**:
|
| 421 |
+
- Use connection pooling from day one
|
| 422 |
+
- Monitor connection metrics in development
|
| 423 |
+
- Test with concurrent users early
|
| 424 |
+
|
| 425 |
+
2. **Authentication Integration**:
|
| 426 |
+
- Design endpoints with JWT in mind (user_id parameter)
|
| 427 |
+
- Defer full implementation to Spec 2
|
| 428 |
+
- Use placeholder authentication for testing
|
| 429 |
+
|
| 430 |
+
3. **App Router Complexity**:
|
| 431 |
+
- Follow Next.js best practices (server components by default)
|
| 432 |
+
- Use client components only for interactivity
|
| 433 |
+
- Reference official documentation
|
| 434 |
+
|
| 435 |
+
4. **Data Isolation**:
|
| 436 |
+
- Always include user_id in WHERE clauses
|
| 437 |
+
- Write comprehensive tests for ownership checks
|
| 438 |
+
- Code review all database queries
|
| 439 |
+
|
| 440 |
+
5. **Performance**:
|
| 441 |
+
- Implement pagination from the start
|
| 442 |
+
- Create database indexes before testing
|
| 443 |
+
- Monitor query performance in development
|
| 444 |
+
|
| 445 |
+
---
|
| 446 |
+
|
| 447 |
+
## Success Metrics
|
| 448 |
+
|
| 449 |
+
### Implementation Success Criteria
|
| 450 |
+
|
| 451 |
+
From spec.md Success Criteria section:
|
| 452 |
+
|
| 453 |
+
**Performance**:
|
| 454 |
+
- [ ] Task creation completes in <10 seconds
|
| 455 |
+
- [ ] Task list loads in <2 seconds
|
| 456 |
+
- [ ] Task updates reflect in <1 second
|
| 457 |
+
- [ ] Completion toggle responds in <500ms
|
| 458 |
+
|
| 459 |
+
**Data Integrity**:
|
| 460 |
+
- [ ] 100% user data isolation (no cross-user access)
|
| 461 |
+
- [ ] 100% data persistence (tasks survive page refresh)
|
| 462 |
+
- [ ] 95% operation success rate on first attempt
|
| 463 |
+
|
| 464 |
+
**Scalability**:
|
| 465 |
+
- [ ] System handles 100 concurrent users without errors
|
| 466 |
+
|
| 467 |
+
**User Experience**:
|
| 468 |
+
- [ ] Intuitive UI (no documentation needed)
|
| 469 |
+
- [ ] Clear error messages
|
| 470 |
+
- [ ] Immediate visual feedback for all actions
|
| 471 |
+
- [ ] Visual distinction between completed/incomplete tasks
|
| 472 |
+
|
| 473 |
+
**Technical**:
|
| 474 |
+
- [ ] All endpoints follow OpenAPI specification
|
| 475 |
+
- [ ] All database queries use proper indexes
|
| 476 |
+
- [ ] Frontend uses Server Components appropriately
|
| 477 |
+
- [ ] Code passes linting and formatting checks
|
| 478 |
+
|
| 479 |
+
### Validation Plan
|
| 480 |
+
|
| 481 |
+
1. **Unit Tests**: Service layer business logic
|
| 482 |
+
2. **Integration Tests**: API endpoints with test database
|
| 483 |
+
3. **Component Tests**: React components in isolation
|
| 484 |
+
4. **Manual Testing**: Full user flows in browser
|
| 485 |
+
5. **Performance Testing**: Load testing with 100 concurrent users
|
| 486 |
+
|
| 487 |
+
---
|
| 488 |
+
|
| 489 |
+
## Next Steps
|
| 490 |
+
|
| 491 |
+
1. **Generate Task List**: Run `/sp.tasks` to create tasks.md
|
| 492 |
+
2. **Review Tasks**: Validate task breakdown matches user stories
|
| 493 |
+
3. **Execute Implementation**: Run `/sp.implement` to build the feature
|
| 494 |
+
4. **Test Implementation**: Verify all success criteria met
|
| 495 |
+
5. **Document ADRs**: Run `/sp.adr` for architectural decisions
|
| 496 |
+
6. **Create Pull Request**: Commit changes and create PR for review
|
| 497 |
+
|
| 498 |
+
---
|
| 499 |
+
|
| 500 |
+
## References
|
| 501 |
+
|
| 502 |
+
- **Feature Specification**: [spec.md](./spec.md)
|
| 503 |
+
- **Research Document**: [research.md](./research.md)
|
| 504 |
+
- **Data Model**: [data-model.md](./data-model.md)
|
| 505 |
+
- **API Contracts**: [contracts/tasks-api.yaml](./contracts/tasks-api.yaml)
|
| 506 |
+
- **Quickstart Guide**: [quickstart.md](./quickstart.md)
|
| 507 |
+
- **Project Constitution**: [.specify/memory/constitution.md](../../.specify/memory/constitution.md)
|
| 508 |
+
|
| 509 |
+
---
|
| 510 |
+
|
| 511 |
+
**Plan Status**: ✅ Complete - Ready for task generation (`/sp.tasks`)
|
| 512 |
+
|
| 513 |
+
**Branch**: `001-task-crud`
|
| 514 |
+
|
| 515 |
+
**Last Updated**: 2026-01-08
|
specs/001-task-crud/quickstart.md
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart Guide: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Feature**: Task CRUD Operations
|
| 4 |
+
**Date**: 2026-01-08
|
| 5 |
+
**Status**: Complete
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This guide provides step-by-step instructions for setting up and running the Task CRUD feature locally. Follow these instructions to get the backend API and frontend UI running on your development machine.
|
| 10 |
+
|
| 11 |
+
## Prerequisites
|
| 12 |
+
|
| 13 |
+
### Required Software
|
| 14 |
+
|
| 15 |
+
- **Python**: 3.11 or higher
|
| 16 |
+
- **Node.js**: 18 or higher
|
| 17 |
+
- **PostgreSQL**: Neon Serverless PostgreSQL account (or local PostgreSQL 14+)
|
| 18 |
+
- **Git**: For version control
|
| 19 |
+
- **Code Editor**: VS Code recommended
|
| 20 |
+
|
| 21 |
+
### Accounts Needed
|
| 22 |
+
|
| 23 |
+
- **Neon Account**: Sign up at https://neon.tech for serverless PostgreSQL
|
| 24 |
+
- **GitHub Account**: For version control (optional)
|
| 25 |
+
|
| 26 |
+
## Project Structure
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
phase-2-full-stack-web-app/
|
| 30 |
+
├── backend/ # FastAPI backend
|
| 31 |
+
├── frontend/ # Next.js frontend
|
| 32 |
+
└── specs/ # Feature specifications
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Backend Setup
|
| 36 |
+
|
| 37 |
+
### 1. Navigate to Backend Directory
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
cd backend
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 2. Create Python Virtual Environment
|
| 44 |
+
|
| 45 |
+
**Windows**:
|
| 46 |
+
```bash
|
| 47 |
+
python -m venv venv
|
| 48 |
+
venv\Scripts\activate
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**macOS/Linux**:
|
| 52 |
+
```bash
|
| 53 |
+
python3 -m venv venv
|
| 54 |
+
source venv/bin/activate
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### 3. Install Dependencies
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
pip install -r requirements.txt
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
**requirements.txt** should contain:
|
| 64 |
+
```
|
| 65 |
+
fastapi==0.104.1
|
| 66 |
+
sqlmodel==0.0.14
|
| 67 |
+
pydantic==2.5.0
|
| 68 |
+
uvicorn[standard]==0.24.0
|
| 69 |
+
alembic==1.13.0
|
| 70 |
+
psycopg2-binary==2.9.9
|
| 71 |
+
python-dotenv==1.0.0
|
| 72 |
+
pytest==7.4.3
|
| 73 |
+
httpx==0.25.2
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 4. Configure Environment Variables
|
| 77 |
+
|
| 78 |
+
Create `.env` file in `backend/` directory:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
cp .env.example .env
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Edit `.env` with your database credentials:
|
| 85 |
+
|
| 86 |
+
```env
|
| 87 |
+
# Database Configuration
|
| 88 |
+
DATABASE_URL=postgresql://user:password@host/database
|
| 89 |
+
|
| 90 |
+
# Neon PostgreSQL Example:
|
| 91 |
+
# DATABASE_URL=postgresql://user:password@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require
|
| 92 |
+
|
| 93 |
+
# Application Settings
|
| 94 |
+
APP_NAME=Task CRUD API
|
| 95 |
+
DEBUG=True
|
| 96 |
+
CORS_ORIGINS=http://localhost:3000
|
| 97 |
+
|
| 98 |
+
# Authentication (Placeholder for Spec 2)
|
| 99 |
+
# JWT_SECRET=your-secret-key-here
|
| 100 |
+
# JWT_ALGORITHM=HS256
|
| 101 |
+
# JWT_EXPIRATION_MINUTES=1440
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### 5. Set Up Database
|
| 105 |
+
|
| 106 |
+
**Initialize Alembic** (if not already done):
|
| 107 |
+
```bash
|
| 108 |
+
alembic init alembic
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
**Create initial migration**:
|
| 112 |
+
```bash
|
| 113 |
+
alembic revision --autogenerate -m "Create tasks table"
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
**Apply migrations**:
|
| 117 |
+
```bash
|
| 118 |
+
alembic upgrade head
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### 6. Run Backend Server
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
**Verify backend is running**:
|
| 128 |
+
- Open browser: http://localhost:8000/docs
|
| 129 |
+
- You should see the FastAPI Swagger UI with task endpoints
|
| 130 |
+
|
| 131 |
+
### 7. Test Backend API
|
| 132 |
+
|
| 133 |
+
**Create a test user** (temporary, until Spec 2):
|
| 134 |
+
```bash
|
| 135 |
+
# Using Python shell
|
| 136 |
+
python
|
| 137 |
+
>>> from src.core.database import engine
|
| 138 |
+
>>> from src.models.user import User
|
| 139 |
+
>>> from sqlmodel import Session
|
| 140 |
+
>>> with Session(engine) as session:
|
| 141 |
+
... user = User(email="test@example.com", name="Test User")
|
| 142 |
+
... session.add(user)
|
| 143 |
+
... session.commit()
|
| 144 |
+
... print(f"Created user with ID: {user.id}")
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
**Test task creation**:
|
| 148 |
+
```bash
|
| 149 |
+
curl -X POST http://localhost:8000/api/tasks \
|
| 150 |
+
-H "Content-Type: application/json" \
|
| 151 |
+
-d '{"title": "Test Task", "description": "Testing API"}'
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
## Frontend Setup
|
| 155 |
+
|
| 156 |
+
### 1. Navigate to Frontend Directory
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
cd frontend
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### 2. Install Dependencies
|
| 163 |
+
|
| 164 |
+
```bash
|
| 165 |
+
npm install
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
**package.json** should contain:
|
| 169 |
+
```json
|
| 170 |
+
{
|
| 171 |
+
"dependencies": {
|
| 172 |
+
"next": "^16.0.0",
|
| 173 |
+
"react": "^18.2.0",
|
| 174 |
+
"react-dom": "^18.2.0",
|
| 175 |
+
"typescript": "^5.3.0",
|
| 176 |
+
"tailwindcss": "^3.4.0"
|
| 177 |
+
},
|
| 178 |
+
"devDependencies": {
|
| 179 |
+
"@types/react": "^18.2.0",
|
| 180 |
+
"@types/node": "^20.10.0",
|
| 181 |
+
"autoprefixer": "^10.4.16",
|
| 182 |
+
"postcss": "^8.4.32"
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### 3. Configure Environment Variables
|
| 188 |
+
|
| 189 |
+
Create `.env.local` file in `frontend/` directory:
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
cp .env.local.example .env.local
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
Edit `.env.local`:
|
| 196 |
+
|
| 197 |
+
```env
|
| 198 |
+
# API Configuration
|
| 199 |
+
NEXT_PUBLIC_API_URL=http://localhost:8000
|
| 200 |
+
|
| 201 |
+
# Authentication (Placeholder for Spec 2)
|
| 202 |
+
# NEXT_PUBLIC_AUTH_URL=http://localhost:8000/auth
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
### 4. Configure Tailwind CSS
|
| 206 |
+
|
| 207 |
+
**tailwind.config.ts**:
|
| 208 |
+
```typescript
|
| 209 |
+
import type { Config } from 'tailwindcss'
|
| 210 |
+
|
| 211 |
+
const config: Config = {
|
| 212 |
+
content: [
|
| 213 |
+
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
| 214 |
+
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
| 215 |
+
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
| 216 |
+
],
|
| 217 |
+
theme: {
|
| 218 |
+
extend: {},
|
| 219 |
+
},
|
| 220 |
+
plugins: [],
|
| 221 |
+
}
|
| 222 |
+
export default config
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
### 5. Run Frontend Development Server
|
| 226 |
+
|
| 227 |
+
```bash
|
| 228 |
+
npm run dev
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
**Verify frontend is running**:
|
| 232 |
+
- Open browser: http://localhost:3000
|
| 233 |
+
- You should see the task list page
|
| 234 |
+
|
| 235 |
+
### 6. Test Frontend
|
| 236 |
+
|
| 237 |
+
1. **View Tasks**: Navigate to http://localhost:3000
|
| 238 |
+
2. **Create Task**: Click "Add Task" button, fill form, submit
|
| 239 |
+
3. **Edit Task**: Click edit icon on a task, modify, save
|
| 240 |
+
4. **Complete Task**: Click checkbox to toggle completion
|
| 241 |
+
5. **Delete Task**: Click delete icon, confirm deletion
|
| 242 |
+
6. **Filter Tasks**: Use filter buttons (All/Active/Completed)
|
| 243 |
+
|
| 244 |
+
## Development Workflow
|
| 245 |
+
|
| 246 |
+
### Running Both Servers Concurrently
|
| 247 |
+
|
| 248 |
+
**Terminal 1 (Backend)**:
|
| 249 |
+
```bash
|
| 250 |
+
cd backend
|
| 251 |
+
source venv/bin/activate # or venv\Scripts\activate on Windows
|
| 252 |
+
uvicorn src.main:app --reload
|
| 253 |
+
```
|
| 254 |
+
|
| 255 |
+
**Terminal 2 (Frontend)**:
|
| 256 |
+
```bash
|
| 257 |
+
cd frontend
|
| 258 |
+
npm run dev
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
### Making Changes
|
| 262 |
+
|
| 263 |
+
1. **Backend Changes**:
|
| 264 |
+
- Edit files in `backend/src/`
|
| 265 |
+
- FastAPI auto-reloads on file changes
|
| 266 |
+
- Check http://localhost:8000/docs for updated API
|
| 267 |
+
|
| 268 |
+
2. **Frontend Changes**:
|
| 269 |
+
- Edit files in `frontend/src/`
|
| 270 |
+
- Next.js auto-reloads on file changes
|
| 271 |
+
- Check browser for updates (hot reload)
|
| 272 |
+
|
| 273 |
+
3. **Database Changes**:
|
| 274 |
+
- Modify SQLModel models in `backend/src/models/`
|
| 275 |
+
- Generate migration: `alembic revision --autogenerate -m "description"`
|
| 276 |
+
- Apply migration: `alembic upgrade head`
|
| 277 |
+
|
| 278 |
+
## Testing
|
| 279 |
+
|
| 280 |
+
### Backend Tests
|
| 281 |
+
|
| 282 |
+
```bash
|
| 283 |
+
cd backend
|
| 284 |
+
pytest
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
**Run specific test file**:
|
| 288 |
+
```bash
|
| 289 |
+
pytest tests/test_task_api.py
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
**Run with coverage**:
|
| 293 |
+
```bash
|
| 294 |
+
pytest --cov=src tests/
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
### Frontend Tests
|
| 298 |
+
|
| 299 |
+
```bash
|
| 300 |
+
cd frontend
|
| 301 |
+
npm test
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
**Run specific test**:
|
| 305 |
+
```bash
|
| 306 |
+
npm test -- TaskList.test.tsx
|
| 307 |
+
```
|
| 308 |
+
|
| 309 |
+
## Troubleshooting
|
| 310 |
+
|
| 311 |
+
### Backend Issues
|
| 312 |
+
|
| 313 |
+
**Database connection error**:
|
| 314 |
+
- Verify `DATABASE_URL` in `.env` is correct
|
| 315 |
+
- Check Neon dashboard for connection string
|
| 316 |
+
- Ensure database exists and is accessible
|
| 317 |
+
|
| 318 |
+
**Import errors**:
|
| 319 |
+
- Verify virtual environment is activated
|
| 320 |
+
- Reinstall dependencies: `pip install -r requirements.txt`
|
| 321 |
+
|
| 322 |
+
**Port already in use**:
|
| 323 |
+
- Change port: `uvicorn src.main:app --reload --port 8001`
|
| 324 |
+
- Or kill process using port 8000
|
| 325 |
+
|
| 326 |
+
### Frontend Issues
|
| 327 |
+
|
| 328 |
+
**Module not found**:
|
| 329 |
+
- Delete `node_modules/` and `.next/`
|
| 330 |
+
- Reinstall: `npm install`
|
| 331 |
+
|
| 332 |
+
**API connection error**:
|
| 333 |
+
- Verify backend is running on http://localhost:8000
|
| 334 |
+
- Check `NEXT_PUBLIC_API_URL` in `.env.local`
|
| 335 |
+
- Check browser console for CORS errors
|
| 336 |
+
|
| 337 |
+
**Port already in use**:
|
| 338 |
+
- Next.js will automatically try port 3001, 3002, etc.
|
| 339 |
+
- Or specify port: `npm run dev -- -p 3001`
|
| 340 |
+
|
| 341 |
+
### Database Issues
|
| 342 |
+
|
| 343 |
+
**Migration conflicts**:
|
| 344 |
+
- Check `alembic/versions/` for conflicting migrations
|
| 345 |
+
- Downgrade: `alembic downgrade -1`
|
| 346 |
+
- Delete conflicting migration file
|
| 347 |
+
- Regenerate: `alembic revision --autogenerate -m "description"`
|
| 348 |
+
|
| 349 |
+
**Data not persisting**:
|
| 350 |
+
- Check database connection
|
| 351 |
+
- Verify migrations applied: `alembic current`
|
| 352 |
+
- Check for transaction rollbacks in logs
|
| 353 |
+
|
| 354 |
+
## API Documentation
|
| 355 |
+
|
| 356 |
+
### Swagger UI (Interactive)
|
| 357 |
+
|
| 358 |
+
http://localhost:8000/docs
|
| 359 |
+
|
| 360 |
+
### ReDoc (Alternative)
|
| 361 |
+
|
| 362 |
+
http://localhost:8000/redoc
|
| 363 |
+
|
| 364 |
+
### OpenAPI JSON
|
| 365 |
+
|
| 366 |
+
http://localhost:8000/openapi.json
|
| 367 |
+
|
| 368 |
+
## Database Management
|
| 369 |
+
|
| 370 |
+
### View Database Contents
|
| 371 |
+
|
| 372 |
+
**Using psql** (if local PostgreSQL):
|
| 373 |
+
```bash
|
| 374 |
+
psql -d your_database
|
| 375 |
+
\dt # List tables
|
| 376 |
+
SELECT * FROM tasks;
|
| 377 |
+
```
|
| 378 |
+
|
| 379 |
+
**Using Neon Console**:
|
| 380 |
+
1. Log in to https://console.neon.tech
|
| 381 |
+
2. Select your project
|
| 382 |
+
3. Go to "SQL Editor"
|
| 383 |
+
4. Run queries
|
| 384 |
+
|
| 385 |
+
### Reset Database
|
| 386 |
+
|
| 387 |
+
**Drop all tables and recreate**:
|
| 388 |
+
```bash
|
| 389 |
+
alembic downgrade base
|
| 390 |
+
alembic upgrade head
|
| 391 |
+
```
|
| 392 |
+
|
| 393 |
+
**Or manually**:
|
| 394 |
+
```sql
|
| 395 |
+
DROP TABLE tasks CASCADE;
|
| 396 |
+
DROP TABLE users CASCADE;
|
| 397 |
+
```
|
| 398 |
+
|
| 399 |
+
Then run migrations again.
|
| 400 |
+
|
| 401 |
+
## Environment Variables Reference
|
| 402 |
+
|
| 403 |
+
### Backend (.env)
|
| 404 |
+
|
| 405 |
+
| Variable | Description | Example |
|
| 406 |
+
|----------|-------------|---------|
|
| 407 |
+
| DATABASE_URL | PostgreSQL connection string | postgresql://user:pass@host/db |
|
| 408 |
+
| APP_NAME | Application name | Task CRUD API |
|
| 409 |
+
| DEBUG | Enable debug mode | True |
|
| 410 |
+
| CORS_ORIGINS | Allowed CORS origins | http://localhost:3000 |
|
| 411 |
+
|
| 412 |
+
### Frontend (.env.local)
|
| 413 |
+
|
| 414 |
+
| Variable | Description | Example |
|
| 415 |
+
|----------|-------------|---------|
|
| 416 |
+
| NEXT_PUBLIC_API_URL | Backend API URL | http://localhost:8000 |
|
| 417 |
+
|
| 418 |
+
## Next Steps
|
| 419 |
+
|
| 420 |
+
1. **Implement Authentication** (Spec 2):
|
| 421 |
+
- Add Better Auth integration
|
| 422 |
+
- Implement JWT token generation/validation
|
| 423 |
+
- Add user registration and login
|
| 424 |
+
|
| 425 |
+
2. **Add Tests**:
|
| 426 |
+
- Write backend API tests
|
| 427 |
+
- Write frontend component tests
|
| 428 |
+
- Add E2E tests with Playwright
|
| 429 |
+
|
| 430 |
+
3. **Deploy to Production**:
|
| 431 |
+
- Set up CI/CD pipeline
|
| 432 |
+
- Deploy backend to cloud provider
|
| 433 |
+
- Deploy frontend to Vercel/Netlify
|
| 434 |
+
- Configure production database
|
| 435 |
+
|
| 436 |
+
## Additional Resources
|
| 437 |
+
|
| 438 |
+
- **FastAPI Documentation**: https://fastapi.tiangolo.com
|
| 439 |
+
- **Next.js Documentation**: https://nextjs.org/docs
|
| 440 |
+
- **SQLModel Documentation**: https://sqlmodel.tiangolo.com
|
| 441 |
+
- **Tailwind CSS Documentation**: https://tailwindcss.com/docs
|
| 442 |
+
- **Neon Documentation**: https://neon.tech/docs
|
| 443 |
+
|
| 444 |
+
## Support
|
| 445 |
+
|
| 446 |
+
For issues or questions:
|
| 447 |
+
1. Check the troubleshooting section above
|
| 448 |
+
2. Review the specification: `specs/001-task-crud/spec.md`
|
| 449 |
+
3. Check API contracts: `specs/001-task-crud/contracts/`
|
| 450 |
+
4. Review data model: `specs/001-task-crud/data-model.md`
|
| 451 |
+
|
| 452 |
+
## Summary
|
| 453 |
+
|
| 454 |
+
You should now have:
|
| 455 |
+
- ✅ Backend API running on http://localhost:8000
|
| 456 |
+
- ✅ Frontend UI running on http://localhost:3000
|
| 457 |
+
- ✅ Database configured and migrated
|
| 458 |
+
- ✅ Ability to create, view, update, delete, and complete tasks
|
| 459 |
+
|
| 460 |
+
**Ready for**: Implementation phase (`/sp.tasks` to generate task list, then `/sp.implement` to execute)
|
specs/001-task-crud/research.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Research: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Feature**: Task CRUD Operations
|
| 4 |
+
**Date**: 2026-01-08
|
| 5 |
+
**Status**: Complete
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This document consolidates technology decisions, best practices, and architectural patterns for implementing the Task CRUD feature. All decisions align with the project constitution and technical constraints defined in the specification.
|
| 10 |
+
|
| 11 |
+
## Technology Stack Decisions
|
| 12 |
+
|
| 13 |
+
### Backend Framework: FastAPI 0.104+
|
| 14 |
+
|
| 15 |
+
**Decision**: Use FastAPI with SQLModel ORM for the backend REST API.
|
| 16 |
+
|
| 17 |
+
**Rationale**:
|
| 18 |
+
- FastAPI provides automatic OpenAPI documentation generation
|
| 19 |
+
- Native async/await support for high concurrency (100+ concurrent users target)
|
| 20 |
+
- Pydantic v2 integration for robust request/response validation
|
| 21 |
+
- SQLModel combines SQLAlchemy ORM with Pydantic models, reducing code duplication
|
| 22 |
+
- Type hints throughout enable better IDE support and catch errors early
|
| 23 |
+
- Excellent performance characteristics (comparable to Node.js and Go)
|
| 24 |
+
|
| 25 |
+
**Alternatives Considered**:
|
| 26 |
+
- Django REST Framework: More batteries-included but heavier, slower, and less modern async support
|
| 27 |
+
- Flask: Lighter but requires more manual setup for validation, documentation, and async
|
| 28 |
+
- Express.js (Node): Would require JavaScript/TypeScript on backend, reducing type safety benefits of Python
|
| 29 |
+
|
| 30 |
+
**Best Practices**:
|
| 31 |
+
- Use dependency injection for database sessions and authentication
|
| 32 |
+
- Separate Pydantic schemas (request/response) from SQLModel models (database)
|
| 33 |
+
- Implement service layer for business logic (keep routes thin)
|
| 34 |
+
- Use HTTPException for consistent error responses
|
| 35 |
+
- Enable CORS middleware for frontend communication
|
| 36 |
+
|
| 37 |
+
### Database: Neon Serverless PostgreSQL
|
| 38 |
+
|
| 39 |
+
**Decision**: Use Neon Serverless PostgreSQL with connection pooling.
|
| 40 |
+
|
| 41 |
+
**Rationale**:
|
| 42 |
+
- Serverless architecture scales automatically with demand
|
| 43 |
+
- Built-in connection pooling reduces overhead
|
| 44 |
+
- PostgreSQL provides ACID compliance for data integrity
|
| 45 |
+
- Native support for indexes, foreign keys, and constraints
|
| 46 |
+
- Compatible with SQLModel/SQLAlchemy ORM
|
| 47 |
+
- Separation of compute and storage enables cost efficiency
|
| 48 |
+
|
| 49 |
+
**Alternatives Considered**:
|
| 50 |
+
- Traditional PostgreSQL (self-hosted): Requires manual scaling and maintenance
|
| 51 |
+
- MySQL: Less feature-rich, weaker JSON support
|
| 52 |
+
- MongoDB: NoSQL not suitable for relational data (Task belongs to User)
|
| 53 |
+
|
| 54 |
+
**Best Practices**:
|
| 55 |
+
- Use connection pooling (pgbouncer or Neon's built-in pooling)
|
| 56 |
+
- Create indexes on user_id and completed columns for filtering
|
| 57 |
+
- Use foreign key constraints to enforce Task-User relationship
|
| 58 |
+
- Enable automatic timestamps (created_at, updated_at) via SQLModel
|
| 59 |
+
- Use Alembic for database migrations (version control for schema)
|
| 60 |
+
|
| 61 |
+
### Frontend Framework: Next.js 16+ (App Router)
|
| 62 |
+
|
| 63 |
+
**Decision**: Use Next.js 16+ with App Router, TypeScript, and Tailwind CSS.
|
| 64 |
+
|
| 65 |
+
**Rationale**:
|
| 66 |
+
- App Router provides server/client component separation (better performance)
|
| 67 |
+
- Server components reduce JavaScript bundle size sent to client
|
| 68 |
+
- Built-in routing, API routes, and optimization features
|
| 69 |
+
- TypeScript ensures type safety across frontend
|
| 70 |
+
- Tailwind CSS enables rapid, consistent styling without CSS files
|
| 71 |
+
- React 18+ with concurrent features for better UX
|
| 72 |
+
|
| 73 |
+
**Alternatives Considered**:
|
| 74 |
+
- Next.js Pages Router: Older pattern, less efficient rendering
|
| 75 |
+
- Create React App: No SSR/SSG, requires manual routing setup
|
| 76 |
+
- Vue.js/Nuxt: Different ecosystem, team less familiar
|
| 77 |
+
|
| 78 |
+
**Best Practices**:
|
| 79 |
+
- Use Server Components by default (TaskList for data fetching)
|
| 80 |
+
- Use Client Components only for interactivity (TaskForm, TaskItem with buttons)
|
| 81 |
+
- Implement optimistic UI updates for better perceived performance
|
| 82 |
+
- Use React Server Actions for form submissions (optional, can use API routes)
|
| 83 |
+
- Organize components by feature (tasks/) and reusability (ui/)
|
| 84 |
+
- Use TypeScript interfaces for API response types
|
| 85 |
+
|
| 86 |
+
## Architecture Patterns
|
| 87 |
+
|
| 88 |
+
### Three-Layer Architecture
|
| 89 |
+
|
| 90 |
+
**Decision**: Implement clear separation between database, API, and UI layers.
|
| 91 |
+
|
| 92 |
+
**Layers**:
|
| 93 |
+
1. **Database Layer**: SQLModel models, database connection, migrations
|
| 94 |
+
2. **API Layer**: FastAPI routes, Pydantic schemas, service layer
|
| 95 |
+
3. **UI Layer**: Next.js components, API client, state management
|
| 96 |
+
|
| 97 |
+
**Rationale**:
|
| 98 |
+
- Clear boundaries enable independent testing and development
|
| 99 |
+
- Service layer encapsulates business logic (reusable across endpoints)
|
| 100 |
+
- Separation of concerns aligns with Maintainable & Consistent Code principle
|
| 101 |
+
- Each layer can be scaled independently
|
| 102 |
+
|
| 103 |
+
**Implementation**:
|
| 104 |
+
```
|
| 105 |
+
Database Layer: backend/src/models/task.py (SQLModel)
|
| 106 |
+
Service Layer: backend/src/services/task_service.py (business logic)
|
| 107 |
+
API Layer: backend/src/api/routes/tasks.py (FastAPI routes)
|
| 108 |
+
UI Layer: frontend/src/components/tasks/ (React components)
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
### RESTful API Design
|
| 112 |
+
|
| 113 |
+
**Decision**: Use REST principles with resource-based URLs and standard HTTP methods.
|
| 114 |
+
|
| 115 |
+
**Endpoint Pattern**:
|
| 116 |
+
```
|
| 117 |
+
GET /api/tasks - List all tasks for authenticated user
|
| 118 |
+
POST /api/tasks - Create new task
|
| 119 |
+
GET /api/tasks/{id} - Get specific task
|
| 120 |
+
PUT /api/tasks/{id} - Update task (full replacement)
|
| 121 |
+
PATCH /api/tasks/{id} - Partial update (e.g., toggle completion)
|
| 122 |
+
DELETE /api/tasks/{id} - Delete task
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
**Rationale**:
|
| 126 |
+
- Standard REST conventions are widely understood
|
| 127 |
+
- HTTP methods map naturally to CRUD operations
|
| 128 |
+
- Resource-based URLs are intuitive and cacheable
|
| 129 |
+
- Aligns with API Compliance standard in constitution
|
| 130 |
+
|
| 131 |
+
**Best Practices**:
|
| 132 |
+
- Use plural nouns for resources (/tasks not /task)
|
| 133 |
+
- Return appropriate HTTP status codes (200, 201, 204, 400, 401, 404, 500)
|
| 134 |
+
- Include resource ID in response body after creation
|
| 135 |
+
- Use query parameters for filtering (?completed=true) and sorting (?sort=created_at)
|
| 136 |
+
- Return consistent error response format
|
| 137 |
+
|
| 138 |
+
### Data Validation Strategy
|
| 139 |
+
|
| 140 |
+
**Decision**: Use Pydantic v2 for request/response validation, SQLModel for database constraints.
|
| 141 |
+
|
| 142 |
+
**Validation Layers**:
|
| 143 |
+
1. **API Layer**: Pydantic schemas validate incoming requests
|
| 144 |
+
2. **Database Layer**: SQLModel/SQLAlchemy constraints enforce data integrity
|
| 145 |
+
3. **Frontend Layer**: TypeScript types + HTML5 validation for UX
|
| 146 |
+
|
| 147 |
+
**Rationale**:
|
| 148 |
+
- Defense in depth: multiple validation layers prevent bad data
|
| 149 |
+
- Pydantic provides clear error messages for API consumers
|
| 150 |
+
- Database constraints ensure integrity even if API bypassed
|
| 151 |
+
- Frontend validation provides immediate user feedback
|
| 152 |
+
|
| 153 |
+
**Implementation**:
|
| 154 |
+
```python
|
| 155 |
+
# API Layer (Pydantic)
|
| 156 |
+
class TaskCreate(BaseModel):
|
| 157 |
+
title: str = Field(min_length=1, max_length=200)
|
| 158 |
+
description: Optional[str] = Field(None, max_length=1000)
|
| 159 |
+
|
| 160 |
+
# Database Layer (SQLModel)
|
| 161 |
+
class Task(SQLModel, table=True):
|
| 162 |
+
title: str = Field(max_length=200, nullable=False)
|
| 163 |
+
description: Optional[str] = Field(max_length=1000)
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## User Data Isolation Strategy
|
| 167 |
+
|
| 168 |
+
**Decision**: Filter all database queries by authenticated user ID.
|
| 169 |
+
|
| 170 |
+
**Implementation Approach**:
|
| 171 |
+
1. Extract user_id from JWT token (Spec 2 will implement)
|
| 172 |
+
2. Add user_id as dependency in FastAPI routes
|
| 173 |
+
3. Include user_id filter in all database queries
|
| 174 |
+
4. Validate task ownership before update/delete operations
|
| 175 |
+
|
| 176 |
+
**Rationale**:
|
| 177 |
+
- Enforces Security & Data Privacy principle
|
| 178 |
+
- Prevents unauthorized access to other users' tasks
|
| 179 |
+
- Simple to implement and audit
|
| 180 |
+
- Aligns with 100% data isolation success criterion
|
| 181 |
+
|
| 182 |
+
**Code Pattern**:
|
| 183 |
+
```python
|
| 184 |
+
# Service layer
|
| 185 |
+
def get_tasks(db: Session, user_id: int) -> List[Task]:
|
| 186 |
+
return db.query(Task).filter(Task.user_id == user_id).all()
|
| 187 |
+
|
| 188 |
+
def get_task(db: Session, task_id: int, user_id: int) -> Optional[Task]:
|
| 189 |
+
return db.query(Task).filter(
|
| 190 |
+
Task.id == task_id,
|
| 191 |
+
Task.user_id == user_id
|
| 192 |
+
).first()
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
## Performance Optimization
|
| 196 |
+
|
| 197 |
+
### Database Indexing
|
| 198 |
+
|
| 199 |
+
**Decision**: Create indexes on frequently queried columns.
|
| 200 |
+
|
| 201 |
+
**Indexes to Create**:
|
| 202 |
+
- `user_id` (foreign key, used in all queries)
|
| 203 |
+
- `completed` (used for filtering active/completed tasks)
|
| 204 |
+
- Composite index on `(user_id, completed)` for filtered queries
|
| 205 |
+
- `created_at` (used for sorting)
|
| 206 |
+
|
| 207 |
+
**Rationale**:
|
| 208 |
+
- Indexes dramatically improve query performance for filtering and sorting
|
| 209 |
+
- user_id index essential for data isolation queries
|
| 210 |
+
- Composite index optimizes common filter combinations
|
| 211 |
+
- Aligns with Scalable Architecture principle
|
| 212 |
+
|
| 213 |
+
### Frontend Optimization
|
| 214 |
+
|
| 215 |
+
**Decision**: Use Server Components for data fetching, Client Components for interactivity.
|
| 216 |
+
|
| 217 |
+
**Strategy**:
|
| 218 |
+
- TaskList: Server Component (fetches data, no JavaScript to client)
|
| 219 |
+
- TaskItem: Client Component (needs onClick handlers for complete/delete)
|
| 220 |
+
- TaskForm: Client Component (needs form state and submission)
|
| 221 |
+
- TaskFilters: Client Component (needs interactive filter/sort controls)
|
| 222 |
+
|
| 223 |
+
**Rationale**:
|
| 224 |
+
- Server Components reduce JavaScript bundle size
|
| 225 |
+
- Data fetching on server is faster (closer to database)
|
| 226 |
+
- Client Components only where interactivity required
|
| 227 |
+
- Improves initial page load time and perceived performance
|
| 228 |
+
|
| 229 |
+
## Error Handling Strategy
|
| 230 |
+
|
| 231 |
+
**Decision**: Implement consistent error responses across all layers.
|
| 232 |
+
|
| 233 |
+
**Error Response Format**:
|
| 234 |
+
```json
|
| 235 |
+
{
|
| 236 |
+
"detail": "Human-readable error message",
|
| 237 |
+
"error_code": "VALIDATION_ERROR",
|
| 238 |
+
"field_errors": {
|
| 239 |
+
"title": ["Title must be between 1 and 200 characters"]
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
**HTTP Status Codes**:
|
| 245 |
+
- 200: Success (GET, PUT, PATCH)
|
| 246 |
+
- 201: Created (POST)
|
| 247 |
+
- 204: No Content (DELETE)
|
| 248 |
+
- 400: Bad Request (validation errors)
|
| 249 |
+
- 401: Unauthorized (missing/invalid JWT)
|
| 250 |
+
- 404: Not Found (task doesn't exist or doesn't belong to user)
|
| 251 |
+
- 500: Internal Server Error (unexpected errors)
|
| 252 |
+
|
| 253 |
+
**Rationale**:
|
| 254 |
+
- Consistent format enables frontend to handle errors uniformly
|
| 255 |
+
- Clear error messages improve developer experience
|
| 256 |
+
- Appropriate status codes enable proper HTTP caching and client behavior
|
| 257 |
+
- Aligns with API Compliance standard
|
| 258 |
+
|
| 259 |
+
## Testing Strategy
|
| 260 |
+
|
| 261 |
+
**Decision**: Implement unit tests for services, integration tests for API endpoints.
|
| 262 |
+
|
| 263 |
+
**Test Coverage**:
|
| 264 |
+
- **Backend Unit Tests**: Service layer business logic (pytest)
|
| 265 |
+
- **Backend Integration Tests**: API endpoints with test database (pytest + TestClient)
|
| 266 |
+
- **Frontend Component Tests**: React components in isolation (Jest + React Testing Library)
|
| 267 |
+
- **E2E Tests**: Full user flows (optional, Playwright)
|
| 268 |
+
|
| 269 |
+
**Rationale**:
|
| 270 |
+
- Unit tests catch logic errors early
|
| 271 |
+
- Integration tests validate API contracts
|
| 272 |
+
- Component tests ensure UI behaves correctly
|
| 273 |
+
- Pyramid approach: many unit tests, fewer integration tests, minimal E2E
|
| 274 |
+
|
| 275 |
+
**Test Database**:
|
| 276 |
+
- Use SQLite in-memory database for fast test execution
|
| 277 |
+
- Or use separate PostgreSQL test database with cleanup between tests
|
| 278 |
+
- Fixtures provide consistent test data
|
| 279 |
+
|
| 280 |
+
## Migration Strategy
|
| 281 |
+
|
| 282 |
+
**Decision**: Use Alembic for database schema migrations.
|
| 283 |
+
|
| 284 |
+
**Workflow**:
|
| 285 |
+
1. Define/modify SQLModel models
|
| 286 |
+
2. Generate migration: `alembic revision --autogenerate -m "description"`
|
| 287 |
+
3. Review generated migration file
|
| 288 |
+
4. Apply migration: `alembic upgrade head`
|
| 289 |
+
5. Commit migration file to version control
|
| 290 |
+
|
| 291 |
+
**Rationale**:
|
| 292 |
+
- Alembic integrates seamlessly with SQLAlchemy/SQLModel
|
| 293 |
+
- Auto-generation reduces manual migration writing
|
| 294 |
+
- Version control for database schema changes
|
| 295 |
+
- Enables rollback if needed
|
| 296 |
+
- Aligns with Database Integrity standard
|
| 297 |
+
|
| 298 |
+
## Security Considerations
|
| 299 |
+
|
| 300 |
+
### Input Validation
|
| 301 |
+
|
| 302 |
+
**Measures**:
|
| 303 |
+
- Pydantic validation for all API inputs
|
| 304 |
+
- SQL injection prevention via SQLModel ORM (parameterized queries)
|
| 305 |
+
- XSS prevention via React's automatic escaping
|
| 306 |
+
- CSRF protection via SameSite cookies (when auth implemented)
|
| 307 |
+
|
| 308 |
+
### Data Isolation
|
| 309 |
+
|
| 310 |
+
**Measures**:
|
| 311 |
+
- User ID filtering on all queries
|
| 312 |
+
- Ownership validation before update/delete
|
| 313 |
+
- No direct task ID access without user verification
|
| 314 |
+
- 401 responses for unauthorized access
|
| 315 |
+
|
| 316 |
+
### Secrets Management
|
| 317 |
+
|
| 318 |
+
**Measures**:
|
| 319 |
+
- Database credentials in environment variables
|
| 320 |
+
- `.env` files excluded from version control (.gitignore)
|
| 321 |
+
- `.env.example` templates for required variables
|
| 322 |
+
- Production secrets in secure secret management (AWS Secrets Manager, etc.)
|
| 323 |
+
|
| 324 |
+
## Dependencies and Versions
|
| 325 |
+
|
| 326 |
+
### Backend (Python 3.11+)
|
| 327 |
+
```
|
| 328 |
+
fastapi==0.104.1
|
| 329 |
+
sqlmodel==0.0.14
|
| 330 |
+
pydantic==2.5.0
|
| 331 |
+
uvicorn[standard]==0.24.0
|
| 332 |
+
alembic==1.13.0
|
| 333 |
+
psycopg2-binary==2.9.9 # PostgreSQL driver
|
| 334 |
+
python-dotenv==1.0.0
|
| 335 |
+
pytest==7.4.3
|
| 336 |
+
httpx==0.25.2 # For TestClient
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
### Frontend (Node.js 18+)
|
| 340 |
+
```json
|
| 341 |
+
{
|
| 342 |
+
"dependencies": {
|
| 343 |
+
"next": "^16.0.0",
|
| 344 |
+
"react": "^18.2.0",
|
| 345 |
+
"react-dom": "^18.2.0",
|
| 346 |
+
"typescript": "^5.3.0",
|
| 347 |
+
"tailwindcss": "^3.4.0"
|
| 348 |
+
},
|
| 349 |
+
"devDependencies": {
|
| 350 |
+
"@types/react": "^18.2.0",
|
| 351 |
+
"@types/node": "^20.10.0",
|
| 352 |
+
"jest": "^29.7.0",
|
| 353 |
+
"@testing-library/react": "^14.1.0"
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
## Deferred Decisions (Spec 2)
|
| 359 |
+
|
| 360 |
+
The following decisions are deferred to the authentication specification:
|
| 361 |
+
- JWT token generation and validation
|
| 362 |
+
- Better Auth integration
|
| 363 |
+
- User registration and login flows
|
| 364 |
+
- Token refresh mechanism
|
| 365 |
+
- Session management
|
| 366 |
+
|
| 367 |
+
**Current Approach**: API endpoints will accept user_id as a parameter (to be replaced with JWT extraction in Spec 2).
|
| 368 |
+
|
| 369 |
+
## Summary
|
| 370 |
+
|
| 371 |
+
All technology decisions align with the project constitution and technical constraints. The three-layer architecture with FastAPI, Neon PostgreSQL, and Next.js 16+ provides a solid foundation for scalable, maintainable task management. User data isolation is enforced at the database query level. Performance optimizations include database indexing and Server Component usage. Error handling is consistent across all layers. Testing strategy covers unit, integration, and component tests.
|
| 372 |
+
|
| 373 |
+
**Ready for Phase 1**: Data model design and API contract generation.
|
specs/001-task-crud/spec.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: Task CRUD Operations
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `001-task-crud`
|
| 4 |
+
**Created**: 2026-01-08
|
| 5 |
+
**Status**: Draft
|
| 6 |
+
**Input**: User description: "Task CRUD Feature – Phase II Todo Web App"
|
| 7 |
+
|
| 8 |
+
## User Scenarios & Testing *(mandatory)*
|
| 9 |
+
|
| 10 |
+
### User Story 1 - View and Create Tasks (Priority: P1)
|
| 11 |
+
|
| 12 |
+
As an authenticated user, I want to view my task list and create new tasks so that I can start managing my to-do items.
|
| 13 |
+
|
| 14 |
+
**Why this priority**: This is the foundational MVP functionality. Without the ability to create and view tasks, the application has no value. This story delivers immediate user value and can be demonstrated independently.
|
| 15 |
+
|
| 16 |
+
**Independent Test**: Can be fully tested by logging in, viewing an empty task list, creating a new task with a title, and seeing it appear in the list. Delivers core value of task creation and visibility.
|
| 17 |
+
|
| 18 |
+
**Acceptance Scenarios**:
|
| 19 |
+
|
| 20 |
+
1. **Given** I am an authenticated user with no tasks, **When** I view my task list, **Then** I see an empty state message indicating no tasks exist
|
| 21 |
+
2. **Given** I am viewing my task list, **When** I click "Add Task" and enter a title "Buy groceries", **Then** the task appears in my list with the title and a default "not completed" status
|
| 22 |
+
3. **Given** I am creating a task, **When** I enter a title and optional description, **Then** both fields are saved and displayed in the task list
|
| 23 |
+
4. **Given** I am viewing my task list, **When** I refresh the page, **Then** all my previously created tasks are still visible (data persists)
|
| 24 |
+
5. **Given** I am an authenticated user, **When** I view my task list, **Then** I only see tasks that I created (not other users' tasks)
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
### User Story 2 - Update and Complete Tasks (Priority: P2)
|
| 29 |
+
|
| 30 |
+
As an authenticated user, I want to edit my tasks and mark them as complete so that I can update task details and track my progress.
|
| 31 |
+
|
| 32 |
+
**Why this priority**: After creating tasks, users need to update them as requirements change and mark them complete to track progress. This builds on P1 and adds essential task management capabilities.
|
| 33 |
+
|
| 34 |
+
**Independent Test**: Can be tested by creating a task (from P1), editing its title or description, and toggling its completion status. Delivers progress tracking value.
|
| 35 |
+
|
| 36 |
+
**Acceptance Scenarios**:
|
| 37 |
+
|
| 38 |
+
1. **Given** I have a task "Buy groceries", **When** I click edit and change the title to "Buy groceries and milk", **Then** the updated title is saved and displayed
|
| 39 |
+
2. **Given** I have a task with a description, **When** I edit the description, **Then** the updated description is saved
|
| 40 |
+
3. **Given** I have an incomplete task, **When** I click the checkbox to mark it complete, **Then** the task is visually marked as completed
|
| 41 |
+
4. **Given** I have a completed task, **When** I click the checkbox again, **Then** the task is marked as incomplete
|
| 42 |
+
5. **Given** I am editing a task, **When** I cancel the edit, **Then** the original task details remain unchanged
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
### User Story 3 - Delete Tasks (Priority: P3)
|
| 47 |
+
|
| 48 |
+
As an authenticated user, I want to delete tasks I no longer need so that my task list stays clean and relevant.
|
| 49 |
+
|
| 50 |
+
**Why this priority**: Task deletion is important for list maintenance but not critical for initial value delivery. Users can work around missing deletion by marking tasks complete.
|
| 51 |
+
|
| 52 |
+
**Independent Test**: Can be tested by creating a task (from P1), deleting it, and verifying it no longer appears in the list. Delivers list management value.
|
| 53 |
+
|
| 54 |
+
**Acceptance Scenarios**:
|
| 55 |
+
|
| 56 |
+
1. **Given** I have a task in my list, **When** I click the delete button, **Then** the task is removed from my list
|
| 57 |
+
2. **Given** I have deleted a task, **When** I refresh the page, **Then** the deleted task does not reappear
|
| 58 |
+
3. **Given** I am about to delete a task, **When** I am prompted for confirmation, **Then** I can confirm or cancel the deletion
|
| 59 |
+
4. **Given** I have multiple tasks, **When** I delete one task, **Then** only that specific task is removed and others remain
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
### User Story 4 - Filter and Sort Tasks (Priority: P4)
|
| 64 |
+
|
| 65 |
+
As an authenticated user, I want to filter tasks by completion status and sort them so that I can focus on relevant tasks.
|
| 66 |
+
|
| 67 |
+
**Why this priority**: Filtering and sorting improve usability but are not essential for core task management. Users can manually scan their list initially.
|
| 68 |
+
|
| 69 |
+
**Independent Test**: Can be tested by creating multiple tasks with different completion statuses, applying filters (show all/active/completed), and verifying the correct subset is displayed. Delivers organization value.
|
| 70 |
+
|
| 71 |
+
**Acceptance Scenarios**:
|
| 72 |
+
|
| 73 |
+
1. **Given** I have both completed and incomplete tasks, **When** I select "Active" filter, **Then** I only see incomplete tasks
|
| 74 |
+
2. **Given** I have both completed and incomplete tasks, **When** I select "Completed" filter, **Then** I only see completed tasks
|
| 75 |
+
3. **Given** I have multiple tasks, **When** I select "All" filter, **Then** I see all tasks regardless of completion status
|
| 76 |
+
4. **Given** I have multiple tasks, **When** I sort by creation date (newest first), **Then** tasks are displayed with most recent at the top
|
| 77 |
+
5. **Given** I have applied a filter, **When** I refresh the page, **Then** the filter preference is maintained
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
### Edge Cases
|
| 82 |
+
|
| 83 |
+
- What happens when a user tries to create a task with an empty title?
|
| 84 |
+
- What happens when a user tries to create a task with a title exceeding 200 characters?
|
| 85 |
+
- What happens when a user tries to create a task with a description exceeding 1000 characters?
|
| 86 |
+
- How does the system handle concurrent updates to the same task from multiple browser tabs?
|
| 87 |
+
- What happens when the backend API is unavailable during task operations?
|
| 88 |
+
- How does the system handle special characters or emojis in task titles and descriptions?
|
| 89 |
+
- What happens when a user tries to access another user's task directly via URL manipulation?
|
| 90 |
+
|
| 91 |
+
## Requirements *(mandatory)*
|
| 92 |
+
|
| 93 |
+
### Functional Requirements
|
| 94 |
+
|
| 95 |
+
- **FR-001**: System MUST allow authenticated users to create tasks with a title (1-200 characters, required) and description (0-1000 characters, optional)
|
| 96 |
+
- **FR-002**: System MUST display all tasks belonging to the authenticated user in a list view
|
| 97 |
+
- **FR-003**: System MUST allow users to edit the title and description of their existing tasks
|
| 98 |
+
- **FR-004**: System MUST allow users to toggle the completion status of their tasks (completed/incomplete)
|
| 99 |
+
- **FR-005**: System MUST allow users to delete their tasks with confirmation
|
| 100 |
+
- **FR-006**: System MUST persist all task data to the database with automatic timestamps (created_at, updated_at)
|
| 101 |
+
- **FR-007**: System MUST enforce data isolation - users can only view, edit, and delete their own tasks
|
| 102 |
+
- **FR-008**: System MUST validate task title length (1-200 characters) and reject invalid submissions
|
| 103 |
+
- **FR-009**: System MUST validate task description length (0-1000 characters) and reject invalid submissions
|
| 104 |
+
- **FR-010**: System MUST provide filtering options: All tasks, Active tasks (incomplete), Completed tasks
|
| 105 |
+
- **FR-011**: System MUST provide sorting options: by creation date (newest/oldest first)
|
| 106 |
+
- **FR-012**: System MUST display task metadata: creation timestamp, last updated timestamp
|
| 107 |
+
- **FR-013**: System MUST provide visual distinction between completed and incomplete tasks
|
| 108 |
+
- **FR-014**: System MUST handle API errors gracefully with user-friendly error messages
|
| 109 |
+
- **FR-015**: System MUST maintain responsive design across mobile, tablet, and desktop viewports
|
| 110 |
+
|
| 111 |
+
### Assumptions
|
| 112 |
+
|
| 113 |
+
- Authentication and JWT token management are handled by a separate authentication feature (Spec 2)
|
| 114 |
+
- The frontend receives a valid JWT token from the authentication system
|
| 115 |
+
- The backend has middleware to verify JWT tokens and extract user identity
|
| 116 |
+
- Database connection and configuration are already established
|
| 117 |
+
- The user ID is available from the authenticated session/token
|
| 118 |
+
|
| 119 |
+
### Key Entities
|
| 120 |
+
|
| 121 |
+
- **Task**: Represents a to-do item with the following attributes:
|
| 122 |
+
- Unique identifier (system-generated)
|
| 123 |
+
- Title (1-200 characters, required)
|
| 124 |
+
- Description (0-1000 characters, optional)
|
| 125 |
+
- Completion status (boolean: completed/incomplete)
|
| 126 |
+
- Owner (reference to the user who created it)
|
| 127 |
+
- Creation timestamp (automatically set)
|
| 128 |
+
- Last updated timestamp (automatically updated)
|
| 129 |
+
- Relationships: Belongs to one User
|
| 130 |
+
|
| 131 |
+
- **User**: Represents an authenticated user (defined in authentication spec)
|
| 132 |
+
- Unique identifier
|
| 133 |
+
- Relationships: Has many Tasks
|
| 134 |
+
|
| 135 |
+
## Success Criteria *(mandatory)*
|
| 136 |
+
|
| 137 |
+
### Measurable Outcomes
|
| 138 |
+
|
| 139 |
+
- **SC-001**: Users can create a new task in under 10 seconds from clicking "Add Task" to seeing it in their list
|
| 140 |
+
- **SC-002**: Users can view their complete task list with all tasks loading and displaying within 2 seconds
|
| 141 |
+
- **SC-003**: Users can edit a task and see the updated information reflected immediately (within 1 second of saving)
|
| 142 |
+
- **SC-004**: Users can mark a task as complete and see the visual change instantly (within 500ms)
|
| 143 |
+
- **SC-005**: Users can delete a task and see it removed from the list within 1 second
|
| 144 |
+
- **SC-006**: System correctly isolates user data - 100% of API requests return only the authenticated user's tasks
|
| 145 |
+
- **SC-007**: System handles 100 concurrent users performing task operations without errors or data corruption
|
| 146 |
+
- **SC-008**: 95% of task operations (create, update, delete, complete) succeed on first attempt without errors
|
| 147 |
+
- **SC-009**: Users can successfully complete all core task operations (create, view, update, complete, delete) on mobile devices with touch interactions
|
| 148 |
+
- **SC-010**: System maintains data integrity - 100% of created tasks persist correctly and are retrievable after page refresh
|
| 149 |
+
|
| 150 |
+
### User Experience Outcomes
|
| 151 |
+
|
| 152 |
+
- **SC-011**: Users can understand how to create, edit, and delete tasks without external documentation
|
| 153 |
+
- **SC-012**: Error messages clearly communicate what went wrong and how to fix it (e.g., "Title must be between 1 and 200 characters")
|
| 154 |
+
- **SC-013**: The interface provides immediate visual feedback for all user actions (loading states, success confirmations, error alerts)
|
| 155 |
+
- **SC-014**: Users can distinguish between completed and incomplete tasks at a glance through clear visual indicators
|
| 156 |
+
|
| 157 |
+
## Out of Scope
|
| 158 |
+
|
| 159 |
+
The following items are explicitly excluded from this specification:
|
| 160 |
+
|
| 161 |
+
- User authentication and authorization implementation (covered in separate authentication spec)
|
| 162 |
+
- JWT token generation, validation, and refresh logic (covered in authentication spec)
|
| 163 |
+
- User registration and login flows (covered in authentication spec)
|
| 164 |
+
- Task sharing or collaboration features
|
| 165 |
+
- Task categories, tags, or labels
|
| 166 |
+
- Task due dates or reminders
|
| 167 |
+
- Task priority levels
|
| 168 |
+
- Task attachments or file uploads
|
| 169 |
+
- Task comments or notes beyond the description field
|
| 170 |
+
- Task history or audit trail
|
| 171 |
+
- Bulk operations (delete multiple tasks, mark multiple complete)
|
| 172 |
+
- Task search functionality
|
| 173 |
+
- Task export/import features
|
| 174 |
+
- Chatbot or AI-powered task suggestions
|
| 175 |
+
- Deployment configuration or environment setup
|
| 176 |
+
- Advanced UI animations or transitions beyond standard interactions
|
| 177 |
+
|
| 178 |
+
## Dependencies
|
| 179 |
+
|
| 180 |
+
- **Authentication System**: This feature requires a functioning authentication system that provides JWT tokens and user identity
|
| 181 |
+
- **Database**: Requires Neon PostgreSQL database to be provisioned and accessible
|
| 182 |
+
- **Backend Framework**: Requires FastAPI backend with SQLModel ORM configured
|
| 183 |
+
- **Frontend Framework**: Requires Next.js 16+ with App Router configured
|
| 184 |
+
|
| 185 |
+
## Technical Constraints
|
| 186 |
+
|
| 187 |
+
- **Frontend**: Must use Next.js 16+ (App Router), TypeScript, Tailwind CSS
|
| 188 |
+
- **Backend**: Must use Python FastAPI with SQLModel ORM
|
| 189 |
+
- **Database**: Must use Neon Serverless PostgreSQL
|
| 190 |
+
- **API Design**: Must follow REST principles with JSON request/response format
|
| 191 |
+
- **Authentication**: All task endpoints must require valid JWT token in Authorization header
|
| 192 |
+
- **Code Generation**: All implementation must be generated via Claude Code referencing this specification
|
| 193 |
+
- **File Structure**: Must follow monorepo organization with frontend/ and backend/ directories
|
| 194 |
+
|
| 195 |
+
## References
|
| 196 |
+
|
| 197 |
+
This specification should be referenced during implementation planning and task generation:
|
| 198 |
+
|
| 199 |
+
- `@specs/001-task-crud/spec.md` (this file)
|
| 200 |
+
- `@specs/001-task-crud/plan.md` (to be created by `/sp.plan`)
|
| 201 |
+
- `@specs/001-task-crud/tasks.md` (to be created by `/sp.tasks`)
|
| 202 |
+
- `@specs/001-task-crud/contracts/` (API endpoint contracts to be defined in planning phase)
|
specs/001-task-crud/tasks.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
|
| 3 |
+
description: "Task list for Task CRUD Operations feature implementation"
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# Tasks: Task CRUD Operations
|
| 7 |
+
|
| 8 |
+
**Input**: Design documents from `/specs/001-task-crud/`
|
| 9 |
+
**Prerequisites**: plan.md (required), spec.md (required for user stories), data-model.md, contracts/
|
| 10 |
+
|
| 11 |
+
**Tests**: Not requested in specification - implementation only
|
| 12 |
+
|
| 13 |
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
| 14 |
+
|
| 15 |
+
## Format: `[ID] [P?] [Story] Description`
|
| 16 |
+
|
| 17 |
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
| 18 |
+
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3, US4)
|
| 19 |
+
- Include exact file paths in descriptions
|
| 20 |
+
|
| 21 |
+
## Path Conventions
|
| 22 |
+
|
| 23 |
+
- **Backend**: `backend/src/` for source code, `backend/tests/` for tests
|
| 24 |
+
- **Frontend**: `frontend/src/` for source code
|
| 25 |
+
- Paths shown below follow monorepo structure from plan.md
|
| 26 |
+
|
| 27 |
+
## Phase 1: Setup (Shared Infrastructure)
|
| 28 |
+
|
| 29 |
+
**Purpose**: Project initialization and basic structure
|
| 30 |
+
|
| 31 |
+
- [x] T001 Create backend directory structure with src/, tests/, alembic/ folders
|
| 32 |
+
- [x] T002 Create frontend directory structure with src/app/, src/components/, src/lib/ folders
|
| 33 |
+
- [x] T003 [P] Initialize Python project with requirements.txt (FastAPI, SQLModel, Pydantic, uvicorn, alembic, psycopg2-binary, python-dotenv)
|
| 34 |
+
- [x] T004 [P] Initialize Node.js project with package.json (Next.js 16+, React 18+, TypeScript, Tailwind CSS)
|
| 35 |
+
- [x] T005 [P] Configure Tailwind CSS in frontend/tailwind.config.ts and frontend/src/styles/globals.css
|
| 36 |
+
- [x] T006 [P] Create backend/.env.example with DATABASE_URL, APP_NAME, DEBUG, CORS_ORIGINS placeholders
|
| 37 |
+
- [x] T007 [P] Create frontend/.env.local.example with NEXT_PUBLIC_API_URL placeholder
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Phase 2: Foundational (Blocking Prerequisites)
|
| 42 |
+
|
| 43 |
+
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
| 44 |
+
|
| 45 |
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
| 46 |
+
|
| 47 |
+
- [x] T008 Create database configuration in backend/src/core/config.py with Settings class
|
| 48 |
+
- [x] T009 Create database connection setup in backend/src/core/database.py with engine and session
|
| 49 |
+
- [x] T010 [P] Create User model stub in backend/src/models/user.py (id, email, name, timestamps)
|
| 50 |
+
- [x] T011 [P] Create Task model in backend/src/models/task.py (id, user_id, title, description, completed, timestamps)
|
| 51 |
+
- [x] T012 Initialize Alembic in backend/alembic/ and configure env.py with SQLModel metadata
|
| 52 |
+
- [x] T013 Generate initial migration for users and tasks tables with indexes
|
| 53 |
+
- [x] T014 Create FastAPI application entry point in backend/src/main.py with CORS middleware
|
| 54 |
+
- [x] T015 [P] Create API dependencies in backend/src/api/deps.py (get_db session, get_current_user stub)
|
| 55 |
+
- [x] T016 [P] Create TypeScript types in frontend/src/lib/types.ts (Task, TaskCreate, TaskUpdate interfaces)
|
| 56 |
+
- [x] T017 [P] Create API client base in frontend/src/lib/api.ts with fetch wrapper and error handling
|
| 57 |
+
|
| 58 |
+
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## Phase 3: User Story 1 - View and Create Tasks (Priority: P1) 🎯 MVP
|
| 63 |
+
|
| 64 |
+
**Goal**: Users can view their task list and create new tasks
|
| 65 |
+
|
| 66 |
+
**Independent Test**: Log in, view empty task list, create task with title, see it appear in list
|
| 67 |
+
|
| 68 |
+
### Implementation for User Story 1
|
| 69 |
+
|
| 70 |
+
- [ ] T018 [P] [US1] Create TaskCreate Pydantic schema in backend/src/schemas/task.py
|
| 71 |
+
- [ ] T019 [P] [US1] Create TaskResponse Pydantic schema in backend/src/schemas/task.py
|
| 72 |
+
- [ ] T020 [P] [US1] Create TaskListResponse Pydantic schema in backend/src/schemas/task.py
|
| 73 |
+
- [ ] T021 [US1] Create TaskService with get_tasks() and create_task() methods in backend/src/services/task_service.py
|
| 74 |
+
- [ ] T022 [US1] Implement GET /api/tasks endpoint in backend/src/api/routes/tasks.py
|
| 75 |
+
- [ ] T023 [US1] Implement POST /api/tasks endpoint in backend/src/api/routes/tasks.py
|
| 76 |
+
- [ ] T024 [US1] Register task routes in backend/src/main.py
|
| 77 |
+
- [ ] T025 [P] [US1] Create TaskList server component in frontend/src/components/tasks/TaskList.tsx
|
| 78 |
+
- [ ] T026 [P] [US1] Create TaskForm client component in frontend/src/components/tasks/TaskForm.tsx
|
| 79 |
+
- [ ] T027 [P] [US1] Create TaskItem client component in frontend/src/components/tasks/TaskItem.tsx
|
| 80 |
+
- [ ] T028 [US1] Implement getTasks() API function in frontend/src/lib/api.ts
|
| 81 |
+
- [ ] T029 [US1] Implement createTask() API function in frontend/src/lib/api.ts
|
| 82 |
+
- [ ] T030 [US1] Create home page in frontend/src/app/page.tsx integrating TaskList and TaskForm
|
| 83 |
+
|
| 84 |
+
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## Phase 4: User Story 2 - Update and Complete Tasks (Priority: P2)
|
| 89 |
+
|
| 90 |
+
**Goal**: Users can edit tasks and mark them as complete
|
| 91 |
+
|
| 92 |
+
**Independent Test**: Create task (from P1), edit title/description, toggle completion status
|
| 93 |
+
|
| 94 |
+
### Implementation for User Story 2
|
| 95 |
+
|
| 96 |
+
- [ ] T031 [P] [US2] Create TaskUpdate Pydantic schema in backend/src/schemas/task.py
|
| 97 |
+
- [ ] T032 [P] [US2] Create TaskPatch Pydantic schema in backend/src/schemas/task.py
|
| 98 |
+
- [ ] T033 [US2] Add get_task(), update_task(), and patch_task() methods to TaskService in backend/src/services/task_service.py
|
| 99 |
+
- [ ] T034 [US2] Implement GET /api/tasks/{task_id} endpoint in backend/src/api/routes/tasks.py
|
| 100 |
+
- [ ] T035 [US2] Implement PUT /api/tasks/{task_id} endpoint in backend/src/api/routes/tasks.py
|
| 101 |
+
- [ ] T036 [US2] Implement PATCH /api/tasks/{task_id} endpoint in backend/src/api/routes/tasks.py
|
| 102 |
+
- [ ] T037 [US2] Add edit mode state and handlers to TaskItem component in frontend/src/components/tasks/TaskItem.tsx
|
| 103 |
+
- [ ] T038 [US2] Add completion toggle handler to TaskItem component in frontend/src/components/tasks/TaskItem.tsx
|
| 104 |
+
- [ ] T039 [US2] Implement updateTask() API function in frontend/src/lib/api.ts
|
| 105 |
+
- [ ] T040 [US2] Implement patchTask() API function in frontend/src/lib/api.ts
|
| 106 |
+
|
| 107 |
+
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Phase 5: User Story 3 - Delete Tasks (Priority: P3)
|
| 112 |
+
|
| 113 |
+
**Goal**: Users can delete tasks they no longer need
|
| 114 |
+
|
| 115 |
+
**Independent Test**: Create task (from P1), delete it, verify it no longer appears
|
| 116 |
+
|
| 117 |
+
### Implementation for User Story 3
|
| 118 |
+
|
| 119 |
+
- [ ] T041 [US3] Add delete_task() method to TaskService in backend/src/services/task_service.py
|
| 120 |
+
- [ ] T042 [US3] Implement DELETE /api/tasks/{task_id} endpoint in backend/src/api/routes/tasks.py
|
| 121 |
+
- [ ] T043 [US3] Add delete button and confirmation dialog to TaskItem component in frontend/src/components/tasks/TaskItem.tsx
|
| 122 |
+
- [ ] T044 [US3] Implement deleteTask() API function in frontend/src/lib/api.ts
|
| 123 |
+
|
| 124 |
+
**Checkpoint**: All user stories 1, 2, and 3 should now be independently functional
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## Phase 6: User Story 4 - Filter and Sort Tasks (Priority: P4)
|
| 129 |
+
|
| 130 |
+
**Goal**: Users can filter by completion status and sort by date
|
| 131 |
+
|
| 132 |
+
**Independent Test**: Create multiple tasks, apply filters (all/active/completed), verify correct subset displayed
|
| 133 |
+
|
| 134 |
+
### Implementation for User Story 4
|
| 135 |
+
|
| 136 |
+
- [ ] T045 [US4] Add filtering and sorting logic to get_tasks() in TaskService (backend/src/services/task_service.py)
|
| 137 |
+
- [ ] T046 [US4] Update GET /api/tasks endpoint to accept query parameters (completed, sort, limit, offset) in backend/src/api/routes/tasks.py
|
| 138 |
+
- [ ] T047 [P] [US4] Create TaskFilters client component in frontend/src/components/tasks/TaskFilters.tsx
|
| 139 |
+
- [ ] T048 [US4] Update getTasks() API function to accept filter/sort parameters in frontend/src/lib/api.ts
|
| 140 |
+
- [ ] T049 [US4] Integrate TaskFilters component into home page in frontend/src/app/page.tsx
|
| 141 |
+
|
| 142 |
+
**Checkpoint**: All user stories should now be independently functional
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Phase 7: Polish & Cross-Cutting Concerns
|
| 147 |
+
|
| 148 |
+
**Purpose**: Improvements that affect multiple user stories
|
| 149 |
+
|
| 150 |
+
- [ ] T050 [P] Add error handling and user-friendly error messages across all API endpoints in backend/src/api/routes/tasks.py
|
| 151 |
+
- [ ] T051 [P] Add loading states and error displays to frontend components in frontend/src/components/tasks/
|
| 152 |
+
- [ ] T052 [P] Add input validation and error messages to TaskForm component in frontend/src/components/tasks/TaskForm.tsx
|
| 153 |
+
- [ ] T053 [P] Create reusable UI components (Button, Input, Checkbox) in frontend/src/components/ui/
|
| 154 |
+
- [ ] T054 [P] Add responsive design styles with Tailwind CSS breakpoints to all task components
|
| 155 |
+
- [ ] T055 [P] Add visual distinction for completed vs incomplete tasks in TaskItem component
|
| 156 |
+
- [ ] T056 Create backend README.md with setup instructions
|
| 157 |
+
- [ ] T057 Create frontend README.md with setup instructions
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## Dependencies & Execution Order
|
| 162 |
+
|
| 163 |
+
### Phase Dependencies
|
| 164 |
+
|
| 165 |
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
| 166 |
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
| 167 |
+
- **User Stories (Phase 3-6)**: All depend on Foundational phase completion
|
| 168 |
+
- User stories can then proceed in parallel (if staffed)
|
| 169 |
+
- Or sequentially in priority order (P1 → P2 → P3 → P4)
|
| 170 |
+
- **Polish (Phase 7)**: Depends on all desired user stories being complete
|
| 171 |
+
|
| 172 |
+
### User Story Dependencies
|
| 173 |
+
|
| 174 |
+
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
| 175 |
+
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - Builds on US1 but independently testable
|
| 176 |
+
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - Builds on US1 but independently testable
|
| 177 |
+
- **User Story 4 (P4)**: Can start after Foundational (Phase 2) - Enhances US1 but independently testable
|
| 178 |
+
|
| 179 |
+
### Within Each User Story
|
| 180 |
+
|
| 181 |
+
- Pydantic schemas before service methods
|
| 182 |
+
- Service methods before API endpoints
|
| 183 |
+
- API endpoints before frontend components
|
| 184 |
+
- API client functions alongside frontend components
|
| 185 |
+
- Core implementation before integration
|
| 186 |
+
|
| 187 |
+
### Parallel Opportunities
|
| 188 |
+
|
| 189 |
+
- All Setup tasks marked [P] can run in parallel
|
| 190 |
+
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
|
| 191 |
+
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
|
| 192 |
+
- Tasks within a user story marked [P] can run in parallel
|
| 193 |
+
- Different user stories can be worked on in parallel by different team members
|
| 194 |
+
|
| 195 |
+
---
|
| 196 |
+
|
| 197 |
+
## Parallel Example: User Story 1
|
| 198 |
+
|
| 199 |
+
```bash
|
| 200 |
+
# Launch Pydantic schemas together:
|
| 201 |
+
Task: "Create TaskCreate schema in backend/src/schemas/task.py"
|
| 202 |
+
Task: "Create TaskResponse schema in backend/src/schemas/task.py"
|
| 203 |
+
Task: "Create TaskListResponse schema in backend/src/schemas/task.py"
|
| 204 |
+
|
| 205 |
+
# Launch frontend components together (after API is ready):
|
| 206 |
+
Task: "Create TaskList component in frontend/src/components/tasks/TaskList.tsx"
|
| 207 |
+
Task: "Create TaskForm component in frontend/src/components/tasks/TaskForm.tsx"
|
| 208 |
+
Task: "Create TaskItem component in frontend/src/components/tasks/TaskItem.tsx"
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## Implementation Strategy
|
| 214 |
+
|
| 215 |
+
### MVP First (User Story 1 Only)
|
| 216 |
+
|
| 217 |
+
1. Complete Phase 1: Setup
|
| 218 |
+
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
|
| 219 |
+
3. Complete Phase 3: User Story 1
|
| 220 |
+
4. **STOP and VALIDATE**: Test User Story 1 independently
|
| 221 |
+
5. Deploy/demo if ready
|
| 222 |
+
|
| 223 |
+
### Incremental Delivery
|
| 224 |
+
|
| 225 |
+
1. Complete Setup + Foundational → Foundation ready
|
| 226 |
+
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
|
| 227 |
+
3. Add User Story 2 → Test independently → Deploy/Demo
|
| 228 |
+
4. Add User Story 3 → Test independently → Deploy/Demo
|
| 229 |
+
5. Add User Story 4 → Test independently → Deploy/Demo
|
| 230 |
+
6. Each story adds value without breaking previous stories
|
| 231 |
+
|
| 232 |
+
### Parallel Team Strategy
|
| 233 |
+
|
| 234 |
+
With multiple developers:
|
| 235 |
+
|
| 236 |
+
1. Team completes Setup + Foundational together
|
| 237 |
+
2. Once Foundational is done:
|
| 238 |
+
- Developer A: User Story 1
|
| 239 |
+
- Developer B: User Story 2
|
| 240 |
+
- Developer C: User Story 3
|
| 241 |
+
3. Stories complete and integrate independently
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## Notes
|
| 246 |
+
|
| 247 |
+
- [P] tasks = different files, no dependencies
|
| 248 |
+
- [Story] label maps task to specific user story for traceability
|
| 249 |
+
- Each user story should be independently completable and testable
|
| 250 |
+
- Commit after each task or logical group
|
| 251 |
+
- Stop at any checkpoint to validate story independently
|
| 252 |
+
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
## Task Summary
|
| 257 |
+
|
| 258 |
+
**Total Tasks**: 57
|
| 259 |
+
- Phase 1 (Setup): 7 tasks
|
| 260 |
+
- Phase 2 (Foundational): 10 tasks
|
| 261 |
+
- Phase 3 (User Story 1 - P1): 13 tasks
|
| 262 |
+
- Phase 4 (User Story 2 - P2): 10 tasks
|
| 263 |
+
- Phase 5 (User Story 3 - P3): 4 tasks
|
| 264 |
+
- Phase 6 (User Story 4 - P4): 5 tasks
|
| 265 |
+
- Phase 7 (Polish): 8 tasks
|
| 266 |
+
|
| 267 |
+
**Parallel Opportunities**: 23 tasks marked [P] can run in parallel within their phase
|
| 268 |
+
|
| 269 |
+
**MVP Scope**: Phases 1-3 (30 tasks) deliver User Story 1 - View and Create Tasks
|
| 270 |
+
|
| 271 |
+
**Independent Test Criteria**:
|
| 272 |
+
- US1: Create and view tasks in list
|
| 273 |
+
- US2: Edit task and toggle completion
|
| 274 |
+
- US3: Delete task from list
|
| 275 |
+
- US4: Filter and sort task list
|
specs/001-todo-ai-chatbot/contracts/chat-api.yaml
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openapi: 3.0.3
|
| 2 |
+
info:
|
| 3 |
+
title: Todo AI Chatbot API
|
| 4 |
+
description: |
|
| 5 |
+
API specification for the Todo AI Chatbot conversational interface.
|
| 6 |
+
This API enables stateless conversational AI interactions with database-persisted state.
|
| 7 |
+
version: 1.0.0
|
| 8 |
+
contact:
|
| 9 |
+
name: Phase III Development Team
|
| 10 |
+
|
| 11 |
+
servers:
|
| 12 |
+
- url: http://localhost:8000
|
| 13 |
+
description: Local development server
|
| 14 |
+
- url: https://api.example.com
|
| 15 |
+
description: Production server (TBD)
|
| 16 |
+
|
| 17 |
+
tags:
|
| 18 |
+
- name: chat
|
| 19 |
+
description: Conversational AI endpoints
|
| 20 |
+
|
| 21 |
+
paths:
|
| 22 |
+
/api/{user_id}/chat:
|
| 23 |
+
post:
|
| 24 |
+
tags:
|
| 25 |
+
- chat
|
| 26 |
+
summary: Send message to AI chatbot
|
| 27 |
+
description: |
|
| 28 |
+
Stateless endpoint for conversational AI interaction.
|
| 29 |
+
|
| 30 |
+
**Flow**:
|
| 31 |
+
1. Load conversation history from database
|
| 32 |
+
2. Execute AI agent with full message history
|
| 33 |
+
3. Save user message and assistant response to database
|
| 34 |
+
4. Return assistant response
|
| 35 |
+
|
| 36 |
+
**Authentication**: Requires valid JWT token in Authorization header.
|
| 37 |
+
The user_id in the path must match the authenticated user from the JWT.
|
| 38 |
+
operationId: sendChatMessage
|
| 39 |
+
parameters:
|
| 40 |
+
- name: user_id
|
| 41 |
+
in: path
|
| 42 |
+
required: true
|
| 43 |
+
description: Authenticated user ID (must match JWT token)
|
| 44 |
+
schema:
|
| 45 |
+
type: integer
|
| 46 |
+
example: 123
|
| 47 |
+
security:
|
| 48 |
+
- BearerAuth: []
|
| 49 |
+
requestBody:
|
| 50 |
+
required: true
|
| 51 |
+
content:
|
| 52 |
+
application/json:
|
| 53 |
+
schema:
|
| 54 |
+
$ref: '#/components/schemas/ChatRequest'
|
| 55 |
+
examples:
|
| 56 |
+
simpleMessage:
|
| 57 |
+
summary: Simple user message
|
| 58 |
+
value:
|
| 59 |
+
message: "Hello, can you help me with my tasks?"
|
| 60 |
+
taskIntent:
|
| 61 |
+
summary: Task-related intent
|
| 62 |
+
value:
|
| 63 |
+
message: "I need to add a new task for tomorrow"
|
| 64 |
+
responses:
|
| 65 |
+
'200':
|
| 66 |
+
description: Successful response from AI assistant
|
| 67 |
+
content:
|
| 68 |
+
application/json:
|
| 69 |
+
schema:
|
| 70 |
+
$ref: '#/components/schemas/ChatResponse'
|
| 71 |
+
examples:
|
| 72 |
+
greeting:
|
| 73 |
+
summary: Greeting response
|
| 74 |
+
value:
|
| 75 |
+
response: "Hello! I'm your AI assistant. I can help you manage your tasks through natural conversation. What would you like to do?"
|
| 76 |
+
conversation_id: 456
|
| 77 |
+
timestamp: "2026-01-14T10:30:00Z"
|
| 78 |
+
taskAcknowledgment:
|
| 79 |
+
summary: Task intent acknowledgment
|
| 80 |
+
value:
|
| 81 |
+
response: "I understand you want to add a new task for tomorrow. In Phase 1, I can acknowledge your intent, but task creation will be available in Phase 2. For now, I'm here to chat and help you plan!"
|
| 82 |
+
conversation_id: 456
|
| 83 |
+
timestamp: "2026-01-14T10:31:00Z"
|
| 84 |
+
'400':
|
| 85 |
+
description: Bad request (invalid input)
|
| 86 |
+
content:
|
| 87 |
+
application/json:
|
| 88 |
+
schema:
|
| 89 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 90 |
+
examples:
|
| 91 |
+
emptyMessage:
|
| 92 |
+
summary: Empty message error
|
| 93 |
+
value:
|
| 94 |
+
error: "Bad Request"
|
| 95 |
+
message: "Message content cannot be empty"
|
| 96 |
+
status_code: 400
|
| 97 |
+
'401':
|
| 98 |
+
description: Unauthorized (missing or invalid JWT token)
|
| 99 |
+
content:
|
| 100 |
+
application/json:
|
| 101 |
+
schema:
|
| 102 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 103 |
+
examples:
|
| 104 |
+
missingToken:
|
| 105 |
+
summary: Missing JWT token
|
| 106 |
+
value:
|
| 107 |
+
error: "Unauthorized"
|
| 108 |
+
message: "Missing or invalid authentication token"
|
| 109 |
+
status_code: 401
|
| 110 |
+
userIdMismatch:
|
| 111 |
+
summary: User ID mismatch
|
| 112 |
+
value:
|
| 113 |
+
error: "Unauthorized"
|
| 114 |
+
message: "User ID in path does not match authenticated user"
|
| 115 |
+
status_code: 401
|
| 116 |
+
'429':
|
| 117 |
+
description: Rate limit exceeded (AI provider rate limit)
|
| 118 |
+
content:
|
| 119 |
+
application/json:
|
| 120 |
+
schema:
|
| 121 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 122 |
+
examples:
|
| 123 |
+
rateLimitExceeded:
|
| 124 |
+
summary: Rate limit error
|
| 125 |
+
value:
|
| 126 |
+
error: "Rate Limit Exceeded"
|
| 127 |
+
message: "AI provider rate limit reached. Please wait a moment and try again."
|
| 128 |
+
status_code: 429
|
| 129 |
+
retry_after: 60
|
| 130 |
+
'500':
|
| 131 |
+
description: Internal server error
|
| 132 |
+
content:
|
| 133 |
+
application/json:
|
| 134 |
+
schema:
|
| 135 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 136 |
+
examples:
|
| 137 |
+
aiProviderError:
|
| 138 |
+
summary: AI provider error
|
| 139 |
+
value:
|
| 140 |
+
error: "Internal Server Error"
|
| 141 |
+
message: "Failed to generate AI response. Please try again."
|
| 142 |
+
status_code: 500
|
| 143 |
+
|
| 144 |
+
/api/{user_id}/conversations:
|
| 145 |
+
get:
|
| 146 |
+
tags:
|
| 147 |
+
- chat
|
| 148 |
+
summary: List user conversations
|
| 149 |
+
description: |
|
| 150 |
+
Retrieve all conversations for the authenticated user.
|
| 151 |
+
Conversations are ordered by most recent activity.
|
| 152 |
+
|
| 153 |
+
**Note**: This endpoint is optional for Phase 1 and may be deferred to Phase 2.
|
| 154 |
+
operationId: listConversations
|
| 155 |
+
parameters:
|
| 156 |
+
- name: user_id
|
| 157 |
+
in: path
|
| 158 |
+
required: true
|
| 159 |
+
description: Authenticated user ID
|
| 160 |
+
schema:
|
| 161 |
+
type: integer
|
| 162 |
+
example: 123
|
| 163 |
+
- name: limit
|
| 164 |
+
in: query
|
| 165 |
+
required: false
|
| 166 |
+
description: Maximum number of conversations to return
|
| 167 |
+
schema:
|
| 168 |
+
type: integer
|
| 169 |
+
default: 20
|
| 170 |
+
minimum: 1
|
| 171 |
+
maximum: 100
|
| 172 |
+
- name: offset
|
| 173 |
+
in: query
|
| 174 |
+
required: false
|
| 175 |
+
description: Number of conversations to skip (for pagination)
|
| 176 |
+
schema:
|
| 177 |
+
type: integer
|
| 178 |
+
default: 0
|
| 179 |
+
minimum: 0
|
| 180 |
+
security:
|
| 181 |
+
- BearerAuth: []
|
| 182 |
+
responses:
|
| 183 |
+
'200':
|
| 184 |
+
description: List of conversations
|
| 185 |
+
content:
|
| 186 |
+
application/json:
|
| 187 |
+
schema:
|
| 188 |
+
$ref: '#/components/schemas/ConversationListResponse'
|
| 189 |
+
'401':
|
| 190 |
+
description: Unauthorized
|
| 191 |
+
content:
|
| 192 |
+
application/json:
|
| 193 |
+
schema:
|
| 194 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 195 |
+
|
| 196 |
+
components:
|
| 197 |
+
securitySchemes:
|
| 198 |
+
BearerAuth:
|
| 199 |
+
type: http
|
| 200 |
+
scheme: bearer
|
| 201 |
+
bearerFormat: JWT
|
| 202 |
+
description: |
|
| 203 |
+
JWT token issued by Better Auth.
|
| 204 |
+
Include in Authorization header as: `Bearer <token>`
|
| 205 |
+
|
| 206 |
+
schemas:
|
| 207 |
+
ChatRequest:
|
| 208 |
+
type: object
|
| 209 |
+
required:
|
| 210 |
+
- message
|
| 211 |
+
properties:
|
| 212 |
+
message:
|
| 213 |
+
type: string
|
| 214 |
+
description: User message content
|
| 215 |
+
minLength: 1
|
| 216 |
+
maxLength: 10000
|
| 217 |
+
example: "Hello, can you help me with my tasks?"
|
| 218 |
+
conversation_id:
|
| 219 |
+
type: integer
|
| 220 |
+
description: |
|
| 221 |
+
Optional conversation ID to continue an existing conversation.
|
| 222 |
+
If not provided, the most recent conversation will be used or a new one created.
|
| 223 |
+
example: 456
|
| 224 |
+
example:
|
| 225 |
+
message: "I need to add a new task for tomorrow"
|
| 226 |
+
|
| 227 |
+
ChatResponse:
|
| 228 |
+
type: object
|
| 229 |
+
required:
|
| 230 |
+
- response
|
| 231 |
+
- conversation_id
|
| 232 |
+
- timestamp
|
| 233 |
+
properties:
|
| 234 |
+
response:
|
| 235 |
+
type: string
|
| 236 |
+
description: AI assistant response
|
| 237 |
+
example: "Hello! I'm your AI assistant. How can I help you today?"
|
| 238 |
+
conversation_id:
|
| 239 |
+
type: integer
|
| 240 |
+
description: Conversation ID for this exchange
|
| 241 |
+
example: 456
|
| 242 |
+
timestamp:
|
| 243 |
+
type: string
|
| 244 |
+
format: date-time
|
| 245 |
+
description: Response timestamp (ISO 8601)
|
| 246 |
+
example: "2026-01-14T10:30:00Z"
|
| 247 |
+
metadata:
|
| 248 |
+
type: object
|
| 249 |
+
description: Optional metadata about the response
|
| 250 |
+
properties:
|
| 251 |
+
model:
|
| 252 |
+
type: string
|
| 253 |
+
description: AI model used for response
|
| 254 |
+
example: "gemini-pro"
|
| 255 |
+
token_count:
|
| 256 |
+
type: integer
|
| 257 |
+
description: Estimated token count for this exchange
|
| 258 |
+
example: 150
|
| 259 |
+
example:
|
| 260 |
+
response: "I understand you want to add a task. Task management will be available in Phase 2!"
|
| 261 |
+
conversation_id: 456
|
| 262 |
+
timestamp: "2026-01-14T10:30:00Z"
|
| 263 |
+
|
| 264 |
+
ConversationListResponse:
|
| 265 |
+
type: object
|
| 266 |
+
required:
|
| 267 |
+
- conversations
|
| 268 |
+
- total
|
| 269 |
+
properties:
|
| 270 |
+
conversations:
|
| 271 |
+
type: array
|
| 272 |
+
items:
|
| 273 |
+
$ref: '#/components/schemas/ConversationSummary'
|
| 274 |
+
total:
|
| 275 |
+
type: integer
|
| 276 |
+
description: Total number of conversations for this user
|
| 277 |
+
example: 5
|
| 278 |
+
limit:
|
| 279 |
+
type: integer
|
| 280 |
+
description: Limit applied to this request
|
| 281 |
+
example: 20
|
| 282 |
+
offset:
|
| 283 |
+
type: integer
|
| 284 |
+
description: Offset applied to this request
|
| 285 |
+
example: 0
|
| 286 |
+
|
| 287 |
+
ConversationSummary:
|
| 288 |
+
type: object
|
| 289 |
+
required:
|
| 290 |
+
- id
|
| 291 |
+
- created_at
|
| 292 |
+
- updated_at
|
| 293 |
+
- message_count
|
| 294 |
+
properties:
|
| 295 |
+
id:
|
| 296 |
+
type: integer
|
| 297 |
+
description: Conversation ID
|
| 298 |
+
example: 456
|
| 299 |
+
title:
|
| 300 |
+
type: string
|
| 301 |
+
description: Optional conversation title
|
| 302 |
+
example: "Task Planning Discussion"
|
| 303 |
+
created_at:
|
| 304 |
+
type: string
|
| 305 |
+
format: date-time
|
| 306 |
+
description: Conversation creation timestamp
|
| 307 |
+
example: "2026-01-14T10:00:00Z"
|
| 308 |
+
updated_at:
|
| 309 |
+
type: string
|
| 310 |
+
format: date-time
|
| 311 |
+
description: Last message timestamp
|
| 312 |
+
example: "2026-01-14T10:30:00Z"
|
| 313 |
+
message_count:
|
| 314 |
+
type: integer
|
| 315 |
+
description: Number of messages in this conversation
|
| 316 |
+
example: 10
|
| 317 |
+
last_message_preview:
|
| 318 |
+
type: string
|
| 319 |
+
description: Preview of the last message (first 100 characters)
|
| 320 |
+
example: "I understand you want to add a task. Task management will be available in Phase 2!"
|
| 321 |
+
|
| 322 |
+
ErrorResponse:
|
| 323 |
+
type: object
|
| 324 |
+
required:
|
| 325 |
+
- error
|
| 326 |
+
- message
|
| 327 |
+
- status_code
|
| 328 |
+
properties:
|
| 329 |
+
error:
|
| 330 |
+
type: string
|
| 331 |
+
description: Error type
|
| 332 |
+
example: "Bad Request"
|
| 333 |
+
message:
|
| 334 |
+
type: string
|
| 335 |
+
description: Human-readable error message
|
| 336 |
+
example: "Message content cannot be empty"
|
| 337 |
+
status_code:
|
| 338 |
+
type: integer
|
| 339 |
+
description: HTTP status code
|
| 340 |
+
example: 400
|
| 341 |
+
details:
|
| 342 |
+
type: object
|
| 343 |
+
description: Optional additional error details
|
| 344 |
+
additionalProperties: true
|
| 345 |
+
retry_after:
|
| 346 |
+
type: integer
|
| 347 |
+
description: Seconds to wait before retrying (for rate limit errors)
|
| 348 |
+
example: 60
|
| 349 |
+
example:
|
| 350 |
+
error: "Rate Limit Exceeded"
|
| 351 |
+
message: "AI provider rate limit reached. Please wait a moment and try again."
|
| 352 |
+
status_code: 429
|
| 353 |
+
retry_after: 60
|
| 354 |
+
|
| 355 |
+
examples:
|
| 356 |
+
ChatRequestExample:
|
| 357 |
+
value:
|
| 358 |
+
message: "Hello, can you help me with my tasks?"
|
| 359 |
+
|
| 360 |
+
ChatResponseExample:
|
| 361 |
+
value:
|
| 362 |
+
response: "Hello! I'm your AI assistant. I can help you manage your tasks through natural conversation. What would you like to do?"
|
| 363 |
+
conversation_id: 456
|
| 364 |
+
timestamp: "2026-01-14T10:30:00Z"
|
specs/001-todo-ai-chatbot/data-model.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Model: Todo AI Chatbot - Phase 1
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-todo-ai-chatbot
|
| 4 |
+
**Date**: 2026-01-14
|
| 5 |
+
**Phase**: Phase 1 - Design & Contracts
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Overview
|
| 10 |
+
|
| 11 |
+
This document defines the database schema for the Todo AI Chatbot feature. The data model supports stateless conversational AI with database-persisted state, enabling conversation continuity across page refreshes and server restarts.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Entity Relationship Diagram
|
| 16 |
+
|
| 17 |
+
```
|
| 18 |
+
┌─────────────┐
|
| 19 |
+
│ User │
|
| 20 |
+
│ (existing) │
|
| 21 |
+
└──────┬──────┘
|
| 22 |
+
│ 1
|
| 23 |
+
│
|
| 24 |
+
│ N
|
| 25 |
+
┌──────▼──────────────┐
|
| 26 |
+
│ Conversation │
|
| 27 |
+
│ │
|
| 28 |
+
│ - id (PK) │
|
| 29 |
+
│ - user_id (FK) │
|
| 30 |
+
│ - created_at │
|
| 31 |
+
│ - updated_at │
|
| 32 |
+
│ - title (optional) │
|
| 33 |
+
└──────┬──────────────┘
|
| 34 |
+
│ 1
|
| 35 |
+
│
|
| 36 |
+
│ N
|
| 37 |
+
┌──────▼──────────────┐
|
| 38 |
+
│ Message │
|
| 39 |
+
│ │
|
| 40 |
+
│ - id (PK) │
|
| 41 |
+
│ - conversation_id │
|
| 42 |
+
│ - role │
|
| 43 |
+
│ - content │
|
| 44 |
+
│ - timestamp │
|
| 45 |
+
│ - token_count │
|
| 46 |
+
└─────────────────────┘
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## Entities
|
| 52 |
+
|
| 53 |
+
### 1. Conversation
|
| 54 |
+
|
| 55 |
+
**Purpose**: Represents a conversation session between a user and the AI assistant.
|
| 56 |
+
|
| 57 |
+
**Table Name**: `conversation`
|
| 58 |
+
|
| 59 |
+
**Attributes**:
|
| 60 |
+
|
| 61 |
+
| Field | Type | Constraints | Description |
|
| 62 |
+
|-------|------|-------------|-------------|
|
| 63 |
+
| `id` | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique conversation identifier |
|
| 64 |
+
| `user_id` | Integer | FOREIGN KEY (user.id), NOT NULL, INDEX | Reference to authenticated user |
|
| 65 |
+
| `created_at` | DateTime | NOT NULL, DEFAULT NOW() | Conversation creation timestamp |
|
| 66 |
+
| `updated_at` | DateTime | NOT NULL, DEFAULT NOW(), ON UPDATE NOW() | Last message timestamp |
|
| 67 |
+
| `title` | String(255) | NULLABLE | Optional conversation title (for future UI) |
|
| 68 |
+
|
| 69 |
+
**Relationships**:
|
| 70 |
+
- **User**: Many-to-One (Many conversations belong to one user)
|
| 71 |
+
- **Message**: One-to-Many (One conversation has many messages)
|
| 72 |
+
|
| 73 |
+
**Indexes**:
|
| 74 |
+
- PRIMARY KEY on `id`
|
| 75 |
+
- INDEX on `user_id` (for efficient user conversation queries)
|
| 76 |
+
- INDEX on `updated_at` (for sorting by recency)
|
| 77 |
+
|
| 78 |
+
**Validation Rules**:
|
| 79 |
+
- `user_id` must reference an existing user
|
| 80 |
+
- `created_at` must be <= `updated_at`
|
| 81 |
+
- `title` max length: 255 characters
|
| 82 |
+
|
| 83 |
+
**State Transitions**: None (conversations are created and persist indefinitely)
|
| 84 |
+
|
| 85 |
+
**SQLModel Implementation**:
|
| 86 |
+
```python
|
| 87 |
+
from sqlmodel import SQLModel, Field, Relationship
|
| 88 |
+
from datetime import datetime
|
| 89 |
+
from typing import List, Optional
|
| 90 |
+
|
| 91 |
+
class Conversation(SQLModel, table=True):
|
| 92 |
+
__tablename__ = "conversation"
|
| 93 |
+
|
| 94 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 95 |
+
user_id: int = Field(foreign_key="user.id", index=True)
|
| 96 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
| 97 |
+
updated_at: datetime = Field(
|
| 98 |
+
default_factory=datetime.utcnow,
|
| 99 |
+
sa_column_kwargs={"onupdate": datetime.utcnow}
|
| 100 |
+
)
|
| 101 |
+
title: Optional[str] = Field(default=None, max_length=255)
|
| 102 |
+
|
| 103 |
+
# Relationships
|
| 104 |
+
messages: List["Message"] = Relationship(
|
| 105 |
+
back_populates="conversation",
|
| 106 |
+
sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
| 107 |
+
)
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
### 2. Message
|
| 113 |
+
|
| 114 |
+
**Purpose**: Represents an individual message within a conversation (user or assistant).
|
| 115 |
+
|
| 116 |
+
**Table Name**: `message`
|
| 117 |
+
|
| 118 |
+
**Attributes**:
|
| 119 |
+
|
| 120 |
+
| Field | Type | Constraints | Description |
|
| 121 |
+
|-------|------|-------------|-------------|
|
| 122 |
+
| `id` | Integer | PRIMARY KEY, AUTO_INCREMENT | Unique message identifier |
|
| 123 |
+
| `conversation_id` | Integer | FOREIGN KEY (conversation.id), NOT NULL, INDEX | Reference to parent conversation |
|
| 124 |
+
| `role` | String(20) | NOT NULL, CHECK IN ('user', 'assistant') | Message sender role |
|
| 125 |
+
| `content` | Text | NOT NULL | Message content (unlimited length) |
|
| 126 |
+
| `timestamp` | DateTime | NOT NULL, DEFAULT NOW() | Message creation timestamp |
|
| 127 |
+
| `token_count` | Integer | NULLABLE | Estimated token count (for context management) |
|
| 128 |
+
|
| 129 |
+
**Relationships**:
|
| 130 |
+
- **Conversation**: Many-to-One (Many messages belong to one conversation)
|
| 131 |
+
|
| 132 |
+
**Indexes**:
|
| 133 |
+
- PRIMARY KEY on `id`
|
| 134 |
+
- INDEX on `conversation_id` (for efficient conversation message queries)
|
| 135 |
+
- INDEX on `timestamp` (for chronological ordering)
|
| 136 |
+
- COMPOSITE INDEX on `(conversation_id, timestamp)` (for efficient conversation history retrieval)
|
| 137 |
+
|
| 138 |
+
**Validation Rules**:
|
| 139 |
+
- `conversation_id` must reference an existing conversation
|
| 140 |
+
- `role` must be either 'user' or 'assistant'
|
| 141 |
+
- `content` must not be empty (min length: 1 character)
|
| 142 |
+
- `token_count` must be >= 0 if provided
|
| 143 |
+
|
| 144 |
+
**State Transitions**: None (messages are immutable once created)
|
| 145 |
+
|
| 146 |
+
**SQLModel Implementation**:
|
| 147 |
+
```python
|
| 148 |
+
from sqlmodel import SQLModel, Field, Relationship
|
| 149 |
+
from datetime import datetime
|
| 150 |
+
from typing import Optional
|
| 151 |
+
|
| 152 |
+
class Message(SQLModel, table=True):
|
| 153 |
+
__tablename__ = "message"
|
| 154 |
+
|
| 155 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 156 |
+
conversation_id: int = Field(
|
| 157 |
+
foreign_key="conversation.id",
|
| 158 |
+
index=True
|
| 159 |
+
)
|
| 160 |
+
role: str = Field(max_length=20)
|
| 161 |
+
content: str = Field(sa_column=Column(Text))
|
| 162 |
+
timestamp: datetime = Field(default_factory=datetime.utcnow, index=True)
|
| 163 |
+
token_count: Optional[int] = Field(default=None, ge=0)
|
| 164 |
+
|
| 165 |
+
# Relationships
|
| 166 |
+
conversation: Conversation = Relationship(back_populates="messages")
|
| 167 |
+
|
| 168 |
+
# Validation
|
| 169 |
+
@validator("role")
|
| 170 |
+
def validate_role(cls, v):
|
| 171 |
+
if v not in ["user", "assistant"]:
|
| 172 |
+
raise ValueError("role must be 'user' or 'assistant'")
|
| 173 |
+
return v
|
| 174 |
+
|
| 175 |
+
@validator("content")
|
| 176 |
+
def validate_content(cls, v):
|
| 177 |
+
if not v or len(v.strip()) == 0:
|
| 178 |
+
raise ValueError("content must not be empty")
|
| 179 |
+
return v
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## Database Constraints
|
| 185 |
+
|
| 186 |
+
### Foreign Key Constraints
|
| 187 |
+
|
| 188 |
+
1. **Conversation.user_id → User.id**
|
| 189 |
+
- ON DELETE: CASCADE (delete conversations when user is deleted)
|
| 190 |
+
- ON UPDATE: CASCADE
|
| 191 |
+
|
| 192 |
+
2. **Message.conversation_id → Conversation.id**
|
| 193 |
+
- ON DELETE: CASCADE (delete messages when conversation is deleted)
|
| 194 |
+
- ON UPDATE: CASCADE
|
| 195 |
+
|
| 196 |
+
### Check Constraints
|
| 197 |
+
|
| 198 |
+
1. **Message.role**: Must be 'user' or 'assistant'
|
| 199 |
+
2. **Message.token_count**: Must be >= 0 if not NULL
|
| 200 |
+
3. **Conversation.created_at**: Must be <= updated_at
|
| 201 |
+
|
| 202 |
+
---
|
| 203 |
+
|
| 204 |
+
## Migration Strategy
|
| 205 |
+
|
| 206 |
+
### Initial Migration (Phase 1)
|
| 207 |
+
|
| 208 |
+
**Migration File**: `backend/alembic/versions/001_add_conversation_tables.py`
|
| 209 |
+
|
| 210 |
+
**Operations**:
|
| 211 |
+
1. Create `conversation` table
|
| 212 |
+
2. Create `message` table
|
| 213 |
+
3. Add foreign key constraints
|
| 214 |
+
4. Add indexes
|
| 215 |
+
5. Add check constraints
|
| 216 |
+
|
| 217 |
+
**Rollback Strategy**:
|
| 218 |
+
1. Drop `message` table (cascade will handle foreign keys)
|
| 219 |
+
2. Drop `conversation` table
|
| 220 |
+
|
| 221 |
+
**SQL Preview**:
|
| 222 |
+
```sql
|
| 223 |
+
-- Create conversation table
|
| 224 |
+
CREATE TABLE conversation (
|
| 225 |
+
id SERIAL PRIMARY KEY,
|
| 226 |
+
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
|
| 227 |
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
| 228 |
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
| 229 |
+
title VARCHAR(255),
|
| 230 |
+
CONSTRAINT check_conversation_dates CHECK (created_at <= updated_at)
|
| 231 |
+
);
|
| 232 |
+
|
| 233 |
+
CREATE INDEX idx_conversation_user_id ON conversation(user_id);
|
| 234 |
+
CREATE INDEX idx_conversation_updated_at ON conversation(updated_at);
|
| 235 |
+
|
| 236 |
+
-- Create message table
|
| 237 |
+
CREATE TABLE message (
|
| 238 |
+
id SERIAL PRIMARY KEY,
|
| 239 |
+
conversation_id INTEGER NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
|
| 240 |
+
role VARCHAR(20) NOT NULL CHECK (role IN ('user', 'assistant')),
|
| 241 |
+
content TEXT NOT NULL,
|
| 242 |
+
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
| 243 |
+
token_count INTEGER CHECK (token_count >= 0),
|
| 244 |
+
CONSTRAINT check_message_content CHECK (LENGTH(TRIM(content)) > 0)
|
| 245 |
+
);
|
| 246 |
+
|
| 247 |
+
CREATE INDEX idx_message_conversation_id ON message(conversation_id);
|
| 248 |
+
CREATE INDEX idx_message_timestamp ON message(timestamp);
|
| 249 |
+
CREATE INDEX idx_message_conversation_timestamp ON message(conversation_id, timestamp);
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
---
|
| 253 |
+
|
| 254 |
+
## Data Access Patterns
|
| 255 |
+
|
| 256 |
+
### 1. Create New Conversation
|
| 257 |
+
|
| 258 |
+
**Use Case**: User starts a new chat session
|
| 259 |
+
|
| 260 |
+
**Query Pattern**:
|
| 261 |
+
```python
|
| 262 |
+
conversation = Conversation(user_id=user_id)
|
| 263 |
+
session.add(conversation)
|
| 264 |
+
session.commit()
|
| 265 |
+
session.refresh(conversation)
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
**Performance**: O(1) - Single INSERT
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
### 2. Get or Create Conversation
|
| 273 |
+
|
| 274 |
+
**Use Case**: Chat endpoint retrieves or creates conversation for user
|
| 275 |
+
|
| 276 |
+
**Query Pattern**:
|
| 277 |
+
```python
|
| 278 |
+
conversation = session.exec(
|
| 279 |
+
select(Conversation)
|
| 280 |
+
.where(Conversation.user_id == user_id)
|
| 281 |
+
.order_by(Conversation.updated_at.desc())
|
| 282 |
+
).first()
|
| 283 |
+
|
| 284 |
+
if not conversation:
|
| 285 |
+
conversation = Conversation(user_id=user_id)
|
| 286 |
+
session.add(conversation)
|
| 287 |
+
session.commit()
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
**Performance**: O(1) with index on user_id
|
| 291 |
+
|
| 292 |
+
---
|
| 293 |
+
|
| 294 |
+
### 3. Load Conversation History
|
| 295 |
+
|
| 296 |
+
**Use Case**: Load all messages for a conversation (for AI context)
|
| 297 |
+
|
| 298 |
+
**Query Pattern**:
|
| 299 |
+
```python
|
| 300 |
+
messages = session.exec(
|
| 301 |
+
select(Message)
|
| 302 |
+
.where(Message.conversation_id == conversation_id)
|
| 303 |
+
.order_by(Message.timestamp.asc())
|
| 304 |
+
).all()
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
**Performance**: O(N) where N = number of messages, optimized by composite index
|
| 308 |
+
|
| 309 |
+
---
|
| 310 |
+
|
| 311 |
+
### 4. Add Message to Conversation
|
| 312 |
+
|
| 313 |
+
**Use Case**: Save user or assistant message
|
| 314 |
+
|
| 315 |
+
**Query Pattern**:
|
| 316 |
+
```python
|
| 317 |
+
message = Message(
|
| 318 |
+
conversation_id=conversation_id,
|
| 319 |
+
role=role,
|
| 320 |
+
content=content,
|
| 321 |
+
token_count=estimate_tokens(content)
|
| 322 |
+
)
|
| 323 |
+
session.add(message)
|
| 324 |
+
|
| 325 |
+
# Update conversation timestamp
|
| 326 |
+
conversation.updated_at = datetime.utcnow()
|
| 327 |
+
session.add(conversation)
|
| 328 |
+
|
| 329 |
+
session.commit()
|
| 330 |
+
```
|
| 331 |
+
|
| 332 |
+
**Performance**: O(1) - Two UPDATEs
|
| 333 |
+
|
| 334 |
+
---
|
| 335 |
+
|
| 336 |
+
### 5. Trim Old Messages (Future Enhancement)
|
| 337 |
+
|
| 338 |
+
**Use Case**: Delete old messages to manage database size
|
| 339 |
+
|
| 340 |
+
**Query Pattern**:
|
| 341 |
+
```python
|
| 342 |
+
# Keep only last N messages per conversation
|
| 343 |
+
subquery = (
|
| 344 |
+
select(Message.id)
|
| 345 |
+
.where(Message.conversation_id == conversation_id)
|
| 346 |
+
.order_by(Message.timestamp.desc())
|
| 347 |
+
.limit(MAX_MESSAGES)
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
session.exec(
|
| 351 |
+
delete(Message)
|
| 352 |
+
.where(Message.conversation_id == conversation_id)
|
| 353 |
+
.where(Message.id.not_in(subquery))
|
| 354 |
+
)
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
**Performance**: O(N) where N = total messages in conversation
|
| 358 |
+
|
| 359 |
+
---
|
| 360 |
+
|
| 361 |
+
## Data Retention Policy
|
| 362 |
+
|
| 363 |
+
### Phase 1 (Current)
|
| 364 |
+
|
| 365 |
+
- **Conversations**: Retained indefinitely
|
| 366 |
+
- **Messages**: Retained indefinitely
|
| 367 |
+
- **Rationale**: Hackathon scope; no retention policy needed
|
| 368 |
+
|
| 369 |
+
### Phase 2 (Future Consideration)
|
| 370 |
+
|
| 371 |
+
- **Conversations**: Retain for 90 days of inactivity
|
| 372 |
+
- **Messages**: Retain last 100 messages per conversation
|
| 373 |
+
- **Archived Data**: Move to cold storage after 1 year
|
| 374 |
+
|
| 375 |
+
---
|
| 376 |
+
|
| 377 |
+
## Scalability Considerations
|
| 378 |
+
|
| 379 |
+
### Current Scale (Phase 1)
|
| 380 |
+
|
| 381 |
+
- **Expected Users**: 10-100 (hackathon scope)
|
| 382 |
+
- **Expected Conversations**: 100-1,000
|
| 383 |
+
- **Expected Messages**: 1,000-10,000
|
| 384 |
+
- **Database Size**: <10 MB
|
| 385 |
+
|
| 386 |
+
### Future Scale (Phase 2+)
|
| 387 |
+
|
| 388 |
+
- **Target Users**: 10,000+
|
| 389 |
+
- **Target Conversations**: 100,000+
|
| 390 |
+
- **Target Messages**: 1,000,000+
|
| 391 |
+
- **Database Size**: 1-10 GB
|
| 392 |
+
|
| 393 |
+
### Optimization Strategies
|
| 394 |
+
|
| 395 |
+
1. **Partitioning**: Partition `message` table by `conversation_id` or `timestamp`
|
| 396 |
+
2. **Archiving**: Move old messages to archive table
|
| 397 |
+
3. **Caching**: Cache recent conversation history in Redis
|
| 398 |
+
4. **Read Replicas**: Use read replicas for conversation history queries
|
| 399 |
+
|
| 400 |
+
---
|
| 401 |
+
|
| 402 |
+
## Security Considerations
|
| 403 |
+
|
| 404 |
+
### Data Access Control
|
| 405 |
+
|
| 406 |
+
1. **User Isolation**: All queries MUST filter by authenticated `user_id`
|
| 407 |
+
2. **JWT Verification**: Backend MUST verify JWT before accessing conversation data
|
| 408 |
+
3. **Authorization**: Users can only access their own conversations and messages
|
| 409 |
+
|
| 410 |
+
### Data Privacy
|
| 411 |
+
|
| 412 |
+
1. **PII Handling**: Message content may contain PII; treat as sensitive data
|
| 413 |
+
2. **Encryption**: Database connection MUST use SSL/TLS
|
| 414 |
+
3. **Audit Logging**: Log all conversation access for security auditing (future)
|
| 415 |
+
|
| 416 |
+
### SQL Injection Prevention
|
| 417 |
+
|
| 418 |
+
1. **Parameterized Queries**: SQLModel uses parameterized queries by default
|
| 419 |
+
2. **Input Validation**: Validate all user inputs before database operations
|
| 420 |
+
3. **ORM Usage**: Use SQLModel ORM; avoid raw SQL queries
|
| 421 |
+
|
| 422 |
+
---
|
| 423 |
+
|
| 424 |
+
## Testing Strategy
|
| 425 |
+
|
| 426 |
+
### Unit Tests
|
| 427 |
+
|
| 428 |
+
1. **Model Validation**: Test Conversation and Message model validation rules
|
| 429 |
+
2. **Relationship Tests**: Test cascade deletes and foreign key constraints
|
| 430 |
+
3. **Timestamp Tests**: Test created_at and updated_at behavior
|
| 431 |
+
|
| 432 |
+
### Integration Tests
|
| 433 |
+
|
| 434 |
+
1. **CRUD Operations**: Test create, read, update, delete for both entities
|
| 435 |
+
2. **Query Performance**: Test query performance with sample data
|
| 436 |
+
3. **Constraint Enforcement**: Test foreign key and check constraints
|
| 437 |
+
|
| 438 |
+
### Test Data
|
| 439 |
+
|
| 440 |
+
```python
|
| 441 |
+
# Sample test data
|
| 442 |
+
test_user_id = 1
|
| 443 |
+
|
| 444 |
+
test_conversation = Conversation(
|
| 445 |
+
user_id=test_user_id,
|
| 446 |
+
title="Test Conversation"
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
test_messages = [
|
| 450 |
+
Message(
|
| 451 |
+
conversation_id=test_conversation.id,
|
| 452 |
+
role="user",
|
| 453 |
+
content="Hello, AI assistant!"
|
| 454 |
+
),
|
| 455 |
+
Message(
|
| 456 |
+
conversation_id=test_conversation.id,
|
| 457 |
+
role="assistant",
|
| 458 |
+
content="Hello! How can I help you today?"
|
| 459 |
+
)
|
| 460 |
+
]
|
| 461 |
+
```
|
| 462 |
+
|
| 463 |
+
---
|
| 464 |
+
|
| 465 |
+
## Summary
|
| 466 |
+
|
| 467 |
+
This data model provides:
|
| 468 |
+
|
| 469 |
+
✅ **Stateless Architecture**: All state persisted to database
|
| 470 |
+
✅ **Conversation Continuity**: History survives page refreshes and server restarts
|
| 471 |
+
✅ **User Isolation**: Conversations scoped to authenticated users
|
| 472 |
+
✅ **Scalability**: Indexed for efficient queries
|
| 473 |
+
✅ **Simplicity**: Minimal schema for Phase 1 requirements
|
| 474 |
+
✅ **Extensibility**: Easy to add fields for Phase 2 (e.g., tool calls, metadata)
|
| 475 |
+
|
| 476 |
+
**Next Steps**: Create API contracts (contracts/chat-api.yaml)
|
specs/001-todo-ai-chatbot/plan.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: Todo AI Chatbot - Phase 1
|
| 2 |
+
|
| 3 |
+
**Branch**: `001-todo-ai-chatbot` | **Date**: 2026-01-14 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
**Input**: Feature specification from `/specs/001-todo-ai-chatbot/spec.md`
|
| 5 |
+
|
| 6 |
+
**Note**: This template is filled in by the `/sp.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
| 7 |
+
|
| 8 |
+
## Summary
|
| 9 |
+
|
| 10 |
+
Build a conversational AI chatbot interface that enables users to interact with an AI assistant through natural language. This Phase 1 implementation focuses on establishing the chat UI, basic agent wiring, and conversation persistence, while explicitly deferring MCP tool execution and task CRUD operations to Spec-2. The system must work with free-tier AI API providers and maintain stateless backend architecture with database-persisted conversation state.
|
| 11 |
+
|
| 12 |
+
## Technical Context
|
| 13 |
+
|
| 14 |
+
**Language/Version**: Python 3.11+ (backend), TypeScript/JavaScript (frontend with Next.js 16+)
|
| 15 |
+
**Primary Dependencies**:
|
| 16 |
+
- Backend: FastAPI, SQLModel, OpenAI Agents SDK (or compatible abstraction), Pydantic
|
| 17 |
+
- Frontend: Next.js 16+ (App Router), OpenAI ChatKit, React, Tailwind CSS
|
| 18 |
+
- Database: Neon Serverless PostgreSQL
|
| 19 |
+
- Authentication: Better Auth (JWT tokens)
|
| 20 |
+
|
| 21 |
+
**Storage**: Neon PostgreSQL (conversation and message persistence via SQLModel)
|
| 22 |
+
**Testing**: pytest (backend), Jest/React Testing Library (frontend - NEEDS CLARIFICATION on existing setup)
|
| 23 |
+
**Target Platform**: Web application (desktop and mobile responsive)
|
| 24 |
+
**Project Type**: Web (frontend + backend monorepo structure)
|
| 25 |
+
**Performance Goals**:
|
| 26 |
+
- <5 seconds AI response time under normal conditions
|
| 27 |
+
- Free-tier API compatibility (Gemini, OpenRouter, Cohere)
|
| 28 |
+
- Conversation history persistence with <1 second load time
|
| 29 |
+
|
| 30 |
+
**Constraints**:
|
| 31 |
+
- Stateless backend (no in-memory session storage)
|
| 32 |
+
- Free-tier API rate limits (aggressive context trimming required)
|
| 33 |
+
- No MCP tool execution in Phase 1 (deferred to Spec-2)
|
| 34 |
+
- No task CRUD operations in Phase 1 (deferred to Spec-2)
|
| 35 |
+
- Must preserve existing folder structure (frontend/, backend/)
|
| 36 |
+
- Must work with at least 3 free-tier AI providers
|
| 37 |
+
|
| 38 |
+
**Scale/Scope**:
|
| 39 |
+
- Hackathon project (Phase III of multi-phase development)
|
| 40 |
+
- Single-user conversations (multi-user via JWT authentication)
|
| 41 |
+
- 10+ message conversation history support
|
| 42 |
+
- Foundation for Spec-2 MCP integration
|
| 43 |
+
|
| 44 |
+
## Constitution Check
|
| 45 |
+
|
| 46 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 47 |
+
|
| 48 |
+
### Phase II Core Principles Compliance
|
| 49 |
+
|
| 50 |
+
| Principle | Status | Notes |
|
| 51 |
+
|-----------|--------|-------|
|
| 52 |
+
| **User-Centric Functionality** | ✅ PASS | Chat interface provides clear UX for natural language interaction; conversation persistence ensures data security |
|
| 53 |
+
| **Spec-Driven Development** | ✅ PASS | Following Spec-Kit Plus workflow; spec.md approved; plan.md in progress; tasks.md will follow |
|
| 54 |
+
| **Security & Data Privacy** | ✅ PASS | JWT authentication required for chat endpoint; user_id extracted from token; conversation data filtered by authenticated user |
|
| 55 |
+
| **Scalable Architecture** | ✅ PASS | Stateless API design; database-persisted state; no server-side sessions; horizontal scaling ready |
|
| 56 |
+
| **Maintainable & Consistent Code** | ✅ PASS | Following Next.js App Router patterns; FastAPI + SQLModel standards; Tailwind CSS for styling |
|
| 57 |
+
|
| 58 |
+
### Phase III Constitutional Compliance
|
| 59 |
+
|
| 60 |
+
| Requirement | Status | Notes |
|
| 61 |
+
|-------------|--------|-------|
|
| 62 |
+
| **Mandatory Development Framework** | ✅ PASS | Using Agentic Dev Stack, Spec-Kit Plus, Claude Code with agent-skill alignment |
|
| 63 |
+
| **Stateless FastAPI Backend** | ✅ PASS | POST /api/{user_id}/chat endpoint is stateless; no in-memory session storage |
|
| 64 |
+
| **MCP Server Implementation** | ⚠️ DEFERRED | Explicitly deferred to Spec-2 per feature scope; Phase 1 establishes foundation only |
|
| 65 |
+
| **OpenAI Agents SDK** | ✅ PASS | Will be used for agent reasoning and orchestration (NEEDS CLARIFICATION on specific SDK choice) |
|
| 66 |
+
| **Database-Persisted State** | ✅ PASS | Conversation and Message models persist all state to Neon PostgreSQL |
|
| 67 |
+
| **ChatKit UI** | ✅ PASS | OpenAI ChatKit will be the sole frontend interface for Phase 1 |
|
| 68 |
+
| **Agent & Skill Governance** | ✅ PASS | Conversational AI Architect Agent (agent-behavior-reasoning) and Backend Systems Agent (backend-mcp-tools) will be used |
|
| 69 |
+
| **Stateless Request Cycle** | ✅ PASS | Load history → Execute agent → Store messages → Return response cycle implemented |
|
| 70 |
+
| **Server Restart Resilience** | ✅ PASS | All state persisted to database; no data loss on server restart |
|
| 71 |
+
| **Conversation Continuity** | ✅ PASS | Conversation history persists across page refreshes and server restarts |
|
| 72 |
+
|
| 73 |
+
### Key Standards Compliance
|
| 74 |
+
|
| 75 |
+
| Standard | Status | Notes |
|
| 76 |
+
|----------|--------|-------|
|
| 77 |
+
| **API Compliance** | ✅ PASS | POST /api/{user_id}/chat endpoint; JSON responses; Pydantic validation; error handling |
|
| 78 |
+
| **Database Integrity** | ✅ PASS | Conversation and Message models with foreign keys; SQLModel ORM; migrations tracked |
|
| 79 |
+
| **Frontend Quality** | ✅ PASS | Next.js App Router; responsive design; Tailwind CSS; proper client/server separation |
|
| 80 |
+
| **Authentication** | ✅ PASS | Better Auth JWT tokens; Authorization header; backend JWT verification |
|
| 81 |
+
| **Spec Adherence** | ✅ PASS | All implementation references specs/001-todo-ai-chatbot/ |
|
| 82 |
+
|
| 83 |
+
### Constitutional Violations Requiring Justification
|
| 84 |
+
|
| 85 |
+
**None identified.** All constitutional requirements are met or explicitly deferred per approved scope boundaries.
|
| 86 |
+
|
| 87 |
+
## Project Structure
|
| 88 |
+
|
| 89 |
+
### Documentation (this feature)
|
| 90 |
+
|
| 91 |
+
```text
|
| 92 |
+
specs/001-todo-ai-chatbot/
|
| 93 |
+
├── spec.md # Feature specification (COMPLETED)
|
| 94 |
+
├── plan.md # This file (/sp.plan command output - IN PROGRESS)
|
| 95 |
+
├── research.md # Phase 0 output (/sp.plan command - PENDING)
|
| 96 |
+
├── data-model.md # Phase 1 output (/sp.plan command - PENDING)
|
| 97 |
+
├── quickstart.md # Phase 1 output (/sp.plan command - PENDING)
|
| 98 |
+
├── contracts/ # Phase 1 output (/sp.plan command - PENDING)
|
| 99 |
+
│ └── chat-api.yaml # OpenAPI spec for chat endpoint
|
| 100 |
+
└── tasks.md # Phase 2 output (/sp.tasks command - NOT created by /sp.plan)
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
### Source Code (repository root)
|
| 104 |
+
|
| 105 |
+
```text
|
| 106 |
+
backend/
|
| 107 |
+
├── src/
|
| 108 |
+
│ ├── models/
|
| 109 |
+
│ │ ├── conversation.py # NEW: Conversation SQLModel
|
| 110 |
+
│ │ └── message.py # NEW: Message SQLModel
|
| 111 |
+
│ ├── services/
|
| 112 |
+
│ │ ├── agent_runner.py # NEW: AI agent orchestration service
|
| 113 |
+
│ │ └── conversation_service.py # NEW: Conversation management service
|
| 114 |
+
│ ├── api/
|
| 115 |
+
│ │ └── chat.py # NEW: POST /api/{user_id}/chat endpoint
|
| 116 |
+
│ ├── schemas/
|
| 117 |
+
│ │ ├── chat_request.py # NEW: Pydantic request schema
|
| 118 |
+
│ │ └── chat_response.py # NEW: Pydantic response schema
|
| 119 |
+
│ └── core/
|
| 120 |
+
│ └── config.py # MODIFY: Add AI provider config
|
| 121 |
+
├── tests/
|
| 122 |
+
│ ├── unit/
|
| 123 |
+
│ │ ├── test_conversation_service.py # NEW
|
| 124 |
+
│ │ └── test_agent_runner.py # NEW
|
| 125 |
+
│ └── integration/
|
| 126 |
+
│ └── test_chat_api.py # NEW
|
| 127 |
+
└── requirements.txt # MODIFY: Add OpenAI SDK, ChatKit dependencies
|
| 128 |
+
|
| 129 |
+
frontend/
|
| 130 |
+
├── src/
|
| 131 |
+
│ ├── app/
|
| 132 |
+
│ │ └── chat/
|
| 133 |
+
│ │ └── page.tsx # NEW: Chat page (App Router)
|
| 134 |
+
│ ├── components/
|
| 135 |
+
│ │ ├── chat/
|
| 136 |
+
│ │ │ ├── ChatInterface.tsx # NEW: Main chat component
|
| 137 |
+
│ │ │ ├── MessageList.tsx # NEW: Message display
|
| 138 |
+
│ │ │ ├── MessageInput.tsx # NEW: Input field
|
| 139 |
+
│ │ │ └── TypingIndicator.tsx # NEW: Loading state
|
| 140 |
+
│ │ └── ui/ # Existing UI components
|
| 141 |
+
│ ├── services/
|
| 142 |
+
│ │ └── chatService.ts # NEW: API client for chat endpoint
|
| 143 |
+
│ └── types/
|
| 144 |
+
│ └── chat.ts # NEW: TypeScript types for chat
|
| 145 |
+
├── tests/
|
| 146 |
+
│ └── components/
|
| 147 |
+
│ └── chat/
|
| 148 |
+
│ └── ChatInterface.test.tsx # NEW
|
| 149 |
+
└── package.json # MODIFY: Add ChatKit dependency
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
**Structure Decision**: Web application structure (Option 2) selected. This is a monorepo with separate `backend/` and `frontend/` directories. All new chat-related code will be added within these existing directories, preserving the current folder structure as required by constraints TC-001, TC-002, and TC-003.
|
| 153 |
+
|
| 154 |
+
## Complexity Tracking
|
| 155 |
+
|
| 156 |
+
> **Fill ONLY if Constitution Check has violations that must be justified**
|
| 157 |
+
|
| 158 |
+
**No violations identified.** All constitutional requirements are satisfied or explicitly deferred per approved scope boundaries. No complexity justification required.
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## Phase 0: Research & Clarifications
|
| 163 |
+
|
| 164 |
+
### Unknowns Requiring Research
|
| 165 |
+
|
| 166 |
+
Based on Technical Context analysis, the following items require clarification:
|
| 167 |
+
|
| 168 |
+
1. **Frontend Testing Setup**: Current testing framework and configuration for frontend
|
| 169 |
+
2. **AI Agent SDK Selection**: Specific SDK/abstraction for agent implementation (OpenAI Agents SDK vs alternatives)
|
| 170 |
+
3. **OpenAI ChatKit Compatibility**: Verify ChatKit compatibility with Next.js 16+ App Router
|
| 171 |
+
4. **Free-Tier AI Provider Integration**: Best practices for Gemini, OpenRouter, Cohere integration
|
| 172 |
+
5. **Conversation History Trimming Strategy**: Algorithm for context window management with free-tier limits
|
| 173 |
+
|
| 174 |
+
### Research Tasks
|
| 175 |
+
|
| 176 |
+
✅ **COMPLETED** - See `research.md` for detailed findings.
|
| 177 |
+
|
| 178 |
+
**Key Decisions**:
|
| 179 |
+
1. **AI Agent SDK**: Custom implementation with direct API calls (fastest, stateless, free-tier compatible)
|
| 180 |
+
2. **Chat UI Library**: @assistant-ui/react (Next.js native, no CDN dependencies)
|
| 181 |
+
3. **Primary AI Provider**: Google Gemini (gemini-pro) with OpenRouter fallback
|
| 182 |
+
4. **History Trimming**: Hybrid approach (max 20 messages + 8000 token budget)
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## Phase 1: Architectural Design
|
| 187 |
+
|
| 188 |
+
### Technology Stack (Finalized)
|
| 189 |
+
|
| 190 |
+
| Layer | Technology | Version | Rationale |
|
| 191 |
+
|-------|-----------|---------|-----------|
|
| 192 |
+
| **Frontend Framework** | Next.js | 16+ | Existing stack, App Router support |
|
| 193 |
+
| **Chat UI Library** | @assistant-ui/react | Latest | Next.js native, Tailwind integration, no CDN |
|
| 194 |
+
| **Frontend State** | Vercel AI SDK | Latest | Streaming, React hooks, tool call support |
|
| 195 |
+
| **Backend Framework** | FastAPI | 0.104.1 | Existing stack, async support |
|
| 196 |
+
| **AI Provider** | Google Gemini | gemini-pro | Best free-tier (60 req/min, 32k context) |
|
| 197 |
+
| **AI Implementation** | Custom | N/A | Stateless, simple, fast, provider-agnostic |
|
| 198 |
+
| **Database** | Neon PostgreSQL | N/A | Existing stack, serverless |
|
| 199 |
+
| **ORM** | SQLModel | 0.0.14 | Existing stack, type-safe |
|
| 200 |
+
| **Authentication** | Better Auth | 1.0.0 | Existing stack, JWT tokens |
|
| 201 |
+
|
| 202 |
+
### Backend Architecture
|
| 203 |
+
|
| 204 |
+
**AI Agent Implementation**:
|
| 205 |
+
- Custom implementation with provider abstraction pattern
|
| 206 |
+
- `LLMProvider` abstract base class for multi-provider support
|
| 207 |
+
- `GeminiProvider`, `OpenRouterProvider`, `CohereProvider` implementations
|
| 208 |
+
- `LLMService` factory for provider selection via environment variable
|
| 209 |
+
|
| 210 |
+
**Conversation Management**:
|
| 211 |
+
- Stateless request cycle: Load history → Execute agent → Save messages → Return response
|
| 212 |
+
- Database-persisted state (no in-memory sessions)
|
| 213 |
+
- `ConversationService` handles CRUD operations for conversations and messages
|
| 214 |
+
- Automatic conversation creation on first user message
|
| 215 |
+
|
| 216 |
+
**Provider Configuration**:
|
| 217 |
+
- Environment-based provider selection (`AI_PROVIDER=gemini`)
|
| 218 |
+
- API keys stored in environment variables
|
| 219 |
+
- No code changes required to switch providers
|
| 220 |
+
|
| 221 |
+
**File Structure**:
|
| 222 |
+
```
|
| 223 |
+
backend/src/
|
| 224 |
+
├── models/
|
| 225 |
+
│ ├── conversation.py # Conversation SQLModel
|
| 226 |
+
│ └── message.py # Message SQLModel
|
| 227 |
+
├── services/
|
| 228 |
+
│ ├── providers/
|
| 229 |
+
│ │ ├── base.py # LLMProvider abstract class
|
| 230 |
+
│ │ ├── gemini.py # Gemini implementation
|
| 231 |
+
│ │ └── openrouter.py # OpenRouter implementation (future)
|
| 232 |
+
│ ├── llm_service.py # LLM service with provider factory
|
| 233 |
+
│ └── conversation_service.py # Conversation management
|
| 234 |
+
├── api/routes/
|
| 235 |
+
│ └── chat.py # POST /api/{user_id}/chat endpoint
|
| 236 |
+
└── schemas/
|
| 237 |
+
├── chat_request.py # Pydantic request schema
|
| 238 |
+
└── chat_response.py # Pydantic response schema
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
### Frontend Architecture
|
| 242 |
+
|
| 243 |
+
**Chat UI Library**: @assistant-ui/react
|
| 244 |
+
- Chosen over OpenAI ChatKit due to Next.js App Router compatibility
|
| 245 |
+
- No CDN dependencies, pure React components
|
| 246 |
+
- Native Tailwind CSS integration
|
| 247 |
+
- Vercel AI SDK compatibility for streaming and tool calls
|
| 248 |
+
|
| 249 |
+
**Component Structure**:
|
| 250 |
+
```
|
| 251 |
+
frontend/src/
|
| 252 |
+
├── app/chat/
|
| 253 |
+
│ └── page.tsx # Chat page (App Router)
|
| 254 |
+
├── components/chat/
|
| 255 |
+
│ ├── ChatInterface.tsx # Main chat component (client)
|
| 256 |
+
│ ├── MessageList.tsx # Message display
|
| 257 |
+
│ ├── MessageInput.tsx # Input field
|
| 258 |
+
│ └── TypingIndicator.tsx # Loading state
|
| 259 |
+
├── services/
|
| 260 |
+
│ └── chatService.ts # API client for chat endpoint
|
| 261 |
+
└── types/
|
| 262 |
+
└── chat.ts # TypeScript types
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
**State Management**:
|
| 266 |
+
- React hooks for local state (messages, loading)
|
| 267 |
+
- Vercel AI SDK `useChat` hook for advanced features
|
| 268 |
+
- Optimistic UI updates for better UX
|
| 269 |
+
|
| 270 |
+
### Database Schema
|
| 271 |
+
|
| 272 |
+
**Conversation Model**:
|
| 273 |
+
- `id` (PK), `user_id` (FK), `created_at`, `updated_at`, `title` (optional)
|
| 274 |
+
- One-to-Many relationship with Message
|
| 275 |
+
- Indexed on `user_id` and `updated_at`
|
| 276 |
+
|
| 277 |
+
**Message Model**:
|
| 278 |
+
- `id` (PK), `conversation_id` (FK), `role`, `content`, `timestamp`, `token_count`
|
| 279 |
+
- Many-to-One relationship with Conversation
|
| 280 |
+
- Indexed on `conversation_id` and `timestamp`
|
| 281 |
+
- Composite index on `(conversation_id, timestamp)` for efficient history retrieval
|
| 282 |
+
|
| 283 |
+
**See `data-model.md` for complete schema details.**
|
| 284 |
+
|
| 285 |
+
### API Design
|
| 286 |
+
|
| 287 |
+
**Primary Endpoint**: `POST /api/{user_id}/chat`
|
| 288 |
+
- Stateless endpoint for conversational AI interaction
|
| 289 |
+
- Requires JWT authentication (Bearer token)
|
| 290 |
+
- Request: `{ message: string, conversation_id?: number }`
|
| 291 |
+
- Response: `{ response: string, conversation_id: number, timestamp: string }`
|
| 292 |
+
|
| 293 |
+
**Error Handling**:
|
| 294 |
+
- 400: Bad request (empty message, invalid input)
|
| 295 |
+
- 401: Unauthorized (missing/invalid JWT, user_id mismatch)
|
| 296 |
+
- 429: Rate limit exceeded (AI provider rate limit)
|
| 297 |
+
- 500: Internal server error (AI provider failure)
|
| 298 |
+
|
| 299 |
+
**See `contracts/chat-api.yaml` for complete API specification.**
|
| 300 |
+
|
| 301 |
+
### Conversation History Management
|
| 302 |
+
|
| 303 |
+
**Trimming Strategy**: Hybrid approach
|
| 304 |
+
- Keep last 20 messages (fixed count)
|
| 305 |
+
- Enforce 8000 token budget (conservative for free-tier)
|
| 306 |
+
- Trim from oldest messages if exceeding budget
|
| 307 |
+
- Simple token estimation: 1 token ≈ 4 characters
|
| 308 |
+
|
| 309 |
+
**Implementation**:
|
| 310 |
+
```python
|
| 311 |
+
MAX_MESSAGES = 20
|
| 312 |
+
MAX_TOKENS = 8000
|
| 313 |
+
|
| 314 |
+
def trim_conversation_history(messages: List[Message]) -> List[Dict]:
|
| 315 |
+
recent_messages = messages[-MAX_MESSAGES:]
|
| 316 |
+
formatted = [{"role": m.role, "content": m.content} for m in recent_messages]
|
| 317 |
+
|
| 318 |
+
while estimate_tokens(formatted) > MAX_TOKENS and len(formatted) > 1:
|
| 319 |
+
formatted.pop(0)
|
| 320 |
+
|
| 321 |
+
return formatted
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
### Agent-Skill Alignment
|
| 325 |
+
|
| 326 |
+
**Agents Required**:
|
| 327 |
+
|
| 328 |
+
1. **Conversational AI Architect Agent**
|
| 329 |
+
- **Skill**: `agent-behavior-reasoning`
|
| 330 |
+
- **Responsibilities**: Agent design, intent detection, response quality
|
| 331 |
+
- **Usage**: Design conversational flow, optimize AI responses
|
| 332 |
+
|
| 333 |
+
2. **Backend Systems Agent**
|
| 334 |
+
- **Skill**: `backend-mcp-tools`
|
| 335 |
+
- **Responsibilities**: API implementation, database operations, provider integration
|
| 336 |
+
- **Usage**: Implement chat endpoint, conversation service, LLM service
|
| 337 |
+
|
| 338 |
+
3. **Frontend UI Builder Agent** (Next.js)
|
| 339 |
+
- **Skill**: `nextjs-ui-generator`
|
| 340 |
+
- **Responsibilities**: Chat page, components, API integration
|
| 341 |
+
- **Usage**: Build chat interface, message components, API client
|
| 342 |
+
|
| 343 |
+
4. **Design & Theme Agent**
|
| 344 |
+
- **Skill**: `design-theme`
|
| 345 |
+
- **Responsibilities**: Chat UI styling, visual consistency
|
| 346 |
+
- **Usage**: Apply Tailwind CSS styling, ensure responsive design
|
| 347 |
+
|
| 348 |
+
### Security Considerations
|
| 349 |
+
|
| 350 |
+
**Authentication**:
|
| 351 |
+
- JWT token verification on all chat endpoints
|
| 352 |
+
- User ID in path must match authenticated user from JWT
|
| 353 |
+
- Unauthorized requests return 401
|
| 354 |
+
|
| 355 |
+
**Data Isolation**:
|
| 356 |
+
- All conversation queries filtered by authenticated `user_id`
|
| 357 |
+
- Users cannot access other users' conversations
|
| 358 |
+
- Database foreign keys enforce referential integrity
|
| 359 |
+
|
| 360 |
+
**Input Validation**:
|
| 361 |
+
- Message content: 1-10,000 characters
|
| 362 |
+
- Pydantic schemas validate all inputs
|
| 363 |
+
- SQLModel validators enforce data integrity
|
| 364 |
+
|
| 365 |
+
**API Security**:
|
| 366 |
+
- Rate limiting (future enhancement)
|
| 367 |
+
- CORS configuration for production
|
| 368 |
+
- Environment variables for secrets (never committed)
|
| 369 |
+
|
| 370 |
+
---
|
| 371 |
+
|
| 372 |
+
## Phase 2: Constitution Check (Post-Design)
|
| 373 |
+
|
| 374 |
+
### Re-evaluation After Design
|
| 375 |
+
|
| 376 |
+
| Requirement | Status | Notes |
|
| 377 |
+
|-------------|--------|-------|
|
| 378 |
+
| **Stateless Backend** | ✅ PASS | Confirmed: Load history → Process → Save → Return pattern |
|
| 379 |
+
| **Database-Persisted State** | ✅ PASS | Confirmed: Conversation and Message models with proper relationships |
|
| 380 |
+
| **Free-Tier Compatibility** | ✅ PASS | Confirmed: Gemini primary (60 req/min), OpenRouter fallback |
|
| 381 |
+
| **Agent-Skill Alignment** | ✅ PASS | Confirmed: Conversational AI Architect, Backend Systems, Frontend UI Builder, Design & Theme |
|
| 382 |
+
| **Next.js App Router** | ✅ PASS | Confirmed: @assistant-ui/react compatible, no CDN dependencies |
|
| 383 |
+
| **JWT Authentication** | ✅ PASS | Confirmed: Bearer token verification, user_id validation |
|
| 384 |
+
| **Conversation Continuity** | ✅ PASS | Confirmed: History persists across page refreshes and server restarts |
|
| 385 |
+
|
| 386 |
+
**All constitutional requirements remain satisfied after architectural design.**
|
specs/001-todo-ai-chatbot/quickstart.md
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart Guide: Todo AI Chatbot - Phase 1
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-todo-ai-chatbot
|
| 4 |
+
**Date**: 2026-01-14
|
| 5 |
+
**Audience**: Developers implementing this feature
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Overview
|
| 10 |
+
|
| 11 |
+
This guide provides step-by-step instructions for implementing the Todo AI Chatbot Phase 1 feature. Follow these steps in order to build a working conversational AI interface with database-persisted state.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Prerequisites
|
| 16 |
+
|
| 17 |
+
### Required Tools
|
| 18 |
+
|
| 19 |
+
- Python 3.11+
|
| 20 |
+
- Node.js 18+
|
| 21 |
+
- PostgreSQL (Neon Serverless)
|
| 22 |
+
- Git
|
| 23 |
+
|
| 24 |
+
### Required Access
|
| 25 |
+
|
| 26 |
+
- Google Gemini API key (free tier)
|
| 27 |
+
- Database connection string (Neon PostgreSQL)
|
| 28 |
+
- Better Auth configuration (existing)
|
| 29 |
+
|
| 30 |
+
### Existing Infrastructure
|
| 31 |
+
|
| 32 |
+
- ✅ FastAPI backend running
|
| 33 |
+
- ✅ Next.js frontend running
|
| 34 |
+
- ✅ Database connectivity established
|
| 35 |
+
- ✅ Better Auth JWT authentication working
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Implementation Steps
|
| 40 |
+
|
| 41 |
+
### Day 1: Backend Foundation
|
| 42 |
+
|
| 43 |
+
#### Step 1.1: Install Dependencies
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
cd backend
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
Add to `requirements.txt`:
|
| 50 |
+
|
| 51 |
+
```txt
|
| 52 |
+
google-generativeai==0.3.2 # Gemini API client
|
| 53 |
+
tiktoken==0.5.2 # Token counting (optional)
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Install:
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
pip install -r requirements.txt
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
#### Step 1.2: Configure Environment Variables
|
| 63 |
+
|
| 64 |
+
Add to `backend/.env`:
|
| 65 |
+
|
| 66 |
+
```env
|
| 67 |
+
# AI Provider Configuration
|
| 68 |
+
AI_PROVIDER=gemini
|
| 69 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 70 |
+
|
| 71 |
+
# Conversation Settings
|
| 72 |
+
MAX_CONVERSATION_MESSAGES=20
|
| 73 |
+
MAX_CONVERSATION_TOKENS=8000
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
#### Step 1.3: Create Database Models
|
| 77 |
+
|
| 78 |
+
**File**: `backend/src/models/conversation.py`
|
| 79 |
+
|
| 80 |
+
```python
|
| 81 |
+
from sqlmodel import SQLModel, Field, Relationship
|
| 82 |
+
from datetime import datetime
|
| 83 |
+
from typing import List, Optional
|
| 84 |
+
|
| 85 |
+
class Conversation(SQLModel, table=True):
|
| 86 |
+
__tablename__ = "conversation"
|
| 87 |
+
|
| 88 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 89 |
+
user_id: int = Field(foreign_key="user.id", index=True)
|
| 90 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
| 91 |
+
updated_at: datetime = Field(
|
| 92 |
+
default_factory=datetime.utcnow,
|
| 93 |
+
sa_column_kwargs={"onupdate": datetime.utcnow}
|
| 94 |
+
)
|
| 95 |
+
title: Optional[str] = Field(default=None, max_length=255)
|
| 96 |
+
|
| 97 |
+
messages: List["Message"] = Relationship(
|
| 98 |
+
back_populates="conversation",
|
| 99 |
+
sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
| 100 |
+
)
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
**File**: `backend/src/models/message.py`
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
from sqlmodel import SQLModel, Field, Relationship, Column, Text
|
| 107 |
+
from datetime import datetime
|
| 108 |
+
from typing import Optional
|
| 109 |
+
from pydantic import validator
|
| 110 |
+
|
| 111 |
+
class Message(SQLModel, table=True):
|
| 112 |
+
__tablename__ = "message"
|
| 113 |
+
|
| 114 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 115 |
+
conversation_id: int = Field(foreign_key="conversation.id", index=True)
|
| 116 |
+
role: str = Field(max_length=20)
|
| 117 |
+
content: str = Field(sa_column=Column(Text))
|
| 118 |
+
timestamp: datetime = Field(default_factory=datetime.utcnow, index=True)
|
| 119 |
+
token_count: Optional[int] = Field(default=None, ge=0)
|
| 120 |
+
|
| 121 |
+
conversation: "Conversation" = Relationship(back_populates="messages")
|
| 122 |
+
|
| 123 |
+
@validator("role")
|
| 124 |
+
def validate_role(cls, v):
|
| 125 |
+
if v not in ["user", "assistant"]:
|
| 126 |
+
raise ValueError("role must be 'user' or 'assistant'")
|
| 127 |
+
return v
|
| 128 |
+
|
| 129 |
+
@validator("content")
|
| 130 |
+
def validate_content(cls, v):
|
| 131 |
+
if not v or len(v.strip()) == 0:
|
| 132 |
+
raise ValueError("content must not be empty")
|
| 133 |
+
return v
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
#### Step 1.4: Create Database Migration
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
cd backend
|
| 140 |
+
alembic revision -m "Add conversation and message tables"
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
Edit the generated migration file:
|
| 144 |
+
|
| 145 |
+
```python
|
| 146 |
+
def upgrade():
|
| 147 |
+
op.create_table(
|
| 148 |
+
'conversation',
|
| 149 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
| 150 |
+
sa.Column('user_id', sa.Integer(), nullable=False),
|
| 151 |
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
| 152 |
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
| 153 |
+
sa.Column('title', sa.String(length=255), nullable=True),
|
| 154 |
+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
|
| 155 |
+
sa.PrimaryKeyConstraint('id')
|
| 156 |
+
)
|
| 157 |
+
op.create_index('idx_conversation_user_id', 'conversation', ['user_id'])
|
| 158 |
+
op.create_index('idx_conversation_updated_at', 'conversation', ['updated_at'])
|
| 159 |
+
|
| 160 |
+
op.create_table(
|
| 161 |
+
'message',
|
| 162 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
| 163 |
+
sa.Column('conversation_id', sa.Integer(), nullable=False),
|
| 164 |
+
sa.Column('role', sa.String(length=20), nullable=False),
|
| 165 |
+
sa.Column('content', sa.Text(), nullable=False),
|
| 166 |
+
sa.Column('timestamp', sa.DateTime(), nullable=False),
|
| 167 |
+
sa.Column('token_count', sa.Integer(), nullable=True),
|
| 168 |
+
sa.ForeignKeyConstraint(['conversation_id'], ['conversation.id'], ondelete='CASCADE'),
|
| 169 |
+
sa.PrimaryKeyConstraint('id'),
|
| 170 |
+
sa.CheckConstraint("role IN ('user', 'assistant')", name='check_message_role')
|
| 171 |
+
)
|
| 172 |
+
op.create_index('idx_message_conversation_id', 'message', ['conversation_id'])
|
| 173 |
+
op.create_index('idx_message_timestamp', 'message', ['timestamp'])
|
| 174 |
+
|
| 175 |
+
def downgrade():
|
| 176 |
+
op.drop_table('message')
|
| 177 |
+
op.drop_table('conversation')
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
Run migration:
|
| 181 |
+
|
| 182 |
+
```bash
|
| 183 |
+
alembic upgrade head
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
#### Step 1.5: Create LLM Provider Abstraction
|
| 187 |
+
|
| 188 |
+
**File**: `backend/src/services/providers/base.py`
|
| 189 |
+
|
| 190 |
+
```python
|
| 191 |
+
from abc import ABC, abstractmethod
|
| 192 |
+
from typing import List, Dict
|
| 193 |
+
|
| 194 |
+
class LLMProvider(ABC):
|
| 195 |
+
@abstractmethod
|
| 196 |
+
async def generate_response(
|
| 197 |
+
self,
|
| 198 |
+
messages: List[Dict[str, str]]
|
| 199 |
+
) -> str:
|
| 200 |
+
"""Generate a response from the LLM."""
|
| 201 |
+
pass
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
**File**: `backend/src/services/providers/gemini.py`
|
| 205 |
+
|
| 206 |
+
```python
|
| 207 |
+
import google.generativeai as genai
|
| 208 |
+
from typing import List, Dict
|
| 209 |
+
from .base import LLMProvider
|
| 210 |
+
|
| 211 |
+
class GeminiProvider(LLMProvider):
|
| 212 |
+
def __init__(self, api_key: str):
|
| 213 |
+
genai.configure(api_key=api_key)
|
| 214 |
+
self.model = genai.GenerativeModel('gemini-pro')
|
| 215 |
+
|
| 216 |
+
async def generate_response(self, messages: List[Dict[str, str]]) -> str:
|
| 217 |
+
# Convert messages to Gemini format
|
| 218 |
+
prompt = self._format_messages(messages)
|
| 219 |
+
|
| 220 |
+
# Generate response
|
| 221 |
+
response = await self.model.generate_content_async(prompt)
|
| 222 |
+
return response.text
|
| 223 |
+
|
| 224 |
+
def _format_messages(self, messages: List[Dict[str, str]]) -> str:
|
| 225 |
+
# Format conversation history as a single prompt
|
| 226 |
+
formatted = []
|
| 227 |
+
for msg in messages:
|
| 228 |
+
role = "User" if msg["role"] == "user" else "Assistant"
|
| 229 |
+
formatted.append(f"{role}: {msg['content']}")
|
| 230 |
+
return "\n\n".join(formatted)
|
| 231 |
+
```
|
| 232 |
+
|
| 233 |
+
#### Step 1.6: Create LLM Service
|
| 234 |
+
|
| 235 |
+
**File**: `backend/src/services/llm_service.py`
|
| 236 |
+
|
| 237 |
+
```python
|
| 238 |
+
from typing import List, Dict
|
| 239 |
+
from ..core.config import settings
|
| 240 |
+
from .providers.base import LLMProvider
|
| 241 |
+
from .providers.gemini import GeminiProvider
|
| 242 |
+
|
| 243 |
+
class LLMService:
|
| 244 |
+
def __init__(self):
|
| 245 |
+
self.provider = self._get_provider()
|
| 246 |
+
|
| 247 |
+
def _get_provider(self) -> LLMProvider:
|
| 248 |
+
provider_name = settings.AI_PROVIDER.lower()
|
| 249 |
+
|
| 250 |
+
if provider_name == "gemini":
|
| 251 |
+
return GeminiProvider(api_key=settings.GEMINI_API_KEY)
|
| 252 |
+
else:
|
| 253 |
+
raise ValueError(f"Unsupported AI provider: {provider_name}")
|
| 254 |
+
|
| 255 |
+
async def generate_response(self, messages: List[Dict[str, str]]) -> str:
|
| 256 |
+
return await self.provider.generate_response(messages)
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
#### Step 1.7: Create Conversation Service
|
| 260 |
+
|
| 261 |
+
**File**: `backend/src/services/conversation_service.py`
|
| 262 |
+
|
| 263 |
+
```python
|
| 264 |
+
from sqlmodel import Session, select
|
| 265 |
+
from typing import List, Dict, Optional
|
| 266 |
+
from ..models.conversation import Conversation
|
| 267 |
+
from ..models.message import Message
|
| 268 |
+
from datetime import datetime
|
| 269 |
+
|
| 270 |
+
class ConversationService:
|
| 271 |
+
def __init__(self, session: Session):
|
| 272 |
+
self.session = session
|
| 273 |
+
|
| 274 |
+
def get_or_create_conversation(self, user_id: int) -> Conversation:
|
| 275 |
+
# Get most recent conversation for user
|
| 276 |
+
conversation = self.session.exec(
|
| 277 |
+
select(Conversation)
|
| 278 |
+
.where(Conversation.user_id == user_id)
|
| 279 |
+
.order_by(Conversation.updated_at.desc())
|
| 280 |
+
).first()
|
| 281 |
+
|
| 282 |
+
if not conversation:
|
| 283 |
+
conversation = Conversation(user_id=user_id)
|
| 284 |
+
self.session.add(conversation)
|
| 285 |
+
self.session.commit()
|
| 286 |
+
self.session.refresh(conversation)
|
| 287 |
+
|
| 288 |
+
return conversation
|
| 289 |
+
|
| 290 |
+
def load_conversation_history(
|
| 291 |
+
self,
|
| 292 |
+
conversation_id: int,
|
| 293 |
+
max_messages: int = 20
|
| 294 |
+
) -> List[Dict[str, str]]:
|
| 295 |
+
messages = self.session.exec(
|
| 296 |
+
select(Message)
|
| 297 |
+
.where(Message.conversation_id == conversation_id)
|
| 298 |
+
.order_by(Message.timestamp.desc())
|
| 299 |
+
.limit(max_messages)
|
| 300 |
+
).all()
|
| 301 |
+
|
| 302 |
+
# Reverse to chronological order
|
| 303 |
+
messages = list(reversed(messages))
|
| 304 |
+
|
| 305 |
+
return [
|
| 306 |
+
{"role": msg.role, "content": msg.content}
|
| 307 |
+
for msg in messages
|
| 308 |
+
]
|
| 309 |
+
|
| 310 |
+
def add_message(
|
| 311 |
+
self,
|
| 312 |
+
conversation_id: int,
|
| 313 |
+
role: str,
|
| 314 |
+
content: str
|
| 315 |
+
) -> Message:
|
| 316 |
+
message = Message(
|
| 317 |
+
conversation_id=conversation_id,
|
| 318 |
+
role=role,
|
| 319 |
+
content=content
|
| 320 |
+
)
|
| 321 |
+
self.session.add(message)
|
| 322 |
+
|
| 323 |
+
# Update conversation timestamp
|
| 324 |
+
conversation = self.session.get(Conversation, conversation_id)
|
| 325 |
+
conversation.updated_at = datetime.utcnow()
|
| 326 |
+
self.session.add(conversation)
|
| 327 |
+
|
| 328 |
+
self.session.commit()
|
| 329 |
+
self.session.refresh(message)
|
| 330 |
+
return message
|
| 331 |
+
```
|
| 332 |
+
|
| 333 |
+
#### Step 1.8: Create Pydantic Schemas
|
| 334 |
+
|
| 335 |
+
**File**: `backend/src/schemas/chat_request.py`
|
| 336 |
+
|
| 337 |
+
```python
|
| 338 |
+
from pydantic import BaseModel, Field
|
| 339 |
+
|
| 340 |
+
class ChatRequest(BaseModel):
|
| 341 |
+
message: str = Field(..., min_length=1, max_length=10000)
|
| 342 |
+
conversation_id: Optional[int] = None
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
**File**: `backend/src/schemas/chat_response.py`
|
| 346 |
+
|
| 347 |
+
```python
|
| 348 |
+
from pydantic import BaseModel
|
| 349 |
+
from datetime import datetime
|
| 350 |
+
|
| 351 |
+
class ChatResponse(BaseModel):
|
| 352 |
+
response: str
|
| 353 |
+
conversation_id: int
|
| 354 |
+
timestamp: datetime
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
#### Step 1.9: Create Chat API Endpoint
|
| 358 |
+
|
| 359 |
+
**File**: `backend/src/api/routes/chat.py`
|
| 360 |
+
|
| 361 |
+
```python
|
| 362 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 363 |
+
from sqlmodel import Session
|
| 364 |
+
from ...core.database import get_session
|
| 365 |
+
from ...core.security import get_current_user
|
| 366 |
+
from ...schemas.chat_request import ChatRequest
|
| 367 |
+
from ...schemas.chat_response import ChatResponse
|
| 368 |
+
from ...services.conversation_service import ConversationService
|
| 369 |
+
from ...services.llm_service import LLMService
|
| 370 |
+
from datetime import datetime
|
| 371 |
+
|
| 372 |
+
router = APIRouter()
|
| 373 |
+
|
| 374 |
+
@router.post("/api/{user_id}/chat", response_model=ChatResponse)
|
| 375 |
+
async def chat(
|
| 376 |
+
user_id: int,
|
| 377 |
+
request: ChatRequest,
|
| 378 |
+
session: Session = Depends(get_session),
|
| 379 |
+
current_user = Depends(get_current_user)
|
| 380 |
+
):
|
| 381 |
+
# Verify user_id matches authenticated user
|
| 382 |
+
if current_user.id != user_id:
|
| 383 |
+
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 384 |
+
|
| 385 |
+
# Initialize services
|
| 386 |
+
conversation_service = ConversationService(session)
|
| 387 |
+
llm_service = LLMService()
|
| 388 |
+
|
| 389 |
+
# Get or create conversation
|
| 390 |
+
conversation = conversation_service.get_or_create_conversation(user_id)
|
| 391 |
+
|
| 392 |
+
# Load conversation history
|
| 393 |
+
history = conversation_service.load_conversation_history(conversation.id)
|
| 394 |
+
|
| 395 |
+
# Add user message to history
|
| 396 |
+
history.append({"role": "user", "content": request.message})
|
| 397 |
+
|
| 398 |
+
# Generate AI response
|
| 399 |
+
try:
|
| 400 |
+
ai_response = await llm_service.generate_response(history)
|
| 401 |
+
except Exception as e:
|
| 402 |
+
raise HTTPException(status_code=500, detail="Failed to generate AI response")
|
| 403 |
+
|
| 404 |
+
# Save messages to database
|
| 405 |
+
conversation_service.add_message(conversation.id, "user", request.message)
|
| 406 |
+
conversation_service.add_message(conversation.id, "assistant", ai_response)
|
| 407 |
+
|
| 408 |
+
return ChatResponse(
|
| 409 |
+
response=ai_response,
|
| 410 |
+
conversation_id=conversation.id,
|
| 411 |
+
timestamp=datetime.utcnow()
|
| 412 |
+
)
|
| 413 |
+
```
|
| 414 |
+
|
| 415 |
+
Register the router in `backend/src/main.py`:
|
| 416 |
+
|
| 417 |
+
```python
|
| 418 |
+
from .api.routes import chat
|
| 419 |
+
|
| 420 |
+
app.include_router(chat.router)
|
| 421 |
+
```
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
### Day 2: Frontend Integration
|
| 426 |
+
|
| 427 |
+
#### Step 2.1: Install Dependencies
|
| 428 |
+
|
| 429 |
+
```bash
|
| 430 |
+
cd frontend
|
| 431 |
+
npm install @assistant-ui/react ai
|
| 432 |
+
```
|
| 433 |
+
|
| 434 |
+
#### Step 2.2: Create Chat Service
|
| 435 |
+
|
| 436 |
+
**File**: `frontend/src/services/chatService.ts`
|
| 437 |
+
|
| 438 |
+
```typescript
|
| 439 |
+
export interface ChatMessage {
|
| 440 |
+
role: "user" | "assistant";
|
| 441 |
+
content: string;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
export interface ChatResponse {
|
| 445 |
+
response: string;
|
| 446 |
+
conversation_id: number;
|
| 447 |
+
timestamp: string;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
export async function sendChatMessage(
|
| 451 |
+
userId: number,
|
| 452 |
+
message: string,
|
| 453 |
+
token: string
|
| 454 |
+
): Promise<ChatResponse> {
|
| 455 |
+
const response = await fetch(`/api/${userId}/chat`, {
|
| 456 |
+
method: "POST",
|
| 457 |
+
headers: {
|
| 458 |
+
"Content-Type": "application/json",
|
| 459 |
+
Authorization: `Bearer ${token}`,
|
| 460 |
+
},
|
| 461 |
+
body: JSON.stringify({ message }),
|
| 462 |
+
});
|
| 463 |
+
|
| 464 |
+
if (!response.ok) {
|
| 465 |
+
throw new Error("Failed to send message");
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
return response.json();
|
| 469 |
+
}
|
| 470 |
+
```
|
| 471 |
+
|
| 472 |
+
#### Step 2.3: Create Chat Components
|
| 473 |
+
|
| 474 |
+
**File**: `frontend/src/components/chat/ChatInterface.tsx`
|
| 475 |
+
|
| 476 |
+
```tsx
|
| 477 |
+
"use client";
|
| 478 |
+
|
| 479 |
+
import { useState } from "react";
|
| 480 |
+
import { MessageList } from "./MessageList";
|
| 481 |
+
import { MessageInput } from "./MessageInput";
|
| 482 |
+
import { TypingIndicator } from "./TypingIndicator";
|
| 483 |
+
import { sendChatMessage } from "@/services/chatService";
|
| 484 |
+
|
| 485 |
+
interface Message {
|
| 486 |
+
role: "user" | "assistant";
|
| 487 |
+
content: string;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
export function ChatInterface({
|
| 491 |
+
userId,
|
| 492 |
+
token,
|
| 493 |
+
}: {
|
| 494 |
+
userId: number;
|
| 495 |
+
token: string;
|
| 496 |
+
}) {
|
| 497 |
+
const [messages, setMessages] = useState<Message[]>([]);
|
| 498 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 499 |
+
|
| 500 |
+
const handleSendMessage = async (content: string) => {
|
| 501 |
+
// Add user message optimistically
|
| 502 |
+
const userMessage: Message = { role: "user", content };
|
| 503 |
+
setMessages((prev) => [...prev, userMessage]);
|
| 504 |
+
setIsLoading(true);
|
| 505 |
+
|
| 506 |
+
try {
|
| 507 |
+
const response = await sendChatMessage(userId, content, token);
|
| 508 |
+
|
| 509 |
+
// Add assistant response
|
| 510 |
+
const assistantMessage: Message = {
|
| 511 |
+
role: "assistant",
|
| 512 |
+
content: response.response,
|
| 513 |
+
};
|
| 514 |
+
setMessages((prev) => [...prev, assistantMessage]);
|
| 515 |
+
} catch (error) {
|
| 516 |
+
console.error("Failed to send message:", error);
|
| 517 |
+
// Handle error (show toast, etc.)
|
| 518 |
+
} finally {
|
| 519 |
+
setIsLoading(false);
|
| 520 |
+
}
|
| 521 |
+
};
|
| 522 |
+
|
| 523 |
+
return (
|
| 524 |
+
<div className="flex flex-col h-[600px] w-full max-w-2xl mx-auto border rounded-lg">
|
| 525 |
+
<MessageList messages={messages} />
|
| 526 |
+
{isLoading && <TypingIndicator />}
|
| 527 |
+
<MessageInput onSend={handleSendMessage} disabled={isLoading} />
|
| 528 |
+
</div>
|
| 529 |
+
);
|
| 530 |
+
}
|
| 531 |
+
```
|
| 532 |
+
|
| 533 |
+
**File**: `frontend/src/components/chat/MessageList.tsx`
|
| 534 |
+
|
| 535 |
+
```tsx
|
| 536 |
+
interface Message {
|
| 537 |
+
role: "user" | "assistant";
|
| 538 |
+
content: string;
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
export function MessageList({ messages }: { messages: Message[] }) {
|
| 542 |
+
return (
|
| 543 |
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
| 544 |
+
{messages.map((message, index) => (
|
| 545 |
+
<div
|
| 546 |
+
key={index}
|
| 547 |
+
className={`flex ${
|
| 548 |
+
message.role === "user" ? "justify-end" : "justify-start"
|
| 549 |
+
}`}
|
| 550 |
+
>
|
| 551 |
+
<div
|
| 552 |
+
className={`max-w-[70%] rounded-lg p-3 ${
|
| 553 |
+
message.role === "user"
|
| 554 |
+
? "bg-blue-500 text-white"
|
| 555 |
+
: "bg-gray-200 text-gray-900"
|
| 556 |
+
}`}
|
| 557 |
+
>
|
| 558 |
+
{message.content}
|
| 559 |
+
</div>
|
| 560 |
+
</div>
|
| 561 |
+
))}
|
| 562 |
+
</div>
|
| 563 |
+
);
|
| 564 |
+
}
|
| 565 |
+
```
|
| 566 |
+
|
| 567 |
+
**File**: `frontend/src/components/chat/MessageInput.tsx`
|
| 568 |
+
|
| 569 |
+
```tsx
|
| 570 |
+
"use client";
|
| 571 |
+
|
| 572 |
+
import { useState } from "react";
|
| 573 |
+
|
| 574 |
+
export function MessageInput({
|
| 575 |
+
onSend,
|
| 576 |
+
disabled,
|
| 577 |
+
}: {
|
| 578 |
+
onSend: (message: string) => void;
|
| 579 |
+
disabled: boolean;
|
| 580 |
+
}) {
|
| 581 |
+
const [input, setInput] = useState("");
|
| 582 |
+
|
| 583 |
+
const handleSubmit = (e: React.FormEvent) => {
|
| 584 |
+
e.preventDefault();
|
| 585 |
+
if (input.trim() && !disabled) {
|
| 586 |
+
onSend(input);
|
| 587 |
+
setInput("");
|
| 588 |
+
}
|
| 589 |
+
};
|
| 590 |
+
|
| 591 |
+
return (
|
| 592 |
+
<form onSubmit={handleSubmit} className="border-t p-4">
|
| 593 |
+
<div className="flex gap-2">
|
| 594 |
+
<input
|
| 595 |
+
type="text"
|
| 596 |
+
value={input}
|
| 597 |
+
onChange={(e) => setInput(e.target.value)}
|
| 598 |
+
placeholder="Type your message..."
|
| 599 |
+
disabled={disabled}
|
| 600 |
+
className="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
| 601 |
+
/>
|
| 602 |
+
<button
|
| 603 |
+
type="submit"
|
| 604 |
+
disabled={disabled || !input.trim()}
|
| 605 |
+
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
|
| 606 |
+
>
|
| 607 |
+
Send
|
| 608 |
+
</button>
|
| 609 |
+
</div>
|
| 610 |
+
</form>
|
| 611 |
+
);
|
| 612 |
+
}
|
| 613 |
+
```
|
| 614 |
+
|
| 615 |
+
**File**: `frontend/src/components/chat/TypingIndicator.tsx`
|
| 616 |
+
|
| 617 |
+
```tsx
|
| 618 |
+
export function TypingIndicator() {
|
| 619 |
+
return <div className="px-4 py-2 text-gray-500 text-sm">AI is typing...</div>;
|
| 620 |
+
}
|
| 621 |
+
```
|
| 622 |
+
|
| 623 |
+
#### Step 2.4: Create Chat Page
|
| 624 |
+
|
| 625 |
+
**File**: `frontend/src/app/chat/page.tsx`
|
| 626 |
+
|
| 627 |
+
```tsx
|
| 628 |
+
import { ChatInterface } from "@/components/chat/ChatInterface";
|
| 629 |
+
import { auth } from "@/lib/auth"; // Your Better Auth instance
|
| 630 |
+
import { redirect } from "next/navigation";
|
| 631 |
+
|
| 632 |
+
export default async function ChatPage() {
|
| 633 |
+
const session = await auth();
|
| 634 |
+
|
| 635 |
+
if (!session) {
|
| 636 |
+
redirect("/auth/signin");
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
return (
|
| 640 |
+
<main className="container mx-auto py-8">
|
| 641 |
+
<h1 className="text-3xl font-bold mb-8">AI Chat Assistant</h1>
|
| 642 |
+
<ChatInterface userId={session.user.id} token={session.token} />
|
| 643 |
+
</main>
|
| 644 |
+
);
|
| 645 |
+
}
|
| 646 |
+
```
|
| 647 |
+
|
| 648 |
+
---
|
| 649 |
+
|
| 650 |
+
### Day 3: Testing & Polish
|
| 651 |
+
|
| 652 |
+
#### Step 3.1: Test Backend
|
| 653 |
+
|
| 654 |
+
```bash
|
| 655 |
+
cd backend
|
| 656 |
+
pytest tests/integration/test_chat_api.py -v
|
| 657 |
+
```
|
| 658 |
+
|
| 659 |
+
#### Step 3.2: Test Frontend
|
| 660 |
+
|
| 661 |
+
```bash
|
| 662 |
+
cd frontend
|
| 663 |
+
npm run dev
|
| 664 |
+
```
|
| 665 |
+
|
| 666 |
+
Navigate to `http://localhost:3000/chat` and test:
|
| 667 |
+
|
| 668 |
+
- ✅ Send a message
|
| 669 |
+
- ✅ Receive AI response
|
| 670 |
+
- ✅ Conversation persists on page refresh
|
| 671 |
+
- ✅ Multiple messages maintain context
|
| 672 |
+
|
| 673 |
+
#### Step 3.3: Test Error Handling
|
| 674 |
+
|
| 675 |
+
- Test with invalid JWT token (should return 401)
|
| 676 |
+
- Test with empty message (should return 400)
|
| 677 |
+
- Test with rate limit exceeded (should return 429)
|
| 678 |
+
|
| 679 |
+
---
|
| 680 |
+
|
| 681 |
+
## Verification Checklist
|
| 682 |
+
|
| 683 |
+
- [ ] Database tables created (conversation, message)
|
| 684 |
+
- [ ] Backend API endpoint responds at POST /api/{user_id}/chat
|
| 685 |
+
- [ ] Frontend chat interface renders correctly
|
| 686 |
+
- [ ] Messages persist to database
|
| 687 |
+
- [ ] Conversation history loads on page refresh
|
| 688 |
+
- [ ] AI responses are generated successfully
|
| 689 |
+
- [ ] JWT authentication works correctly
|
| 690 |
+
- [ ] Error handling works for common scenarios
|
| 691 |
+
|
| 692 |
+
---
|
| 693 |
+
|
| 694 |
+
## Troubleshooting
|
| 695 |
+
|
| 696 |
+
### Issue: "Gemini API key invalid"
|
| 697 |
+
|
| 698 |
+
**Solution**: Verify GEMINI_API_KEY in backend/.env
|
| 699 |
+
|
| 700 |
+
### Issue: "Database connection failed"
|
| 701 |
+
|
| 702 |
+
**Solution**: Check DATABASE_URL in backend/.env
|
| 703 |
+
|
| 704 |
+
### Issue: "401 Unauthorized"
|
| 705 |
+
|
| 706 |
+
**Solution**: Verify JWT token is being sent in Authorization header
|
| 707 |
+
|
| 708 |
+
### Issue: "Chat interface not rendering"
|
| 709 |
+
|
| 710 |
+
**Solution**: Ensure 'use client' directive is present in client components
|
| 711 |
+
|
| 712 |
+
---
|
| 713 |
+
|
| 714 |
+
## Next Steps
|
| 715 |
+
|
| 716 |
+
After completing Phase 1:
|
| 717 |
+
|
| 718 |
+
1. Review implementation against spec.md acceptance criteria
|
| 719 |
+
2. Create PHR (Prompt History Record) documenting implementation
|
| 720 |
+
3. Prepare for Phase 2 (Spec-2): MCP tools and task CRUD operations
|
| 721 |
+
|
| 722 |
+
---
|
| 723 |
+
|
| 724 |
+
## Resources
|
| 725 |
+
|
| 726 |
+
- [Gemini API Documentation](https://ai.google.dev/docs)
|
| 727 |
+
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
| 728 |
+
- [Next.js App Router](https://nextjs.org/docs/app)
|
| 729 |
+
- [SQLModel Documentation](https://sqlmodel.tiangolo.com/)
|
specs/001-todo-ai-chatbot/research.md
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Research Findings: Todo AI Chatbot - Phase 1
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-todo-ai-chatbot
|
| 4 |
+
**Date**: 2026-01-14
|
| 5 |
+
**Phase**: Phase 0 - Research & Clarifications
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Research Questions
|
| 10 |
+
|
| 11 |
+
This document consolidates research findings for unknowns identified in the Technical Context:
|
| 12 |
+
|
| 13 |
+
1. Frontend Testing Setup
|
| 14 |
+
2. AI Agent SDK Selection
|
| 15 |
+
3. OpenAI ChatKit Compatibility
|
| 16 |
+
4. Free-Tier AI Provider Integration
|
| 17 |
+
5. Conversation History Trimming Strategy
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## 1. Frontend Testing Setup
|
| 22 |
+
|
| 23 |
+
### Current State
|
| 24 |
+
|
| 25 |
+
**Findings from package.json analysis:**
|
| 26 |
+
- No testing framework currently installed
|
| 27 |
+
- No test scripts defined in package.json
|
| 28 |
+
- Frontend uses Next.js 16+ with TypeScript
|
| 29 |
+
|
| 30 |
+
### Decision
|
| 31 |
+
|
| 32 |
+
**DEFERRED TO IMPLEMENTATION**: Testing setup will be configured during implementation phase. Recommended stack:
|
| 33 |
+
- Jest + React Testing Library for component tests
|
| 34 |
+
- Playwright or Cypress for E2E tests (if needed)
|
| 35 |
+
|
| 36 |
+
**Rationale**: Testing infrastructure is not blocking for Phase 1 planning. Can be added incrementally during implementation.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## 2. AI Agent SDK Selection
|
| 41 |
+
|
| 42 |
+
### Research Summary
|
| 43 |
+
|
| 44 |
+
Evaluated four options for AI agent implementation:
|
| 45 |
+
|
| 46 |
+
| Option | Free-Tier Support | Stateless | FastAPI Integration | Tool Calling | Complexity |
|
| 47 |
+
|--------|------------------|-----------|---------------------|--------------|------------|
|
| 48 |
+
| OpenAI Agents SDK | ❌ No | ⚠️ Custom | ⚠️ Moderate | ✅ Excellent | Low |
|
| 49 |
+
| LangChain | ✅ Yes | ⚠️ Custom | ✅ Good | ✅ Excellent | High |
|
| 50 |
+
| LlamaIndex | ✅ Yes | ⚠️ Custom | ✅ Good | ✅ Good | High |
|
| 51 |
+
| Custom Implementation | ✅ Yes | ✅ Native | ✅ Excellent | ⚠️ Manual | Low |
|
| 52 |
+
|
| 53 |
+
### Decision
|
| 54 |
+
|
| 55 |
+
**SELECTED: Custom Implementation with Direct API Calls**
|
| 56 |
+
|
| 57 |
+
**Rationale**:
|
| 58 |
+
1. **Meets all Phase 1 requirements**: Supports free-tier providers (Gemini, OpenRouter, Cohere), stateless operation, FastAPI integration
|
| 59 |
+
2. **Fastest implementation**: Can build working chat in 1-2 days (critical for hackathon timeline)
|
| 60 |
+
3. **Minimal complexity**: No framework abstractions to learn; transparent behavior
|
| 61 |
+
4. **Perfect for stateless requirement**: Native database-driven state management (FR-018)
|
| 62 |
+
5. **Aligns with constraints**: TC-007 (free-tier), BC-001 (time constraints)
|
| 63 |
+
|
| 64 |
+
**Implementation Approach**:
|
| 65 |
+
```python
|
| 66 |
+
# Abstract provider interface
|
| 67 |
+
class LLMProvider(ABC):
|
| 68 |
+
@abstractmethod
|
| 69 |
+
async def generate_response(
|
| 70 |
+
self,
|
| 71 |
+
messages: List[Dict[str, str]]
|
| 72 |
+
) -> str:
|
| 73 |
+
pass
|
| 74 |
+
|
| 75 |
+
# Provider implementations
|
| 76 |
+
class GeminiProvider(LLMProvider): ...
|
| 77 |
+
class CohereProvider(LLMProvider): ...
|
| 78 |
+
class OpenRouterProvider(LLMProvider): ...
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
**Phase 2 Consideration**: If tool orchestration becomes complex in Spec-2, re-evaluate migration to LangChain for built-in agent patterns.
|
| 82 |
+
|
| 83 |
+
### Alternatives Considered
|
| 84 |
+
|
| 85 |
+
**LangChain**:
|
| 86 |
+
- **Pros**: Excellent tool calling, large ecosystem, multi-provider support
|
| 87 |
+
- **Cons**: Steep learning curve, requires custom stateless implementation, may be over-engineered for Phase 1
|
| 88 |
+
- **Verdict**: Good for Phase 2 if tool complexity justifies it
|
| 89 |
+
|
| 90 |
+
**OpenAI Agents SDK**:
|
| 91 |
+
- **Pros**: Excellent documentation, native tool calling
|
| 92 |
+
- **Cons**: No free-tier support (critical blocker), OpenAI-only
|
| 93 |
+
- **Verdict**: Rejected due to FR-025 violation
|
| 94 |
+
|
| 95 |
+
**LlamaIndex**:
|
| 96 |
+
- **Pros**: Multi-provider support, good tool calling
|
| 97 |
+
- **Cons**: Focused on RAG/document search (not pure chat), less mature chat features
|
| 98 |
+
- **Verdict**: Misaligned with requirements
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## 3. OpenAI ChatKit Compatibility
|
| 103 |
+
|
| 104 |
+
### Research Summary
|
| 105 |
+
|
| 106 |
+
**OpenAI ChatKit (@openai/chatkit-react v1.4.1)**:
|
| 107 |
+
- ✅ React 18.2.0 compatible
|
| 108 |
+
- ✅ TypeScript support
|
| 109 |
+
- ⚠️ **CRITICAL ISSUE**: Web component architecture incompatible with Next.js App Router
|
| 110 |
+
- ❌ Requires CDN script loading
|
| 111 |
+
- ❌ Cannot be used in Server Components
|
| 112 |
+
- ❌ No official Next.js documentation
|
| 113 |
+
|
| 114 |
+
**Compatibility Issues**:
|
| 115 |
+
1. Uses custom web component (`<openai-chatkit>`) requiring browser APIs
|
| 116 |
+
2. Requires external CDN script: `https://cdn.platform.openai.com/deployments/chatkit/chatkit.js`
|
| 117 |
+
3. All official examples use Vite, not Next.js
|
| 118 |
+
4. Potential SSR/hydration issues
|
| 119 |
+
|
| 120 |
+
### Decision
|
| 121 |
+
|
| 122 |
+
**REJECTED: OpenAI ChatKit**
|
| 123 |
+
**SELECTED: @assistant-ui/react**
|
| 124 |
+
|
| 125 |
+
**Rationale**:
|
| 126 |
+
1. **Next.js App Router native**: Built specifically for Next.js with full SSR support
|
| 127 |
+
2. **No CDN dependencies**: Pure React components, no external scripts
|
| 128 |
+
3. **Tailwind CSS integration**: Matches existing frontend stack
|
| 129 |
+
4. **Vercel AI SDK compatible**: Enables streaming responses and tool calls
|
| 130 |
+
5. **Shadcn UI style**: Compatible with existing Radix UI components
|
| 131 |
+
6. **Active development**: Well-maintained with strong community support
|
| 132 |
+
|
| 133 |
+
**Implementation Approach**:
|
| 134 |
+
```bash
|
| 135 |
+
npm install @assistant-ui/react ai
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
```tsx
|
| 139 |
+
// app/chat/page.tsx
|
| 140 |
+
'use client';
|
| 141 |
+
import { Thread } from '@assistant-ui/react';
|
| 142 |
+
import { useChat } from 'ai/react';
|
| 143 |
+
|
| 144 |
+
export default function ChatPage() {
|
| 145 |
+
const chat = useChat({ api: '/api/chat' });
|
| 146 |
+
return <Thread />;
|
| 147 |
+
}
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### Alternatives Considered
|
| 151 |
+
|
| 152 |
+
**@chatscope/chat-ui-kit-react**:
|
| 153 |
+
- **Pros**: Pure React, extensive customization
|
| 154 |
+
- **Cons**: Less Next.js-specific, more manual setup
|
| 155 |
+
- **Verdict**: Good alternative but @assistant-ui/react is better fit
|
| 156 |
+
|
| 157 |
+
**stream-chat-react**:
|
| 158 |
+
- **Pros**: Enterprise-grade, real-time messaging
|
| 159 |
+
- **Cons**: Overkill for requirements, external service dependency
|
| 160 |
+
- **Verdict**: Too complex for hackathon scope
|
| 161 |
+
|
| 162 |
+
---
|
| 163 |
+
|
| 164 |
+
## 4. Free-Tier AI Provider Integration
|
| 165 |
+
|
| 166 |
+
### Research Summary
|
| 167 |
+
|
| 168 |
+
**Supported Providers**:
|
| 169 |
+
|
| 170 |
+
| Provider | Free Tier | Rate Limits | Context Window | Best For |
|
| 171 |
+
|----------|-----------|-------------|----------------|----------|
|
| 172 |
+
| **Google Gemini** | ✅ Yes | 60 req/min | 32k tokens | General chat, fast responses |
|
| 173 |
+
| **OpenRouter** | ✅ Yes (some models) | Varies by model | Varies | Model flexibility, fallback |
|
| 174 |
+
| **Cohere** | ✅ Yes (trial) | 100 req/min | 4k tokens | Command models, structured output |
|
| 175 |
+
|
| 176 |
+
### Decision
|
| 177 |
+
|
| 178 |
+
**PRIMARY: Google Gemini (gemini-pro)**
|
| 179 |
+
**FALLBACK: OpenRouter (free models)**
|
| 180 |
+
|
| 181 |
+
**Rationale**:
|
| 182 |
+
1. **Gemini**: Best free-tier offering (60 req/min, 32k context, no credit card required)
|
| 183 |
+
2. **OpenRouter**: Good fallback with multiple free models
|
| 184 |
+
3. **Cohere**: Trial-based, less suitable for long-term development
|
| 185 |
+
|
| 186 |
+
**Implementation Strategy**:
|
| 187 |
+
```python
|
| 188 |
+
# backend/src/core/config.py
|
| 189 |
+
class Settings(BaseSettings):
|
| 190 |
+
AI_PROVIDER: str = "gemini" # gemini | openrouter | cohere
|
| 191 |
+
GEMINI_API_KEY: Optional[str] = None
|
| 192 |
+
OPENROUTER_API_KEY: Optional[str] = None
|
| 193 |
+
COHERE_API_KEY: Optional[str] = None
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
**Provider Selection Logic**:
|
| 197 |
+
- Environment variable determines active provider
|
| 198 |
+
- Easy switching for testing and rate limit management
|
| 199 |
+
- No code changes required to switch providers
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## 5. Conversation History Trimming Strategy
|
| 204 |
+
|
| 205 |
+
### Research Summary
|
| 206 |
+
|
| 207 |
+
**Free-Tier Context Limits**:
|
| 208 |
+
- Gemini: 32k tokens (~24k words)
|
| 209 |
+
- OpenRouter: Varies (4k-32k depending on model)
|
| 210 |
+
- Cohere: 4k tokens (~3k words)
|
| 211 |
+
|
| 212 |
+
**Trimming Strategies Evaluated**:
|
| 213 |
+
|
| 214 |
+
1. **Fixed Message Count**: Keep last N messages
|
| 215 |
+
- **Pros**: Simple, predictable
|
| 216 |
+
- **Cons**: Doesn't account for message length variance
|
| 217 |
+
|
| 218 |
+
2. **Token-Based Trimming**: Keep messages within token budget
|
| 219 |
+
- **Pros**: Precise, maximizes context usage
|
| 220 |
+
- **Cons**: Requires token counting library
|
| 221 |
+
|
| 222 |
+
3. **Sliding Window**: Keep recent messages + system prompt
|
| 223 |
+
- **Pros**: Balances context and recency
|
| 224 |
+
- **Cons**: May lose important context
|
| 225 |
+
|
| 226 |
+
4. **Summarization**: Summarize old messages
|
| 227 |
+
- **Pros**: Preserves context semantically
|
| 228 |
+
- **Cons**: Requires additional API calls, complexity
|
| 229 |
+
|
| 230 |
+
### Decision
|
| 231 |
+
|
| 232 |
+
**SELECTED: Hybrid Approach (Fixed Count + Token Budget)**
|
| 233 |
+
|
| 234 |
+
**Implementation**:
|
| 235 |
+
```python
|
| 236 |
+
MAX_MESSAGES = 20 # Keep last 20 messages
|
| 237 |
+
MAX_TOKENS = 8000 # Conservative limit for free-tier
|
| 238 |
+
|
| 239 |
+
def trim_conversation_history(messages: List[Message]) -> List[Dict]:
|
| 240 |
+
# Step 1: Keep only last MAX_MESSAGES
|
| 241 |
+
recent_messages = messages[-MAX_MESSAGES:]
|
| 242 |
+
|
| 243 |
+
# Step 2: Estimate tokens (rough: 1 token ≈ 4 chars)
|
| 244 |
+
formatted = [{"role": m.role, "content": m.content} for m in recent_messages]
|
| 245 |
+
|
| 246 |
+
# Step 3: Trim from oldest if exceeding token budget
|
| 247 |
+
while estimate_tokens(formatted) > MAX_TOKENS and len(formatted) > 1:
|
| 248 |
+
formatted.pop(0) # Remove oldest message
|
| 249 |
+
|
| 250 |
+
return formatted
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
**Rationale**:
|
| 254 |
+
1. **Simple to implement**: No external token counting library needed
|
| 255 |
+
2. **Conservative limits**: Ensures compatibility with all providers
|
| 256 |
+
3. **Predictable behavior**: Users understand "last 20 messages" concept
|
| 257 |
+
4. **Room for growth**: Can add token counting library later if needed
|
| 258 |
+
|
| 259 |
+
**Phase 2 Enhancement**: Consider adding conversation summarization for long-running conversations.
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## Architectural Decisions Summary
|
| 264 |
+
|
| 265 |
+
### Backend Architecture
|
| 266 |
+
|
| 267 |
+
**AI Agent Implementation**: Custom implementation with provider abstraction
|
| 268 |
+
- `backend/src/services/providers/base.py` - Abstract provider interface
|
| 269 |
+
- `backend/src/services/providers/gemini.py` - Gemini implementation
|
| 270 |
+
- `backend/src/services/providers/openrouter.py` - OpenRouter implementation
|
| 271 |
+
- `backend/src/services/llm_service.py` - LLM service layer with provider factory
|
| 272 |
+
|
| 273 |
+
**Conversation Management**: Stateless with database persistence
|
| 274 |
+
- Load conversation history from database on each request
|
| 275 |
+
- Execute AI agent with full history
|
| 276 |
+
- Save new messages to database
|
| 277 |
+
- Return response to frontend
|
| 278 |
+
|
| 279 |
+
**Provider Configuration**: Environment-based selection
|
| 280 |
+
- `AI_PROVIDER` environment variable determines active provider
|
| 281 |
+
- API keys stored in environment variables
|
| 282 |
+
- No code changes required to switch providers
|
| 283 |
+
|
| 284 |
+
### Frontend Architecture
|
| 285 |
+
|
| 286 |
+
**Chat UI Library**: @assistant-ui/react
|
| 287 |
+
- Native Next.js App Router support
|
| 288 |
+
- Tailwind CSS integration
|
| 289 |
+
- Vercel AI SDK compatibility
|
| 290 |
+
- No CDN dependencies
|
| 291 |
+
|
| 292 |
+
**Component Structure**:
|
| 293 |
+
- `frontend/src/app/chat/page.tsx` - Chat page (App Router)
|
| 294 |
+
- `frontend/src/components/chat/ChatInterface.tsx` - Main chat component
|
| 295 |
+
- `frontend/src/services/chatService.ts` - API client
|
| 296 |
+
|
| 297 |
+
**State Management**: React hooks + Vercel AI SDK
|
| 298 |
+
- `useChat` hook for message state
|
| 299 |
+
- Streaming responses support
|
| 300 |
+
- Optimistic UI updates
|
| 301 |
+
|
| 302 |
+
### Database Schema
|
| 303 |
+
|
| 304 |
+
**Conversation Model**:
|
| 305 |
+
```python
|
| 306 |
+
class Conversation(SQLModel, table=True):
|
| 307 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 308 |
+
user_id: int = Field(foreign_key="user.id")
|
| 309 |
+
created_at: datetime
|
| 310 |
+
updated_at: datetime
|
| 311 |
+
messages: List["Message"] = Relationship(back_populates="conversation")
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
**Message Model**:
|
| 315 |
+
```python
|
| 316 |
+
class Message(SQLModel, table=True):
|
| 317 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 318 |
+
conversation_id: int = Field(foreign_key="conversation.id")
|
| 319 |
+
role: str # "user" or "assistant"
|
| 320 |
+
content: str
|
| 321 |
+
timestamp: datetime
|
| 322 |
+
conversation: Conversation = Relationship(back_populates="messages")
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
---
|
| 326 |
+
|
| 327 |
+
## Technology Stack Finalized
|
| 328 |
+
|
| 329 |
+
| Layer | Technology | Version | Rationale |
|
| 330 |
+
|-------|-----------|---------|-----------|
|
| 331 |
+
| **Frontend Framework** | Next.js | 16+ | Existing stack, App Router support |
|
| 332 |
+
| **Frontend UI** | @assistant-ui/react | Latest | Next.js native, Tailwind integration |
|
| 333 |
+
| **Frontend State** | Vercel AI SDK | Latest | Streaming, tool calls, React hooks |
|
| 334 |
+
| **Backend Framework** | FastAPI | 0.104.1 | Existing stack, async support |
|
| 335 |
+
| **AI Provider** | Google Gemini | gemini-pro | Best free-tier offering |
|
| 336 |
+
| **AI Implementation** | Custom | N/A | Stateless, simple, fast |
|
| 337 |
+
| **Database** | Neon PostgreSQL | N/A | Existing stack, serverless |
|
| 338 |
+
| **ORM** | SQLModel | 0.0.14 | Existing stack, type-safe |
|
| 339 |
+
| **Authentication** | Better Auth | 1.0.0 | Existing stack, JWT tokens |
|
| 340 |
+
|
| 341 |
+
---
|
| 342 |
+
|
| 343 |
+
## Implementation Priorities
|
| 344 |
+
|
| 345 |
+
### Phase 1 (Current Spec) - Days 1-3
|
| 346 |
+
|
| 347 |
+
1. **Day 1: Backend Foundation**
|
| 348 |
+
- Create Conversation and Message models
|
| 349 |
+
- Implement Gemini provider
|
| 350 |
+
- Create LLM service layer
|
| 351 |
+
- Build chat API endpoint
|
| 352 |
+
|
| 353 |
+
2. **Day 2: Frontend Integration**
|
| 354 |
+
- Install @assistant-ui/react
|
| 355 |
+
- Create chat page and components
|
| 356 |
+
- Integrate with backend API
|
| 357 |
+
- Implement conversation persistence
|
| 358 |
+
|
| 359 |
+
3. **Day 3: Testing & Polish**
|
| 360 |
+
- Test with Gemini API
|
| 361 |
+
- Add error handling
|
| 362 |
+
- Implement history trimming
|
| 363 |
+
- Test responsive design
|
| 364 |
+
|
| 365 |
+
### Phase 2 (Spec-2) - Future
|
| 366 |
+
|
| 367 |
+
- MCP server implementation
|
| 368 |
+
- Task CRUD tools
|
| 369 |
+
- Tool execution capabilities
|
| 370 |
+
- Advanced agent orchestration
|
| 371 |
+
|
| 372 |
+
---
|
| 373 |
+
|
| 374 |
+
## Risks & Mitigations
|
| 375 |
+
|
| 376 |
+
| Risk | Impact | Mitigation |
|
| 377 |
+
|------|--------|------------|
|
| 378 |
+
| **Gemini API rate limits** | High | Implement OpenRouter fallback, aggressive history trimming |
|
| 379 |
+
| **@assistant-ui/react learning curve** | Medium | Allocate time for documentation review, use examples |
|
| 380 |
+
| **Custom AI implementation complexity** | Medium | Start simple, iterate based on needs |
|
| 381 |
+
| **Conversation history growth** | Low | Implement trimming from day 1, monitor database size |
|
| 382 |
+
|
| 383 |
+
---
|
| 384 |
+
|
| 385 |
+
## Open Questions
|
| 386 |
+
|
| 387 |
+
**None remaining.** All critical unknowns have been resolved through research.
|
| 388 |
+
|
| 389 |
+
---
|
| 390 |
+
|
| 391 |
+
## Next Steps
|
| 392 |
+
|
| 393 |
+
1. ✅ Research complete
|
| 394 |
+
2. ⏭️ Update plan.md with architectural decisions
|
| 395 |
+
3. ⏭️ Create data-model.md
|
| 396 |
+
4. ⏭️ Create API contracts (contracts/chat-api.yaml)
|
| 397 |
+
5. ⏭️ Create quickstart.md
|
| 398 |
+
6. ⏭️ Generate tasks.md (/sp.tasks command)
|
specs/001-todo-ai-chatbot/spec.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: Todo AI Chatbot - Phase 1 (Conversational UI + Basic Agent Wiring)
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `001-todo-ai-chatbot`
|
| 4 |
+
**Created**: 2026-01-13
|
| 5 |
+
**Status**: Draft
|
| 6 |
+
**Input**: User description: "Todo-AI-Chatbot – Spec 1 (Conversational UI + Basic Agent Wiring) - Building the Todo AI Chatbot user-facing experience and basic AI agent wiring. This spec focuses on frontend UI, conversational flow, and initial agent integration, while strictly preserving the existing project folder structure."
|
| 7 |
+
|
| 8 |
+
## User Scenarios & Testing *(mandatory)*
|
| 9 |
+
|
| 10 |
+
### User Story 1 - Basic Chat Interaction (Priority: P1)
|
| 11 |
+
|
| 12 |
+
As a user, I want to interact with an AI chatbot through a conversational interface so that I can communicate naturally about my todo tasks without learning complex UI patterns.
|
| 13 |
+
|
| 14 |
+
**Why this priority**: This is the foundational capability that enables all other features. Without a working chat interface, users cannot interact with the AI assistant at all. This represents the minimum viable product.
|
| 15 |
+
|
| 16 |
+
**Independent Test**: Can be fully tested by opening the chat page, sending a message, and receiving a response from the AI agent. Delivers immediate value by establishing the conversational interaction pattern.
|
| 17 |
+
|
| 18 |
+
**Acceptance Scenarios**:
|
| 19 |
+
|
| 20 |
+
1. **Given** I am on the chat page, **When** I type a message and press send, **Then** my message appears in the chat history and the AI responds with a relevant reply
|
| 21 |
+
2. **Given** I have sent a message, **When** the AI is processing my request, **Then** I see a typing indicator showing the AI is working
|
| 22 |
+
3. **Given** I am using a mobile device, **When** I access the chat interface, **Then** the UI adapts responsively to my screen size
|
| 23 |
+
4. **Given** I have an active conversation, **When** I refresh the page, **Then** my conversation history persists and I can continue where I left off
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
### User Story 2 - Todo Intent Recognition (Priority: P2)
|
| 28 |
+
|
| 29 |
+
As a user, I want the AI to understand and acknowledge my todo-related requests so that I know the system recognizes my intent even if it cannot execute actions yet.
|
| 30 |
+
|
| 31 |
+
**Why this priority**: This validates that the AI agent can interpret user intent correctly, which is essential before adding tool execution capabilities in Spec-2. It provides user confidence that the system understands their needs.
|
| 32 |
+
|
| 33 |
+
**Independent Test**: Can be tested by sending various todo-related messages (e.g., "add a task", "show my tasks", "mark task as done") and verifying the AI acknowledges the intent with appropriate responses.
|
| 34 |
+
|
| 35 |
+
**Acceptance Scenarios**:
|
| 36 |
+
|
| 37 |
+
1. **Given** I am chatting with the AI, **When** I say "I need to add a new task", **Then** the AI acknowledges my intent and explains what it can do
|
| 38 |
+
2. **Given** I ask about my existing tasks, **When** the AI responds, **Then** it provides a friendly explanation that task management will be available soon
|
| 39 |
+
3. **Given** I use ambiguous language, **When** the AI is unsure of my intent, **Then** it asks clarifying questions to better understand my needs
|
| 40 |
+
4. **Given** I make a general inquiry, **When** the AI responds, **Then** it maintains a conversational and helpful tone
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
### User Story 3 - Multi-Turn Conversations (Priority: P3)
|
| 45 |
+
|
| 46 |
+
As a user, I want to have multi-turn conversations with the AI where it remembers context from earlier messages so that I can have natural, flowing discussions without repeating myself.
|
| 47 |
+
|
| 48 |
+
**Why this priority**: This enhances the conversational experience by making interactions feel more natural and human-like. While important for user experience, it's not critical for the initial MVP.
|
| 49 |
+
|
| 50 |
+
**Independent Test**: Can be tested by having a conversation with multiple back-and-forth exchanges and verifying the AI maintains context throughout the conversation.
|
| 51 |
+
|
| 52 |
+
**Acceptance Scenarios**:
|
| 53 |
+
|
| 54 |
+
1. **Given** I have mentioned a specific task in a previous message, **When** I refer to "that task" in a follow-up message, **Then** the AI understands the reference from context
|
| 55 |
+
2. **Given** I am in the middle of a conversation, **When** I ask a follow-up question, **Then** the AI responds based on the full conversation history
|
| 56 |
+
3. **Given** I have a long conversation history, **When** the context window limit is approached, **Then** the system gracefully trims older messages while preserving recent context
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
### User Story 4 - Free-Tier API Compatibility (Priority: P1)
|
| 61 |
+
|
| 62 |
+
As a developer/user, I want the system to work reliably with free-tier AI API providers so that I can use the chatbot without incurring significant costs.
|
| 63 |
+
|
| 64 |
+
**Why this priority**: This is a critical constraint for the hackathon project and ensures accessibility. Without this, the system would be too expensive to run during development and testing.
|
| 65 |
+
|
| 66 |
+
**Independent Test**: Can be tested by configuring different free-tier providers (Gemini, OpenRouter, Cohere) and verifying the chatbot works correctly with each, respecting rate limits and handling failures gracefully.
|
| 67 |
+
|
| 68 |
+
**Acceptance Scenarios**:
|
| 69 |
+
|
| 70 |
+
1. **Given** I am using a free-tier API key, **When** I send messages to the chatbot, **Then** the system respects rate limits and does not exceed free-tier quotas
|
| 71 |
+
2. **Given** the API rate limit is reached, **When** I send a new message, **Then** the system displays a user-friendly error message and suggests waiting
|
| 72 |
+
3. **Given** I switch between different AI providers, **When** I configure the system, **Then** the chatbot works consistently across all supported providers
|
| 73 |
+
4. **Given** the conversation history is growing, **When** the context window approaches the limit, **Then** the system automatically trims history to stay within free-tier constraints
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
### Edge Cases
|
| 78 |
+
|
| 79 |
+
- What happens when the user sends an empty message?
|
| 80 |
+
- How does the system handle network failures during message transmission?
|
| 81 |
+
- What happens when the AI API is temporarily unavailable?
|
| 82 |
+
- How does the system handle extremely long user messages that exceed API limits?
|
| 83 |
+
- What happens when the user rapidly sends multiple messages in quick succession?
|
| 84 |
+
- How does the system handle special characters, emojis, and non-English text in messages?
|
| 85 |
+
- What happens when the conversation history grows very large (100+ messages)?
|
| 86 |
+
- How does the system handle concurrent requests from the same user?
|
| 87 |
+
|
| 88 |
+
## Requirements *(mandatory)*
|
| 89 |
+
|
| 90 |
+
### Functional Requirements
|
| 91 |
+
|
| 92 |
+
#### Frontend Requirements
|
| 93 |
+
|
| 94 |
+
- **FR-001**: System MUST provide a chat interface using OpenAI ChatKit integrated within the existing Next.js app structure
|
| 95 |
+
- **FR-002**: System MUST display a message input field, message list, typing indicator, and loading states
|
| 96 |
+
- **FR-003**: System MUST render the chat UI responsively for both desktop and mobile devices
|
| 97 |
+
- **FR-004**: System MUST display user messages and AI responses in a clear, visually distinct manner
|
| 98 |
+
- **FR-005**: System MUST show a typing indicator when the AI is processing a response
|
| 99 |
+
- **FR-006**: System MUST persist conversation history across page refreshes
|
| 100 |
+
- **FR-007**: System MUST allow users to scroll through conversation history
|
| 101 |
+
- **FR-008**: System MUST provide visual feedback when a message is being sent
|
| 102 |
+
- **FR-009**: Frontend MUST NOT include direct task manipulation UI (deferred to Spec-2)
|
| 103 |
+
|
| 104 |
+
#### Backend Requirements
|
| 105 |
+
|
| 106 |
+
- **FR-010**: System MUST provide a stateless chat endpoint at POST /api/{user_id}/chat
|
| 107 |
+
- **FR-011**: System MUST accept user messages through the chat endpoint
|
| 108 |
+
- **FR-012**: System MUST create or retrieve conversation records for each user
|
| 109 |
+
- **FR-013**: System MUST persist both user and assistant messages using SQLModel
|
| 110 |
+
- **FR-014**: System MUST implement a Conversation model to track conversation metadata
|
| 111 |
+
- **FR-015**: System MUST implement a Message model to store individual messages with role (user/assistant), content, and timestamp
|
| 112 |
+
- **FR-016**: System MUST call the AI agent runner to generate responses
|
| 113 |
+
- **FR-017**: System MUST return assistant responses to the frontend in a structured format
|
| 114 |
+
- **FR-018**: Backend MUST remain stateless between requests (no in-memory session storage)
|
| 115 |
+
|
| 116 |
+
#### AI Agent Requirements
|
| 117 |
+
|
| 118 |
+
- **FR-019**: System MUST use OpenAI Agents SDK or a compatible abstraction for agent implementation
|
| 119 |
+
- **FR-020**: AI agent MUST maintain a conversational and friendly tone in all responses
|
| 120 |
+
- **FR-021**: AI agent MUST ask clarifying questions when user intent is ambiguous
|
| 121 |
+
- **FR-022**: AI agent MUST acknowledge user requests related to todos with natural language confirmations
|
| 122 |
+
- **FR-023**: AI agent MUST provide friendly guidance about its current capabilities
|
| 123 |
+
- **FR-024**: AI agent MUST NOT execute tool calls or task CRUD operations (deferred to Spec-2)
|
| 124 |
+
- **FR-025**: System MUST support configuration for multiple free-tier AI providers (Gemini, OpenRouter, Cohere)
|
| 125 |
+
- **FR-026**: System MUST respect free-tier API rate limits and avoid long context windows
|
| 126 |
+
- **FR-027**: System MUST fail gracefully when rate-limited, providing user-friendly error messages
|
| 127 |
+
- **FR-028**: System MUST trim conversation history when approaching context window limits
|
| 128 |
+
|
| 129 |
+
#### Conversation Flow Requirements
|
| 130 |
+
|
| 131 |
+
- **FR-029**: System MUST load conversation history from the database when a user sends a message
|
| 132 |
+
- **FR-030**: System MUST append new user messages to the conversation history
|
| 133 |
+
- **FR-031**: System MUST run the AI agent with the full message history as context
|
| 134 |
+
- **FR-032**: System MUST store assistant replies in the database before returning them to the frontend
|
| 135 |
+
- **FR-033**: System MUST handle errors at each step of the conversation flow and provide meaningful feedback
|
| 136 |
+
|
| 137 |
+
### Key Entities *(include if feature involves data)*
|
| 138 |
+
|
| 139 |
+
- **Conversation**: Represents a conversation session between a user and the AI assistant. Key attributes include conversation ID, user ID, creation timestamp, last updated timestamp, and conversation metadata (e.g., title, status).
|
| 140 |
+
|
| 141 |
+
- **Message**: Represents an individual message within a conversation. Key attributes include message ID, conversation ID (foreign key), role (user or assistant), content (message text), timestamp, and optional metadata (e.g., token count, model used).
|
| 142 |
+
|
| 143 |
+
- **User**: Represents the authenticated user interacting with the chatbot. Relationship: One user can have many conversations.
|
| 144 |
+
|
| 145 |
+
## Success Criteria *(mandatory)*
|
| 146 |
+
|
| 147 |
+
### Measurable Outcomes
|
| 148 |
+
|
| 149 |
+
- **SC-001**: Users can send a message and receive an AI response within 5 seconds under normal conditions
|
| 150 |
+
- **SC-002**: Conversation history persists correctly across page refreshes with 100% accuracy
|
| 151 |
+
- **SC-003**: The chat interface renders correctly on mobile devices (320px width) and desktop devices (1920px width)
|
| 152 |
+
- **SC-004**: The system successfully handles at least 3 different free-tier AI providers (Gemini, OpenRouter, Cohere) without code changes
|
| 153 |
+
- **SC-005**: The system gracefully handles rate limiting with user-friendly error messages in 100% of rate-limit scenarios
|
| 154 |
+
- **SC-006**: Users can complete a basic chat interaction (send message, receive response) in under 30 seconds
|
| 155 |
+
- **SC-007**: The AI agent correctly acknowledges todo-related intent in at least 90% of test cases
|
| 156 |
+
- **SC-008**: The system maintains conversation context across at least 10 consecutive message exchanges
|
| 157 |
+
- **SC-009**: The existing folder structure remains unchanged (all frontend code in frontend/, all backend code in backend/)
|
| 158 |
+
- **SC-010**: The implementation provides a clear foundation for Spec-2 MCP integration with well-defined extension points
|
| 159 |
+
|
| 160 |
+
## Scope Boundaries *(mandatory)*
|
| 161 |
+
|
| 162 |
+
### In Scope
|
| 163 |
+
|
| 164 |
+
- Chat-based UI using OpenAI ChatKit
|
| 165 |
+
- Stateless chat API endpoint (POST /api/{user_id}/chat)
|
| 166 |
+
- AI agent configuration compatible with free-tier API keys
|
| 167 |
+
- Conversation and message persistence using SQLModel
|
| 168 |
+
- End-to-end conversational loop (UI → API → Agent → UI)
|
| 169 |
+
- Basic intent recognition and acknowledgment
|
| 170 |
+
- Responsive UI design for desktop and mobile
|
| 171 |
+
- Error handling and graceful degradation
|
| 172 |
+
|
| 173 |
+
### Out of Scope (Explicitly Deferred to Spec-2)
|
| 174 |
+
|
| 175 |
+
- MCP server implementation
|
| 176 |
+
- Task CRUD tools (add_task, list_tasks, update_task, delete_task)
|
| 177 |
+
- Tool execution capabilities for the AI agent
|
| 178 |
+
- Advanced backend orchestration and tool chaining
|
| 179 |
+
- Performance optimizations and production hardening
|
| 180 |
+
- User authentication and authorization (assumes existing auth system)
|
| 181 |
+
- Multi-user conversation support
|
| 182 |
+
- Conversation search and filtering
|
| 183 |
+
- Export/import conversation history
|
| 184 |
+
|
| 185 |
+
## Constraints *(mandatory)*
|
| 186 |
+
|
| 187 |
+
### Technical Constraints
|
| 188 |
+
|
| 189 |
+
- **TC-001**: All frontend work MUST be implemented within the existing `frontend/` folder
|
| 190 |
+
- **TC-002**: All backend work MUST be implemented within the existing `backend/` folder
|
| 191 |
+
- **TC-003**: No restructuring or relocation of existing files is allowed
|
| 192 |
+
- **TC-004**: Must use FastAPI for backend (already structured)
|
| 193 |
+
- **TC-005**: Must use Next.js for frontend (already structured)
|
| 194 |
+
- **TC-006**: Must use SQLModel for database models
|
| 195 |
+
- **TC-007**: Must be compatible with free-tier AI API providers (Gemini, OpenRouter, Cohere)
|
| 196 |
+
- **TC-008**: Must respect free-tier API rate limits
|
| 197 |
+
- **TC-009**: Must avoid long context windows to minimize API costs
|
| 198 |
+
|
| 199 |
+
### Development Constraints
|
| 200 |
+
|
| 201 |
+
- **DC-001**: No manual coding outside Claude Code execution
|
| 202 |
+
- **DC-002**: Must follow constitution and CLAUDE.md rules
|
| 203 |
+
- **DC-003**: Must adhere to Spec-Driven Development workflow
|
| 204 |
+
- **DC-004**: Must create PHR (Prompt History Record) after completion
|
| 205 |
+
|
| 206 |
+
### Business Constraints
|
| 207 |
+
|
| 208 |
+
- **BC-001**: This is Phase III of a hackathon project with time constraints
|
| 209 |
+
- **BC-002**: Must provide a clear foundation for Spec-2 implementation
|
| 210 |
+
- **BC-003**: Must demonstrate working end-to-end functionality for hackathon evaluation
|
| 211 |
+
|
| 212 |
+
## Assumptions *(mandatory)*
|
| 213 |
+
|
| 214 |
+
- **A-001**: The existing Next.js frontend and FastAPI backend are functional and properly configured
|
| 215 |
+
- **A-002**: Database connectivity is already established and working
|
| 216 |
+
- **A-003**: User authentication is already implemented and provides user_id for API calls
|
| 217 |
+
- **A-004**: OpenAI ChatKit library is compatible with the existing Next.js version
|
| 218 |
+
- **A-005**: Free-tier API keys for at least one provider (Gemini, OpenRouter, or Cohere) are available
|
| 219 |
+
- **A-006**: The existing database supports SQLModel and can store conversation/message data
|
| 220 |
+
- **A-007**: Network connectivity is reliable for API calls to AI providers
|
| 221 |
+
- **A-008**: The OpenAI Agents SDK or compatible abstraction is available and documented
|
| 222 |
+
|
| 223 |
+
## Dependencies *(mandatory)*
|
| 224 |
+
|
| 225 |
+
### External Dependencies
|
| 226 |
+
|
| 227 |
+
- **ED-001**: OpenAI ChatKit library for chat UI components
|
| 228 |
+
- **ED-002**: OpenAI Agents SDK or compatible abstraction for agent implementation
|
| 229 |
+
- **ED-003**: Free-tier AI API providers (Gemini, OpenRouter, Cohere)
|
| 230 |
+
- **ED-004**: SQLModel library for database models
|
| 231 |
+
- **ED-005**: FastAPI framework for backend API
|
| 232 |
+
- **ED-006**: Next.js framework for frontend
|
| 233 |
+
|
| 234 |
+
### Internal Dependencies
|
| 235 |
+
|
| 236 |
+
- **ID-001**: Existing authentication system to provide user_id
|
| 237 |
+
- **ID-002**: Existing database infrastructure
|
| 238 |
+
- **ID-003**: Existing frontend and backend folder structures
|
| 239 |
+
- **ID-004**: Existing task management data models (for future Spec-2 integration)
|
| 240 |
+
|
| 241 |
+
### Blocking Dependencies
|
| 242 |
+
|
| 243 |
+
- **BD-001**: Access to at least one free-tier AI API key (Gemini, OpenRouter, or Cohere)
|
| 244 |
+
- **BD-002**: Confirmation that OpenAI ChatKit is compatible with the current Next.js version
|
| 245 |
+
|
| 246 |
+
## Risks *(mandatory)*
|
| 247 |
+
|
| 248 |
+
### Technical Risks
|
| 249 |
+
|
| 250 |
+
- **TR-001**: OpenAI ChatKit may have compatibility issues with the existing Next.js setup
|
| 251 |
+
- **Mitigation**: Test ChatKit integration early; have fallback plan to use alternative chat UI library
|
| 252 |
+
|
| 253 |
+
- **TR-002**: Free-tier API rate limits may be too restrictive for development and testing
|
| 254 |
+
- **Mitigation**: Implement aggressive conversation history trimming; use multiple API keys for testing
|
| 255 |
+
|
| 256 |
+
- **TR-003**: AI agent responses may be inconsistent across different providers
|
| 257 |
+
- **Mitigation**: Implement provider-agnostic response handling; test with all three providers early
|
| 258 |
+
|
| 259 |
+
- **TR-004**: Conversation history may grow too large and impact performance
|
| 260 |
+
- **Mitigation**: Implement automatic history trimming; set maximum conversation length limits
|
| 261 |
+
|
| 262 |
+
### Project Risks
|
| 263 |
+
|
| 264 |
+
- **PR-001**: Scope creep may lead to implementing Spec-2 features prematurely
|
| 265 |
+
- **Mitigation**: Strictly adhere to scope boundaries; defer all tool execution to Spec-2
|
| 266 |
+
|
| 267 |
+
- **PR-002**: Integration with existing codebase may reveal unexpected issues
|
| 268 |
+
- **Mitigation**: Conduct early integration testing; document all assumptions about existing code
|
| 269 |
+
|
| 270 |
+
## Notes for Next Spec (Spec-2)
|
| 271 |
+
|
| 272 |
+
Spec-2 will introduce:
|
| 273 |
+
- MCP server implementation for tool execution
|
| 274 |
+
- Task CRUD tools (add_task, list_tasks, update_task, delete_task, complete_task)
|
| 275 |
+
- Tool-driven agent behavior with function calling
|
| 276 |
+
- Full todo functionality integrated with the conversational interface
|
| 277 |
+
- Advanced backend orchestration and tool chaining
|
| 278 |
+
- Performance optimizations and production hardening
|
specs/001-todo-ai-chatbot/tasks.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Tasks: Todo AI Chatbot - Phase 1
|
| 2 |
+
|
| 3 |
+
**Feature**: 001-todo-ai-chatbot
|
| 4 |
+
**Branch**: `001-todo-ai-chatbot`
|
| 5 |
+
**Spec**: [spec.md](./spec.md) | **Plan**: [plan.md](./plan.md)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Overview
|
| 10 |
+
|
| 11 |
+
This document defines the implementation tasks for the Todo AI Chatbot Phase 1 feature. Tasks are organized by user story to enable independent implementation and testing.
|
| 12 |
+
|
| 13 |
+
**Total Tasks**: 28
|
| 14 |
+
**Estimated Timeline**: 2-3 days
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Task Summary by User Story
|
| 19 |
+
|
| 20 |
+
| User Story | Priority | Task Count | Parallel Opportunities |
|
| 21 |
+
|------------|----------|------------|------------------------|
|
| 22 |
+
| Setup | N/A | 5 | 2 parallel tasks |
|
| 23 |
+
| Foundational | N/A | 4 | 3 parallel tasks |
|
| 24 |
+
| US1 + US4: Basic Chat + Free-Tier | P1 | 11 | 6 parallel tasks |
|
| 25 |
+
| US2: Intent Recognition | P2 | 3 | 2 parallel tasks |
|
| 26 |
+
| US3: Multi-Turn Conversations | P3 | 2 | 1 parallel task |
|
| 27 |
+
| Polish & Cross-Cutting | N/A | 3 | 2 parallel tasks |
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Dependencies & Execution Order
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
Phase 1: Setup
|
| 35 |
+
↓
|
| 36 |
+
Phase 2: Foundational (Models & Base Abstractions)
|
| 37 |
+
↓
|
| 38 |
+
Phase 3: US1 + US4 (Basic Chat + Free-Tier) ← MVP Scope
|
| 39 |
+
↓
|
| 40 |
+
Phase 4: US2 (Intent Recognition)
|
| 41 |
+
↓
|
| 42 |
+
Phase 5: US3 (Multi-Turn Conversations)
|
| 43 |
+
↓
|
| 44 |
+
Phase 6: Polish & Cross-Cutting
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
**MVP Recommendation**: Complete Phase 1-3 only (Setup + Foundational + US1+US4) for minimum viable product.
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## Phase 1: Setup
|
| 52 |
+
|
| 53 |
+
**Goal**: Configure project dependencies and environment for AI chatbot development.
|
| 54 |
+
|
| 55 |
+
**Agent**: Backend Systems Agent (`backend-mcp-tools`)
|
| 56 |
+
|
| 57 |
+
### Tasks
|
| 58 |
+
|
| 59 |
+
- [X] T001 Install backend dependencies in backend/requirements.txt (google-generativeai==0.3.2, tiktoken==0.5.2)
|
| 60 |
+
- [X] T002 [P] Install frontend dependencies in frontend/package.json (@assistant-ui/react, ai)
|
| 61 |
+
- [X] T003 Configure environment variables in backend/.env (AI_PROVIDER, GEMINI_API_KEY, MAX_CONVERSATION_MESSAGES, MAX_CONVERSATION_TOKENS)
|
| 62 |
+
- [X] T004 Create database migration for conversation and message tables in backend/alembic/versions/
|
| 63 |
+
- [X] T005 Run database migration with alembic upgrade head
|
| 64 |
+
|
| 65 |
+
**Parallel Execution**: T002 can run in parallel with T001.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## Phase 2: Foundational (Blocking Prerequisites)
|
| 70 |
+
|
| 71 |
+
**Goal**: Implement core database models and base abstractions required by all user stories.
|
| 72 |
+
|
| 73 |
+
**Agent**: Backend Systems Agent (`backend-mcp-tools`)
|
| 74 |
+
|
| 75 |
+
### Tasks
|
| 76 |
+
|
| 77 |
+
- [X] T006 [P] Create Conversation SQLModel in backend/src/models/conversation.py
|
| 78 |
+
- [X] T007 [P] Create Message SQLModel in backend/src/models/message.py
|
| 79 |
+
- [X] T008 [P] Create LLMProvider abstract base class in backend/src/services/providers/base.py
|
| 80 |
+
- [X] T009 Create GeminiProvider implementation in backend/src/services/providers/gemini.py
|
| 81 |
+
|
| 82 |
+
**Parallel Execution**: T006, T007, T008 can run in parallel (different files, no dependencies).
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## Phase 3: User Story 1 + 4 - Basic Chat Interaction + Free-Tier API Compatibility (P1)
|
| 87 |
+
|
| 88 |
+
**Story Goal**: Enable users to interact with an AI chatbot through a conversational interface that works reliably with free-tier AI providers.
|
| 89 |
+
|
| 90 |
+
**Why Combined**: Both are P1 priority and tightly coupled - basic chat requires free-tier provider integration from the start.
|
| 91 |
+
|
| 92 |
+
**Independent Test Criteria**:
|
| 93 |
+
- ✅ User can open chat page and send a message
|
| 94 |
+
- ✅ AI responds with relevant reply using Gemini free-tier API
|
| 95 |
+
- ✅ Conversation history persists across page refreshes
|
| 96 |
+
- ✅ Typing indicator shows while AI is processing
|
| 97 |
+
- ✅ UI is responsive on mobile and desktop
|
| 98 |
+
- ✅ System respects rate limits and handles errors gracefully
|
| 99 |
+
|
| 100 |
+
**Agents**:
|
| 101 |
+
- Backend Systems Agent (`backend-mcp-tools`) - Backend implementation
|
| 102 |
+
- Frontend UI Builder Agent (`nextjs-ui-generator`) - Frontend components
|
| 103 |
+
- Design & Theme Agent (`design-theme`) - UI styling
|
| 104 |
+
|
| 105 |
+
### Backend Tasks
|
| 106 |
+
|
| 107 |
+
- [X] T010 [P] [US1] Create LLMService with provider factory in backend/src/services/llm_service.py
|
| 108 |
+
- [X] T011 [P] [US1] Create ConversationService for CRUD operations in backend/src/services/conversation_service.py
|
| 109 |
+
- [X] T012 [P] [US1] Create ChatRequest Pydantic schema in backend/src/schemas/chat_request.py
|
| 110 |
+
- [X] T013 [P] [US1] Create ChatResponse Pydantic schema in backend/src/schemas/chat_response.py
|
| 111 |
+
- [X] T014 [US1] Create chat API endpoint POST /api/{user_id}/chat in backend/src/api/routes/chat.py
|
| 112 |
+
- [X] T015 [US1] Register chat router in backend/src/main.py
|
| 113 |
+
|
| 114 |
+
### Frontend Tasks
|
| 115 |
+
|
| 116 |
+
- [X] T016 [P] [US1] Create chat service API client in frontend/src/services/chatService.ts
|
| 117 |
+
- [X] T017 [P] [US1] Create TypeScript types for chat in frontend/src/types/chat.ts
|
| 118 |
+
- [X] T018 [US1] Create ChatInterface component in frontend/src/components/chat/ChatInterface.tsx
|
| 119 |
+
- [X] T019 [US1] Create MessageList component in frontend/src/components/chat/MessageList.tsx
|
| 120 |
+
- [X] T020 [US1] Create MessageInput component in frontend/src/components/chat/MessageInput.tsx
|
| 121 |
+
- [X] T021 [US1] Create TypingIndicator component in frontend/src/components/chat/TypingIndicator.tsx
|
| 122 |
+
- [X] T022 [US1] Create chat page in frontend/src/app/chat/page.tsx
|
| 123 |
+
|
| 124 |
+
**Parallel Execution**:
|
| 125 |
+
- Backend: T010, T011, T012, T013 can run in parallel
|
| 126 |
+
- Frontend: T016, T017 can run in parallel
|
| 127 |
+
- After T014-T015 complete (backend), all frontend tasks T016-T022 can proceed in parallel
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Phase 4: User Story 2 - Todo Intent Recognition (P2)
|
| 132 |
+
|
| 133 |
+
**Story Goal**: AI understands and acknowledges todo-related requests, providing user confidence that the system recognizes their intent.
|
| 134 |
+
|
| 135 |
+
**Independent Test Criteria**:
|
| 136 |
+
- ✅ AI acknowledges "add a task" intent with appropriate response
|
| 137 |
+
- ✅ AI explains task management will be available in Phase 2
|
| 138 |
+
- ✅ AI asks clarifying questions for ambiguous requests
|
| 139 |
+
- ✅ AI maintains conversational and helpful tone
|
| 140 |
+
|
| 141 |
+
**Agent**: Conversational AI Architect Agent (`agent-behavior-reasoning`)
|
| 142 |
+
|
| 143 |
+
### Tasks
|
| 144 |
+
|
| 145 |
+
- [X] T023 [P] [US2] Add intent detection prompt engineering to GeminiProvider in backend/src/services/providers/gemini.py
|
| 146 |
+
- [X] T024 [P] [US2] Create system prompt for todo intent recognition in backend/src/services/llm_service.py
|
| 147 |
+
- [X] T025 [US2] Add intent acknowledgment response templates in backend/src/services/llm_service.py
|
| 148 |
+
|
| 149 |
+
**Parallel Execution**: T023 and T024 can run in parallel (different concerns).
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## Phase 5: User Story 3 - Multi-Turn Conversations (P3)
|
| 154 |
+
|
| 155 |
+
**Story Goal**: Enable multi-turn conversations where AI remembers context from earlier messages for natural, flowing discussions.
|
| 156 |
+
|
| 157 |
+
**Independent Test Criteria**:
|
| 158 |
+
- ✅ AI understands references to previous messages ("that task")
|
| 159 |
+
- ✅ AI responds based on full conversation history
|
| 160 |
+
- ✅ System gracefully trims older messages when approaching context limit
|
| 161 |
+
|
| 162 |
+
**Agent**: Backend Systems Agent (`backend-mcp-tools`)
|
| 163 |
+
|
| 164 |
+
### Tasks
|
| 165 |
+
|
| 166 |
+
- [X] T026 [P] [US3] Implement conversation history trimming logic in backend/src/services/conversation_service.py
|
| 167 |
+
- [X] T027 [US3] Add context window management to LLMService in backend/src/services/llm_service.py
|
| 168 |
+
|
| 169 |
+
**Parallel Execution**: T026 can be implemented independently and integrated in T027.
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## Phase 6: Polish & Cross-Cutting Concerns
|
| 174 |
+
|
| 175 |
+
**Goal**: Enhance error handling, responsive design, and documentation.
|
| 176 |
+
|
| 177 |
+
**Agents**:
|
| 178 |
+
- Backend Systems Agent (`backend-mcp-tools`) - Error handling
|
| 179 |
+
- Design & Theme Agent (`design-theme`) - Responsive design
|
| 180 |
+
|
| 181 |
+
### Tasks
|
| 182 |
+
|
| 183 |
+
- [X] T028 [P] Add comprehensive error handling to chat endpoint in backend/src/api/routes/chat.py (400, 401, 429, 500 errors)
|
| 184 |
+
- [X] T029 [P] Verify responsive design for mobile (320px) and desktop (1920px) in frontend/src/components/chat/
|
| 185 |
+
- [X] T030 Update README with setup instructions and API documentation in backend/README.md
|
| 186 |
+
|
| 187 |
+
**Parallel Execution**: T028 and T029 can run in parallel (backend vs frontend).
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## Parallel Execution Examples
|
| 192 |
+
|
| 193 |
+
### Phase 1: Setup
|
| 194 |
+
```bash
|
| 195 |
+
# Terminal 1: Backend dependencies
|
| 196 |
+
cd backend && pip install -r requirements.txt
|
| 197 |
+
|
| 198 |
+
# Terminal 2: Frontend dependencies (parallel)
|
| 199 |
+
cd frontend && npm install
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### Phase 2: Foundational
|
| 203 |
+
```bash
|
| 204 |
+
# All three models can be created in parallel
|
| 205 |
+
# Terminal 1: Conversation model
|
| 206 |
+
# Terminal 2: Message model
|
| 207 |
+
# Terminal 3: LLMProvider base class
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
### Phase 3: User Story 1 + 4
|
| 211 |
+
```bash
|
| 212 |
+
# Backend tasks T010-T013 in parallel
|
| 213 |
+
# Then T014-T015 sequentially
|
| 214 |
+
# Then all frontend tasks T016-T022 in parallel
|
| 215 |
+
```
|
| 216 |
+
|
| 217 |
+
---
|
| 218 |
+
|
| 219 |
+
## Implementation Strategy
|
| 220 |
+
|
| 221 |
+
### MVP Scope (Phases 1-3)
|
| 222 |
+
|
| 223 |
+
**Recommended for initial delivery**:
|
| 224 |
+
- Phase 1: Setup (T001-T005)
|
| 225 |
+
- Phase 2: Foundational (T006-T009)
|
| 226 |
+
- Phase 3: US1 + US4 (T010-T022)
|
| 227 |
+
|
| 228 |
+
**Delivers**:
|
| 229 |
+
- Working chat interface
|
| 230 |
+
- AI responses via Gemini free-tier
|
| 231 |
+
- Conversation persistence
|
| 232 |
+
- Responsive UI
|
| 233 |
+
- Basic error handling
|
| 234 |
+
|
| 235 |
+
**Timeline**: 2 days
|
| 236 |
+
|
| 237 |
+
### Full Feature Scope (All Phases)
|
| 238 |
+
|
| 239 |
+
**Includes MVP + enhancements**:
|
| 240 |
+
- Phase 4: US2 - Intent Recognition (T023-T025)
|
| 241 |
+
- Phase 5: US3 - Multi-Turn Conversations (T026-T027)
|
| 242 |
+
- Phase 6: Polish (T028-T030)
|
| 243 |
+
|
| 244 |
+
**Timeline**: 3 days
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
## Acceptance Criteria
|
| 249 |
+
|
| 250 |
+
### Phase 1-3 (MVP) Acceptance
|
| 251 |
+
|
| 252 |
+
- [ ] User can navigate to /chat page
|
| 253 |
+
- [ ] User can send a message and receive AI response
|
| 254 |
+
- [ ] Conversation history persists on page refresh
|
| 255 |
+
- [ ] Typing indicator shows during AI processing
|
| 256 |
+
- [ ] UI is responsive on mobile and desktop
|
| 257 |
+
- [ ] System works with Gemini free-tier API
|
| 258 |
+
- [ ] JWT authentication protects chat endpoint
|
| 259 |
+
- [ ] Errors are handled gracefully
|
| 260 |
+
|
| 261 |
+
### Phase 4-6 (Full Feature) Acceptance
|
| 262 |
+
|
| 263 |
+
- [ ] AI acknowledges todo-related intents
|
| 264 |
+
- [ ] AI maintains context across multiple messages
|
| 265 |
+
- [ ] System trims conversation history appropriately
|
| 266 |
+
- [ ] All error scenarios return user-friendly messages
|
| 267 |
+
- [ ] Documentation is complete and accurate
|
| 268 |
+
|
| 269 |
+
---
|
| 270 |
+
|
| 271 |
+
## Risk Mitigation
|
| 272 |
+
|
| 273 |
+
| Risk | Mitigation Task |
|
| 274 |
+
|------|----------------|
|
| 275 |
+
| Gemini API rate limits | T003 (configure rate limit handling), T028 (error handling) |
|
| 276 |
+
| Frontend-backend integration issues | T016 (API client with proper error handling) |
|
| 277 |
+
| Conversation history growth | T026 (history trimming logic) |
|
| 278 |
+
| Mobile responsiveness issues | T029 (responsive design verification) |
|
| 279 |
+
|
| 280 |
+
---
|
| 281 |
+
|
| 282 |
+
## Next Steps After Task Completion
|
| 283 |
+
|
| 284 |
+
1. Run full integration test (send message, verify response, check persistence)
|
| 285 |
+
2. Test with different free-tier providers (Gemini, OpenRouter)
|
| 286 |
+
3. Verify responsive design on actual mobile devices
|
| 287 |
+
4. Create PHR documenting implementation
|
| 288 |
+
5. Prepare for Phase 2 (Spec-2): MCP tools and task CRUD operations
|
| 289 |
+
|
| 290 |
+
---
|
| 291 |
+
|
| 292 |
+
## Notes
|
| 293 |
+
|
| 294 |
+
- **No tests requested**: Spec does not explicitly request TDD approach, so test tasks are omitted
|
| 295 |
+
- **Agent-skill alignment**: All tasks reference appropriate agents from Agent-Skill Enforcement Matrix
|
| 296 |
+
- **File paths**: All tasks include specific file paths for implementation
|
| 297 |
+
- **Parallelization**: 15 tasks marked [P] for parallel execution opportunities
|
| 298 |
+
- **User story mapping**: All implementation tasks mapped to user stories for traceability
|
specs/002-fullstack-ui-integration/checklists/requirements.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Specification Quality Checklist: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 4 |
+
**Created**: 2026-01-09
|
| 5 |
+
**Feature**: [spec.md](../spec.md)
|
| 6 |
+
|
| 7 |
+
## Content Quality
|
| 8 |
+
|
| 9 |
+
- [x] No implementation details (languages, frameworks, APIs)
|
| 10 |
+
- [x] Focused on user value and business needs
|
| 11 |
+
- [x] Written for non-technical stakeholders
|
| 12 |
+
- [x] All mandatory sections completed
|
| 13 |
+
|
| 14 |
+
## Requirement Completeness
|
| 15 |
+
|
| 16 |
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
| 17 |
+
- [x] Requirements are testable and unambiguous
|
| 18 |
+
- [x] Success criteria are measurable
|
| 19 |
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
| 20 |
+
- [x] All acceptance scenarios are defined
|
| 21 |
+
- [x] Edge cases are identified
|
| 22 |
+
- [x] Scope is clearly bounded
|
| 23 |
+
- [x] Dependencies and assumptions identified
|
| 24 |
+
|
| 25 |
+
## Feature Readiness
|
| 26 |
+
|
| 27 |
+
- [x] All functional requirements have clear acceptance criteria
|
| 28 |
+
- [x] User scenarios cover primary flows
|
| 29 |
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
| 30 |
+
- [x] No implementation details leak into specification
|
| 31 |
+
|
| 32 |
+
## Validation Results
|
| 33 |
+
|
| 34 |
+
### Content Quality Assessment
|
| 35 |
+
|
| 36 |
+
✅ **Pass**: The specification focuses on user experience and integration outcomes without prescribing implementation details. While it mentions existing technologies (Next.js, FastAPI, etc.) in the constraints and dependencies sections, these are appropriately documented as context rather than requirements.
|
| 37 |
+
|
| 38 |
+
✅ **Pass**: The specification is written for business stakeholders and hackathon reviewers, focusing on "what" users need rather than "how" to build it.
|
| 39 |
+
|
| 40 |
+
✅ **Pass**: All mandatory sections are complete: User Scenarios, Requirements, Success Criteria, Assumptions, Dependencies, Out of Scope, and References.
|
| 41 |
+
|
| 42 |
+
### Requirement Completeness Assessment
|
| 43 |
+
|
| 44 |
+
✅ **Pass**: No [NEEDS CLARIFICATION] markers present. All requirements are specific and actionable.
|
| 45 |
+
|
| 46 |
+
✅ **Pass**: All 20 functional requirements are testable with clear acceptance criteria. Each requirement uses "MUST" and describes a specific, verifiable capability.
|
| 47 |
+
|
| 48 |
+
✅ **Pass**: All 10 success criteria are measurable with specific metrics:
|
| 49 |
+
- SC-001: "under 3 minutes" (time-based)
|
| 50 |
+
- SC-002: "within 100ms" (performance-based)
|
| 51 |
+
- SC-003: "80% of new users" (percentage-based)
|
| 52 |
+
- SC-004: "90% of the time" (percentage-based)
|
| 53 |
+
- SC-005: "320px to 1920px" (range-based)
|
| 54 |
+
- SC-006: "zero accidental clicks" (count-based)
|
| 55 |
+
- SC-007: "zero application crashes" (count-based)
|
| 56 |
+
- SC-008: "zero unhandled promise rejections" (count-based)
|
| 57 |
+
- SC-009: "under 10 minutes" (time-based)
|
| 58 |
+
- SC-010: "works end-to-end" (binary outcome)
|
| 59 |
+
|
| 60 |
+
✅ **Pass**: Success criteria are technology-agnostic and focus on user outcomes rather than implementation details.
|
| 61 |
+
|
| 62 |
+
✅ **Pass**: All 5 user stories have detailed acceptance scenarios with Given-When-Then format. Total of 30 acceptance scenarios across all stories.
|
| 63 |
+
|
| 64 |
+
✅ **Pass**: 8 edge cases identified covering token expiration, rapid API calls, server errors, special characters, unauthorized access, navigation, localStorage availability, and concurrent edits.
|
| 65 |
+
|
| 66 |
+
✅ **Pass**: Scope is clearly bounded with comprehensive "Out of Scope" section listing 15 explicitly excluded items.
|
| 67 |
+
|
| 68 |
+
✅ **Pass**: Dependencies section lists both internal (Spec 1, Spec 2, Backend API, Database) and external (Next.js, React, TypeScript, etc.) dependencies. Assumptions section lists 10 clear assumptions.
|
| 69 |
+
|
| 70 |
+
### Feature Readiness Assessment
|
| 71 |
+
|
| 72 |
+
✅ **Pass**: Each of the 20 functional requirements maps to specific acceptance scenarios in the user stories.
|
| 73 |
+
|
| 74 |
+
✅ **Pass**: 5 user stories cover the complete integration flow from authentication (P1) through UI states (P2), responsive design (P3), API communication (P4), and environment setup (P5).
|
| 75 |
+
|
| 76 |
+
✅ **Pass**: The feature delivers measurable outcomes that can be verified without knowing implementation details. All success criteria focus on user experience and system behavior.
|
| 77 |
+
|
| 78 |
+
✅ **Pass**: The specification maintains separation between requirements (what) and implementation (how). Technology mentions are appropriately scoped to constraints and dependencies sections.
|
| 79 |
+
|
| 80 |
+
## Notes
|
| 81 |
+
|
| 82 |
+
**Strengths**:
|
| 83 |
+
1. Comprehensive coverage of integration and UI experience concerns
|
| 84 |
+
2. Clear prioritization with P1-P5 user stories
|
| 85 |
+
3. Detailed acceptance scenarios (30 total) provide excellent testability
|
| 86 |
+
4. Success criteria are specific and measurable
|
| 87 |
+
5. Well-defined scope boundaries with explicit out-of-scope items
|
| 88 |
+
6. Strong focus on user experience and feedback mechanisms
|
| 89 |
+
|
| 90 |
+
**Observations**:
|
| 91 |
+
1. This is an integration/polish spec rather than a new feature spec, which is appropriate for Phase II
|
| 92 |
+
2. The spec correctly builds on Specs 1 and 2 without duplicating their functionality
|
| 93 |
+
3. The focus on loading states, error handling, and responsive design demonstrates maturity
|
| 94 |
+
4. The environment coordination story (P5) ensures the application is reviewable by hackathon judges
|
| 95 |
+
|
| 96 |
+
**Recommendation**: ✅ **APPROVED** - Specification is ready for planning phase (`/sp.plan`)
|
| 97 |
+
|
| 98 |
+
All checklist items pass validation. The specification is complete, testable, and ready for implementation planning.
|
specs/002-fullstack-ui-integration/contracts/existing-api-reference.yaml
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openapi: 3.0.3
|
| 2 |
+
info:
|
| 3 |
+
title: Phase II Todo Web App - API Reference
|
| 4 |
+
description: |
|
| 5 |
+
Complete API reference for the Full-Stack Todo Web Application.
|
| 6 |
+
This document references existing endpoints from Specs 1 (Task CRUD) and 2 (Authentication & API Security).
|
| 7 |
+
|
| 8 |
+
**Base URL**: http://localhost:8000
|
| 9 |
+
|
| 10 |
+
**Authentication**: All task endpoints require JWT Bearer token in Authorization header.
|
| 11 |
+
|
| 12 |
+
**Note**: This is a reference document for integration purposes. No new endpoints are introduced in Spec 002.
|
| 13 |
+
version: 1.0.0
|
| 14 |
+
contact:
|
| 15 |
+
name: Phase II Todo Web App
|
| 16 |
+
|
| 17 |
+
servers:
|
| 18 |
+
- url: http://localhost:8000
|
| 19 |
+
description: Local development server
|
| 20 |
+
|
| 21 |
+
tags:
|
| 22 |
+
- name: Authentication
|
| 23 |
+
description: User signup, signin, and profile endpoints (Spec 2)
|
| 24 |
+
- name: Tasks
|
| 25 |
+
description: Task CRUD operations (Spec 1)
|
| 26 |
+
|
| 27 |
+
security:
|
| 28 |
+
- BearerAuth: []
|
| 29 |
+
|
| 30 |
+
paths:
|
| 31 |
+
/api/auth/signup:
|
| 32 |
+
post:
|
| 33 |
+
tags:
|
| 34 |
+
- Authentication
|
| 35 |
+
summary: Register new user account
|
| 36 |
+
description: Creates a new user account with email, password, and name. Password is hashed with bcrypt before storage.
|
| 37 |
+
operationId: signup
|
| 38 |
+
security: []
|
| 39 |
+
requestBody:
|
| 40 |
+
required: true
|
| 41 |
+
content:
|
| 42 |
+
application/json:
|
| 43 |
+
schema:
|
| 44 |
+
$ref: '#/components/schemas/SignupRequest'
|
| 45 |
+
example:
|
| 46 |
+
email: user@example.com
|
| 47 |
+
password: SecurePass123
|
| 48 |
+
name: John Doe
|
| 49 |
+
responses:
|
| 50 |
+
'201':
|
| 51 |
+
description: User created successfully
|
| 52 |
+
content:
|
| 53 |
+
application/json:
|
| 54 |
+
schema:
|
| 55 |
+
$ref: '#/components/schemas/SignupResponse'
|
| 56 |
+
example:
|
| 57 |
+
id: 1
|
| 58 |
+
email: user@example.com
|
| 59 |
+
name: John Doe
|
| 60 |
+
created_at: "2026-01-09T10:00:00Z"
|
| 61 |
+
'400':
|
| 62 |
+
description: Validation error (invalid email, weak password)
|
| 63 |
+
content:
|
| 64 |
+
application/json:
|
| 65 |
+
schema:
|
| 66 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 67 |
+
example:
|
| 68 |
+
detail: Password must be at least 8 characters with uppercase, lowercase, and number
|
| 69 |
+
error_code: VALIDATION_ERROR
|
| 70 |
+
'409':
|
| 71 |
+
description: Email already registered
|
| 72 |
+
content:
|
| 73 |
+
application/json:
|
| 74 |
+
schema:
|
| 75 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 76 |
+
example:
|
| 77 |
+
detail: Email already registered
|
| 78 |
+
error_code: EMAIL_EXISTS
|
| 79 |
+
|
| 80 |
+
/api/auth/signin:
|
| 81 |
+
post:
|
| 82 |
+
tags:
|
| 83 |
+
- Authentication
|
| 84 |
+
summary: Authenticate user and issue JWT token
|
| 85 |
+
description: Verifies email and password, returns JWT token with 7-day expiration.
|
| 86 |
+
operationId: signin
|
| 87 |
+
security: []
|
| 88 |
+
requestBody:
|
| 89 |
+
required: true
|
| 90 |
+
content:
|
| 91 |
+
application/json:
|
| 92 |
+
schema:
|
| 93 |
+
$ref: '#/components/schemas/SigninRequest'
|
| 94 |
+
example:
|
| 95 |
+
email: user@example.com
|
| 96 |
+
password: SecurePass123
|
| 97 |
+
responses:
|
| 98 |
+
'200':
|
| 99 |
+
description: Authentication successful
|
| 100 |
+
content:
|
| 101 |
+
application/json:
|
| 102 |
+
schema:
|
| 103 |
+
$ref: '#/components/schemas/TokenResponse'
|
| 104 |
+
example:
|
| 105 |
+
access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
| 106 |
+
token_type: bearer
|
| 107 |
+
expires_in: 604800
|
| 108 |
+
user:
|
| 109 |
+
id: 1
|
| 110 |
+
email: user@example.com
|
| 111 |
+
name: John Doe
|
| 112 |
+
created_at: "2026-01-09T10:00:00Z"
|
| 113 |
+
'401':
|
| 114 |
+
description: Invalid credentials
|
| 115 |
+
content:
|
| 116 |
+
application/json:
|
| 117 |
+
schema:
|
| 118 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 119 |
+
example:
|
| 120 |
+
detail: Invalid email or password
|
| 121 |
+
error_code: INVALID_CREDENTIALS
|
| 122 |
+
|
| 123 |
+
/api/auth/me:
|
| 124 |
+
get:
|
| 125 |
+
tags:
|
| 126 |
+
- Authentication
|
| 127 |
+
summary: Get current user profile
|
| 128 |
+
description: Returns profile information for the authenticated user.
|
| 129 |
+
operationId: getCurrentUser
|
| 130 |
+
responses:
|
| 131 |
+
'200':
|
| 132 |
+
description: User profile retrieved successfully
|
| 133 |
+
content:
|
| 134 |
+
application/json:
|
| 135 |
+
schema:
|
| 136 |
+
$ref: '#/components/schemas/UserProfile'
|
| 137 |
+
example:
|
| 138 |
+
id: 1
|
| 139 |
+
email: user@example.com
|
| 140 |
+
name: John Doe
|
| 141 |
+
created_at: "2026-01-09T10:00:00Z"
|
| 142 |
+
'401':
|
| 143 |
+
description: Not authenticated or invalid token
|
| 144 |
+
content:
|
| 145 |
+
application/json:
|
| 146 |
+
schema:
|
| 147 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 148 |
+
example:
|
| 149 |
+
detail: Not authenticated
|
| 150 |
+
error_code: TOKEN_MISSING
|
| 151 |
+
|
| 152 |
+
/api/tasks:
|
| 153 |
+
get:
|
| 154 |
+
tags:
|
| 155 |
+
- Tasks
|
| 156 |
+
summary: List user's tasks with filtering and sorting
|
| 157 |
+
description: Returns all tasks for the authenticated user. Supports filtering by completion status and sorting.
|
| 158 |
+
operationId: getTasks
|
| 159 |
+
parameters:
|
| 160 |
+
- name: completed
|
| 161 |
+
in: query
|
| 162 |
+
description: Filter by completion status (true/false/null for all)
|
| 163 |
+
required: false
|
| 164 |
+
schema:
|
| 165 |
+
type: boolean
|
| 166 |
+
nullable: true
|
| 167 |
+
- name: sort
|
| 168 |
+
in: query
|
| 169 |
+
description: Sort field
|
| 170 |
+
required: false
|
| 171 |
+
schema:
|
| 172 |
+
type: string
|
| 173 |
+
enum: [created_at, updated_at]
|
| 174 |
+
default: created_at
|
| 175 |
+
- name: order
|
| 176 |
+
in: query
|
| 177 |
+
description: Sort order
|
| 178 |
+
required: false
|
| 179 |
+
schema:
|
| 180 |
+
type: string
|
| 181 |
+
enum: [asc, desc]
|
| 182 |
+
default: desc
|
| 183 |
+
- name: limit
|
| 184 |
+
in: query
|
| 185 |
+
description: Maximum number of results
|
| 186 |
+
required: false
|
| 187 |
+
schema:
|
| 188 |
+
type: integer
|
| 189 |
+
minimum: 1
|
| 190 |
+
maximum: 100
|
| 191 |
+
default: 50
|
| 192 |
+
- name: offset
|
| 193 |
+
in: query
|
| 194 |
+
description: Number of results to skip
|
| 195 |
+
required: false
|
| 196 |
+
schema:
|
| 197 |
+
type: integer
|
| 198 |
+
minimum: 0
|
| 199 |
+
default: 0
|
| 200 |
+
responses:
|
| 201 |
+
'200':
|
| 202 |
+
description: Tasks retrieved successfully
|
| 203 |
+
content:
|
| 204 |
+
application/json:
|
| 205 |
+
schema:
|
| 206 |
+
$ref: '#/components/schemas/TaskListResponse'
|
| 207 |
+
example:
|
| 208 |
+
tasks:
|
| 209 |
+
- id: 1
|
| 210 |
+
user_id: 1
|
| 211 |
+
title: Buy groceries
|
| 212 |
+
description: Milk, eggs, bread
|
| 213 |
+
completed: false
|
| 214 |
+
created_at: "2026-01-09T10:00:00Z"
|
| 215 |
+
updated_at: "2026-01-09T10:00:00Z"
|
| 216 |
+
- id: 2
|
| 217 |
+
user_id: 1
|
| 218 |
+
title: Finish project
|
| 219 |
+
description: null
|
| 220 |
+
completed: true
|
| 221 |
+
created_at: "2026-01-08T15:30:00Z"
|
| 222 |
+
updated_at: "2026-01-09T09:00:00Z"
|
| 223 |
+
total: 2
|
| 224 |
+
limit: 50
|
| 225 |
+
offset: 0
|
| 226 |
+
'401':
|
| 227 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 228 |
+
|
| 229 |
+
post:
|
| 230 |
+
tags:
|
| 231 |
+
- Tasks
|
| 232 |
+
summary: Create new task
|
| 233 |
+
description: Creates a new task for the authenticated user.
|
| 234 |
+
operationId: createTask
|
| 235 |
+
requestBody:
|
| 236 |
+
required: true
|
| 237 |
+
content:
|
| 238 |
+
application/json:
|
| 239 |
+
schema:
|
| 240 |
+
$ref: '#/components/schemas/TaskCreate'
|
| 241 |
+
example:
|
| 242 |
+
title: Buy groceries
|
| 243 |
+
description: Milk, eggs, bread
|
| 244 |
+
responses:
|
| 245 |
+
'201':
|
| 246 |
+
description: Task created successfully
|
| 247 |
+
content:
|
| 248 |
+
application/json:
|
| 249 |
+
schema:
|
| 250 |
+
$ref: '#/components/schemas/Task'
|
| 251 |
+
example:
|
| 252 |
+
id: 1
|
| 253 |
+
user_id: 1
|
| 254 |
+
title: Buy groceries
|
| 255 |
+
description: Milk, eggs, bread
|
| 256 |
+
completed: false
|
| 257 |
+
created_at: "2026-01-09T10:00:00Z"
|
| 258 |
+
updated_at: "2026-01-09T10:00:00Z"
|
| 259 |
+
'400':
|
| 260 |
+
description: Validation error
|
| 261 |
+
content:
|
| 262 |
+
application/json:
|
| 263 |
+
schema:
|
| 264 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 265 |
+
example:
|
| 266 |
+
detail: Title is required
|
| 267 |
+
error_code: VALIDATION_ERROR
|
| 268 |
+
'401':
|
| 269 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 270 |
+
|
| 271 |
+
/api/tasks/{task_id}:
|
| 272 |
+
get:
|
| 273 |
+
tags:
|
| 274 |
+
- Tasks
|
| 275 |
+
summary: Get single task
|
| 276 |
+
description: Returns a specific task if it belongs to the authenticated user.
|
| 277 |
+
operationId: getTask
|
| 278 |
+
parameters:
|
| 279 |
+
- name: task_id
|
| 280 |
+
in: path
|
| 281 |
+
required: true
|
| 282 |
+
schema:
|
| 283 |
+
type: integer
|
| 284 |
+
responses:
|
| 285 |
+
'200':
|
| 286 |
+
description: Task retrieved successfully
|
| 287 |
+
content:
|
| 288 |
+
application/json:
|
| 289 |
+
schema:
|
| 290 |
+
$ref: '#/components/schemas/Task'
|
| 291 |
+
'401':
|
| 292 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 293 |
+
'404':
|
| 294 |
+
description: Task not found or doesn't belong to user
|
| 295 |
+
content:
|
| 296 |
+
application/json:
|
| 297 |
+
schema:
|
| 298 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 299 |
+
example:
|
| 300 |
+
detail: Task not found
|
| 301 |
+
error_code: NOT_FOUND
|
| 302 |
+
|
| 303 |
+
put:
|
| 304 |
+
tags:
|
| 305 |
+
- Tasks
|
| 306 |
+
summary: Update task (replace all fields)
|
| 307 |
+
description: Replaces all task fields. All fields are required.
|
| 308 |
+
operationId: updateTask
|
| 309 |
+
parameters:
|
| 310 |
+
- name: task_id
|
| 311 |
+
in: path
|
| 312 |
+
required: true
|
| 313 |
+
schema:
|
| 314 |
+
type: integer
|
| 315 |
+
requestBody:
|
| 316 |
+
required: true
|
| 317 |
+
content:
|
| 318 |
+
application/json:
|
| 319 |
+
schema:
|
| 320 |
+
$ref: '#/components/schemas/TaskUpdate'
|
| 321 |
+
example:
|
| 322 |
+
title: Buy groceries (updated)
|
| 323 |
+
description: Milk, eggs, bread, cheese
|
| 324 |
+
completed: false
|
| 325 |
+
responses:
|
| 326 |
+
'200':
|
| 327 |
+
description: Task updated successfully
|
| 328 |
+
content:
|
| 329 |
+
application/json:
|
| 330 |
+
schema:
|
| 331 |
+
$ref: '#/components/schemas/Task'
|
| 332 |
+
'400':
|
| 333 |
+
description: Validation error
|
| 334 |
+
content:
|
| 335 |
+
application/json:
|
| 336 |
+
schema:
|
| 337 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 338 |
+
'401':
|
| 339 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 340 |
+
'404':
|
| 341 |
+
$ref: '#/components/responses/NotFoundError'
|
| 342 |
+
|
| 343 |
+
patch:
|
| 344 |
+
tags:
|
| 345 |
+
- Tasks
|
| 346 |
+
summary: Partially update task
|
| 347 |
+
description: Updates only the provided fields. Other fields remain unchanged.
|
| 348 |
+
operationId: patchTask
|
| 349 |
+
parameters:
|
| 350 |
+
- name: task_id
|
| 351 |
+
in: path
|
| 352 |
+
required: true
|
| 353 |
+
schema:
|
| 354 |
+
type: integer
|
| 355 |
+
requestBody:
|
| 356 |
+
required: true
|
| 357 |
+
content:
|
| 358 |
+
application/json:
|
| 359 |
+
schema:
|
| 360 |
+
$ref: '#/components/schemas/TaskPatch'
|
| 361 |
+
example:
|
| 362 |
+
completed: true
|
| 363 |
+
responses:
|
| 364 |
+
'200':
|
| 365 |
+
description: Task updated successfully
|
| 366 |
+
content:
|
| 367 |
+
application/json:
|
| 368 |
+
schema:
|
| 369 |
+
$ref: '#/components/schemas/Task'
|
| 370 |
+
'400':
|
| 371 |
+
description: Validation error
|
| 372 |
+
content:
|
| 373 |
+
application/json:
|
| 374 |
+
schema:
|
| 375 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 376 |
+
'401':
|
| 377 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 378 |
+
'404':
|
| 379 |
+
$ref: '#/components/responses/NotFoundError'
|
| 380 |
+
|
| 381 |
+
delete:
|
| 382 |
+
tags:
|
| 383 |
+
- Tasks
|
| 384 |
+
summary: Delete task
|
| 385 |
+
description: Permanently deletes a task if it belongs to the authenticated user.
|
| 386 |
+
operationId: deleteTask
|
| 387 |
+
parameters:
|
| 388 |
+
- name: task_id
|
| 389 |
+
in: path
|
| 390 |
+
required: true
|
| 391 |
+
schema:
|
| 392 |
+
type: integer
|
| 393 |
+
responses:
|
| 394 |
+
'204':
|
| 395 |
+
description: Task deleted successfully
|
| 396 |
+
'401':
|
| 397 |
+
$ref: '#/components/responses/UnauthorizedError'
|
| 398 |
+
'404':
|
| 399 |
+
$ref: '#/components/responses/NotFoundError'
|
| 400 |
+
|
| 401 |
+
components:
|
| 402 |
+
securitySchemes:
|
| 403 |
+
BearerAuth:
|
| 404 |
+
type: http
|
| 405 |
+
scheme: bearer
|
| 406 |
+
bearerFormat: JWT
|
| 407 |
+
description: JWT token obtained from /api/auth/signin
|
| 408 |
+
|
| 409 |
+
schemas:
|
| 410 |
+
SignupRequest:
|
| 411 |
+
type: object
|
| 412 |
+
required:
|
| 413 |
+
- email
|
| 414 |
+
- password
|
| 415 |
+
- name
|
| 416 |
+
properties:
|
| 417 |
+
email:
|
| 418 |
+
type: string
|
| 419 |
+
format: email
|
| 420 |
+
description: Valid email address (RFC 5322)
|
| 421 |
+
password:
|
| 422 |
+
type: string
|
| 423 |
+
minLength: 8
|
| 424 |
+
maxLength: 100
|
| 425 |
+
description: Min 8 chars with uppercase, lowercase, and number
|
| 426 |
+
name:
|
| 427 |
+
type: string
|
| 428 |
+
minLength: 1
|
| 429 |
+
maxLength: 100
|
| 430 |
+
description: User's display name
|
| 431 |
+
|
| 432 |
+
SignupResponse:
|
| 433 |
+
type: object
|
| 434 |
+
properties:
|
| 435 |
+
id:
|
| 436 |
+
type: integer
|
| 437 |
+
email:
|
| 438 |
+
type: string
|
| 439 |
+
name:
|
| 440 |
+
type: string
|
| 441 |
+
created_at:
|
| 442 |
+
type: string
|
| 443 |
+
format: date-time
|
| 444 |
+
|
| 445 |
+
SigninRequest:
|
| 446 |
+
type: object
|
| 447 |
+
required:
|
| 448 |
+
- email
|
| 449 |
+
- password
|
| 450 |
+
properties:
|
| 451 |
+
email:
|
| 452 |
+
type: string
|
| 453 |
+
format: email
|
| 454 |
+
password:
|
| 455 |
+
type: string
|
| 456 |
+
|
| 457 |
+
TokenResponse:
|
| 458 |
+
type: object
|
| 459 |
+
properties:
|
| 460 |
+
access_token:
|
| 461 |
+
type: string
|
| 462 |
+
description: JWT token
|
| 463 |
+
token_type:
|
| 464 |
+
type: string
|
| 465 |
+
enum: [bearer]
|
| 466 |
+
expires_in:
|
| 467 |
+
type: integer
|
| 468 |
+
description: Token expiration in seconds (604800 = 7 days)
|
| 469 |
+
user:
|
| 470 |
+
$ref: '#/components/schemas/UserProfile'
|
| 471 |
+
|
| 472 |
+
UserProfile:
|
| 473 |
+
type: object
|
| 474 |
+
properties:
|
| 475 |
+
id:
|
| 476 |
+
type: integer
|
| 477 |
+
email:
|
| 478 |
+
type: string
|
| 479 |
+
name:
|
| 480 |
+
type: string
|
| 481 |
+
created_at:
|
| 482 |
+
type: string
|
| 483 |
+
format: date-time
|
| 484 |
+
|
| 485 |
+
Task:
|
| 486 |
+
type: object
|
| 487 |
+
properties:
|
| 488 |
+
id:
|
| 489 |
+
type: integer
|
| 490 |
+
user_id:
|
| 491 |
+
type: integer
|
| 492 |
+
title:
|
| 493 |
+
type: string
|
| 494 |
+
maxLength: 200
|
| 495 |
+
description:
|
| 496 |
+
type: string
|
| 497 |
+
maxLength: 1000
|
| 498 |
+
nullable: true
|
| 499 |
+
completed:
|
| 500 |
+
type: boolean
|
| 501 |
+
created_at:
|
| 502 |
+
type: string
|
| 503 |
+
format: date-time
|
| 504 |
+
updated_at:
|
| 505 |
+
type: string
|
| 506 |
+
format: date-time
|
| 507 |
+
|
| 508 |
+
TaskCreate:
|
| 509 |
+
type: object
|
| 510 |
+
required:
|
| 511 |
+
- title
|
| 512 |
+
properties:
|
| 513 |
+
title:
|
| 514 |
+
type: string
|
| 515 |
+
minLength: 1
|
| 516 |
+
maxLength: 200
|
| 517 |
+
description:
|
| 518 |
+
type: string
|
| 519 |
+
maxLength: 1000
|
| 520 |
+
nullable: true
|
| 521 |
+
|
| 522 |
+
TaskUpdate:
|
| 523 |
+
type: object
|
| 524 |
+
required:
|
| 525 |
+
- title
|
| 526 |
+
- completed
|
| 527 |
+
properties:
|
| 528 |
+
title:
|
| 529 |
+
type: string
|
| 530 |
+
minLength: 1
|
| 531 |
+
maxLength: 200
|
| 532 |
+
description:
|
| 533 |
+
type: string
|
| 534 |
+
maxLength: 1000
|
| 535 |
+
nullable: true
|
| 536 |
+
completed:
|
| 537 |
+
type: boolean
|
| 538 |
+
|
| 539 |
+
TaskPatch:
|
| 540 |
+
type: object
|
| 541 |
+
properties:
|
| 542 |
+
title:
|
| 543 |
+
type: string
|
| 544 |
+
minLength: 1
|
| 545 |
+
maxLength: 200
|
| 546 |
+
description:
|
| 547 |
+
type: string
|
| 548 |
+
maxLength: 1000
|
| 549 |
+
nullable: true
|
| 550 |
+
completed:
|
| 551 |
+
type: boolean
|
| 552 |
+
|
| 553 |
+
TaskListResponse:
|
| 554 |
+
type: object
|
| 555 |
+
properties:
|
| 556 |
+
tasks:
|
| 557 |
+
type: array
|
| 558 |
+
items:
|
| 559 |
+
$ref: '#/components/schemas/Task'
|
| 560 |
+
total:
|
| 561 |
+
type: integer
|
| 562 |
+
limit:
|
| 563 |
+
type: integer
|
| 564 |
+
offset:
|
| 565 |
+
type: integer
|
| 566 |
+
|
| 567 |
+
ErrorResponse:
|
| 568 |
+
type: object
|
| 569 |
+
properties:
|
| 570 |
+
detail:
|
| 571 |
+
type: string
|
| 572 |
+
description: Human-readable error message
|
| 573 |
+
error_code:
|
| 574 |
+
type: string
|
| 575 |
+
description: Machine-readable error code
|
| 576 |
+
enum:
|
| 577 |
+
- VALIDATION_ERROR
|
| 578 |
+
- EMAIL_EXISTS
|
| 579 |
+
- INVALID_CREDENTIALS
|
| 580 |
+
- TOKEN_MISSING
|
| 581 |
+
- TOKEN_EXPIRED
|
| 582 |
+
- TOKEN_INVALID
|
| 583 |
+
- NOT_FOUND
|
| 584 |
+
field_errors:
|
| 585 |
+
type: object
|
| 586 |
+
additionalProperties:
|
| 587 |
+
type: array
|
| 588 |
+
items:
|
| 589 |
+
type: string
|
| 590 |
+
description: Field-specific validation errors
|
| 591 |
+
|
| 592 |
+
responses:
|
| 593 |
+
UnauthorizedError:
|
| 594 |
+
description: Not authenticated or invalid token
|
| 595 |
+
content:
|
| 596 |
+
application/json:
|
| 597 |
+
schema:
|
| 598 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 599 |
+
example:
|
| 600 |
+
detail: Not authenticated
|
| 601 |
+
error_code: TOKEN_MISSING
|
| 602 |
+
|
| 603 |
+
NotFoundError:
|
| 604 |
+
description: Resource not found or doesn't belong to user
|
| 605 |
+
content:
|
| 606 |
+
application/json:
|
| 607 |
+
schema:
|
| 608 |
+
$ref: '#/components/schemas/ErrorResponse'
|
| 609 |
+
example:
|
| 610 |
+
detail: Task not found
|
| 611 |
+
error_code: NOT_FOUND
|
specs/002-fullstack-ui-integration/data-model.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Model: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Feature**: 002-fullstack-ui-integration
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Status**: Reference Only (No New Entities)
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This feature does not introduce new data entities. It integrates and polishes existing functionality from Specs 1 (Task CRUD) and 2 (Authentication & API Security). This document references the existing data model for completeness.
|
| 10 |
+
|
| 11 |
+
## Existing Entities
|
| 12 |
+
|
| 13 |
+
### User (from Spec 2: Authentication & API Security)
|
| 14 |
+
|
| 15 |
+
**Purpose**: Represents an authenticated user with task management capabilities
|
| 16 |
+
|
| 17 |
+
**Attributes**:
|
| 18 |
+
- `id` (integer, primary key): Unique identifier for the user
|
| 19 |
+
- `email` (string, unique, required): User's email address for authentication
|
| 20 |
+
- `name` (string, required): User's display name
|
| 21 |
+
- `password_hash` (string, required): Bcrypt-hashed password (never exposed in API)
|
| 22 |
+
- `created_at` (datetime, auto): Timestamp of account creation
|
| 23 |
+
- `updated_at` (datetime, auto): Timestamp of last profile update
|
| 24 |
+
|
| 25 |
+
**Relationships**:
|
| 26 |
+
- One-to-Many with Task: A user can have multiple tasks
|
| 27 |
+
|
| 28 |
+
**Validation Rules**:
|
| 29 |
+
- Email must be valid RFC 5322 format
|
| 30 |
+
- Email must be unique across all users
|
| 31 |
+
- Password must be at least 8 characters with uppercase, lowercase, and number
|
| 32 |
+
- Name must be 1-100 characters
|
| 33 |
+
|
| 34 |
+
**Security**:
|
| 35 |
+
- Password is hashed with bcrypt (cost factor 12) before storage
|
| 36 |
+
- Password hash is never returned in API responses
|
| 37 |
+
- User ID is extracted from JWT token for all authenticated requests
|
| 38 |
+
|
| 39 |
+
**Database Table**: `users`
|
| 40 |
+
|
| 41 |
+
**Indexes**:
|
| 42 |
+
- Primary key on `id`
|
| 43 |
+
- Unique index on `email`
|
| 44 |
+
|
| 45 |
+
**Source**: `backend/src/models/user.py`
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
### Task (from Spec 1: Task CRUD)
|
| 50 |
+
|
| 51 |
+
**Purpose**: Represents a todo item belonging to a specific user
|
| 52 |
+
|
| 53 |
+
**Attributes**:
|
| 54 |
+
- `id` (integer, primary key): Unique identifier for the task
|
| 55 |
+
- `user_id` (integer, foreign key, required): Owner of the task (references User.id)
|
| 56 |
+
- `title` (string, required): Task title (max 200 characters)
|
| 57 |
+
- `description` (string, optional): Task description (max 1000 characters)
|
| 58 |
+
- `completed` (boolean, default false): Completion status
|
| 59 |
+
- `created_at` (datetime, auto): Timestamp of task creation
|
| 60 |
+
- `updated_at` (datetime, auto): Timestamp of last task update
|
| 61 |
+
|
| 62 |
+
**Relationships**:
|
| 63 |
+
- Many-to-One with User: Each task belongs to exactly one user
|
| 64 |
+
|
| 65 |
+
**Validation Rules**:
|
| 66 |
+
- Title is required and must be 1-200 characters
|
| 67 |
+
- Description is optional, max 1000 characters
|
| 68 |
+
- Completed defaults to false
|
| 69 |
+
- User ID must reference an existing user
|
| 70 |
+
|
| 71 |
+
**Business Rules**:
|
| 72 |
+
- Users can only access their own tasks (enforced by JWT authentication)
|
| 73 |
+
- Tasks are automatically filtered by authenticated user_id in all queries
|
| 74 |
+
- Deleting a user cascades to delete all their tasks
|
| 75 |
+
|
| 76 |
+
**Database Table**: `tasks`
|
| 77 |
+
|
| 78 |
+
**Indexes**:
|
| 79 |
+
- Primary key on `id`
|
| 80 |
+
- Index on `user_id` (for filtering by user)
|
| 81 |
+
- Index on `completed` (for filtering by status)
|
| 82 |
+
- Composite index on `(user_id, completed)` (for combined filtering)
|
| 83 |
+
- Index on `created_at` (for sorting)
|
| 84 |
+
|
| 85 |
+
**Source**: `backend/src/models/task.py`
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
### AuthSession (Frontend Only - from Spec 2)
|
| 90 |
+
|
| 91 |
+
**Purpose**: Client-side session state for authenticated users
|
| 92 |
+
|
| 93 |
+
**Attributes**:
|
| 94 |
+
- `token` (string, nullable): JWT token from backend
|
| 95 |
+
- `user` (object, nullable): User profile information
|
| 96 |
+
- `id` (integer): User ID
|
| 97 |
+
- `email` (string): User email
|
| 98 |
+
- `name` (string): User display name
|
| 99 |
+
|
| 100 |
+
**Storage**: Browser localStorage (key: `auth_session`)
|
| 101 |
+
|
| 102 |
+
**Lifecycle**:
|
| 103 |
+
- Created on successful signin (POST /api/auth/signin)
|
| 104 |
+
- Persisted across page refreshes
|
| 105 |
+
- Cleared on signout or 401 Unauthorized response
|
| 106 |
+
- Expires when JWT token expires (7 days)
|
| 107 |
+
|
| 108 |
+
**Security**:
|
| 109 |
+
- Token is included in Authorization header for all API requests
|
| 110 |
+
- Session is cleared on any authentication error
|
| 111 |
+
- No sensitive data stored (password never stored client-side)
|
| 112 |
+
|
| 113 |
+
**Source**: `frontend/src/lib/auth.ts`
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## Entity Relationships
|
| 118 |
+
|
| 119 |
+
```
|
| 120 |
+
User (1) ----< (Many) Task
|
| 121 |
+
|
|
| 122 |
+
| JWT Token (stateless)
|
| 123 |
+
|
|
| 124 |
+
v
|
| 125 |
+
AuthSession (Frontend)
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
**Relationship Details**:
|
| 129 |
+
|
| 130 |
+
1. **User → Task** (One-to-Many):
|
| 131 |
+
- A user can have zero or more tasks
|
| 132 |
+
- Each task belongs to exactly one user
|
| 133 |
+
- Foreign key: `Task.user_id` references `User.id`
|
| 134 |
+
- Cascade delete: Deleting a user deletes all their tasks
|
| 135 |
+
|
| 136 |
+
2. **User → AuthSession** (Stateless):
|
| 137 |
+
- JWT token contains user_id and email
|
| 138 |
+
- No server-side session storage
|
| 139 |
+
- Frontend stores token and user profile in localStorage
|
| 140 |
+
- Token is verified on every API request
|
| 141 |
+
|
| 142 |
+
## Data Flow
|
| 143 |
+
|
| 144 |
+
### Authentication Flow
|
| 145 |
+
|
| 146 |
+
```
|
| 147 |
+
1. User signs up/signs in
|
| 148 |
+
↓
|
| 149 |
+
2. Backend creates JWT token with user_id
|
| 150 |
+
↓
|
| 151 |
+
3. Frontend stores token + user profile in AuthSession
|
| 152 |
+
↓
|
| 153 |
+
4. Frontend includes token in Authorization header
|
| 154 |
+
↓
|
| 155 |
+
5. Backend verifies token and extracts user_id
|
| 156 |
+
↓
|
| 157 |
+
6. Backend filters all queries by user_id
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
### Task Management Flow
|
| 161 |
+
|
| 162 |
+
```
|
| 163 |
+
1. User creates/updates/deletes task
|
| 164 |
+
↓
|
| 165 |
+
2. Frontend sends request with JWT token
|
| 166 |
+
↓
|
| 167 |
+
3. Backend verifies token → extracts user_id
|
| 168 |
+
↓
|
| 169 |
+
4. Backend performs operation (filtered by user_id)
|
| 170 |
+
↓
|
| 171 |
+
5. Backend returns result
|
| 172 |
+
↓
|
| 173 |
+
6. Frontend updates UI (optimistic or after response)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Data Isolation
|
| 177 |
+
|
| 178 |
+
**Critical Security Requirement**: All task queries MUST be filtered by authenticated user_id
|
| 179 |
+
|
| 180 |
+
**Implementation**:
|
| 181 |
+
- JWT token contains user_id in 'sub' claim
|
| 182 |
+
- `get_current_user()` dependency extracts user_id from token
|
| 183 |
+
- All task endpoints use `current_user_id = Depends(get_current_user)`
|
| 184 |
+
- SQLModel queries include `.where(Task.user_id == current_user_id)`
|
| 185 |
+
|
| 186 |
+
**Verification**:
|
| 187 |
+
- User A cannot access User B's tasks
|
| 188 |
+
- API returns 404 (not 403) for unauthorized task access
|
| 189 |
+
- No data leakage through error messages
|
| 190 |
+
|
| 191 |
+
## State Transitions
|
| 192 |
+
|
| 193 |
+
### Task State Transitions
|
| 194 |
+
|
| 195 |
+
```
|
| 196 |
+
[New Task]
|
| 197 |
+
↓
|
| 198 |
+
[Active] ←→ [Completed]
|
| 199 |
+
↓
|
| 200 |
+
[Deleted]
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
**Transitions**:
|
| 204 |
+
- New → Active: Task created with `completed=false`
|
| 205 |
+
- Active → Completed: User marks task as done (`completed=true`)
|
| 206 |
+
- Completed → Active: User marks task as not done (`completed=false`)
|
| 207 |
+
- Any → Deleted: User deletes task (hard delete from database)
|
| 208 |
+
|
| 209 |
+
**No Soft Deletes**: Tasks are permanently deleted (no `deleted_at` field)
|
| 210 |
+
|
| 211 |
+
### User State Transitions
|
| 212 |
+
|
| 213 |
+
```
|
| 214 |
+
[New User]
|
| 215 |
+
↓
|
| 216 |
+
[Active]
|
| 217 |
+
↓
|
| 218 |
+
[Deleted] (future - not implemented)
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
**Current Implementation**:
|
| 222 |
+
- New → Active: User signs up successfully
|
| 223 |
+
- No user deletion implemented yet (out of scope)
|
| 224 |
+
|
| 225 |
+
## Schema Migrations
|
| 226 |
+
|
| 227 |
+
**Existing Migrations**:
|
| 228 |
+
1. `001_initial.py`: Created users and tasks tables (Spec 1)
|
| 229 |
+
2. `002_add_user_password.py`: Added password_hash to users table (Spec 2)
|
| 230 |
+
|
| 231 |
+
**No New Migrations Required**: This feature does not modify the database schema
|
| 232 |
+
|
| 233 |
+
## Data Validation
|
| 234 |
+
|
| 235 |
+
### Backend Validation (Pydantic Schemas)
|
| 236 |
+
|
| 237 |
+
**User Validation** (`backend/src/schemas/auth.py`):
|
| 238 |
+
- Email: RFC 5322 format validation
|
| 239 |
+
- Password: Min 8 chars, uppercase, lowercase, number
|
| 240 |
+
- Name: 1-100 characters
|
| 241 |
+
|
| 242 |
+
**Task Validation** (`backend/src/schemas/task.py`):
|
| 243 |
+
- Title: Required, 1-200 characters
|
| 244 |
+
- Description: Optional, max 1000 characters
|
| 245 |
+
- Completed: Boolean (defaults to false)
|
| 246 |
+
|
| 247 |
+
### Frontend Validation
|
| 248 |
+
|
| 249 |
+
**Client-Side Validation**:
|
| 250 |
+
- Email format validation (regex)
|
| 251 |
+
- Password strength validation (min 8 chars, complexity)
|
| 252 |
+
- Form field required/optional indicators
|
| 253 |
+
- Inline error messages
|
| 254 |
+
|
| 255 |
+
**Note**: Backend validation is authoritative - frontend validation is for UX only
|
| 256 |
+
|
| 257 |
+
## Performance Considerations
|
| 258 |
+
|
| 259 |
+
**Indexes** (already implemented):
|
| 260 |
+
- `users.email` (unique): Fast user lookup during signin
|
| 261 |
+
- `tasks.user_id`: Fast filtering of user's tasks
|
| 262 |
+
- `tasks.completed`: Fast filtering by completion status
|
| 263 |
+
- `tasks.(user_id, completed)`: Fast combined filtering
|
| 264 |
+
- `tasks.created_at`: Fast sorting by creation date
|
| 265 |
+
|
| 266 |
+
**Query Patterns**:
|
| 267 |
+
- Most common: Get all tasks for user (filtered by user_id)
|
| 268 |
+
- Second most common: Get active/completed tasks for user
|
| 269 |
+
- Sorting: By created_at or updated_at
|
| 270 |
+
|
| 271 |
+
**No N+1 Queries**: All queries are direct (no nested loops)
|
| 272 |
+
|
| 273 |
+
## Summary
|
| 274 |
+
|
| 275 |
+
This feature reuses the existing data model from Specs 1 and 2:
|
| 276 |
+
- **User**: Authentication and ownership
|
| 277 |
+
- **Task**: Todo items with user isolation
|
| 278 |
+
- **AuthSession**: Frontend session state
|
| 279 |
+
|
| 280 |
+
No new entities, relationships, or migrations are required. The focus is on UI integration and polish rather than data model changes.
|
specs/002-fullstack-ui-integration/plan.md
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Branch**: `002-fullstack-ui-integration` | **Date**: 2026-01-09 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
**Input**: Feature specification from `/specs/002-fullstack-ui-integration/spec.md`
|
| 5 |
+
|
| 6 |
+
**Note**: This template is filled in by the `/sp.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
| 7 |
+
|
| 8 |
+
## Summary
|
| 9 |
+
|
| 10 |
+
This feature focuses on integrating and polishing existing functionality from Specs 1 (Task CRUD) and 2 (Authentication & API Security) into a cohesive, professional user experience. The primary requirement is to ensure seamless end-to-end flows with proper UI feedback (loading states, empty states, error handling), responsive design across devices, and centralized API communication. The technical approach emphasizes frontend refinement, consistent error handling patterns, and responsive layout implementation using existing Next.js App Router and Tailwind CSS infrastructure.
|
| 11 |
+
|
| 12 |
+
## Technical Context
|
| 13 |
+
|
| 14 |
+
**Language/Version**:
|
| 15 |
+
- Frontend: TypeScript 5.x with Next.js 16+ (App Router)
|
| 16 |
+
- Backend: Python 3.11+ with FastAPI (already implemented)
|
| 17 |
+
|
| 18 |
+
**Primary Dependencies**:
|
| 19 |
+
- Frontend: Next.js 16+, React 18+, TypeScript 5.x, Tailwind CSS 3.x, Better Auth
|
| 20 |
+
- Backend: FastAPI, SQLModel, PyJWT, passlib (already implemented in Specs 1 & 2)
|
| 21 |
+
|
| 22 |
+
**Storage**: PostgreSQL (Neon Serverless) - already configured with User and Task tables
|
| 23 |
+
|
| 24 |
+
**Testing**:
|
| 25 |
+
- Frontend: Manual testing of UI states, responsive layouts, and user flows
|
| 26 |
+
- Backend: Existing API endpoints already tested in Specs 1 & 2
|
| 27 |
+
- Integration: End-to-end testing of authentication → task management flow
|
| 28 |
+
|
| 29 |
+
**Target Platform**:
|
| 30 |
+
- Web browsers (Chrome, Firefox, Safari, Edge - latest 2 versions)
|
| 31 |
+
- Responsive design for mobile (320px), tablet (768px), and desktop (1920px)
|
| 32 |
+
|
| 33 |
+
**Project Type**: Web application (frontend + backend monorepo)
|
| 34 |
+
|
| 35 |
+
**Performance Goals**:
|
| 36 |
+
- Loading states appear within 100ms of user action
|
| 37 |
+
- Page transitions complete within 500ms
|
| 38 |
+
- API responses within 200ms (backend already optimized in Spec 1)
|
| 39 |
+
- Smooth 60fps animations and transitions
|
| 40 |
+
|
| 41 |
+
**Constraints**:
|
| 42 |
+
- No new backend endpoints (reuse existing from Spec 1)
|
| 43 |
+
- No new authentication mechanisms (reuse JWT from Spec 2)
|
| 44 |
+
- Tailwind CSS only (no inline styles or CSS files)
|
| 45 |
+
- Next.js App Router patterns (server components by default)
|
| 46 |
+
- Must work in local development environment
|
| 47 |
+
|
| 48 |
+
**Scale/Scope**:
|
| 49 |
+
- 5 user stories (P1-P5) focused on integration and polish
|
| 50 |
+
- ~10-15 frontend component refinements
|
| 51 |
+
- Responsive layouts for 3 breakpoints (mobile, tablet, desktop)
|
| 52 |
+
- Centralized API client with error handling
|
| 53 |
+
- Environment configuration documentation
|
| 54 |
+
|
| 55 |
+
## Constitution Check
|
| 56 |
+
|
| 57 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 58 |
+
|
| 59 |
+
### Principle I: User-Centric Functionality ✅ PASS
|
| 60 |
+
|
| 61 |
+
**Evaluation**: This feature directly serves end-users by improving UX through clear feedback mechanisms (loading, empty, error states), responsive design, and seamless authentication flows. All 5 user stories focus on user experience improvements.
|
| 62 |
+
|
| 63 |
+
**Alignment**:
|
| 64 |
+
- P1 (Authentication Flow): Ensures users can easily sign up and sign in
|
| 65 |
+
- P2 (UI States): Provides clear feedback during all operations
|
| 66 |
+
- P3 (Responsive Design): Makes app accessible on all devices
|
| 67 |
+
- P4 (API Communication): Ensures reliable backend communication
|
| 68 |
+
- P5 (Environment Setup): Enables developers/reviewers to run the app
|
| 69 |
+
|
| 70 |
+
**Verdict**: ✅ Fully aligned - all features directly benefit end-users
|
| 71 |
+
|
| 72 |
+
### Principle II: Spec-Driven Development ✅ PASS
|
| 73 |
+
|
| 74 |
+
**Evaluation**: This implementation follows the Spec-Kit Plus workflow. The specification (spec.md) defines 5 prioritized user stories with 30 acceptance scenarios. This plan.md will generate research.md, data-model.md, contracts/, and quickstart.md before tasks.md generation.
|
| 75 |
+
|
| 76 |
+
**Alignment**:
|
| 77 |
+
- Specification created via `/sp.specify` command
|
| 78 |
+
- Planning via `/sp.plan` command (this document)
|
| 79 |
+
- Tasks will be generated via `/sp.tasks` command
|
| 80 |
+
- Implementation via `/sp.implement` command
|
| 81 |
+
- All code references specs in `/specs/002-fullstack-ui-integration/`
|
| 82 |
+
|
| 83 |
+
**Verdict**: ✅ Fully aligned - follows spec-driven workflow
|
| 84 |
+
|
| 85 |
+
### Principle III: Security & Data Privacy ✅ PASS
|
| 86 |
+
|
| 87 |
+
**Evaluation**: This feature builds on Spec 2 (Authentication & API Security) which already implements JWT authentication, user data isolation, and secure token handling. No new security mechanisms are introduced - this feature focuses on UI integration of existing security.
|
| 88 |
+
|
| 89 |
+
**Alignment**:
|
| 90 |
+
- JWT authentication already implemented (Spec 2)
|
| 91 |
+
- User data isolation already enforced (Spec 2)
|
| 92 |
+
- API client will include JWT tokens automatically (FR-012)
|
| 93 |
+
- 401 errors trigger signin redirect (FR-014)
|
| 94 |
+
- No hardcoded secrets (uses environment variables)
|
| 95 |
+
|
| 96 |
+
**Verification**:
|
| 97 |
+
- ✅ No new authentication mechanisms
|
| 98 |
+
- ✅ Reuses existing JWT verification from Spec 2
|
| 99 |
+
- ✅ Frontend API client includes Authorization headers
|
| 100 |
+
- ✅ Error handling preserves security (generic error messages)
|
| 101 |
+
|
| 102 |
+
**Verdict**: ✅ Fully aligned - builds on existing security implementation
|
| 103 |
+
|
| 104 |
+
### Principle IV: Scalable Architecture ✅ PASS
|
| 105 |
+
|
| 106 |
+
**Evaluation**: This feature maintains the stateless architecture established in Specs 1 & 2. The centralized API client (P4) improves maintainability without changing the stateless JWT-based design. Responsive design (P3) ensures the frontend scales across devices.
|
| 107 |
+
|
| 108 |
+
**Alignment**:
|
| 109 |
+
- Stateless API design maintained (JWT-based)
|
| 110 |
+
- No server-side sessions introduced
|
| 111 |
+
- Frontend components remain reusable
|
| 112 |
+
- API client centralizes communication logic
|
| 113 |
+
- Responsive design supports multiple devices
|
| 114 |
+
|
| 115 |
+
**Verification**:
|
| 116 |
+
- ✅ No state stored on backend (JWT tokens only)
|
| 117 |
+
- ✅ Frontend components are composable
|
| 118 |
+
- ✅ API client is a shared utility (not per-component)
|
| 119 |
+
- ✅ Responsive layouts use Tailwind breakpoints
|
| 120 |
+
|
| 121 |
+
**Verdict**: ✅ Fully aligned - maintains scalable architecture
|
| 122 |
+
|
| 123 |
+
### Principle V: Maintainable & Consistent Code ✅ PASS
|
| 124 |
+
|
| 125 |
+
**Evaluation**: This feature enforces consistency through centralized API communication (P4), standardized error handling, and Tailwind CSS usage. The focus on loading/empty/error states creates consistent patterns across all components.
|
| 126 |
+
|
| 127 |
+
**Alignment**:
|
| 128 |
+
- Centralized API client (fetchAPI function)
|
| 129 |
+
- Consistent error handling across all API calls
|
| 130 |
+
- Tailwind CSS utilities for all styling (FR-020)
|
| 131 |
+
- Reusable loading/empty/error state components
|
| 132 |
+
- Next.js App Router patterns maintained
|
| 133 |
+
|
| 134 |
+
**Verification**:
|
| 135 |
+
- ✅ All API calls use centralized fetchAPI function
|
| 136 |
+
- ✅ Error responses formatted consistently
|
| 137 |
+
- ✅ Loading states follow same pattern
|
| 138 |
+
- ✅ Tailwind CSS only (no inline styles)
|
| 139 |
+
|
| 140 |
+
**Verdict**: ✅ Fully aligned - improves code consistency
|
| 141 |
+
|
| 142 |
+
### Key Standards Compliance
|
| 143 |
+
|
| 144 |
+
**API Compliance** ✅ PASS
|
| 145 |
+
- Reuses existing REST endpoints from Spec 1
|
| 146 |
+
- No new endpoints introduced
|
| 147 |
+
- API client handles errors consistently (FR-013)
|
| 148 |
+
- 401 responses trigger signin redirect (FR-014)
|
| 149 |
+
|
| 150 |
+
**Database Integrity** ✅ PASS
|
| 151 |
+
- No database changes required
|
| 152 |
+
- Reuses existing User and Task tables from Specs 1 & 2
|
| 153 |
+
- No new migrations needed
|
| 154 |
+
|
| 155 |
+
**Frontend Quality** ✅ PASS
|
| 156 |
+
- Next.js App Router patterns (server components by default)
|
| 157 |
+
- Client components for interactivity (forms, buttons)
|
| 158 |
+
- Responsive design for mobile, tablet, desktop (P3)
|
| 159 |
+
- Tailwind CSS for all styling (FR-020)
|
| 160 |
+
|
| 161 |
+
**Authentication** ✅ PASS
|
| 162 |
+
- Better Auth already implemented (Spec 2)
|
| 163 |
+
- JWT tokens already issued and verified (Spec 2)
|
| 164 |
+
- Frontend includes JWT in Authorization header (FR-012)
|
| 165 |
+
- Token expiry handled with signin redirect (FR-006)
|
| 166 |
+
|
| 167 |
+
**Spec Adherence** ✅ PASS
|
| 168 |
+
- Implementation references spec.md
|
| 169 |
+
- All 20 functional requirements documented
|
| 170 |
+
- 5 user stories with acceptance criteria
|
| 171 |
+
- No implementation without spec
|
| 172 |
+
|
| 173 |
+
### Constraints Compliance
|
| 174 |
+
|
| 175 |
+
**Tech Stack** ✅ PASS
|
| 176 |
+
- Frontend: Next.js 16+ (App Router), TypeScript, Tailwind CSS ✅
|
| 177 |
+
- Backend: FastAPI, SQLModel ✅ (no changes)
|
| 178 |
+
- Database: Neon PostgreSQL ✅ (no changes)
|
| 179 |
+
- Authentication: Better Auth (JWT) ✅ (no changes)
|
| 180 |
+
|
| 181 |
+
**Endpoint Authorization** ✅ PASS
|
| 182 |
+
- All task endpoints already require JWT (Spec 2)
|
| 183 |
+
- No new endpoints introduced
|
| 184 |
+
- API client includes JWT automatically (FR-012)
|
| 185 |
+
|
| 186 |
+
**Monorepo Structure** ✅ PASS
|
| 187 |
+
- Maintains existing structure
|
| 188 |
+
- CLAUDE.md files already in place
|
| 189 |
+
- specs/002-fullstack-ui-integration/ created
|
| 190 |
+
- No structural changes required
|
| 191 |
+
|
| 192 |
+
**No Manual Coding** ✅ PASS
|
| 193 |
+
- All implementation via Claude Code
|
| 194 |
+
- References specifications
|
| 195 |
+
- Follows spec-driven workflow
|
| 196 |
+
|
| 197 |
+
**Security** ✅ PASS
|
| 198 |
+
- JWT token expiry already implemented (7 days, Spec 2)
|
| 199 |
+
- BETTER_AUTH_SECRET shared via environment variables
|
| 200 |
+
- No new security mechanisms introduced
|
| 201 |
+
|
| 202 |
+
### Constitution Check Summary
|
| 203 |
+
|
| 204 |
+
**Overall Verdict**: ✅ **APPROVED** - All principles, standards, and constraints satisfied
|
| 205 |
+
|
| 206 |
+
**Justification**: This is an integration and polish feature that builds on existing implementations from Specs 1 and 2. It introduces no new architectural patterns, security mechanisms, or backend logic. The focus is entirely on frontend refinement and user experience improvements, which aligns perfectly with constitutional principles.
|
| 207 |
+
|
| 208 |
+
**No Violations**: No complexity justification required.
|
| 209 |
+
|
| 210 |
+
## Project Structure
|
| 211 |
+
|
| 212 |
+
### Documentation (this feature)
|
| 213 |
+
|
| 214 |
+
```text
|
| 215 |
+
specs/002-fullstack-ui-integration/
|
| 216 |
+
├── spec.md # Feature specification (completed)
|
| 217 |
+
├── plan.md # This file (in progress)
|
| 218 |
+
├── research.md # Phase 0 output (to be generated)
|
| 219 |
+
├── data-model.md # Phase 1 output (to be generated)
|
| 220 |
+
├── quickstart.md # Phase 1 output (to be generated)
|
| 221 |
+
├── contracts/ # Phase 1 output (to be generated)
|
| 222 |
+
│ └── existing-api-reference.yaml
|
| 223 |
+
├── checklists/
|
| 224 |
+
│ └── requirements.md # Specification validation (completed)
|
| 225 |
+
└── tasks.md # Phase 2 output (NOT created by /sp.plan)
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
### Source Code (repository root)
|
| 229 |
+
|
| 230 |
+
```text
|
| 231 |
+
# Web application structure (frontend + backend monorepo)
|
| 232 |
+
|
| 233 |
+
backend/
|
| 234 |
+
├── src/
|
| 235 |
+
│ ├── api/
|
| 236 |
+
│ │ ├── deps.py # JWT verification (Spec 2) ✅
|
| 237 |
+
│ │ └── routes/
|
| 238 |
+
│ │ ├── auth.py # Auth endpoints (Spec 2) ✅
|
| 239 |
+
│ │ └── tasks.py # Task CRUD (Spec 1) ✅
|
| 240 |
+
│ ├── core/
|
| 241 |
+
│ │ ├── config.py # Environment config ✅
|
| 242 |
+
│ │ ├── database.py # DB connection ✅
|
| 243 |
+
│ │ └── security.py # JWT & password hashing (Spec 2) ✅
|
| 244 |
+
│ ├── models/
|
| 245 |
+
│ │ ├── user.py # User model (Spec 2) ✅
|
| 246 |
+
│ │ └── task.py # Task model (Spec 1) ✅
|
| 247 |
+
│ ├── schemas/
|
| 248 |
+
│ │ ├── auth.py # Auth schemas (Spec 2) ✅
|
| 249 |
+
│ │ └── task.py # Task schemas (Spec 1) ✅
|
| 250 |
+
│ ├── services/
|
| 251 |
+
│ │ ├── auth_service.py # Auth logic (Spec 2) ✅
|
| 252 |
+
│ │ └── task_service.py # Task logic (Spec 1) ✅
|
| 253 |
+
│ └── main.py # FastAPI app ✅
|
| 254 |
+
├── alembic/
|
| 255 |
+
│ └── versions/ # Migrations (Specs 1 & 2) ✅
|
| 256 |
+
├── tests/ # Backend tests (future)
|
| 257 |
+
├── .env # Backend environment variables ✅
|
| 258 |
+
└── requirements.txt # Python dependencies ✅
|
| 259 |
+
|
| 260 |
+
frontend/
|
| 261 |
+
├── src/
|
| 262 |
+
│ ├── app/
|
| 263 |
+
│ │ ├── layout.tsx # Root layout with AuthProvider ✅
|
| 264 |
+
│ │ ├── page.tsx # Home page (protected) ✅
|
| 265 |
+
│ │ ├── auth/
|
| 266 |
+
│ │ │ ├── signin/
|
| 267 |
+
│ │ │ │ └── page.tsx # Signin page (Spec 2) ✅
|
| 268 |
+
│ │ │ └── signup/
|
| 269 |
+
│ │ │ └── page.tsx # Signup page (Spec 2) ✅
|
| 270 |
+
│ │ └── globals.css # Global Tailwind styles ✅
|
| 271 |
+
│ ├── components/
|
| 272 |
+
│ │ ├── auth/
|
| 273 |
+
│ │ │ ├── SignInForm.tsx # Signin form (Spec 2) ✅
|
| 274 |
+
│ │ │ └── SignUpForm.tsx # Signup form (Spec 2) ✅
|
| 275 |
+
│ │ └── tasks/
|
| 276 |
+
│ │ ├── TaskForm.tsx # Create task (Spec 1) ✅
|
| 277 |
+
│ │ ├── TaskItem.tsx # Task display (Spec 1) ✅
|
| 278 |
+
│ │ ├── TaskList.tsx # Task list (Spec 1) ✅
|
| 279 |
+
│ │ └── TaskFilters.tsx # Filters (Spec 1) ✅
|
| 280 |
+
│ ├── lib/
|
| 281 |
+
│ │ ├── api.ts # API client (needs refinement) 🔄
|
| 282 |
+
│ │ ├── auth.ts # Auth session (Spec 2) ✅
|
| 283 |
+
│ │ └── types.ts # TypeScript types ✅
|
| 284 |
+
│ └── providers/
|
| 285 |
+
│ └── AuthProvider.tsx # Auth context (Spec 2) ✅
|
| 286 |
+
├── public/ # Static assets
|
| 287 |
+
├── .env.local # Frontend environment variables ✅
|
| 288 |
+
├── package.json # Node dependencies ✅
|
| 289 |
+
├── tailwind.config.ts # Tailwind configuration ✅
|
| 290 |
+
└── tsconfig.json # TypeScript configuration ✅
|
| 291 |
+
|
| 292 |
+
specs/
|
| 293 |
+
├── 001-auth-security/ # Spec 2 (Authentication) ✅
|
| 294 |
+
└── 002-fullstack-ui-integration/ # This feature (in progress)
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
**Structure Decision**: The existing web application structure is maintained. This feature focuses on refining frontend components and the API client rather than adding new files. Key areas of work:
|
| 298 |
+
|
| 299 |
+
1. **API Client Refinement** (`frontend/src/lib/api.ts`):
|
| 300 |
+
- Already includes JWT token injection ✅
|
| 301 |
+
- Already handles 401 redirects ✅
|
| 302 |
+
- Needs: Consistent error formatting, loading state management
|
| 303 |
+
|
| 304 |
+
2. **Component Enhancements**:
|
| 305 |
+
- Add loading states to all async operations
|
| 306 |
+
- Add empty states to TaskList
|
| 307 |
+
- Add error states with retry buttons
|
| 308 |
+
- Improve responsive layouts
|
| 309 |
+
|
| 310 |
+
3. **Responsive Design**:
|
| 311 |
+
- Refine Tailwind breakpoints in existing components
|
| 312 |
+
- Ensure 44x44px touch targets
|
| 313 |
+
- Test layouts at 320px, 768px, 1920px
|
| 314 |
+
|
| 315 |
+
4. **Documentation**:
|
| 316 |
+
- Update README files with setup instructions
|
| 317 |
+
- Create quickstart guide for reviewers
|
| 318 |
+
|
| 319 |
+
**No new directories or major structural changes required.**
|
| 320 |
+
|
| 321 |
+
## Complexity Tracking
|
| 322 |
+
|
| 323 |
+
> **Fill ONLY if Constitution Check has violations that must be justified**
|
| 324 |
+
|
| 325 |
+
**No violations detected** - Complexity tracking not required.
|
| 326 |
+
|
| 327 |
+
This feature maintains existing architecture and focuses on polish/integration. All constitutional principles are satisfied without introducing additional complexity.
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## Post-Design Constitution Check
|
| 332 |
+
|
| 333 |
+
*Re-evaluation after Phase 0 (Research) and Phase 1 (Design) completion*
|
| 334 |
+
|
| 335 |
+
### Design Artifacts Generated
|
| 336 |
+
|
| 337 |
+
1. **research.md**: 10 technical decisions documented
|
| 338 |
+
- UI state management patterns (React hooks)
|
| 339 |
+
- Loading/empty/error state designs
|
| 340 |
+
- Responsive design breakpoints (Tailwind defaults)
|
| 341 |
+
- Touch target sizing (44x44px minimum)
|
| 342 |
+
- API client error handling (existing implementation)
|
| 343 |
+
- Form validation patterns (existing implementation)
|
| 344 |
+
- Optimistic UI updates
|
| 345 |
+
- Environment configuration
|
| 346 |
+
|
| 347 |
+
2. **data-model.md**: Reference to existing entities
|
| 348 |
+
- User (from Spec 2)
|
| 349 |
+
- Task (from Spec 1)
|
| 350 |
+
- AuthSession (frontend only)
|
| 351 |
+
- No new entities introduced
|
| 352 |
+
|
| 353 |
+
3. **contracts/existing-api-reference.yaml**: OpenAPI 3.0 specification
|
| 354 |
+
- Authentication endpoints (Spec 2)
|
| 355 |
+
- Task CRUD endpoints (Spec 1)
|
| 356 |
+
- No new endpoints introduced
|
| 357 |
+
|
| 358 |
+
4. **quickstart.md**: Testing and validation guide
|
| 359 |
+
- Setup instructions (5 minutes)
|
| 360 |
+
- Test scenarios for all 5 user stories
|
| 361 |
+
- Common issues and solutions
|
| 362 |
+
- Performance benchmarks
|
| 363 |
+
- Validation checklist
|
| 364 |
+
|
| 365 |
+
### Re-evaluation Results
|
| 366 |
+
|
| 367 |
+
**Principle I: User-Centric Functionality** ✅ PASS
|
| 368 |
+
- Research confirms focus on user feedback (loading, empty, error states)
|
| 369 |
+
- Responsive design ensures accessibility across devices
|
| 370 |
+
- No changes to core functionality
|
| 371 |
+
|
| 372 |
+
**Principle II: Spec-Driven Development** ✅ PASS
|
| 373 |
+
- All design artifacts generated via spec-driven workflow
|
| 374 |
+
- Research documents existing patterns and decisions
|
| 375 |
+
- No ad-hoc implementations
|
| 376 |
+
|
| 377 |
+
**Principle III: Security & Data Privacy** ✅ PASS
|
| 378 |
+
- No new security mechanisms introduced
|
| 379 |
+
- Reuses existing JWT authentication
|
| 380 |
+
- API client maintains Authorization header inclusion
|
| 381 |
+
- No changes to data isolation logic
|
| 382 |
+
|
| 383 |
+
**Principle IV: Scalable Architecture** ✅ PASS
|
| 384 |
+
- Maintains stateless architecture
|
| 385 |
+
- No new backend state introduced
|
| 386 |
+
- Responsive design scales across devices
|
| 387 |
+
- Centralized API client improves maintainability
|
| 388 |
+
|
| 389 |
+
**Principle V: Maintainable & Consistent Code** ✅ PASS
|
| 390 |
+
- Research documents consistent patterns (loading states, error handling)
|
| 391 |
+
- Tailwind CSS usage enforced
|
| 392 |
+
- Reusable component patterns identified
|
| 393 |
+
- No new complexity introduced
|
| 394 |
+
|
| 395 |
+
### Key Standards Compliance (Post-Design)
|
| 396 |
+
|
| 397 |
+
**API Compliance** ✅ PASS
|
| 398 |
+
- OpenAPI specification documents all existing endpoints
|
| 399 |
+
- No new endpoints introduced
|
| 400 |
+
- Error handling patterns documented
|
| 401 |
+
|
| 402 |
+
**Database Integrity** ✅ PASS
|
| 403 |
+
- No database changes
|
| 404 |
+
- Existing schema maintained
|
| 405 |
+
- No new migrations required
|
| 406 |
+
|
| 407 |
+
**Frontend Quality** ✅ PASS
|
| 408 |
+
- Responsive design patterns documented
|
| 409 |
+
- Tailwind CSS usage confirmed
|
| 410 |
+
- Component enhancement patterns identified
|
| 411 |
+
|
| 412 |
+
**Authentication** ✅ PASS
|
| 413 |
+
- Existing Better Auth + JWT maintained
|
| 414 |
+
- No changes to authentication flow
|
| 415 |
+
- Token handling patterns documented
|
| 416 |
+
|
| 417 |
+
**Spec Adherence** ✅ PASS
|
| 418 |
+
- All design artifacts reference spec.md
|
| 419 |
+
- Implementation will reference plan.md, research.md, data-model.md
|
| 420 |
+
- No deviations from specification
|
| 421 |
+
|
| 422 |
+
### Post-Design Verdict
|
| 423 |
+
|
| 424 |
+
**Overall Verdict**: ✅ **APPROVED** - All principles and standards remain satisfied after design phase
|
| 425 |
+
|
| 426 |
+
**Confirmation**: The design phase (research, data model, contracts, quickstart) confirms that this feature:
|
| 427 |
+
1. Introduces no new architectural complexity
|
| 428 |
+
2. Maintains all existing security mechanisms
|
| 429 |
+
3. Focuses entirely on UI polish and integration
|
| 430 |
+
4. Follows established patterns from Specs 1 & 2
|
| 431 |
+
5. Requires no database or backend changes
|
| 432 |
+
|
| 433 |
+
**Ready for Task Generation**: Proceed to `/sp.tasks` command to generate implementation tasks
|
| 434 |
+
|
| 435 |
+
---
|
| 436 |
+
|
| 437 |
+
## Planning Summary
|
| 438 |
+
|
| 439 |
+
### Artifacts Generated
|
| 440 |
+
|
| 441 |
+
| Artifact | Status | Purpose |
|
| 442 |
+
|----------|--------|---------|
|
| 443 |
+
| spec.md | ✅ Complete | Feature specification with 5 user stories |
|
| 444 |
+
| plan.md | ✅ Complete | This file - implementation plan |
|
| 445 |
+
| research.md | ✅ Complete | 10 technical decisions documented |
|
| 446 |
+
| data-model.md | ✅ Complete | Reference to existing entities |
|
| 447 |
+
| contracts/existing-api-reference.yaml | ✅ Complete | OpenAPI 3.0 specification |
|
| 448 |
+
| quickstart.md | ✅ Complete | Testing and validation guide |
|
| 449 |
+
| checklists/requirements.md | ✅ Complete | Specification validation (all passed) |
|
| 450 |
+
|
| 451 |
+
### Next Steps
|
| 452 |
+
|
| 453 |
+
1. **Generate tasks.md**: Run `/sp.tasks` command to create implementation tasks
|
| 454 |
+
2. **Implement**: Run `/sp.implement` command to execute tasks
|
| 455 |
+
3. **Test**: Follow quickstart.md to validate all user stories
|
| 456 |
+
4. **Document**: Update README files with any new patterns
|
| 457 |
+
|
| 458 |
+
**Status**: ✅ Planning complete - Ready for task generation
|
specs/002-fullstack-ui-integration/quickstart.md
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart Guide: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Feature**: 002-fullstack-ui-integration
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Purpose**: Testing and validation guide for integration and UI polish
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This guide helps developers and reviewers quickly set up, test, and validate the Full-Stack Integration & UI Experience feature. Since this feature builds on existing implementations (Specs 1 & 2), most setup is already complete.
|
| 10 |
+
|
| 11 |
+
## Prerequisites
|
| 12 |
+
|
| 13 |
+
Before testing this feature, ensure:
|
| 14 |
+
|
| 15 |
+
1. **Specs 1 & 2 are complete**:
|
| 16 |
+
- ✅ Task CRUD endpoints working (Spec 1)
|
| 17 |
+
- ✅ Authentication & JWT working (Spec 2)
|
| 18 |
+
- ✅ Database migrations applied
|
| 19 |
+
- ✅ Environment variables configured
|
| 20 |
+
|
| 21 |
+
2. **Development environment**:
|
| 22 |
+
- Node.js 18+ installed
|
| 23 |
+
- Python 3.11+ installed
|
| 24 |
+
- PostgreSQL database accessible (Neon or local)
|
| 25 |
+
- Git repository cloned
|
| 26 |
+
|
| 27 |
+
## Quick Setup (5 minutes)
|
| 28 |
+
|
| 29 |
+
### 1. Backend Setup
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
# Navigate to backend directory
|
| 33 |
+
cd backend
|
| 34 |
+
|
| 35 |
+
# Install dependencies (if not already done)
|
| 36 |
+
pip install -r requirements.txt
|
| 37 |
+
|
| 38 |
+
# Verify environment variables
|
| 39 |
+
cat .env
|
| 40 |
+
# Should contain:
|
| 41 |
+
# - DATABASE_URL
|
| 42 |
+
# - BETTER_AUTH_SECRET
|
| 43 |
+
# - JWT_ALGORITHM=HS256
|
| 44 |
+
# - JWT_EXPIRATION_DAYS=7
|
| 45 |
+
|
| 46 |
+
# Apply migrations (if not already done)
|
| 47 |
+
python -m alembic upgrade head
|
| 48 |
+
|
| 49 |
+
# Start backend server
|
| 50 |
+
python -m uvicorn src.main:app --reload
|
| 51 |
+
|
| 52 |
+
# Server should start at http://localhost:8000
|
| 53 |
+
# Verify: Open http://localhost:8000/docs (Swagger UI)
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
### 2. Frontend Setup
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
# Navigate to frontend directory (in new terminal)
|
| 60 |
+
cd frontend
|
| 61 |
+
|
| 62 |
+
# Install dependencies (if not already done)
|
| 63 |
+
npm install
|
| 64 |
+
|
| 65 |
+
# Verify environment variables
|
| 66 |
+
cat .env.local
|
| 67 |
+
# Should contain:
|
| 68 |
+
# - NEXT_PUBLIC_API_URL=http://localhost:8000
|
| 69 |
+
# - BETTER_AUTH_SECRET (same as backend)
|
| 70 |
+
|
| 71 |
+
# Start frontend server
|
| 72 |
+
npm run dev
|
| 73 |
+
|
| 74 |
+
# Server should start at http://localhost:3000
|
| 75 |
+
# Verify: Open http://localhost:3000
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### 3. Verify Setup
|
| 79 |
+
|
| 80 |
+
**Backend Health Check**:
|
| 81 |
+
```bash
|
| 82 |
+
curl http://localhost:8000/health
|
| 83 |
+
# Expected: {"status":"healthy"}
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**Frontend Access**:
|
| 87 |
+
- Open http://localhost:3000
|
| 88 |
+
- Should redirect to http://localhost:3000/auth/signin
|
| 89 |
+
- Signin page should load without errors
|
| 90 |
+
|
| 91 |
+
## Testing User Stories
|
| 92 |
+
|
| 93 |
+
### P1: Complete Authentication Flow (MVP)
|
| 94 |
+
|
| 95 |
+
**Test Scenario**: New user signup → signin → task management
|
| 96 |
+
|
| 97 |
+
**Steps**:
|
| 98 |
+
|
| 99 |
+
1. **Navigate to signup**:
|
| 100 |
+
```
|
| 101 |
+
Open: http://localhost:3000/auth/signup
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
2. **Test validation errors**:
|
| 105 |
+
- Try empty email → Should show "Email is required"
|
| 106 |
+
- Try invalid email (e.g., "notanemail") → Should show "Invalid email format"
|
| 107 |
+
- Try weak password (e.g., "pass") → Should show password requirements
|
| 108 |
+
- Try short name → Should show "Name is required"
|
| 109 |
+
|
| 110 |
+
3. **Create account**:
|
| 111 |
+
- Email: `test@example.com`
|
| 112 |
+
- Password: `TestPass123`
|
| 113 |
+
- Name: `Test User`
|
| 114 |
+
- Click "Sign Up"
|
| 115 |
+
- **Expected**: Redirect to signin page with success message
|
| 116 |
+
|
| 117 |
+
4. **Sign in**:
|
| 118 |
+
- Email: `test@example.com`
|
| 119 |
+
- Password: `TestPass123`
|
| 120 |
+
- Click "Sign In"
|
| 121 |
+
- **Expected**: Redirect to home page (http://localhost:3000)
|
| 122 |
+
|
| 123 |
+
5. **Verify authenticated state**:
|
| 124 |
+
- Header should show "Welcome, Test User"
|
| 125 |
+
- "Sign Out" button should be visible
|
| 126 |
+
- Task form and list should be visible
|
| 127 |
+
|
| 128 |
+
6. **Sign out**:
|
| 129 |
+
- Click "Sign Out" button
|
| 130 |
+
- **Expected**: Redirect to signin page
|
| 131 |
+
- **Expected**: Cannot access home page without signin
|
| 132 |
+
|
| 133 |
+
**Pass Criteria**:
|
| 134 |
+
- ✅ Validation errors display inline
|
| 135 |
+
- ✅ Signup creates account successfully
|
| 136 |
+
- ✅ Signin issues JWT token
|
| 137 |
+
- ✅ Home page shows user profile
|
| 138 |
+
- ✅ Sign out clears session
|
| 139 |
+
|
| 140 |
+
---
|
| 141 |
+
|
| 142 |
+
### P2: Responsive UI States
|
| 143 |
+
|
| 144 |
+
**Test Scenario**: Loading, empty, and error states
|
| 145 |
+
|
| 146 |
+
**Steps**:
|
| 147 |
+
|
| 148 |
+
1. **Test loading state**:
|
| 149 |
+
- Sign in as test user
|
| 150 |
+
- Observe task list loading
|
| 151 |
+
- **Expected**: Loading spinner with "Loading tasks..." message
|
| 152 |
+
- **Expected**: Spinner disappears when tasks load
|
| 153 |
+
|
| 154 |
+
2. **Test empty state**:
|
| 155 |
+
- If no tasks exist:
|
| 156 |
+
- **Expected**: "No tasks yet" message
|
| 157 |
+
- **Expected**: Call-to-action to create first task
|
| 158 |
+
- **Expected**: Empty state is centered and clear
|
| 159 |
+
|
| 160 |
+
3. **Test error state**:
|
| 161 |
+
- Stop backend server (Ctrl+C in backend terminal)
|
| 162 |
+
- Try to create a task
|
| 163 |
+
- **Expected**: Error message "Unable to connect to server"
|
| 164 |
+
- **Expected**: Retry button appears
|
| 165 |
+
- Restart backend server
|
| 166 |
+
- Click retry button
|
| 167 |
+
- **Expected**: Operation succeeds
|
| 168 |
+
|
| 169 |
+
4. **Test form loading state**:
|
| 170 |
+
- Create a task
|
| 171 |
+
- Observe submit button during API call
|
| 172 |
+
- **Expected**: Button shows "Creating..." and is disabled
|
| 173 |
+
- **Expected**: Button returns to normal after success
|
| 174 |
+
|
| 175 |
+
5. **Test token expiration** (optional - requires waiting 7 days or manual token manipulation):
|
| 176 |
+
- With expired token, try to access home page
|
| 177 |
+
- **Expected**: Redirect to signin with "Session expired" message
|
| 178 |
+
|
| 179 |
+
**Pass Criteria**:
|
| 180 |
+
- ✅ Loading states appear within 100ms
|
| 181 |
+
- ✅ Empty states provide clear guidance
|
| 182 |
+
- ✅ Error messages are actionable
|
| 183 |
+
- ✅ Form buttons show loading state
|
| 184 |
+
- ✅ Token expiration handled gracefully
|
| 185 |
+
|
| 186 |
+
---
|
| 187 |
+
|
| 188 |
+
### P3: Responsive Design
|
| 189 |
+
|
| 190 |
+
**Test Scenario**: Mobile, tablet, desktop layouts
|
| 191 |
+
|
| 192 |
+
**Steps**:
|
| 193 |
+
|
| 194 |
+
1. **Test desktop layout (≥1024px)**:
|
| 195 |
+
- Open browser DevTools (F12)
|
| 196 |
+
- Set viewport to 1920x1080
|
| 197 |
+
- **Expected**: Three-column layout
|
| 198 |
+
- **Expected**: Task form (left), filters (middle), task list (right)
|
| 199 |
+
|
| 200 |
+
2. **Test tablet layout (768px-1023px)**:
|
| 201 |
+
- Set viewport to 768x1024
|
| 202 |
+
- **Expected**: Two-column layout
|
| 203 |
+
- **Expected**: Task form and filters stacked (left), task list (right)
|
| 204 |
+
|
| 205 |
+
3. **Test mobile layout (<768px)**:
|
| 206 |
+
- Set viewport to 375x667 (iPhone SE)
|
| 207 |
+
- **Expected**: Single-column layout
|
| 208 |
+
- **Expected**: All elements stacked vertically
|
| 209 |
+
- **Expected**: No horizontal scrolling
|
| 210 |
+
|
| 211 |
+
4. **Test touch targets**:
|
| 212 |
+
- On mobile viewport, inspect buttons
|
| 213 |
+
- **Expected**: All buttons are at least 44x44px
|
| 214 |
+
- **Expected**: Adequate spacing between interactive elements
|
| 215 |
+
|
| 216 |
+
5. **Test signin/signup forms**:
|
| 217 |
+
- Navigate to signin page on mobile
|
| 218 |
+
- **Expected**: Form is centered and readable
|
| 219 |
+
- **Expected**: Input fields use appropriate types (email, password)
|
| 220 |
+
- **Expected**: Keyboard doesn't obscure form fields
|
| 221 |
+
|
| 222 |
+
**Pass Criteria**:
|
| 223 |
+
- ✅ Layouts adapt to viewport width
|
| 224 |
+
- ✅ No horizontal scrolling on any device
|
| 225 |
+
- ✅ Touch targets are 44x44px minimum
|
| 226 |
+
- ✅ Forms are usable on mobile
|
| 227 |
+
- ✅ Text is readable without zooming
|
| 228 |
+
|
| 229 |
+
---
|
| 230 |
+
|
| 231 |
+
### P4: Centralized API Communication
|
| 232 |
+
|
| 233 |
+
**Test Scenario**: Verify API client consistency
|
| 234 |
+
|
| 235 |
+
**Steps**:
|
| 236 |
+
|
| 237 |
+
1. **Verify JWT token inclusion**:
|
| 238 |
+
- Sign in as test user
|
| 239 |
+
- Open browser DevTools → Network tab
|
| 240 |
+
- Create a task
|
| 241 |
+
- Inspect POST /api/tasks request
|
| 242 |
+
- **Expected**: Authorization header present: `Bearer <token>`
|
| 243 |
+
|
| 244 |
+
2. **Verify 401 handling**:
|
| 245 |
+
- Clear localStorage (DevTools → Application → Local Storage → Clear)
|
| 246 |
+
- Try to access home page
|
| 247 |
+
- **Expected**: Automatic redirect to signin
|
| 248 |
+
- **Expected**: No console errors
|
| 249 |
+
|
| 250 |
+
3. **Verify error formatting**:
|
| 251 |
+
- Sign in
|
| 252 |
+
- Stop backend server
|
| 253 |
+
- Try to create a task
|
| 254 |
+
- Open browser console
|
| 255 |
+
- **Expected**: APIError with status, detail, error_code
|
| 256 |
+
- **Expected**: Error displayed in UI (not just console)
|
| 257 |
+
|
| 258 |
+
4. **Verify all endpoints use fetchAPI**:
|
| 259 |
+
- Review code: `frontend/src/lib/api.ts`
|
| 260 |
+
- **Expected**: All API functions use fetchAPI helper
|
| 261 |
+
- **Expected**: No direct fetch() calls in components
|
| 262 |
+
|
| 263 |
+
**Pass Criteria**:
|
| 264 |
+
- ✅ JWT tokens included automatically
|
| 265 |
+
- ✅ 401 errors trigger signin redirect
|
| 266 |
+
- ✅ Errors formatted consistently
|
| 267 |
+
- ✅ All API calls use centralized client
|
| 268 |
+
- ✅ No unhandled promise rejections
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
### P5: Environment Coordination
|
| 273 |
+
|
| 274 |
+
**Test Scenario**: Setup and configuration
|
| 275 |
+
|
| 276 |
+
**Steps**:
|
| 277 |
+
|
| 278 |
+
1. **Verify environment variables**:
|
| 279 |
+
```bash
|
| 280 |
+
# Backend
|
| 281 |
+
grep BETTER_AUTH_SECRET backend/.env
|
| 282 |
+
|
| 283 |
+
# Frontend
|
| 284 |
+
grep BETTER_AUTH_SECRET frontend/.env.local
|
| 285 |
+
|
| 286 |
+
# Expected: Both values match exactly
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
2. **Test with missing environment variable**:
|
| 290 |
+
- Temporarily rename `backend/.env` to `backend/.env.backup`
|
| 291 |
+
- Try to start backend
|
| 292 |
+
- **Expected**: Clear error message about missing variables
|
| 293 |
+
- Restore `backend/.env`
|
| 294 |
+
|
| 295 |
+
3. **Test with mismatched secrets**:
|
| 296 |
+
- Change BETTER_AUTH_SECRET in `frontend/.env.local`
|
| 297 |
+
- Sign in
|
| 298 |
+
- **Expected**: Token verification fails
|
| 299 |
+
- **Expected**: Clear error message
|
| 300 |
+
- Restore correct secret
|
| 301 |
+
|
| 302 |
+
4. **Verify README documentation**:
|
| 303 |
+
- Read `backend/README.md`
|
| 304 |
+
- **Expected**: Authentication setup instructions present
|
| 305 |
+
- **Expected**: Environment variable documentation
|
| 306 |
+
- Read `frontend/README.md`
|
| 307 |
+
- **Expected**: Better Auth configuration notes
|
| 308 |
+
- **Expected**: Setup instructions
|
| 309 |
+
|
| 310 |
+
**Pass Criteria**:
|
| 311 |
+
- ✅ Environment variables documented
|
| 312 |
+
- ✅ Missing variables show clear errors
|
| 313 |
+
- ✅ Mismatched secrets are detected
|
| 314 |
+
- ✅ README files are up-to-date
|
| 315 |
+
- ✅ Setup takes under 10 minutes
|
| 316 |
+
|
| 317 |
+
---
|
| 318 |
+
|
| 319 |
+
## Common Issues & Solutions
|
| 320 |
+
|
| 321 |
+
### Issue: "404 Not Found" on /api/auth/signup
|
| 322 |
+
|
| 323 |
+
**Cause**: Auth router not registered in backend/src/main.py
|
| 324 |
+
|
| 325 |
+
**Solution**:
|
| 326 |
+
```python
|
| 327 |
+
# In backend/src/main.py, ensure:
|
| 328 |
+
from .api.routes import tasks, auth
|
| 329 |
+
|
| 330 |
+
app.include_router(auth.router) # Must be present
|
| 331 |
+
app.include_router(tasks.router)
|
| 332 |
+
```
|
| 333 |
+
|
| 334 |
+
### Issue: "Token signature verification failed"
|
| 335 |
+
|
| 336 |
+
**Cause**: BETTER_AUTH_SECRET differs between frontend and backend
|
| 337 |
+
|
| 338 |
+
**Solution**:
|
| 339 |
+
```bash
|
| 340 |
+
# Verify secrets match:
|
| 341 |
+
grep BETTER_AUTH_SECRET backend/.env
|
| 342 |
+
grep BETTER_AUTH_SECRET frontend/.env.local
|
| 343 |
+
|
| 344 |
+
# If different, copy backend secret to frontend
|
| 345 |
+
```
|
| 346 |
+
|
| 347 |
+
### Issue: "Unable to connect to database"
|
| 348 |
+
|
| 349 |
+
**Cause**: DATABASE_URL is incorrect or database is not running
|
| 350 |
+
|
| 351 |
+
**Solution**:
|
| 352 |
+
```bash
|
| 353 |
+
# For Neon PostgreSQL:
|
| 354 |
+
# Verify connection string in backend/.env includes ?sslmode=require
|
| 355 |
+
|
| 356 |
+
# For local PostgreSQL:
|
| 357 |
+
# Ensure PostgreSQL is running:
|
| 358 |
+
# Windows: Check Services
|
| 359 |
+
# Mac/Linux: sudo systemctl status postgresql
|
| 360 |
+
```
|
| 361 |
+
|
| 362 |
+
### Issue: Frontend shows blank page
|
| 363 |
+
|
| 364 |
+
**Cause**: JavaScript error or build issue
|
| 365 |
+
|
| 366 |
+
**Solution**:
|
| 367 |
+
```bash
|
| 368 |
+
# Check browser console for errors
|
| 369 |
+
# Clear Next.js cache:
|
| 370 |
+
cd frontend
|
| 371 |
+
rm -rf .next
|
| 372 |
+
npm run dev
|
| 373 |
+
```
|
| 374 |
+
|
| 375 |
+
### Issue: Tasks not loading
|
| 376 |
+
|
| 377 |
+
**Cause**: JWT token missing or invalid
|
| 378 |
+
|
| 379 |
+
**Solution**:
|
| 380 |
+
```bash
|
| 381 |
+
# Check localStorage in browser DevTools:
|
| 382 |
+
# Application → Local Storage → http://localhost:3000
|
| 383 |
+
# Look for 'auth_session' key
|
| 384 |
+
|
| 385 |
+
# If missing or invalid, sign out and sign in again
|
| 386 |
+
```
|
| 387 |
+
|
| 388 |
+
## Performance Benchmarks
|
| 389 |
+
|
| 390 |
+
**Expected Performance**:
|
| 391 |
+
- Loading states appear: <100ms
|
| 392 |
+
- Page transitions: <500ms
|
| 393 |
+
- API responses: <200ms
|
| 394 |
+
- Task list load (10 tasks): <300ms
|
| 395 |
+
- Signup/signin: <1s
|
| 396 |
+
|
| 397 |
+
**How to Measure**:
|
| 398 |
+
```javascript
|
| 399 |
+
// In browser console:
|
| 400 |
+
performance.mark('start');
|
| 401 |
+
// Perform action (e.g., create task)
|
| 402 |
+
performance.mark('end');
|
| 403 |
+
performance.measure('action', 'start', 'end');
|
| 404 |
+
console.log(performance.getEntriesByType('measure'));
|
| 405 |
+
```
|
| 406 |
+
|
| 407 |
+
## Validation Checklist
|
| 408 |
+
|
| 409 |
+
Before marking this feature complete, verify:
|
| 410 |
+
|
| 411 |
+
**Authentication Flow**:
|
| 412 |
+
- [ ] Signup form validates inputs
|
| 413 |
+
- [ ] Signup creates user in database
|
| 414 |
+
- [ ] Signin issues JWT token
|
| 415 |
+
- [ ] Home page shows user profile
|
| 416 |
+
- [ ] Sign out clears session
|
| 417 |
+
|
| 418 |
+
**UI States**:
|
| 419 |
+
- [ ] Loading spinners appear during async operations
|
| 420 |
+
- [ ] Empty states show helpful messages
|
| 421 |
+
- [ ] Error messages are clear and actionable
|
| 422 |
+
- [ ] Form buttons show loading state
|
| 423 |
+
|
| 424 |
+
**Responsive Design**:
|
| 425 |
+
- [ ] Desktop layout (3 columns) works at 1920px
|
| 426 |
+
- [ ] Tablet layout (2 columns) works at 768px
|
| 427 |
+
- [ ] Mobile layout (1 column) works at 375px
|
| 428 |
+
- [ ] No horizontal scrolling on any device
|
| 429 |
+
- [ ] Touch targets are 44x44px minimum
|
| 430 |
+
|
| 431 |
+
**API Communication**:
|
| 432 |
+
- [ ] JWT tokens included in all requests
|
| 433 |
+
- [ ] 401 errors trigger signin redirect
|
| 434 |
+
- [ ] Errors formatted consistently
|
| 435 |
+
- [ ] No unhandled promise rejections
|
| 436 |
+
|
| 437 |
+
**Environment Setup**:
|
| 438 |
+
- [ ] Backend starts without errors
|
| 439 |
+
- [ ] Frontend starts without errors
|
| 440 |
+
- [ ] Environment variables documented
|
| 441 |
+
- [ ] README files are accurate
|
| 442 |
+
|
| 443 |
+
## Next Steps
|
| 444 |
+
|
| 445 |
+
After validating all user stories:
|
| 446 |
+
|
| 447 |
+
1. **Mark tasks complete** in `tasks.md`
|
| 448 |
+
2. **Document any issues** found during testing
|
| 449 |
+
3. **Create git commit** with implementation
|
| 450 |
+
4. **Prepare for demo** (if hackathon submission)
|
| 451 |
+
|
| 452 |
+
## Support
|
| 453 |
+
|
| 454 |
+
For issues or questions:
|
| 455 |
+
- Review specification: `specs/002-fullstack-ui-integration/spec.md`
|
| 456 |
+
- Review implementation plan: `specs/002-fullstack-ui-integration/plan.md`
|
| 457 |
+
- Check API reference: `specs/002-fullstack-ui-integration/contracts/existing-api-reference.yaml`
|
| 458 |
+
- Review existing specs: `specs/001-auth-security/`
|
specs/002-fullstack-ui-integration/research.md
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Research: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Feature**: 002-fullstack-ui-integration
|
| 4 |
+
**Date**: 2026-01-09
|
| 5 |
+
**Status**: Complete
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This research document captures technical decisions, patterns, and best practices for integrating existing functionality (Specs 1 & 2) into a cohesive user experience. Since this is a polish/integration feature rather than new functionality, most decisions reference existing implementations.
|
| 10 |
+
|
| 11 |
+
## Research Areas
|
| 12 |
+
|
| 13 |
+
### 1. UI State Management Patterns
|
| 14 |
+
|
| 15 |
+
**Decision**: Use React hooks (useState, useEffect) with loading/error/data states
|
| 16 |
+
|
| 17 |
+
**Rationale**:
|
| 18 |
+
- Already established pattern in existing components (TaskList, TaskForm)
|
| 19 |
+
- Simple and effective for component-level state
|
| 20 |
+
- No need for global state management (Redux, Zustand) for this scope
|
| 21 |
+
- Aligns with Next.js App Router best practices
|
| 22 |
+
|
| 23 |
+
**Pattern**:
|
| 24 |
+
```typescript
|
| 25 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 26 |
+
const [error, setError] = useState<string | null>(null);
|
| 27 |
+
const [data, setData] = useState<T | null>(null);
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
**Alternatives Considered**:
|
| 31 |
+
- React Query / TanStack Query: Overkill for current scope, adds dependency
|
| 32 |
+
- Redux: Too complex for simple loading/error states
|
| 33 |
+
- Context API: Not needed - state is component-local
|
| 34 |
+
|
| 35 |
+
**References**:
|
| 36 |
+
- Existing: `frontend/src/components/tasks/TaskList.tsx` (lines 10-15)
|
| 37 |
+
- Next.js Data Fetching: https://nextjs.org/docs/app/building-your-application/data-fetching
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
### 2. Loading State Indicators
|
| 42 |
+
|
| 43 |
+
**Decision**: Use Tailwind CSS spinner with descriptive text
|
| 44 |
+
|
| 45 |
+
**Rationale**:
|
| 46 |
+
- Consistent with existing Tailwind-only styling constraint
|
| 47 |
+
- Accessible (includes text for screen readers)
|
| 48 |
+
- Lightweight (no external animation libraries)
|
| 49 |
+
- Fast to implement and customize
|
| 50 |
+
|
| 51 |
+
**Pattern**:
|
| 52 |
+
```tsx
|
| 53 |
+
{isLoading && (
|
| 54 |
+
<div className="flex items-center justify-center p-8">
|
| 55 |
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
| 56 |
+
<span className="ml-3 text-gray-600">Loading tasks...</span>
|
| 57 |
+
</div>
|
| 58 |
+
)}
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
**Alternatives Considered**:
|
| 62 |
+
- Skeleton screens: More complex, better for content-heavy pages
|
| 63 |
+
- Progress bars: Not suitable for indeterminate loading
|
| 64 |
+
- Third-party libraries (react-spinners): Adds dependency, unnecessary
|
| 65 |
+
|
| 66 |
+
**References**:
|
| 67 |
+
- Tailwind Animation: https://tailwindcss.com/docs/animation
|
| 68 |
+
- Accessibility: Include aria-live="polite" for screen readers
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
### 3. Empty State Design
|
| 73 |
+
|
| 74 |
+
**Decision**: Centered message with icon and call-to-action
|
| 75 |
+
|
| 76 |
+
**Rationale**:
|
| 77 |
+
- Guides users toward next action (create first task)
|
| 78 |
+
- Reduces confusion when no data exists
|
| 79 |
+
- Industry standard pattern (GitHub, Notion, Linear)
|
| 80 |
+
- Improves onboarding experience
|
| 81 |
+
|
| 82 |
+
**Pattern**:
|
| 83 |
+
```tsx
|
| 84 |
+
{tasks.length === 0 && !isLoading && (
|
| 85 |
+
<div className="text-center py-12">
|
| 86 |
+
<p className="text-gray-500 text-lg mb-4">No tasks yet</p>
|
| 87 |
+
<p className="text-gray-400 mb-6">Create your first task to get started</p>
|
| 88 |
+
<button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
|
| 89 |
+
Create Task
|
| 90 |
+
</button>
|
| 91 |
+
</div>
|
| 92 |
+
)}
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
**Alternatives Considered**:
|
| 96 |
+
- Blank screen: Poor UX, users don't know what to do
|
| 97 |
+
- Tutorial overlay: Too intrusive for simple app
|
| 98 |
+
- Animated illustrations: Adds complexity, not needed
|
| 99 |
+
|
| 100 |
+
**References**:
|
| 101 |
+
- Empty States Best Practices: https://www.nngroup.com/articles/empty-state-design/
|
| 102 |
+
- Material Design Empty States: https://m2.material.io/design/communication/empty-states.html
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
### 4. Error Handling & Display
|
| 107 |
+
|
| 108 |
+
**Decision**: Inline error messages with retry button
|
| 109 |
+
|
| 110 |
+
**Rationale**:
|
| 111 |
+
- Keeps user in context (no modal dialogs)
|
| 112 |
+
- Provides actionable recovery (retry button)
|
| 113 |
+
- Consistent with existing API error handling
|
| 114 |
+
- Follows progressive disclosure principle
|
| 115 |
+
|
| 116 |
+
**Pattern**:
|
| 117 |
+
```tsx
|
| 118 |
+
{error && (
|
| 119 |
+
<div className="bg-red-50 border border-red-200 rounded-md p-4 mb-4">
|
| 120 |
+
<div className="flex items-start">
|
| 121 |
+
<div className="flex-1">
|
| 122 |
+
<h3 className="text-sm font-medium text-red-800">Error</h3>
|
| 123 |
+
<p className="text-sm text-red-700 mt-1">{error}</p>
|
| 124 |
+
</div>
|
| 125 |
+
<button
|
| 126 |
+
onClick={handleRetry}
|
| 127 |
+
className="ml-3 text-sm font-medium text-red-600 hover:text-red-500"
|
| 128 |
+
>
|
| 129 |
+
Retry
|
| 130 |
+
</button>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
)}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
**Alternatives Considered**:
|
| 137 |
+
- Toast notifications: Disappear too quickly, users miss them
|
| 138 |
+
- Modal dialogs: Disruptive, blocks entire UI
|
| 139 |
+
- Console.error only: Not user-facing, poor UX
|
| 140 |
+
|
| 141 |
+
**References**:
|
| 142 |
+
- Existing: `frontend/src/lib/api.ts` APIError class
|
| 143 |
+
- Error Message Guidelines: https://www.nngroup.com/articles/error-message-guidelines/
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
### 5. Responsive Design Breakpoints
|
| 148 |
+
|
| 149 |
+
**Decision**: Use Tailwind's default breakpoints (sm: 640px, md: 768px, lg: 1024px)
|
| 150 |
+
|
| 151 |
+
**Rationale**:
|
| 152 |
+
- Already configured in existing tailwind.config.ts
|
| 153 |
+
- Industry-standard breakpoints
|
| 154 |
+
- Covers mobile (320px-767px), tablet (768px-1023px), desktop (1024px+)
|
| 155 |
+
- No custom breakpoints needed for this scope
|
| 156 |
+
|
| 157 |
+
**Pattern**:
|
| 158 |
+
```tsx
|
| 159 |
+
<div className="grid gap-6 lg:grid-cols-3 md:grid-cols-2 grid-cols-1">
|
| 160 |
+
{/* Mobile: 1 column, Tablet: 2 columns, Desktop: 3 columns */}
|
| 161 |
+
</div>
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
**Breakpoint Strategy**:
|
| 165 |
+
- **Mobile (<768px)**: Single column, stacked layout
|
| 166 |
+
- **Tablet (768px-1023px)**: Two columns where appropriate
|
| 167 |
+
- **Desktop (≥1024px)**: Three columns, full layout
|
| 168 |
+
|
| 169 |
+
**Alternatives Considered**:
|
| 170 |
+
- Custom breakpoints: Unnecessary complexity
|
| 171 |
+
- Container queries: Not widely supported yet
|
| 172 |
+
- Fixed pixel widths: Not responsive
|
| 173 |
+
|
| 174 |
+
**References**:
|
| 175 |
+
- Existing: `frontend/tailwind.config.ts`
|
| 176 |
+
- Tailwind Responsive Design: https://tailwindcss.com/docs/responsive-design
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
### 6. Touch Target Sizing
|
| 181 |
+
|
| 182 |
+
**Decision**: Minimum 44x44px for all interactive elements
|
| 183 |
+
|
| 184 |
+
**Rationale**:
|
| 185 |
+
- WCAG 2.1 Level AAA guideline (44x44px)
|
| 186 |
+
- Apple Human Interface Guidelines (44x44pt)
|
| 187 |
+
- Material Design (48x48dp)
|
| 188 |
+
- Prevents accidental taps on mobile devices
|
| 189 |
+
|
| 190 |
+
**Pattern**:
|
| 191 |
+
```tsx
|
| 192 |
+
<button className="min-h-[44px] min-w-[44px] px-4 py-2">
|
| 193 |
+
Click Me
|
| 194 |
+
</button>
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
**Implementation**:
|
| 198 |
+
- Buttons: `min-h-[44px]` class
|
| 199 |
+
- Links: Adequate padding (py-2 px-3 minimum)
|
| 200 |
+
- Form inputs: `h-11` or `h-12` classes
|
| 201 |
+
- Checkboxes: `w-5 h-5` (20px) with larger clickable area via padding
|
| 202 |
+
|
| 203 |
+
**Alternatives Considered**:
|
| 204 |
+
- 48x48px: More generous but takes more space
|
| 205 |
+
- 40x40px: Below accessibility guidelines
|
| 206 |
+
- Variable sizing: Inconsistent, harder to maintain
|
| 207 |
+
|
| 208 |
+
**References**:
|
| 209 |
+
- WCAG 2.1 Success Criterion 2.5.5: https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
| 210 |
+
- Apple HIG: https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/
|
| 211 |
+
|
| 212 |
+
---
|
| 213 |
+
|
| 214 |
+
### 7. API Client Error Handling
|
| 215 |
+
|
| 216 |
+
**Decision**: Centralized error handling in fetchAPI with typed errors
|
| 217 |
+
|
| 218 |
+
**Rationale**:
|
| 219 |
+
- Already implemented in `frontend/src/lib/api.ts`
|
| 220 |
+
- Consistent error structure across all API calls
|
| 221 |
+
- TypeScript types for error responses
|
| 222 |
+
- Automatic 401 handling with signin redirect
|
| 223 |
+
|
| 224 |
+
**Existing Implementation**:
|
| 225 |
+
```typescript
|
| 226 |
+
class APIError extends Error {
|
| 227 |
+
constructor(
|
| 228 |
+
message: string,
|
| 229 |
+
public status: number,
|
| 230 |
+
public errorCode?: string,
|
| 231 |
+
public fieldErrors?: Record<string, string[]>
|
| 232 |
+
) {
|
| 233 |
+
super(message);
|
| 234 |
+
this.name = 'APIError';
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
```
|
| 238 |
+
|
| 239 |
+
**Enhancement Needed**: None - existing implementation is sufficient
|
| 240 |
+
|
| 241 |
+
**Alternatives Considered**:
|
| 242 |
+
- Per-component error handling: Inconsistent, duplicated code
|
| 243 |
+
- Global error boundary: Too coarse-grained, loses context
|
| 244 |
+
- Axios interceptors: Adds dependency, fetch is sufficient
|
| 245 |
+
|
| 246 |
+
**References**:
|
| 247 |
+
- Existing: `frontend/src/lib/api.ts` (lines 6-16, 18-59)
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
### 8. Form Validation Patterns
|
| 252 |
+
|
| 253 |
+
**Decision**: Client-side validation with inline error messages
|
| 254 |
+
|
| 255 |
+
**Rationale**:
|
| 256 |
+
- Already implemented in SignUpForm and SignInForm
|
| 257 |
+
- Immediate feedback improves UX
|
| 258 |
+
- Reduces unnecessary API calls
|
| 259 |
+
- Backend validation still enforced (defense in depth)
|
| 260 |
+
|
| 261 |
+
**Existing Pattern**:
|
| 262 |
+
```typescript
|
| 263 |
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
| 264 |
+
|
| 265 |
+
const validateEmail = (email: string): boolean => {
|
| 266 |
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
| 267 |
+
return emailRegex.test(email);
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
+
// Display errors inline
|
| 271 |
+
{errors.email && (
|
| 272 |
+
<p className="text-red-600 text-sm mt-1">{errors.email}</p>
|
| 273 |
+
)}
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
**Enhancement Needed**: None - existing validation is sufficient
|
| 277 |
+
|
| 278 |
+
**Alternatives Considered**:
|
| 279 |
+
- Form libraries (React Hook Form, Formik): Overkill for simple forms
|
| 280 |
+
- Schema validation (Zod, Yup): Adds complexity, not needed
|
| 281 |
+
- Server-side only: Poor UX, slow feedback
|
| 282 |
+
|
| 283 |
+
**References**:
|
| 284 |
+
- Existing: `frontend/src/components/auth/SignUpForm.tsx` (lines 20-40)
|
| 285 |
+
- Existing: `frontend/src/components/auth/SignInForm.tsx`
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
### 9. Optimistic UI Updates
|
| 290 |
+
|
| 291 |
+
**Decision**: Update UI immediately, rollback on error
|
| 292 |
+
|
| 293 |
+
**Rationale**:
|
| 294 |
+
- Improves perceived performance
|
| 295 |
+
- Makes app feel responsive
|
| 296 |
+
- Standard pattern for modern web apps
|
| 297 |
+
- Easy to implement with React state
|
| 298 |
+
|
| 299 |
+
**Pattern**:
|
| 300 |
+
```typescript
|
| 301 |
+
const handleToggleComplete = async (taskId: number) => {
|
| 302 |
+
// Optimistic update
|
| 303 |
+
setTasks(tasks.map(t =>
|
| 304 |
+
t.id === taskId ? { ...t, completed: !t.completed } : t
|
| 305 |
+
));
|
| 306 |
+
|
| 307 |
+
try {
|
| 308 |
+
await patchTask(taskId, { completed: !task.completed });
|
| 309 |
+
} catch (error) {
|
| 310 |
+
// Rollback on error
|
| 311 |
+
setTasks(tasks.map(t =>
|
| 312 |
+
t.id === taskId ? { ...t, completed: task.completed } : t
|
| 313 |
+
));
|
| 314 |
+
setError('Failed to update task');
|
| 315 |
+
}
|
| 316 |
+
};
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
**Alternatives Considered**:
|
| 320 |
+
- Wait for server response: Slower, less responsive
|
| 321 |
+
- No rollback: Inconsistent state on errors
|
| 322 |
+
- Pessimistic updates: Poor UX
|
| 323 |
+
|
| 324 |
+
**References**:
|
| 325 |
+
- React Optimistic Updates: https://react.dev/reference/react/useOptimistic
|
| 326 |
+
- Existing: Partially implemented in TaskItem component
|
| 327 |
+
|
| 328 |
+
---
|
| 329 |
+
|
| 330 |
+
### 10. Environment Configuration
|
| 331 |
+
|
| 332 |
+
**Decision**: Use .env files with clear documentation
|
| 333 |
+
|
| 334 |
+
**Rationale**:
|
| 335 |
+
- Already established in Specs 1 & 2
|
| 336 |
+
- Standard practice for web applications
|
| 337 |
+
- Keeps secrets out of source code
|
| 338 |
+
- Easy to configure for different environments
|
| 339 |
+
|
| 340 |
+
**Existing Configuration**:
|
| 341 |
+
- Backend: `backend/.env` (DATABASE_URL, BETTER_AUTH_SECRET, JWT_ALGORITHM, JWT_EXPIRATION_DAYS)
|
| 342 |
+
- Frontend: `frontend/.env.local` (NEXT_PUBLIC_API_URL, BETTER_AUTH_SECRET)
|
| 343 |
+
|
| 344 |
+
**Enhancement Needed**: Document in README files and quickstart.md
|
| 345 |
+
|
| 346 |
+
**Alternatives Considered**:
|
| 347 |
+
- Hardcoded values: Security risk, not flexible
|
| 348 |
+
- Config files: Less standard than .env
|
| 349 |
+
- Cloud secret managers: Overkill for local development
|
| 350 |
+
|
| 351 |
+
**References**:
|
| 352 |
+
- Existing: `backend/.env`, `frontend/.env.local`
|
| 353 |
+
- Next.js Environment Variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
|
| 354 |
+
|
| 355 |
+
---
|
| 356 |
+
|
| 357 |
+
## Summary of Decisions
|
| 358 |
+
|
| 359 |
+
| Area | Decision | Status |
|
| 360 |
+
|------|----------|--------|
|
| 361 |
+
| UI State Management | React hooks (useState, useEffect) | ✅ Existing |
|
| 362 |
+
| Loading Indicators | Tailwind CSS spinner with text | 🔄 To implement |
|
| 363 |
+
| Empty States | Centered message with CTA | 🔄 To implement |
|
| 364 |
+
| Error Display | Inline errors with retry button | 🔄 To implement |
|
| 365 |
+
| Responsive Design | Tailwind default breakpoints | ✅ Existing |
|
| 366 |
+
| Touch Targets | Minimum 44x44px | 🔄 To verify |
|
| 367 |
+
| API Error Handling | Centralized fetchAPI with typed errors | ✅ Existing |
|
| 368 |
+
| Form Validation | Client-side with inline errors | ✅ Existing |
|
| 369 |
+
| Optimistic Updates | Immediate UI update with rollback | 🔄 To implement |
|
| 370 |
+
| Environment Config | .env files with documentation | ✅ Existing |
|
| 371 |
+
|
| 372 |
+
**Legend**:
|
| 373 |
+
- ✅ Existing: Already implemented in Specs 1 & 2
|
| 374 |
+
- 🔄 To implement: Needs to be added in this feature
|
| 375 |
+
- 🔄 To verify: Needs to be checked/refined
|
| 376 |
+
|
| 377 |
+
## Implementation Priorities
|
| 378 |
+
|
| 379 |
+
Based on user story priorities (P1-P5):
|
| 380 |
+
|
| 381 |
+
1. **P1 (Authentication Flow)**: Verify existing implementation works end-to-end
|
| 382 |
+
2. **P2 (UI States)**: Implement loading, empty, and error states
|
| 383 |
+
3. **P3 (Responsive Design)**: Verify and refine responsive layouts
|
| 384 |
+
4. **P4 (API Communication)**: Verify centralized API client works correctly
|
| 385 |
+
5. **P5 (Environment Setup)**: Document configuration in README files
|
| 386 |
+
|
| 387 |
+
## Next Steps
|
| 388 |
+
|
| 389 |
+
1. Generate `data-model.md` (reference existing User and Task entities)
|
| 390 |
+
2. Generate `contracts/` (document existing API endpoints)
|
| 391 |
+
3. Generate `quickstart.md` (testing and setup guide)
|
| 392 |
+
4. Proceed to task generation (`/sp.tasks`)
|
specs/002-fullstack-ui-integration/spec.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `002-fullstack-ui-integration`
|
| 4 |
+
**Created**: 2026-01-09
|
| 5 |
+
**Status**: Draft
|
| 6 |
+
**Input**: User description: "Full-Stack Integration & UI Experience – Phase II Todo Web App"
|
| 7 |
+
|
| 8 |
+
## User Scenarios & Testing *(mandatory)*
|
| 9 |
+
|
| 10 |
+
### User Story 1 - Complete Authentication Flow (Priority: P1) 🎯 MVP
|
| 11 |
+
|
| 12 |
+
A new user visits the application, creates an account, signs in, and immediately sees a clean, responsive interface ready for task management.
|
| 13 |
+
|
| 14 |
+
**Why this priority**: This is the entry point for all users. Without a seamless authentication experience, users cannot access any functionality. This story validates the entire authentication integration from Spec 2 works end-to-end with proper UI feedback.
|
| 15 |
+
|
| 16 |
+
**Independent Test**: Navigate to the application URL, complete signup form, verify redirect to signin, sign in with credentials, and land on the task management page with user profile displayed in header.
|
| 17 |
+
|
| 18 |
+
**Acceptance Scenarios**:
|
| 19 |
+
|
| 20 |
+
1. **Given** a new user visits the application root URL, **When** they are not authenticated, **Then** they are automatically redirected to the signin page with a link to signup
|
| 21 |
+
2. **Given** a user on the signup page, **When** they submit valid credentials (email, password, name), **Then** they see a success message and are redirected to signin page
|
| 22 |
+
3. **Given** a user on the signup page, **When** they submit invalid data (weak password, invalid email), **Then** they see clear inline validation errors without page reload
|
| 23 |
+
4. **Given** a registered user on the signin page, **When** they submit correct credentials, **Then** they are redirected to the home page with their name displayed in the header
|
| 24 |
+
5. **Given** a user on the signin page, **When** they submit incorrect credentials, **Then** they see a generic error message "Invalid email or password" without revealing which field is wrong
|
| 25 |
+
6. **Given** an authenticated user on the home page, **When** they click the "Sign Out" button, **Then** their session is cleared and they are redirected to the signin page
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
### User Story 2 - Responsive UI States (Priority: P2)
|
| 30 |
+
|
| 31 |
+
Users experience appropriate visual feedback during all application states: loading data, empty states, error conditions, and successful operations.
|
| 32 |
+
|
| 33 |
+
**Why this priority**: Professional applications provide clear feedback. Users should never wonder if the app is working or broken. This story ensures the UI communicates system state effectively.
|
| 34 |
+
|
| 35 |
+
**Independent Test**: Sign in, observe loading spinner while tasks load, create first task and see empty state disappear, disconnect network and see error state, reconnect and see recovery.
|
| 36 |
+
|
| 37 |
+
**Acceptance Scenarios**:
|
| 38 |
+
|
| 39 |
+
1. **Given** a user signs in successfully, **When** the task list is loading, **Then** they see a loading spinner with "Loading tasks..." message
|
| 40 |
+
2. **Given** a new user with no tasks, **When** the task list finishes loading, **Then** they see an empty state with "No tasks yet" message and a call-to-action to create their first task
|
| 41 |
+
3. **Given** a user viewing their task list, **When** a network error occurs, **Then** they see a clear error message "Unable to load tasks. Please check your connection." with a retry button
|
| 42 |
+
4. **Given** a user creating a new task, **When** the API request is in progress, **Then** the submit button shows "Creating..." and is disabled to prevent duplicate submissions
|
| 43 |
+
5. **Given** a user on any page, **When** their JWT token expires, **Then** they are automatically redirected to signin with a message "Your session has expired. Please sign in again."
|
| 44 |
+
6. **Given** a user completing a task, **When** the update succeeds, **Then** the task UI updates immediately (optimistic update) without requiring a full page reload
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
### User Story 3 - Responsive Design (Priority: P3)
|
| 49 |
+
|
| 50 |
+
Users can access and use the application seamlessly across desktop, tablet, and mobile devices with appropriate layout adjustments.
|
| 51 |
+
|
| 52 |
+
**Why this priority**: Modern web applications must work on all screen sizes. This story ensures the UI adapts gracefully to different viewports, making the app accessible to users on any device.
|
| 53 |
+
|
| 54 |
+
**Independent Test**: Open the application on desktop (1920px), tablet (768px), and mobile (375px) viewports. Verify all functionality is accessible and layouts adjust appropriately.
|
| 55 |
+
|
| 56 |
+
**Acceptance Scenarios**:
|
| 57 |
+
|
| 58 |
+
1. **Given** a user on a desktop browser (≥1024px), **When** they view the home page, **Then** they see a three-column layout: task form (left), filters (middle), task list (right)
|
| 59 |
+
2. **Given** a user on a tablet (768px-1023px), **When** they view the home page, **Then** they see a two-column layout: task form and filters stacked (left), task list (right)
|
| 60 |
+
3. **Given** a user on a mobile device (<768px), **When** they view the home page, **Then** they see a single-column layout with task form, filters, and task list stacked vertically
|
| 61 |
+
4. **Given** a user on any device, **When** they interact with buttons and form inputs, **Then** touch targets are at least 44x44px for comfortable interaction
|
| 62 |
+
5. **Given** a user on mobile, **When** they view the signin/signup forms, **Then** the forms are centered, readable, and keyboard-friendly with appropriate input types (email, password)
|
| 63 |
+
6. **Given** a user on any device, **When** they navigate the application, **Then** all text is readable without horizontal scrolling and maintains proper contrast ratios
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
### User Story 4 - Centralized API Communication (Priority: P4)
|
| 68 |
+
|
| 69 |
+
All frontend-backend communication flows through a unified API client that handles authentication, error handling, and request/response formatting consistently.
|
| 70 |
+
|
| 71 |
+
**Why this priority**: Consistent API communication prevents bugs and makes the codebase maintainable. This story ensures all API calls follow the same patterns for auth, errors, and data handling.
|
| 72 |
+
|
| 73 |
+
**Independent Test**: Review the codebase to verify all API calls use the centralized `fetchAPI` function. Test that JWT tokens are automatically included, 401 errors trigger signin redirect, and error responses are consistently formatted.
|
| 74 |
+
|
| 75 |
+
**Acceptance Scenarios**:
|
| 76 |
+
|
| 77 |
+
1. **Given** any component making an API request, **When** the request is initiated, **Then** the JWT token is automatically included in the Authorization header without manual intervention
|
| 78 |
+
2. **Given** an API request in progress, **When** the backend returns a 401 Unauthorized, **Then** the user is automatically redirected to signin and their session is cleared
|
| 79 |
+
3. **Given** an API request fails, **When** the backend returns an error response, **Then** the error is caught and formatted consistently with `{ detail, error_code, field_errors }` structure
|
| 80 |
+
4. **Given** a component making multiple API calls, **When** any call fails, **Then** the error is handled locally without crashing the entire application
|
| 81 |
+
5. **Given** the backend is unreachable, **When** an API request times out, **Then** the user sees a clear error message "Unable to connect to server. Please try again later."
|
| 82 |
+
6. **Given** a successful API response, **When** the data is returned, **Then** it is automatically parsed as JSON and typed correctly for TypeScript consumers
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
### User Story 5 - Environment Coordination (Priority: P5)
|
| 87 |
+
|
| 88 |
+
The application runs successfully in local development with proper environment variable configuration and clear setup instructions.
|
| 89 |
+
|
| 90 |
+
**Why this priority**: Developers and reviewers need to run the application easily. This story ensures environment setup is straightforward and well-documented.
|
| 91 |
+
|
| 92 |
+
**Independent Test**: Clone the repository, follow README instructions to set up environment variables, start backend and frontend, and verify the application works end-to-end.
|
| 93 |
+
|
| 94 |
+
**Acceptance Scenarios**:
|
| 95 |
+
|
| 96 |
+
1. **Given** a developer cloning the repository, **When** they follow the backend README, **Then** they can set up the database, configure environment variables, and start the backend server successfully
|
| 97 |
+
2. **Given** a developer with the backend running, **When** they follow the frontend README, **Then** they can configure environment variables and start the frontend development server successfully
|
| 98 |
+
3. **Given** both servers running, **When** a user accesses `http://localhost:3000`, **Then** the frontend successfully communicates with the backend at `http://localhost:8000`
|
| 99 |
+
4. **Given** environment variables are missing, **When** the application starts, **Then** clear error messages indicate which variables are required (e.g., "BETTER_AUTH_SECRET is required")
|
| 100 |
+
5. **Given** the BETTER_AUTH_SECRET differs between frontend and backend, **When** a user tries to sign in, **Then** token verification fails with a clear error message
|
| 101 |
+
6. **Given** the database is not running, **When** the backend starts, **Then** it shows a clear error message "Unable to connect to database at [URL]"
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
### Edge Cases
|
| 106 |
+
|
| 107 |
+
- What happens when a user's JWT token expires while they're actively using the application (e.g., editing a task)?
|
| 108 |
+
- How does the system handle rapid successive API calls (e.g., user clicking "Create Task" multiple times)?
|
| 109 |
+
- What happens when the backend returns a 500 Internal Server Error?
|
| 110 |
+
- How does the UI behave when task titles or descriptions contain special characters, emojis, or very long text?
|
| 111 |
+
- What happens when a user tries to access a protected route by manually typing the URL while unauthenticated?
|
| 112 |
+
- How does the application handle browser back/forward navigation after signin/signout?
|
| 113 |
+
- What happens when localStorage is disabled or unavailable in the browser?
|
| 114 |
+
- How does the system handle concurrent edits (user edits same task in two browser tabs)?
|
| 115 |
+
|
| 116 |
+
## Requirements *(mandatory)*
|
| 117 |
+
|
| 118 |
+
### Functional Requirements
|
| 119 |
+
|
| 120 |
+
- **FR-001**: System MUST redirect unauthenticated users from protected routes to the signin page automatically
|
| 121 |
+
- **FR-002**: System MUST display user profile information (name or email) in the application header when authenticated
|
| 122 |
+
- **FR-003**: System MUST show loading indicators during all asynchronous operations (API calls, page transitions)
|
| 123 |
+
- **FR-004**: System MUST display empty states with helpful messages when no data exists (e.g., "No tasks yet. Create your first task!")
|
| 124 |
+
- **FR-005**: System MUST show clear error messages when operations fail, with actionable guidance (e.g., "Retry" button)
|
| 125 |
+
- **FR-006**: System MUST handle JWT token expiration gracefully by redirecting to signin with an appropriate message
|
| 126 |
+
- **FR-007**: System MUST prevent duplicate form submissions by disabling submit buttons during API requests
|
| 127 |
+
- **FR-008**: System MUST validate all form inputs on the client side before submission with inline error messages
|
| 128 |
+
- **FR-009**: System MUST adapt layout and component sizing based on viewport width (responsive design)
|
| 129 |
+
- **FR-010**: System MUST ensure all interactive elements have minimum touch target size of 44x44px for mobile usability
|
| 130 |
+
- **FR-011**: System MUST route all API requests through a centralized client that handles authentication automatically
|
| 131 |
+
- **FR-012**: System MUST include JWT tokens in Authorization headers for all protected API endpoints
|
| 132 |
+
- **FR-013**: System MUST catch and format all API errors consistently across the application
|
| 133 |
+
- **FR-014**: System MUST clear user session and redirect to signin on 401 Unauthorized responses
|
| 134 |
+
- **FR-015**: System MUST persist authentication state across page refreshes using localStorage
|
| 135 |
+
- **FR-016**: System MUST provide clear setup instructions in README files for both frontend and backend
|
| 136 |
+
- **FR-017**: System MUST validate that required environment variables are present on application startup
|
| 137 |
+
- **FR-018**: System MUST use the same BETTER_AUTH_SECRET in both frontend and backend for JWT verification
|
| 138 |
+
- **FR-019**: System MUST display appropriate CORS configuration to allow frontend-backend communication
|
| 139 |
+
- **FR-020**: System MUST use Tailwind CSS utility classes exclusively for styling (no inline styles)
|
| 140 |
+
|
| 141 |
+
### Key Entities
|
| 142 |
+
|
| 143 |
+
This feature focuses on integration and UI experience rather than introducing new data entities. It leverages existing entities from Specs 1 and 2:
|
| 144 |
+
|
| 145 |
+
- **User**: Authenticated user with profile information (from Spec 2)
|
| 146 |
+
- **Task**: User's todo items with CRUD operations (from Spec 1)
|
| 147 |
+
- **AuthSession**: Frontend session state containing JWT token and user profile (from Spec 2)
|
| 148 |
+
|
| 149 |
+
## Success Criteria *(mandatory)*
|
| 150 |
+
|
| 151 |
+
### Measurable Outcomes
|
| 152 |
+
|
| 153 |
+
- **SC-001**: Users can complete the full flow from signup to creating their first task in under 3 minutes
|
| 154 |
+
- **SC-002**: All loading states appear within 100ms of initiating an action to provide immediate feedback
|
| 155 |
+
- **SC-003**: Empty states provide clear guidance, resulting in 80% of new users creating their first task within 2 minutes
|
| 156 |
+
- **SC-004**: Error messages are clear enough that users can resolve issues without external help 90% of the time
|
| 157 |
+
- **SC-005**: The application layout adapts correctly to viewport widths from 320px (mobile) to 1920px (desktop) without horizontal scrolling
|
| 158 |
+
- **SC-006**: All interactive elements are accessible and usable on touch devices with no accidental clicks
|
| 159 |
+
- **SC-007**: JWT token expiration is handled gracefully with zero application crashes or undefined states
|
| 160 |
+
- **SC-008**: API errors are caught and displayed consistently across all features with zero unhandled promise rejections
|
| 161 |
+
- **SC-009**: Developers can set up and run the application locally in under 10 minutes following README instructions
|
| 162 |
+
- **SC-010**: The application works end-to-end in local development with proper environment configuration
|
| 163 |
+
|
| 164 |
+
## Assumptions *(mandatory)*
|
| 165 |
+
|
| 166 |
+
1. **Existing Functionality**: Specs 1 (Task CRUD) and 2 (Authentication) are fully implemented and functional
|
| 167 |
+
2. **Browser Support**: Modern browsers with ES6+ support (Chrome, Firefox, Safari, Edge - latest 2 versions)
|
| 168 |
+
3. **JavaScript Enabled**: Users have JavaScript enabled in their browsers
|
| 169 |
+
4. **Network Connectivity**: Users have stable internet connection for API communication
|
| 170 |
+
5. **LocalStorage Available**: Browser supports and allows localStorage for session persistence
|
| 171 |
+
6. **Development Environment**: Developers have Node.js 18+, Python 3.11+, and PostgreSQL installed
|
| 172 |
+
7. **Screen Sizes**: Target devices range from 320px (small mobile) to 1920px (desktop)
|
| 173 |
+
8. **Single User Session**: Users are expected to use one browser session at a time (concurrent sessions not optimized)
|
| 174 |
+
9. **English Language**: All UI text and error messages are in English
|
| 175 |
+
10. **No Offline Support**: Application requires active internet connection (no offline mode)
|
| 176 |
+
|
| 177 |
+
## Dependencies *(mandatory)*
|
| 178 |
+
|
| 179 |
+
### Internal Dependencies
|
| 180 |
+
|
| 181 |
+
- **Spec 1 - Task CRUD**: All task management endpoints must be functional
|
| 182 |
+
- **Spec 2 - Authentication & API Security**: JWT authentication and user management must be working
|
| 183 |
+
- **Backend API**: FastAPI server must be running and accessible
|
| 184 |
+
- **Database**: PostgreSQL database with all migrations applied
|
| 185 |
+
- **Environment Variables**: BETTER_AUTH_SECRET, DATABASE_URL, API URLs configured correctly
|
| 186 |
+
|
| 187 |
+
### External Dependencies
|
| 188 |
+
|
| 189 |
+
- **Next.js 16+**: Frontend framework with App Router
|
| 190 |
+
- **React 18+**: UI library
|
| 191 |
+
- **TypeScript 5.x**: Type safety
|
| 192 |
+
- **Tailwind CSS 3.x**: Styling framework
|
| 193 |
+
- **FastAPI**: Backend framework
|
| 194 |
+
- **SQLModel**: ORM for database operations
|
| 195 |
+
- **Better Auth**: Authentication library
|
| 196 |
+
- **JWT**: Token-based authentication
|
| 197 |
+
|
| 198 |
+
## Out of Scope *(mandatory)*
|
| 199 |
+
|
| 200 |
+
The following are explicitly NOT included in this specification:
|
| 201 |
+
|
| 202 |
+
1. **New Backend Logic**: No new API endpoints or business logic (handled in Spec 1)
|
| 203 |
+
2. **New Authentication Mechanisms**: No OAuth, SSO, or MFA (handled in Spec 2)
|
| 204 |
+
3. **Advanced Animations**: No complex transitions, animations, or motion design
|
| 205 |
+
4. **Design System**: No comprehensive component library or design tokens
|
| 206 |
+
5. **Mobile Native Apps**: No iOS or Android native applications
|
| 207 |
+
6. **Progressive Web App (PWA)**: No offline support, service workers, or installability
|
| 208 |
+
7. **Internationalization (i18n)**: No multi-language support
|
| 209 |
+
8. **Accessibility Audit**: No WCAG compliance testing (basic accessibility assumed)
|
| 210 |
+
9. **Performance Optimization**: No advanced caching, code splitting beyond Next.js defaults, or CDN setup
|
| 211 |
+
10. **CI/CD Pipelines**: No automated testing, deployment, or infrastructure scripts
|
| 212 |
+
11. **Docker Deployment**: No production Docker configuration (local development only)
|
| 213 |
+
12. **Monitoring & Analytics**: No error tracking, user analytics, or performance monitoring
|
| 214 |
+
13. **SEO Optimization**: No meta tags, sitemaps, or search engine optimization
|
| 215 |
+
14. **Email Notifications**: No email verification, password reset emails, or notifications
|
| 216 |
+
15. **Real-time Features**: No WebSockets, live updates, or collaborative editing
|
| 217 |
+
|
| 218 |
+
## References
|
| 219 |
+
|
| 220 |
+
- **@specs/ui/components.md**: Component design specifications (if exists)
|
| 221 |
+
- **@specs/ui/pages.md**: Page layout specifications (if exists)
|
| 222 |
+
- **@specs/overview.md**: Project overview and architecture (if exists)
|
| 223 |
+
- **@specs/architecture.md**: Technical architecture decisions (if exists)
|
| 224 |
+
- **Spec 1**: Task CRUD API implementation
|
| 225 |
+
- **Spec 2**: Authentication & API Security implementation
|
| 226 |
+
- **Next.js App Router Documentation**: https://nextjs.org/docs/app
|
| 227 |
+
- **Tailwind CSS Documentation**: https://tailwindcss.com/docs
|
| 228 |
+
- **Better Auth Documentation**: https://better-auth.com/docs
|
| 229 |
+
|
| 230 |
+
## Notes
|
| 231 |
+
|
| 232 |
+
This specification focuses on polishing and integrating existing functionality rather than building new features. The goal is to create a cohesive, professional user experience that demonstrates the full capabilities of the Phase II Todo Web App for hackathon evaluation.
|
| 233 |
+
|
| 234 |
+
Key integration points:
|
| 235 |
+
- Frontend ↔ Backend: Unified API client with automatic JWT handling
|
| 236 |
+
- Authentication ↔ UI: Seamless auth state management across all pages
|
| 237 |
+
- Components ↔ Styling: Consistent Tailwind CSS usage throughout
|
| 238 |
+
- Development ↔ Production: Clear environment setup and configuration
|
| 239 |
+
|
| 240 |
+
Success depends on attention to detail in error handling, loading states, and responsive design rather than adding new functionality.
|
specs/002-fullstack-ui-integration/tasks.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tasks: Full-Stack Integration & UI Experience
|
| 2 |
+
|
| 3 |
+
**Input**: Design documents from `/specs/002-fullstack-ui-integration/`
|
| 4 |
+
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md
|
| 5 |
+
|
| 6 |
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
| 7 |
+
|
| 8 |
+
**Note**: Tests are not included as they were not explicitly requested in the feature specification. This feature focuses on integration and polish of existing functionality.
|
| 9 |
+
|
| 10 |
+
## Format: `[ID] [P?] [Story] Description`
|
| 11 |
+
|
| 12 |
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
| 13 |
+
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
| 14 |
+
- Include exact file paths in descriptions
|
| 15 |
+
|
| 16 |
+
## Path Conventions
|
| 17 |
+
|
| 18 |
+
- **Web app**: `backend/src/`, `frontend/src/`
|
| 19 |
+
- All paths are relative to repository root
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Phase 1: Setup (6 tasks)
|
| 24 |
+
|
| 25 |
+
**Purpose**: Verify project structure and dependencies
|
| 26 |
+
|
| 27 |
+
- [x] T001 Verify backend project structure matches plan.md (backend/src/ with api/, core/, models/, schemas/, services/)
|
| 28 |
+
- [x] T002 Verify frontend project structure matches plan.md (frontend/src/ with app/, components/, lib/, providers/)
|
| 29 |
+
- [x] T003 [P] Verify backend dependencies installed (FastAPI, SQLModel, PyJWT, passlib, alembic)
|
| 30 |
+
- [x] T004 [P] Verify frontend dependencies installed (Next.js 16+, React 18+, TypeScript 5.x, Tailwind CSS 3.x)
|
| 31 |
+
- [x] T005 [P] Verify database connection works (backend can connect to PostgreSQL)
|
| 32 |
+
- [x] T006 [P] Verify environment variables exist (backend/.env and frontend/.env.local)
|
| 33 |
+
|
| 34 |
+
**Checkpoint**: Project structure and dependencies verified
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## Phase 2: Foundational (7 tasks)
|
| 39 |
+
|
| 40 |
+
**Purpose**: Verify core infrastructure from Specs 1 & 2 works correctly
|
| 41 |
+
|
| 42 |
+
**⚠️ CRITICAL**: These tasks verify existing implementations are functional before adding UI polish
|
| 43 |
+
|
| 44 |
+
- [x] T007 Verify auth router is registered in backend/src/main.py (POST /api/auth/signup, POST /api/auth/signin, GET /api/auth/me)
|
| 45 |
+
- [x] T008 Verify task router is registered in backend/src/main.py (GET/POST /api/tasks, GET/PUT/PATCH/DELETE /api/tasks/{id})
|
| 46 |
+
- [x] T009 [P] Verify JWT token generation works in backend/src/core/security.py (create_access_token function)
|
| 47 |
+
- [x] T010 [P] Verify JWT token verification works in backend/src/api/deps.py (get_current_user dependency)
|
| 48 |
+
- [x] T011 [P] Verify API client exists in frontend/src/lib/api.ts with fetchAPI function
|
| 49 |
+
- [x] T012 [P] Verify AuthProvider exists in frontend/src/providers/AuthProvider.tsx with session management
|
| 50 |
+
- [x] T013 Test end-to-end flow: signup → signin → create task → signout (manual verification)
|
| 51 |
+
|
| 52 |
+
**Checkpoint**: Foundation verified - all existing implementations functional
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## Phase 3: User Story 1 - Complete Authentication Flow (Priority: P1) 🎯 MVP (8 tasks)
|
| 57 |
+
|
| 58 |
+
**Goal**: Ensure seamless authentication experience from signup to task management
|
| 59 |
+
|
| 60 |
+
**Independent Test**: Navigate to application URL, complete signup, signin, and land on task management page with user profile displayed
|
| 61 |
+
|
| 62 |
+
### Implementation for User Story 1
|
| 63 |
+
|
| 64 |
+
- [x] T014 [P] [US1] Verify signup page exists at frontend/src/app/auth/signup/page.tsx with validation
|
| 65 |
+
- [x] T015 [P] [US1] Verify signin page exists at frontend/src/app/auth/signin/page.tsx with validation
|
| 66 |
+
- [x] T016 [US1] Verify protected route redirect in frontend/src/app/page.tsx (redirects unauthenticated users to signin)
|
| 67 |
+
- [x] T017 [US1] Add user profile display to header in frontend/src/app/layout.tsx (show "Welcome, {name}" when authenticated)
|
| 68 |
+
- [x] T018 [US1] Add signout button to header in frontend/src/app/layout.tsx (clears session and redirects to signin)
|
| 69 |
+
- [x] T019 [US1] Verify inline validation errors display in frontend/src/components/auth/SignUpForm.tsx (email, password, name)
|
| 70 |
+
- [x] T020 [US1] Verify inline validation errors display in frontend/src/components/auth/SignInForm.tsx (email, password)
|
| 71 |
+
- [x] T021 [US1] Test complete authentication flow: signup → signin → home page → signout (manual validation per quickstart.md)
|
| 72 |
+
|
| 73 |
+
**Checkpoint**: User Story 1 complete - authentication flow works end-to-end with proper UI feedback
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Phase 4: User Story 2 - Responsive UI States (Priority: P2) (8 tasks)
|
| 78 |
+
|
| 79 |
+
**Goal**: Provide clear visual feedback during all application states
|
| 80 |
+
|
| 81 |
+
**Independent Test**: Sign in, observe loading spinner, create first task and see empty state disappear, disconnect network and see error state
|
| 82 |
+
|
| 83 |
+
### Implementation for User Story 2
|
| 84 |
+
|
| 85 |
+
- [x] T022 [P] [US2] Add loading state to TaskList in frontend/src/components/tasks/TaskList.tsx (spinner with "Loading tasks..." message)
|
| 86 |
+
- [x] T023 [P] [US2] Add empty state to TaskList in frontend/src/components/tasks/TaskList.tsx (centered "No tasks yet" with CTA)
|
| 87 |
+
- [x] T024 [P] [US2] Add error state to TaskList in frontend/src/components/tasks/TaskList.tsx (error message with retry button)
|
| 88 |
+
- [x] T025 [P] [US2] Add loading state to TaskForm in frontend/src/components/tasks/TaskForm.tsx (disable submit button, show "Creating...")
|
| 89 |
+
- [x] T026 [US2] Add token expiration handling in frontend/src/lib/api.ts (redirect to signin with "Session expired" message on 401)
|
| 90 |
+
- [x] T027 [US2] Implement optimistic update for task completion in frontend/src/components/tasks/TaskItem.tsx (immediate UI update with rollback on error)
|
| 91 |
+
- [x] T028 [US2] Add loading state to task update operations in frontend/src/components/tasks/TaskItem.tsx (disable buttons during update)
|
| 92 |
+
- [x] T029 [US2] Test all UI states: loading, empty, error, token expiration (manual validation per quickstart.md)
|
| 93 |
+
|
| 94 |
+
**Checkpoint**: User Story 2 complete - all UI states provide clear feedback
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## Phase 5: User Story 3 - Responsive Design (Priority: P3) (8 tasks)
|
| 99 |
+
|
| 100 |
+
**Goal**: Ensure application works seamlessly across desktop, tablet, and mobile devices
|
| 101 |
+
|
| 102 |
+
**Independent Test**: Open application at 1920px, 768px, and 375px viewports - verify layouts adjust appropriately
|
| 103 |
+
|
| 104 |
+
### Implementation for User Story 3
|
| 105 |
+
|
| 106 |
+
- [x] T030 [P] [US3] Verify/refine desktop layout in frontend/src/app/page.tsx (3-column: form, filters, list at ≥1024px)
|
| 107 |
+
- [x] T031 [P] [US3] Verify/refine tablet layout in frontend/src/app/page.tsx (2-column: form+filters, list at 768px-1023px)
|
| 108 |
+
- [x] T032 [P] [US3] Verify/refine mobile layout in frontend/src/app/page.tsx (1-column: stacked at <768px)
|
| 109 |
+
- [x] T033 [P] [US3] Verify touch target sizes in all buttons (min-h-[44px] min-w-[44px] classes)
|
| 110 |
+
- [x] T034 [P] [US3] Verify form responsiveness in frontend/src/components/auth/SignInForm.tsx (centered, readable on mobile)
|
| 111 |
+
- [x] T035 [P] [US3] Verify form responsiveness in frontend/src/components/auth/SignUpForm.tsx (centered, readable on mobile)
|
| 112 |
+
- [x] T036 [US3] Test responsive layouts at breakpoints: 375px (mobile), 768px (tablet), 1920px (desktop) using browser DevTools
|
| 113 |
+
- [x] T037 [US3] Verify no horizontal scrolling at any viewport width (manual validation per quickstart.md)
|
| 114 |
+
|
| 115 |
+
**Checkpoint**: User Story 3 complete - responsive design works across all devices
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Phase 6: User Story 4 - Centralized API Communication (Priority: P4) (7 tasks)
|
| 120 |
+
|
| 121 |
+
**Goal**: Ensure all API communication flows through unified client with consistent error handling
|
| 122 |
+
|
| 123 |
+
**Independent Test**: Review codebase to verify all API calls use fetchAPI, test JWT inclusion and 401 handling
|
| 124 |
+
|
| 125 |
+
### Implementation for User Story 4
|
| 126 |
+
|
| 127 |
+
- [x] T038 [P] [US4] Verify fetchAPI includes JWT token automatically in frontend/src/lib/api.ts (Authorization: Bearer header)
|
| 128 |
+
- [x] T039 [P] [US4] Verify 401 handling redirects to signin in frontend/src/lib/api.ts (clear session and redirect)
|
| 129 |
+
- [x] T040 [P] [US4] Verify error formatting consistency in frontend/src/lib/api.ts (APIError with detail, error_code, field_errors)
|
| 130 |
+
- [x] T041 [US4] Audit all components to ensure they use fetchAPI (TaskList, TaskForm, TaskItem, SignUpForm, SignInForm)
|
| 131 |
+
- [x] T042 [US4] Add timeout handling to fetchAPI in frontend/src/lib/api.ts (show "Unable to connect to server" on timeout)
|
| 132 |
+
- [x] T043 [US4] Test error scenarios: 401 Unauthorized, 500 Internal Server Error, network timeout (manual validation)
|
| 133 |
+
- [x] T044 [US4] Verify no unhandled promise rejections in browser console during error scenarios
|
| 134 |
+
|
| 135 |
+
**Checkpoint**: User Story 4 complete - API communication is centralized and consistent
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## Phase 7: User Story 5 - Environment Coordination (Priority: P5) (6 tasks)
|
| 140 |
+
|
| 141 |
+
**Goal**: Ensure developers can set up and run the application easily
|
| 142 |
+
|
| 143 |
+
**Independent Test**: Follow README instructions to set up environment variables, start servers, and verify end-to-end functionality
|
| 144 |
+
|
| 145 |
+
### Implementation for User Story 5
|
| 146 |
+
|
| 147 |
+
- [x] T045 [P] [US5] Update backend/README.md with setup instructions (database setup, environment variables, migrations, server start)
|
| 148 |
+
- [x] T046 [P] [US5] Update frontend/README.md with setup instructions (environment variables, dependencies, server start)
|
| 149 |
+
- [x] T047 [P] [US5] Document all environment variables in backend/README.md (DATABASE_URL, BETTER_AUTH_SECRET, JWT_ALGORITHM, JWT_EXPIRATION_DAYS)
|
| 150 |
+
- [x] T048 [P] [US5] Document all environment variables in frontend/README.md (NEXT_PUBLIC_API_URL, BETTER_AUTH_SECRET)
|
| 151 |
+
- [x] T049 [US5] Add environment variable validation on backend startup in backend/src/main.py (check required vars, show clear errors)
|
| 152 |
+
- [x] T050 [US5] Test setup from scratch: clone repo, follow README, verify application works (manual validation per quickstart.md)
|
| 153 |
+
|
| 154 |
+
**Checkpoint**: User Story 5 complete - environment setup is documented and validated
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
## Phase 8: Polish & Cross-Cutting Concerns (4 tasks)
|
| 159 |
+
|
| 160 |
+
**Purpose**: Final refinements and validation
|
| 161 |
+
|
| 162 |
+
- [x] T051 [P] Code cleanup: Remove console.logs, unused imports, commented code across frontend/src/
|
| 163 |
+
- [x] T052 [P] Verify all Tailwind CSS classes are used correctly (no inline styles) across frontend/src/
|
| 164 |
+
- [ ] T053 Manual testing: Complete all test scenarios in specs/002-fullstack-ui-integration/quickstart.md
|
| 165 |
+
- [ ] T054 Final validation: Run through all 5 user stories end-to-end and verify acceptance criteria
|
| 166 |
+
|
| 167 |
+
**Checkpoint**: Feature complete and ready for demo/review
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## Dependencies & Execution Order
|
| 172 |
+
|
| 173 |
+
### Phase Dependencies
|
| 174 |
+
|
| 175 |
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
| 176 |
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
| 177 |
+
- **User Stories (Phase 3-7)**: All depend on Foundational phase completion
|
| 178 |
+
- User stories can proceed in parallel (if staffed)
|
| 179 |
+
- Or sequentially in priority order (P1 → P2 → P3 → P4 → P5)
|
| 180 |
+
- **Polish (Phase 8)**: Depends on all user stories being complete
|
| 181 |
+
|
| 182 |
+
### User Story Dependencies
|
| 183 |
+
|
| 184 |
+
- **User Story 1 (P1 - MVP)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
| 185 |
+
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - Independent of US1 but builds on existing components
|
| 186 |
+
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - Independent of US1/US2
|
| 187 |
+
- **User Story 4 (P4)**: Can start after Foundational (Phase 2) - Independent of US1/US2/US3
|
| 188 |
+
- **User Story 5 (P5)**: Can start after Foundational (Phase 2) - Independent of all other stories
|
| 189 |
+
|
| 190 |
+
### Within Each User Story
|
| 191 |
+
|
| 192 |
+
- Tasks marked [P] can run in parallel (different files)
|
| 193 |
+
- Tasks without [P] may have dependencies on previous tasks in the same story
|
| 194 |
+
- Complete all tasks in a story before moving to next priority
|
| 195 |
+
|
| 196 |
+
### Parallel Opportunities
|
| 197 |
+
|
| 198 |
+
- **Phase 1**: T003, T004, T005, T006 can run in parallel
|
| 199 |
+
- **Phase 2**: T009, T010, T011, T012 can run in parallel
|
| 200 |
+
- **Phase 3 (US1)**: T014, T015 can run in parallel
|
| 201 |
+
- **Phase 4 (US2)**: T022, T023, T024, T025 can run in parallel
|
| 202 |
+
- **Phase 5 (US3)**: T030, T031, T032, T033, T034, T035 can run in parallel
|
| 203 |
+
- **Phase 6 (US4)**: T038, T039, T040 can run in parallel
|
| 204 |
+
- **Phase 7 (US5)**: T045, T046, T047, T048 can run in parallel
|
| 205 |
+
- **Phase 8**: T051, T052 can run in parallel
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## Parallel Example: User Story 2 (Responsive UI States)
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
# Launch all UI state additions in parallel:
|
| 213 |
+
Task: "Add loading state to TaskList in frontend/src/components/tasks/TaskList.tsx"
|
| 214 |
+
Task: "Add empty state to TaskList in frontend/src/components/tasks/TaskList.tsx"
|
| 215 |
+
Task: "Add error state to TaskList in frontend/src/components/tasks/TaskList.tsx"
|
| 216 |
+
Task: "Add loading state to TaskForm in frontend/src/components/tasks/TaskForm.tsx"
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
---
|
| 220 |
+
|
| 221 |
+
## Implementation Strategy
|
| 222 |
+
|
| 223 |
+
### MVP First (User Story 1 Only)
|
| 224 |
+
|
| 225 |
+
1. Complete Phase 1: Setup (6 tasks)
|
| 226 |
+
2. Complete Phase 2: Foundational (7 tasks) - CRITICAL
|
| 227 |
+
3. Complete Phase 3: User Story 1 (8 tasks)
|
| 228 |
+
4. **STOP and VALIDATE**: Test authentication flow end-to-end
|
| 229 |
+
5. Demo/review if ready
|
| 230 |
+
|
| 231 |
+
**Total MVP tasks**: 21 tasks
|
| 232 |
+
|
| 233 |
+
### Incremental Delivery
|
| 234 |
+
|
| 235 |
+
1. Complete Setup + Foundational → Foundation verified (13 tasks)
|
| 236 |
+
2. Add User Story 1 → Test independently → Demo (MVP!) (8 tasks)
|
| 237 |
+
3. Add User Story 2 → Test independently → Demo (8 tasks)
|
| 238 |
+
4. Add User Story 3 → Test independently → Demo (8 tasks)
|
| 239 |
+
5. Add User Story 4 → Test independently → Demo (7 tasks)
|
| 240 |
+
6. Add User Story 5 → Test independently → Demo (6 tasks)
|
| 241 |
+
7. Polish → Final validation → Demo (4 tasks)
|
| 242 |
+
|
| 243 |
+
**Total tasks**: 54 tasks
|
| 244 |
+
|
| 245 |
+
### Parallel Team Strategy
|
| 246 |
+
|
| 247 |
+
With multiple developers:
|
| 248 |
+
|
| 249 |
+
1. Team completes Setup + Foundational together (13 tasks)
|
| 250 |
+
2. Once Foundational is done:
|
| 251 |
+
- Developer A: User Story 1 (8 tasks)
|
| 252 |
+
- Developer B: User Story 2 (8 tasks)
|
| 253 |
+
- Developer C: User Story 3 (8 tasks)
|
| 254 |
+
3. Then:
|
| 255 |
+
- Developer A: User Story 4 (7 tasks)
|
| 256 |
+
- Developer B: User Story 5 (6 tasks)
|
| 257 |
+
- Developer C: Polish (4 tasks)
|
| 258 |
+
|
| 259 |
+
---
|
| 260 |
+
|
| 261 |
+
## Task Summary
|
| 262 |
+
|
| 263 |
+
| Phase | User Story | Task Count | Parallel Tasks |
|
| 264 |
+
|-------|------------|------------|----------------|
|
| 265 |
+
| Phase 1: Setup | - | 6 | 4 |
|
| 266 |
+
| Phase 2: Foundational | - | 7 | 4 |
|
| 267 |
+
| Phase 3: US1 (MVP) | Complete Authentication Flow | 8 | 2 |
|
| 268 |
+
| Phase 4: US2 | Responsive UI States | 8 | 4 |
|
| 269 |
+
| Phase 5: US3 | Responsive Design | 8 | 6 |
|
| 270 |
+
| Phase 6: US4 | Centralized API Communication | 7 | 3 |
|
| 271 |
+
| Phase 7: US5 | Environment Coordination | 6 | 4 |
|
| 272 |
+
| Phase 8: Polish | Cross-Cutting Concerns | 4 | 2 |
|
| 273 |
+
| **TOTAL** | **5 User Stories** | **54** | **29** |
|
| 274 |
+
|
| 275 |
+
---
|
| 276 |
+
|
| 277 |
+
## Notes
|
| 278 |
+
|
| 279 |
+
- [P] tasks = different files, no dependencies - can run in parallel
|
| 280 |
+
- [Story] label maps task to specific user story for traceability
|
| 281 |
+
- Each user story should be independently completable and testable
|
| 282 |
+
- Most tasks are verification/refinement since Specs 1 & 2 already implemented core functionality
|
| 283 |
+
- Focus is on UI polish (loading states, empty states, error handling) and integration
|
| 284 |
+
- Manual testing per quickstart.md is critical for validation
|
| 285 |
+
- Commit after each task or logical group
|
| 286 |
+
- Stop at any checkpoint to validate story independently
|