From 9cee3f78483532828a2f72c65eb2b952b2ded670 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 13 Jan 2026 15:03:20 -1000 Subject: remove agent access feature --- .env.example | 5 - AI_AGENT_ACCESS.md | 340 --------------------------------------- AI_AGENT_SETUP.md | 276 ------------------------------- PROJECT_SUMMARY.md | 43 +---- README.md | 5 +- SECURITY_CHECKLIST.md | 59 +++---- SESSION_STATE.md | 41 ++--- internal/handlers/ai_handlers.go | 273 ------------------------------- 8 files changed, 39 insertions(+), 1003 deletions(-) delete mode 100644 AI_AGENT_ACCESS.md delete mode 100644 AI_AGENT_SETUP.md delete mode 100644 internal/handlers/ai_handlers.go diff --git a/.env.example b/.env.example index a86520c..dddb927 100644 --- a/.env.example +++ b/.env.example @@ -33,8 +33,3 @@ CACHE_TTL_MINUTES=5 # Development Settings # Set to "true" to enable debug logging DEBUG=false - -# AI Agent Access (Optional) -# Generate with: openssl rand -hex 32 -# Used by Claude.ai to access dashboard via /api/claude/snapshot -# AI_AGENT_API_KEY= diff --git a/AI_AGENT_ACCESS.md b/AI_AGENT_ACCESS.md deleted file mode 100644 index 40fe6a1..0000000 --- a/AI_AGENT_ACCESS.md +++ /dev/null @@ -1,340 +0,0 @@ -# AI Agent Access Design - -## Overview -Add a dedicated API endpoint optimized for AI agents (like Claude) to access dashboard data in a structured, machine-readable format with simple authentication. - -## Authentication Strategy - -### API Key-Based Auth -Simple bearer token authentication that's easy to use from Claude.ai: -- Generate a long, random API key for AI access -- Store in environment variable: `AI_AGENT_API_KEY` -- Use standard `Authorization: Bearer {key}` header -- No complex OAuth flows or JWT rotation - -### Why This Approach? -1. **Simple for Claude**: Just include auth header in requests -2. **Stateless**: No session management needed -3. **Revocable**: Change key in .env to revoke access -4. **Standard**: Uses common HTTP authorization pattern - -## API Endpoints - -### GET /api/ai/dashboard -Returns complete dashboard data in AI-optimized JSON format. - -**Request:** -```http -GET /api/ai/dashboard HTTP/1.1 -Host: localhost:8080 -Authorization: Bearer {AI_AGENT_API_KEY} -``` - -**Response (Success - 200 OK):** -```json -{ - "status": "success", - "timestamp": "2026-01-09T14:30:00Z", - "data": { - "trello_boards": [ - { - "id": "board123", - "name": "Work Projects", - "cards": [ - { - "id": "card1", - "name": "Complete project proposal", - "list": "In Progress", - "due_date": "2026-01-15", - "url": "https://trello.com/c/card1" - } - ] - } - ], - "todoist_tasks": [ - { - "id": "task1", - "content": "Buy groceries", - "project": "Personal", - "priority": 1, - "due_date": "2026-01-10", - "completed": false, - "labels": ["shopping"], - "url": "https://todoist.com/task/1" - } - ], - "obsidian_notes": [ - { - "filename": "meeting-notes.md", - "title": "Team Meeting Notes", - "content_preview": "Discussed Q1 goals...", - "modified": "2026-01-09T10:00:00Z", - "tags": ["meetings", "work"] - } - ], - "plantoeat_meals": [ - { - "id": "meal1", - "recipe": "Spaghetti Carbonara", - "date": "2026-01-09", - "meal_type": "dinner", - "url": "https://plantoeat.com/recipe/123" - } - ] - }, - "metadata": { - "cache_age_seconds": 45, - "sources_refreshed": ["trello", "todoist"], - "errors": [] - } -} -``` - -**Response (Unauthorized - 401):** -```json -{ - "status": "error", - "error": "Invalid or missing API key", - "hint": "Include 'Authorization: Bearer {key}' header" -} -``` - -### GET /api/ai/boards/{board_id}/cards -Get cards from a specific Trello board. - -**Request:** -```http -GET /api/ai/boards/board123/cards HTTP/1.1 -Authorization: Bearer {AI_AGENT_API_KEY} -``` - -### GET /api/ai/tasks?filter={filter} -Get filtered tasks (today, week, overdue, completed). - -**Request:** -```http -GET /api/ai/tasks?filter=today HTTP/1.1 -Authorization: Bearer {AI_AGENT_API_KEY} -``` - -### POST /api/ai/task -Create a new Todoist task (Phase 2). - -**Request:** -```http -POST /api/ai/task HTTP/1.1 -Authorization: Bearer {AI_AGENT_API_KEY} -Content-Type: application/json - -{ - "content": "Buy milk", - "project": "Personal", - "due_date": "2026-01-10", - "priority": 1, - "labels": ["shopping"] -} -``` - -## AI-Optimized Response Format - -### Design Principles -1. **Flat structure**: Avoid deep nesting where possible -2. **Explicit field names**: Use `due_date` not `dueDate` for clarity -3. **Include metadata**: Provide context about data freshness -4. **Clear errors**: Return actionable error messages -5. **Consistent types**: Always use ISO 8601 for dates - -### Example Claude.ai Usage -```markdown -User: "What tasks do I have due today?" - -Claude uses MCP tool or API call: -GET /api/ai/tasks?filter=today -Authorization: Bearer sk-ai-1234567890abcdef - -Claude response: "You have 3 tasks due today: -1. Buy groceries (Personal, Priority 1) -2. Complete project proposal (Work, Priority 2) -3. Call dentist (Health, Priority 3)" -``` - -## Implementation - -### Middleware: Auth Check -```go -// internal/middleware/ai_auth.go -func AIAuthMiddleware(apiKey string) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - - if !strings.HasPrefix(authHeader, "Bearer ") { - respondJSON(w, 401, map[string]string{ - "status": "error", - "error": "Missing or invalid Authorization header", - "hint": "Include 'Authorization: Bearer {key}' header", - }) - return - } - - token := strings.TrimPrefix(authHeader, "Bearer ") - if token != apiKey { - respondJSON(w, 401, map[string]string{ - "status": "error", - "error": "Invalid API key", - }) - return - } - - next.ServeHTTP(w, r) - }) - } -} -``` - -### Handler: AI Dashboard -```go -// internal/handlers/ai_handlers.go -func (h *Handler) HandleAIDashboard(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // Fetch all data - data, err := h.fetchDashboardData(ctx, false) - if err != nil { - respondJSON(w, 500, map[string]interface{}{ - "status": "error", - "error": err.Error(), - }) - return - } - - // Format for AI consumption - response := map[string]interface{}{ - "status": "success", - "timestamp": time.Now().Format(time.RFC3339), - "data": map[string]interface{}{ - "trello_boards": formatBoardsForAI(data.Boards), - "todoist_tasks": formatTasksForAI(data.Tasks), - "obsidian_notes": formatNotesForAI(data.Notes), - "plantoeat_meals": formatMealsForAI(data.Meals), - }, - "metadata": map[string]interface{}{ - "errors": data.Errors, - }, - } - - respondJSON(w, 200, response) -} -``` - -### Router Setup -```go -// cmd/dashboard/main.go -func main() { - // ... existing setup ... - - // AI Agent routes (protected by auth middleware) - if cfg.AIAgentAPIKey != "" { - aiRouter := chi.NewRouter() - aiRouter.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) - - r.Route("/api/ai", func(r chi.Router) { - r.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) - r.Get("/dashboard", h.HandleAIDashboard) - r.Get("/boards/{boardID}/cards", h.HandleAIBoardCards) - r.Get("/tasks", h.HandleAITasks) - // Phase 2: Write operations - // r.Post("/task", h.HandleAICreateTask) - }) - } -} -``` - -## Security Considerations - -1. **Key Storage**: Never commit API key to git -2. **Key Rotation**: Easy to change in .env file -3. **Rate Limiting**: Consider adding rate limits per key -4. **HTTPS Only**: Use HTTPS in production -5. **Logging**: Log API key usage (last 4 chars only) - -## Configuration - -### .env.example -```bash -# AI Agent Access -AI_AGENT_API_KEY=sk-ai-1234567890abcdef... -# Generate with: openssl rand -hex 32 -``` - -### config.go -```go -type Config struct { - // ... existing fields ... - AIAgentAPIKey string -} - -func Load() (*Config, error) { - cfg := &Config{ - // ... existing fields ... - AIAgentAPIKey: os.Getenv("AI_AGENT_API_KEY"), - } - return cfg, nil -} -``` - -## Usage from Claude.ai - -### With MCP (Model Context Protocol) -```json -{ - "mcpServers": { - "dashboard": { - "command": "curl", - "args": [ - "-H", "Authorization: Bearer ${AI_AGENT_API_KEY}", - "http://localhost:8080/api/ai/dashboard" - ] - } - } -} -``` - -### Direct API Call -Claude can make HTTP requests directly: -``` -GET http://localhost:8080/api/ai/dashboard -Authorization: Bearer sk-ai-1234567890abcdef -``` - -## Future Enhancements - -1. **Webhook Support**: POST updates to Claude.ai when data changes -2. **Streaming**: Server-sent events for real-time updates -3. **Scoped Keys**: Different keys for read vs write access -4. **GraphQL**: More flexible querying for AI agents -5. **Natural Language Queries**: `/api/ai/query?q=What's due today?` - -## Testing - -### Manual Test -```bash -# Generate API key -export AI_KEY=$(openssl rand -hex 32) - -# Add to .env -echo "AI_AGENT_API_KEY=sk-ai-$AI_KEY" >> .env - -# Test endpoint -curl -H "Authorization: Bearer sk-ai-$AI_KEY" \ - http://localhost:8080/api/ai/dashboard | jq . -``` - -### Unit Test -```go -func TestAIAuthMiddleware(t *testing.T) { - // Test valid key - // Test invalid key - // Test missing header -} -``` diff --git a/AI_AGENT_SETUP.md b/AI_AGENT_SETUP.md deleted file mode 100644 index acf2da4..0000000 --- a/AI_AGENT_SETUP.md +++ /dev/null @@ -1,276 +0,0 @@ -# AI Agent Access Setup Guide - -## Quick Start - -### 1. Generate API Key -```bash -# Generate a secure 64-character API key -openssl rand -hex 32 -``` - -Example output: `a1b2c3d4e5f6...` - -### 2. Add to Environment -Add to your `.env` file: -```bash -AI_AGENT_API_KEY=a1b2c3d4e5f6... -``` - -### 3. Start the Dashboard -```bash -go run cmd/dashboard/main.go -``` - -You should see: -``` -AI agent access enabled at /api/claude/snapshot -Starting server on http://localhost:8080 -``` - -### 4. Test the Endpoint -```bash -curl -H "Authorization: Bearer a1b2c3d4e5f6..." \ - http://localhost:8080/api/claude/snapshot | jq . -``` - -## Usage from Claude.ai - -### Method 1: Direct WebFetch -When conversing with Claude, share: - -**URL:** `http://localhost:8080/api/claude/snapshot` -**Token:** `a1b2c3d4e5f6...` (share separately, not in chat history) - -Claude can then fetch your dashboard: -``` -User: "What tasks do I have today?" - -Claude: [Calls WebFetch with Authorization header] -Claude: "You have 3 tasks due today: -1. Review PRs (Work, Priority 4) -2. Buy groceries (Personal, Priority 1) -3. Call dentist (Health, Priority 2)" -``` - -### Method 2: MCP Server (Advanced) -If you have Claude Desktop with MCP support: - -Add to your MCP config: -```json -{ - "mcpServers": { - "personal-dashboard": { - "command": "curl", - "args": [ - "-H", - "Authorization: Bearer YOUR_TOKEN_HERE", - "-s", - "http://localhost:8080/api/claude/snapshot" - ] - } - } -} -``` - -## Response Format - -### Successful Response (200 OK) -```json -{ - "generated_at": "2026-01-09T15:30:00Z", - "tasks": { - "today": [ - { - "id": "task_123", - "content": "Review PRs", - "priority": 4, - "due": "2026-01-09T17:00:00Z", - "project": "Work", - "completed": false - } - ], - "overdue": [], - "next_7_days": [] - }, - "meals": { - "today": { - "date": "2026-01-09", - "breakfast": "Oatmeal with protein powder", - "lunch": "Chicken salad", - "dinner": "Salmon with veggies" - }, - "next_7_days": [] - }, - "notes": { - "recent": [ - { - "title": "Sprint planning notes", - "modified": "2026-01-09T10:15:00Z", - "preview": "Discussed Q1 goals and team capacity...", - "path": "work/sprint-planning.md" - } - ] - }, - "trello_boards": [ - { - "id": "board_123", - "name": "Work Projects", - "cards": [ - { - "id": "card_456", - "name": "Complete project proposal", - "list": "In Progress", - "due": "2026-01-15T00:00:00Z", - "url": "https://trello.com/c/card456" - } - ] - } - ] -} -``` - -### Error Response (401 Unauthorized) -```json -{ - "error": "unauthorized", - "message": "Invalid or missing token" -} -``` - -## Security Best Practices - -### ✅ Do -- Generate a long, random key (32+ bytes) -- Store key in `.env` file (gitignored) -- Use HTTPS in production -- Rotate key periodically (change in `.env`) -- Share token with Claude separately from URL - -### ❌ Don't -- Commit API key to git -- Share key in chat history -- Use simple/guessable keys -- Expose over HTTP in production -- Include key in URL query parameters - -## Data Limits - -The endpoint is optimized for AI consumption: - -- **Tasks**: Today + overdue + next 7 days only -- **Notes**: 10 most recent, 150 char preview each -- **Meals**: Today + next 7 days -- **Trello**: All boards and cards -- **Total Response**: < 100KB (typically 10-50KB) - -## Caching - -- Responses are cached for 5 minutes -- Same cache as web dashboard -- Reduces load on external APIs (Todoist, Trello, etc.) - -## Troubleshooting - -### "Invalid or missing token" -- Check `Authorization: Bearer` prefix is included -- Verify token matches `.env` file exactly -- No extra spaces or newlines in token - -### "AI agent access not configured" -- Ensure `AI_AGENT_API_KEY` is set in `.env` -- Restart the server after changing `.env` - -### Empty response sections -- Trello/Todoist/PlanToEat APIs may be down -- Check dashboard web UI to see if data loads there -- Review server logs for API errors - -## Production Deployment - -### With Caddy (Automatic HTTPS) -```caddyfile -dashboard.yourdomain.com { - reverse_proxy localhost:8080 -} -``` - -### With nginx -```nginx -server { - listen 443 ssl http2; - server_name dashboard.yourdomain.com; - - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/key.pem; - - location / { - proxy_pass http://localhost:8080; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} -``` - -Then share with Claude: -- **URL**: `https://dashboard.yourdomain.com/api/claude/snapshot` -- **Token**: (your API key) - -## Example Claude Conversations - -### Check Today's Tasks -``` -User: "What do I need to do today?" - -Claude: [Fetches /api/claude/snapshot] - -Claude: "Here's what you have for today: - -Tasks: -- Review PRs (Work, Priority 4, due 5pm) -- Buy groceries (Personal, Priority 1) - -Meals: -- Breakfast: Oatmeal -- Lunch: Chicken salad -- Dinner: Salmon with veggies" -``` - -### Check Overdue Items -``` -User: "Do I have anything overdue?" - -Claude: [Fetches snapshot] - -Claude: "You have 2 overdue tasks: -1. Submit expense report (Work, was due Jan 5) -2. Schedule dentist appointment (Health, was due Jan 7)" -``` - -### Review Recent Notes -``` -User: "What notes have I created recently?" - -Claude: [Fetches snapshot] - -Claude: "Your 3 most recent notes: -1. Sprint planning notes (modified today at 10:15am) -2. Customer feedback summary (modified Jan 8) -3. Architecture decision record (modified Jan 6)" -``` - -## Rate Limiting (Future) - -Currently no rate limiting. For production use, consider adding: -- 100 requests/hour per token -- Implement using middleware -- Return 429 status when exceeded - -## Next Steps - -1. ✅ Generate API key -2. ✅ Add to .env -3. ✅ Test with curl -4. ✅ Share URL + token with Claude -5. ✅ Start conversing! - -Enjoy your AI-powered personal dashboard! 🚀 diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index de7cd00..daa36eb 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -8,8 +8,6 @@ A **unified personal dashboard** that aggregates your productivity data into one - 📝 **Obsidian notes** (optional) - 🍽️ **PlanToEat meals** (optional) -Plus a dedicated **AI agent API** for Claude.ai to access your data. - ## Current Status: ✅ Phase 1 Complete ### What Works @@ -20,7 +18,6 @@ Plus a dedicated **AI agent API** for Claude.ai to access your data. - ✅ Mobile-responsive web UI - ✅ SQLite caching (5-min TTL) - ✅ Auto-refresh (5 min) -- ✅ AI agent endpoint (`/api/claude/snapshot`) - ✅ Full test coverage (9/9 tests passing) ### Statistics @@ -70,8 +67,6 @@ http://localhost:8080 - **[QUICKSTART.md](QUICKSTART.md)** - Get running in 5 minutes - **[SETUP_GUIDE.md](SETUP_GUIDE.md)** - Detailed setup instructions -- **[AI_AGENT_SETUP.md](AI_AGENT_SETUP.md)** - Claude.ai integration guide -- **[CLAUDE.md](CLAUDE.md)** - For future Claude Code instances - **[README.md](README.md)** - Full project documentation ## Architecture @@ -101,9 +96,6 @@ internal/ │ └── plantoeat.go Meal planning ├── handlers/ │ ├── handlers.go Web handlers - │ └── ai_handlers.go AI endpoint - ├── middleware/ - │ └── ai_auth.go Bearer token auth ├── config/config.go Environment config └── store/sqlite.go Database ops web/ @@ -121,17 +113,11 @@ web/ - `GET /api/meals` - Meals JSON - `GET /api/boards` - Trello boards JSON -### AI Agent -- `GET /api/claude/snapshot` - AI-optimized JSON - - Auth: `Authorization: Bearer ` - - Returns: tasks (today/overdue/week), meals (7 days), notes (10 recent), all Trello boards - ## What's Optional ### Optional Features - **PlanToEat:** API not publicly available - leave blank - **Obsidian:** Only if you use Obsidian notes -- **AI Access:** Only if you want Claude.ai integration ### Required Features - **Todoist:** Yes - tasks integration @@ -149,7 +135,6 @@ TRELLO_TOKEN=... # Optional OBSIDIAN_VAULT_PATH=/path/to/vault -AI_AGENT_API_KEY=... # Server (with defaults) PORT=8080 @@ -175,23 +160,6 @@ go install github.com/cosmtrek/air@latest air ``` -## For Claude.ai - -### Setup -1. Generate API key: `openssl rand -hex 32` -2. Add to `.env`: `AI_AGENT_API_KEY=...` -3. Share with Claude: - - URL: `http://localhost:8080/api/claude/snapshot` - - Token: (your key) - -### Usage Examples -- "What tasks do I have today?" -- "What's for dinner?" -- "Show me my overdue items" -- "What have I been working on?" - -Claude fetches your dashboard and answers naturally. - ## Troubleshooting ### "TODOIST_API_KEY is required" @@ -213,17 +181,16 @@ TRELLO_TOKEN=something ## What's Next -### Phase 2 (Future) -- Create tasks via AI -- Mark tasks complete -- Quick note capture -- Create Trello cards +### Phase 2 (In Progress) +- [x] Mark tasks complete +- [x] Create Trello cards +- [x] Create Todoist tasks +- [ ] Quick note capture ### Phase 3 (Future) - Unified search - Daily digest - PWA support -- Rate limiting for AI ## License diff --git a/README.md b/README.md index ae05263..01ddb3a 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,6 @@ All configuration is done through environment variables. See `.env.example` for - `OBSIDIAN_VAULT_PATH`: Path to your Obsidian vault - `PLANTOEAT_API_KEY`: PlanToEat API key (not publicly available - leave empty) -- `AI_AGENT_API_KEY`: For Claude.ai access (generate with `openssl rand -hex 32`) - `PORT`: Server port (default: 8080) - `CACHE_TTL_MINUTES`: Cache duration (default: 5) - `DEBUG`: Enable debug logging (default: false) @@ -166,8 +165,8 @@ go build -o dashboard cmd/dashboard/main.go - [ ] Responsive UI ### Phase 2: Write Operations -- [ ] Create Todoist tasks -- [ ] Mark tasks complete +- [x] Create Todoist tasks +- [x] Mark tasks complete - [ ] Create Obsidian notes - [ ] Add meals to planner diff --git a/SECURITY_CHECKLIST.md b/SECURITY_CHECKLIST.md index 4e63174..46b8cea 100644 --- a/SECURITY_CHECKLIST.md +++ b/SECURITY_CHECKLIST.md @@ -2,14 +2,8 @@ ## Critical Security Issues (Must Fix Before Production) -### Authentication & Authorization -- [ ] **Timing Attack in AI Auth** (15 min) - - File: `internal/middleware/ai_auth.go:31` - - Change: Use `crypto/subtle.ConstantTimeCompare()` instead of `!=` - - Impact: Prevents token brute-forcing - ### Database Security -- [ ] **SQL Injection in GetNotes()** (15 min) +- [x] **SQL Injection in GetNotes()** (15 min) - File: `internal/store/sqlite.go:208` - Change: Use parameterized query for LIMIT clause - Impact: Prevents SQL injection attacks @@ -30,11 +24,6 @@ - Change: Validate paths stay within vault, skip symlinks - Impact: Prevents arbitrary file read attacks -- [ ] **JSON Injection in Error Responses** (15 min) - - File: `internal/middleware/ai_auth.go:42-45` - - Change: Use `json.Encoder` instead of string concatenation - - Impact: Prevents JSON structure manipulation - ### Network Security - [ ] **HTTPS Support** (1 hour) - File: `cmd/dashboard/main.go:86-94` @@ -119,16 +108,6 @@ - Impact: Protection against malicious API servers ### Testing -- [ ] **Add AI Handler Tests** (2 hours) - - File: `internal/handlers/ai_handlers_test.go` (new) - - Tests: Task categorization, meal grouping, response size - - Impact: Better test coverage - -- [ ] **Add Middleware Tests** (1 hour) - - File: `internal/middleware/ai_auth_test.go` (new) - - Tests: Valid/invalid tokens, missing headers - - Impact: Better test coverage - - [ ] **Add Edge Case Tests** (2 hours) - Files: Various test files - Tests: Empty responses, malformed JSON, network errors @@ -202,22 +181,22 @@ | Priority | Count | Estimated Time | |----------|-------|----------------| -| Critical | 6 items | ~4 hours | +| Critical | 4 items | ~2.5 hours | | High | 7 items | ~6.5 hours | -| Medium | 11 items | ~13.5 hours | +| Medium | 8 items | ~9.5 hours | | Low | 8 items | ~14 hours | -| **Total** | **32 items** | **~38 hours** | +| **Total** | **27 items** | **~32.5 hours** | ### Recommended Sprint 1 (Critical + High Priority) - **Duration**: 1-2 weeks part-time -- **Items**: 13 items -- **Time**: ~10.5 hours +- **Items**: 11 items +- **Time**: ~9 hours - **Focus**: Security hardening and performance ### Recommended Sprint 2 (Medium Priority) - **Duration**: 1-2 weeks part-time -- **Items**: 11 items -- **Time**: ~13.5 hours +- **Items**: 8 items +- **Time**: ~9.5 hours - **Focus**: Code quality and testing --- @@ -226,18 +205,16 @@ These can be done in small chunks: -1. ✓ Timing attack fix (15 min) -2. ✓ SQL injection fix (15 min) -3. ✓ JSON injection fix (15 min) -4. ✓ Database permissions (15 min) -5. ✓ Health check endpoint (15 min) -6. ✓ Security headers middleware (30 min) -7. ✓ Database ping check (15 min) -8. ✓ Check JSON unmarshal errors (30 min) -9. ✓ Extract constants (30 min) -10. ✓ Config validation (30 min) - -**Total Quick Wins**: ~4 hours, addresses 10 issues +1. ✓ SQL injection fix (15 min) +2. ✓ Database permissions (15 min) +3. ✓ Health check endpoint (15 min) +4. ✓ Security headers middleware (30 min) +5. ✓ Database ping check (15 min) +6. ✓ Check JSON unmarshal errors (30 min) +7. ✓ Extract constants (30 min) +8. ✓ Config validation (30 min) + +**Total Quick Wins**: ~3.5 hours, addresses 8 issues --- diff --git a/SESSION_STATE.md b/SESSION_STATE.md index 3755940..cff3103 100644 --- a/SESSION_STATE.md +++ b/SESSION_STATE.md @@ -1,32 +1,19 @@ # Session State -## Current Phase: Phase 3 - Interactivity & Write Operations +**Current Phase:** Phase 3: Write Operations & UI Integration +**Current Step:** Step 6: Trello Tasks Heuristic & Tasks Tab -**Goal:** Transform the dashboard from a read-only viewer into an interactive tool. +**Recent Completed Steps:** +* Phase 3 Step 1: Trello Write Ops (Backend) +* Phase 3 Step 2: Trello Lists Support +* Phase 3 Step 3: Trello UI (Boards & Add Card) +* Phase 3 Step 4: Todoist Write Ops +* Phase 3 Step 5: Fix Tasks Tab (Identified as needing Heuristic) -### Progress -- [x] **Phase 1: Core Infrastructure** (Completed) -- [x] **Phase 2: Read-Only Dashboard** (Completed) -- [x] **Phase 2.5: Visual Overhaul** (Completed) - - [x] Glassmorphism UI -- [ ] **Phase 3: Interactivity** (In Progress) - - [x] Step 1: Trello Write Ops (Backend) - - [x] Step 2: Trello Lists Support - - [x] Step 3: Trello UI Integration - - [ ] **Step 4: Todoist Write Ops** (Active) - - [ ] Step 5: Unified Quick Add +**Current Focus:** +Implementing heuristics to extract actionable tasks from Trello boards and displaying them in a unified list on the Tasks tab. -## Active Task: Todoist Write Operations -We are implementing `CreateTask` and `CompleteTask` for Todoist, along with the UI integration. - -### Immediate Next Steps -1. Refactor Todoist client for testability. -2. Implement write operations. -3. Update handlers and UI. - -## Known Issues -- None currently. - -## Context -- **Project Root:** `//wsl.localhost/Ubuntu-Preview/home/terst/workspace/task-dashboard` -- **Design System:** Glassmorphism (see `design_system.md`) +**Next Steps:** +1. Implement Heuristics in Handlers. +2. Create Trello Tasks Partial. +3. Update Tasks Tab Template. diff --git a/internal/handlers/ai_handlers.go b/internal/handlers/ai_handlers.go deleted file mode 100644 index 26c945e..0000000 --- a/internal/handlers/ai_handlers.go +++ /dev/null @@ -1,273 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log" - "net/http" - "time" - - "task-dashboard/internal/models" -) - -// AISnapshotResponse matches the exact format requested by the user -type AISnapshotResponse struct { - GeneratedAt string `json:"generated_at"` - Tasks AITasksSection `json:"tasks"` - Meals AIMealsSection `json:"meals"` - Notes AINotesSection `json:"notes"` - TrelloBoards []AITrelloBoard `json:"trello_boards,omitempty"` -} - -type AITasksSection struct { - Today []AITask `json:"today"` - Overdue []AITask `json:"overdue"` - Next7Days []AITask `json:"next_7_days"` -} - -type AITask struct { - ID string `json:"id"` - Content string `json:"content"` - Priority int `json:"priority"` - Due *string `json:"due,omitempty"` - Project string `json:"project"` - Completed bool `json:"completed"` -} - -type AIMealsSection struct { - Today AIDayMeals `json:"today"` - Next7Days []AIDayMeals `json:"next_7_days"` -} - -type AIDayMeals struct { - Date string `json:"date"` - Breakfast string `json:"breakfast,omitempty"` - Lunch string `json:"lunch,omitempty"` - Dinner string `json:"dinner,omitempty"` - Snack string `json:"snack,omitempty"` -} - -type AINotesSection struct { - Recent []AINote `json:"recent"` -} - -type AINote struct { - Title string `json:"title"` - Modified string `json:"modified"` - Preview string `json:"preview"` - Path string `json:"path"` -} - -type AITrelloBoard struct { - ID string `json:"id"` - Name string `json:"name"` - Cards []AITrelloCard `json:"cards"` -} - -type AITrelloCard struct { - ID string `json:"id"` - Name string `json:"name"` - List string `json:"list"` - Due *string `json:"due,omitempty"` - URL string `json:"url"` -} - -// HandleAISnapshot returns a complete dashboard snapshot optimized for AI consumption -func (h *Handler) HandleAISnapshot(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // Fetch all data (with caching) - data, err := h.aggregateData(ctx, false) - if err != nil { - respondJSON(w, http.StatusInternalServerError, map[string]string{ - "error": "server_error", - "message": "Failed to fetch dashboard data", - }) - log.Printf("AI snapshot error: %v", err) - return - } - - // Build AI-optimized response - response := AISnapshotResponse{ - GeneratedAt: time.Now().UTC().Format(time.RFC3339), - Tasks: buildAITasksSection(data.Tasks), - Meals: buildAIMealsSection(data.Meals), - Notes: buildAINotesSection(data.Notes), - TrelloBoards: buildAITrelloBoardsSection(data.Boards), - } - - respondJSON(w, http.StatusOK, response) -} - -// buildAITasksSection organizes tasks by time window -func buildAITasksSection(tasks []models.Task) AITasksSection { - now := time.Now() - today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) - next7Days := today.AddDate(0, 0, 7) - - section := AITasksSection{ - Today: []AITask{}, - Overdue: []AITask{}, - Next7Days: []AITask{}, - } - - for _, task := range tasks { - if task.Completed { - continue // Skip completed tasks - } - - aiTask := AITask{ - ID: task.ID, - Content: task.Content, - Priority: task.Priority, - Project: task.ProjectName, - Completed: task.Completed, - } - - if task.DueDate != nil { - dueStr := task.DueDate.UTC().Format(time.RFC3339) - aiTask.Due = &dueStr - - taskDay := time.Date(task.DueDate.Year(), task.DueDate.Month(), task.DueDate.Day(), 0, 0, 0, 0, task.DueDate.Location()) - - if taskDay.Before(today) { - section.Overdue = append(section.Overdue, aiTask) - } else if taskDay.Equal(today) { - section.Today = append(section.Today, aiTask) - } else if taskDay.Before(next7Days) { - section.Next7Days = append(section.Next7Days, aiTask) - } - } - } - - return section -} - -// buildAIMealsSection organizes meals by day -func buildAIMealsSection(meals []models.Meal) AIMealsSection { - now := time.Now() - today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) - next7Days := today.AddDate(0, 0, 7) - - section := AIMealsSection{ - Today: AIDayMeals{Date: today.Format("2006-01-02")}, - Next7Days: []AIDayMeals{}, - } - - // Group meals by date - mealsByDate := make(map[string]*AIDayMeals) - - for _, meal := range meals { - mealDay := time.Date(meal.Date.Year(), meal.Date.Month(), meal.Date.Day(), 0, 0, 0, 0, meal.Date.Location()) - - if mealDay.Before(today) || mealDay.After(next7Days) { - continue // Skip meals outside our window - } - - dateStr := mealDay.Format("2006-01-02") - - if _, exists := mealsByDate[dateStr]; !exists { - mealsByDate[dateStr] = &AIDayMeals{Date: dateStr} - } - - dayMeals := mealsByDate[dateStr] - - switch meal.MealType { - case "breakfast": - dayMeals.Breakfast = meal.RecipeName - case "lunch": - dayMeals.Lunch = meal.RecipeName - case "dinner": - dayMeals.Dinner = meal.RecipeName - case "snack": - dayMeals.Snack = meal.RecipeName - } - } - - // Assign today's meals - if todayMeals, exists := mealsByDate[today.Format("2006-01-02")]; exists { - section.Today = *todayMeals - } - - // Collect next 7 days (excluding today) - for i := 1; i <= 7; i++ { - day := today.AddDate(0, 0, i) - dateStr := day.Format("2006-01-02") - if dayMeals, exists := mealsByDate[dateStr]; exists { - section.Next7Days = append(section.Next7Days, *dayMeals) - } - } - - return section -} - -// buildAINotesSection returns the 10 most recent notes with previews -func buildAINotesSection(notes []models.Note) AINotesSection { - section := AINotesSection{ - Recent: []AINote{}, - } - - // Limit to 10 most recent - limit := 10 - if len(notes) < limit { - limit = len(notes) - } - - for i := 0; i < limit; i++ { - note := notes[i] - - // Limit preview to 150 chars - preview := note.Content - if len(preview) > 150 { - preview = preview[:150] + "..." - } - - section.Recent = append(section.Recent, AINote{ - Title: note.Title, - Modified: note.Modified.UTC().Format(time.RFC3339), - Preview: preview, - Path: note.Path, - }) - } - - return section -} - -// buildAITrelloBoardsSection formats Trello boards for AI -func buildAITrelloBoardsSection(boards []models.Board) []AITrelloBoard { - aiBoards := []AITrelloBoard{} - - for _, board := range boards { - aiBoard := AITrelloBoard{ - ID: board.ID, - Name: board.Name, - Cards: []AITrelloCard{}, - } - - for _, card := range board.Cards { - aiCard := AITrelloCard{ - ID: card.ID, - Name: card.Name, - List: card.ListName, - URL: card.URL, - } - - if card.DueDate != nil { - dueStr := card.DueDate.UTC().Format(time.RFC3339) - aiCard.Due = &dueStr - } - - aiBoard.Cards = append(aiBoard.Cards, aiCard) - } - - aiBoards = append(aiBoards, aiBoard) - } - - return aiBoards -} - -// respondJSON sends a JSON response -func respondJSON(w http.ResponseWriter, status int, data interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - json.NewEncoder(w).Encode(data) -} -- cgit v1.2.3