Archive completed changes and sync their delta specs to main: - wahoo-import: new wahoo-import spec, updated journal-auth and account-settings - journal-route-previews: new route-preview spec, updated map-display and route-management Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.6 KiB
3.6 KiB
1. Sync Framework & Database
- 1.1 Add
sync_connectionstable topackages/db/src/schema/journal.ts(user_id FK, provider, access_token, refresh_token, expires_at, provider_user_id, created_at) - 1.2 Add
sync_importstable (user_id FK, provider, external_workout_id, activity_id FK, imported_at) - 1.3 Define
SyncProviderTypeScript interface inapps/journal/app/lib/sync/types.ts - 1.4 Create provider registry in
apps/journal/app/lib/sync/registry.ts(array of providers, lookup by id) - 1.5 Create token storage helpers:
saveConnection(),getConnection(),deleteConnection(),updateTokens()inapps/journal/app/lib/sync/connections.server.ts - 1.6 Create import tracking helpers:
recordImport(),isAlreadyImported(),getImportedIds()inapps/journal/app/lib/sync/imports.server.ts - 1.7 Add WAHOO_CLIENT_ID and WAHOO_CLIENT_SECRET to
infrastructure/secrets.app.envvia SOPS - 1.8 Run
pnpm db:pushto apply schema changes
2. Wahoo Provider Implementation
- 2.1 Create
apps/journal/app/lib/sync/providers/wahoo.tsimplementingSyncProvider - 2.2 Implement
getAuthUrl()with scopesworkouts_read,user_read,offline_data - 2.3 Implement
exchangeCode()andrefreshToken()using Wahoo's OAuth endpoints - 2.4 Implement
listWorkouts()with pagination (Wahoo'sGET /v1/workouts) - 2.5 Implement
downloadFile()to fetch FIT file from Wahoo CDN - 2.6 Implement
parseWebhook()to extract workout info fromworkout_summarypayload
3. FIT → GPX Conversion
- 3.1 Add
fit-file-parserdependency - 3.2 Implement
convertToGpx()in Wahoo provider: parse FIT, extract track records, convert semicircles to degrees, generate GPX viagenerateGpx - 3.3 Handle indoor workouts (no GPS) — return null GPX, create activity with stats only
4. OAuth Routes
- 4.1 Create
apps/journal/app/routes/api.sync.connect.$provider.ts— generates auth URL from provider, redirects - 4.2 Create
apps/journal/app/routes/api.sync.callback.$provider.ts— exchanges code, stores tokens, redirects to settings - 4.3 Create
apps/journal/app/routes/api.sync.disconnect.$provider.ts— deletes connection - 4.4 Register routes in
apps/journal/app/routes.ts
5. Webhook Route
- 5.1 Create
apps/journal/app/routes/api.sync.webhook.$provider.ts— receives webhook, looks up user, downloads file, converts, creates activity - 5.2 Verify webhook by matching
provider_user_idto async_connection - 5.3 Handle duplicate detection via
sync_imports - 5.4 Register route in
routes.ts
6. Import Page
- 6.1 Create
apps/journal/app/routes/sync.import.$provider.tsx— lists workouts with import buttons - 6.2 Show workout date, type, duration, distance per row
- 6.3 Mark already-imported workouts via
sync_importslookup - 6.4 Import action: download file, convert, create activity, record import
- 6.5 Pagination for workout list
- 6.6 Register route in
routes.ts
7. Settings Integration
- 7.1 Add "Connected Services" section to settings page
- 7.2 Iterate over registered providers to show connect/disconnect per provider
- 7.3 Show connection status and link to import page when connected
8. i18n
- 8.1 Add translation keys for sync UI (en + de): connect/disconnect, import page, webhook status, provider names
9. Testing
- 9.1 Unit test: FIT→GPX conversion with sample FIT data
- 9.2 Unit test: duplicate detection via sync_imports
- 9.3 Unit test: OAuth URL generation and token exchange (mock fetch)
- 9.4 E2E test: settings page shows Connected Services section