Implement Garmin token management endpoints (P2.2, P2.3)

Added three Garmin API endpoints for token management:

- POST /api/garmin/tokens: Accepts oauth1, oauth2, expires_at;
  encrypts tokens using AES-256-GCM; stores in user record;
  returns daysUntilExpiry

- DELETE /api/garmin/tokens: Clears encrypted tokens from user
  record and sets garminConnected to false

- GET /api/garmin/status: Returns connection status, days until
  expiry, expired flag, and warning level (critical ≤7 days,
  warning 8-14 days)

All endpoints use withAuth() middleware for authentication.
Added 26 tests covering encryption, validation, auth, and
warning level thresholds.

Also added pb_data/ to .gitignore for PocketBase data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 19:45:16 +00:00
parent 24b7c0fd3e
commit 0fc25a49f1
6 changed files with 832 additions and 23 deletions

View File

@@ -32,9 +32,9 @@ This file is maintained by Ralph. Run `./ralph-sandbox.sh plan 3` to generate ta
| GET /api/today | **COMPLETE** | Returns decision, cycle, biometrics, nutrition (22 tests) |
| POST /api/overrides | **COMPLETE** | Adds override to user.activeOverrides (14 tests) |
| DELETE /api/overrides | **COMPLETE** | Removes override from user.activeOverrides (14 tests) |
| POST /api/garmin/tokens | 501 | Returns Not Implemented |
| DELETE /api/garmin/tokens | 501 | Returns Not Implemented |
| GET /api/garmin/status | 501 | Returns Not Implemented |
| POST /api/garmin/tokens | **COMPLETE** | Stores encrypted Garmin OAuth tokens (15 tests) |
| DELETE /api/garmin/tokens | **COMPLETE** | Clears tokens and disconnects Garmin (15 tests) |
| GET /api/garmin/status | **COMPLETE** | Returns connection status, expiry, warning level (11 tests) |
| GET /api/calendar/[userId]/[token].ics | 501 | Has param extraction, core logic TODO |
| POST /api/calendar/regenerate-token | 501 | Returns Not Implemented |
| POST /api/cron/garmin-sync | 501 | Has CRON_SECRET auth check, core logic TODO |
@@ -82,6 +82,8 @@ This file is maintained by Ralph. Run `./ralph-sandbox.sh plan 3` to generate ta
| `src/lib/ics.test.ts` | **EXISTS** - 23 tests (ICS format validation, 90-day event generation, timezone handling) |
| `src/lib/encryption.test.ts` | **EXISTS** - 14 tests (encrypt/decrypt round-trip, error handling, key validation) |
| `src/lib/garmin.test.ts` | **EXISTS** - 33 tests (fetchGarminData, fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes, token expiry, error handling) |
| `src/app/api/garmin/tokens/route.test.ts` | **EXISTS** - 15 tests (POST/DELETE tokens, encryption, validation, auth) |
| `src/app/api/garmin/status/route.test.ts` | **EXISTS** - 11 tests (connection status, expiry, warning levels) |
| E2E tests | **NONE** |
### Critical Business Rules (from Spec)
@@ -260,21 +262,31 @@ Full feature set for production use.
- `fetchIntensityMinutes()` - Fetches weekly moderate + vigorous intensity minutes
- **Why:** Real biometric data is required for accurate decisions
### P2.2: POST/DELETE /api/garmin/tokens Implementation
- [ ] Store encrypted Garmin OAuth tokens
### P2.2: POST/DELETE /api/garmin/tokens Implementation ✅ COMPLETE
- [x] Store encrypted Garmin OAuth tokens
- **Files:**
- `src/app/api/garmin/tokens/route.ts` - Implement with encryption.ts
- `src/app/api/garmin/tokens/route.ts` - POST/DELETE handlers with encryption, validation
- **Tests:**
- `src/app/api/garmin/tokens/route.test.ts` - Test encryption, validation, storage
- `src/app/api/garmin/tokens/route.test.ts` - 15 tests covering encryption, validation, storage, auth, deletion
- **Features Implemented:**
- POST: Accepts oauth1, oauth2, expires_at; encrypts tokens; stores in user record
- DELETE: Clears tokens and sets garminConnected to false
- Validation for required fields and types
- Returns daysUntilExpiry in POST response
- **Why:** Users need to connect their Garmin accounts
- **Depends On:** P0.1, P0.2
### P2.3: GET /api/garmin/status Implementation
- [ ] Return Garmin connection status and days until expiry
### P2.3: GET /api/garmin/status Implementation ✅ COMPLETE
- [x] Return Garmin connection status and days until expiry
- **Files:**
- `src/app/api/garmin/status/route.ts` - Implement status check
- `src/app/api/garmin/status/route.ts` - GET handler with expiry calculation
- **Tests:**
- `src/app/api/garmin/status/route.test.ts` - Test connected/disconnected states, expiry calc
- `src/app/api/garmin/status/route.test.ts` - 11 tests covering connected/disconnected states, expiry calc, warning levels
- **Response Shape:**
- `connected` - Boolean indicating if tokens exist
- `daysUntilExpiry` - Days until token expires (null if not connected)
- `expired` - Boolean indicating if tokens have expired
- `warningLevel` - "critical" (≤7 days), "warning" (8-14 days), or null
- **Why:** Users need visibility into their Garmin connection
- **Depends On:** P0.1, P0.2, P2.1
@@ -553,6 +565,9 @@ P2.14 Mini calendar
- [x] **GET /api/today** - Returns complete daily snapshot with decision, biometrics, nutrition, 22 tests (P1.4)
- [x] **POST /api/overrides** - Adds override to user.activeOverrides array, 14 tests (P1.5)
- [x] **DELETE /api/overrides** - Removes override from user.activeOverrides array, 14 tests (P1.5)
- [x] **POST /api/garmin/tokens** - Stores encrypted Garmin OAuth tokens, 15 tests (P2.2)
- [x] **DELETE /api/garmin/tokens** - Clears tokens and disconnects Garmin, 15 tests (P2.2)
- [x] **GET /api/garmin/status** - Returns connection status, expiry, warning level, 11 tests (P2.3)
### Pages
- [x] **Login Page** - Email/password form with PocketBase auth, error handling, loading states, redirect, 14 tests (P1.6)