2.8 KiB
2.8 KiB
Garmin Integration Specification
Job to Be Done
When I connect my Garmin account, I want the app to automatically sync my biometrics daily, so that training decisions are based on real physiological data.
OAuth Flow
Initial Token Bootstrap (Manual)
Garmin's Connect API uses OAuth 1.0a → OAuth 2.0 exchange.
Process:
- User runs
scripts/garmin_auth.pylocally - Script obtains OAuth 1.0a request token
- User authorizes in browser
- Script exchanges for OAuth 2.0 access token
- User pastes tokens into Settings > Garmin page
Tokens Stored:
oauth1- OAuth 1.0a credentials (JSON, encrypted)oauth2- OAuth 2.0 access token (JSON, encrypted)expires_at- Token expiration timestamp
Token Storage
Tokens encrypted with AES-256-GCM using ENCRYPTION_KEY env var.
Functions (src/lib/encryption.ts):
encrypt(plaintext: string): string- Returns base64 ciphertextdecrypt(ciphertext: string): string- Returns plaintext
API Endpoints
GET /api/garmin/status
Returns current Garmin connection status.
Response:
{
"connected": true,
"daysUntilExpiry": 85,
"lastSync": "2024-01-10T07:00:00Z"
}
POST /api/garmin/tokens
Updates stored Garmin tokens.
Request Body:
{
"oauth1": { ... },
"oauth2": { ... },
"expires_at": "2024-04-10T00:00:00Z"
}
Garmin Data Fetching
Daily Sync (/api/cron/garmin-sync)
Runs via cron job at 6:00 AM user's timezone.
Endpoints Called:
/usersummary-service/stats/bodyBattery/dates/{date}- Body Battery/hrv-service/hrv/{date}- HRV Status/fitnessstats-service/activity- Intensity Minutes
Data Extracted:
bodyBatteryCurrent- Latest BB valuebodyBatteryYesterdayLow- Yesterday's minimum BBhrvStatus- "Balanced" or "Unbalanced"weekIntensityMinutes- 7-day rolling sum
Garmin Client (src/lib/garmin.ts)
Functions:
fetchGarminData(endpoint, { oauth2Token })- Generic API callerisTokenExpired(tokens)- Check if refresh neededdaysUntilExpiry(tokens)- Days until token expires
Token Expiration Handling
Garmin OAuth 2.0 tokens expire after ~90 days.
Warning Triggers:
- 14 days before: Yellow warning in Settings
- 7 days before: Email notification
- 0 days: Red alert, manual refresh required
Success Criteria
- Tokens stored encrypted at rest
- Daily sync completes before 7 AM notification
- Token expiration warnings sent 14 and 7 days before
- Failed syncs logged with actionable error messages
Acceptance Tests
- Encrypt/decrypt round-trips correctly
/api/garmin/statusreturns accurate days until expiry/api/garmin/tokensvalidates token structure- Cron sync fetches all three Garmin endpoints
- Expired token triggers appropriate error handling
- Token expiration warning at 14-day threshold