Ullrich Schäfer
8e5b6d6fe9
Wahoo capability adapters + caller migration (groups 3-5)
...
Implements tasks 1.3, 3.2-3.3, 4.1-4.4, 5.1-5.6, 6.1 of
deepen-connected-services. Built TDD: contract test red, adapter green
for each capability seam.
Capability adapters (providers/wahoo/):
- importer.ts: Importer seam — listImportable + importOne against
Wahoo /v1/workouts. Filters fitness_app_id >= 1000 (Wahoo doesn't
share third-party data). 2 contract tests green.
- pusher.ts: RoutePusher seam — pushRoute(ctx, input) -> {remoteId,
version}. FIT-Course conversion, route:<id> external_id, PUT-vs-POST
decision, PUT->POST-on-404 fallback all internal to the adapter
(per ADR-0003). Idempotency via sync_pushes preserved. 4 contract
tests green.
- webhook.ts: WebhookReceiver seam — parseWebhook + handle. Routes
events to local users via provider_user_id; unknown user returns
silently. 6 contract tests green.
- manifest.ts: declares credential_kind=oauth, OAuth config, scopes,
buildAuthUrl, exchangeCode, and references each capability adapter.
Module shape:
- connected-services/index.ts: side-effect imports providers/index.ts
to register manifests, then re-exports manager + registry + types.
- connected-services/providers/index.ts: barrel that calls
registerManifest(wahooManifest).
- connected-services/oauth-state.server.ts: OAuth state encode/decode
(extracted from legacy pushes.server.ts).
- connected-services/push-action.server.ts: orchestration above the
RoutePusher seam — load route, ownership check, scope check, build
RoutePushInput, invoke pusher, return PushOutcome union. Replaces
the legacy pushRouteToProvider in lib/sync/pushes.server.ts.
Caller migration (group 5):
- api.sync.connect.$provider.ts -> manifest.buildAuthUrl
- api.sync.callback.$provider.ts -> manifest.exchangeCode + link()
- api.sync.disconnect.$provider.ts -> unlinkByUserProvider (calls
best-effort revoke via the credential adapter, then deletes locally)
- api.sync.webhook.$provider.ts -> manifest.webhookReceiver dispatch
- api.sync.push.$provider.$routeId.ts -> push-action.pushRouteToProvider
- sync.import.$provider.tsx -> manifest.importer.listImportable +
inline FIT->GPX in the action (form-supplied metadata bypasses the
Importer seam which is reserved for automatic / webhook imports)
- routes.$id.tsx -> getService instead of getConnection
- settings.connections.tsx -> getAllManifests instead of legacy registry
Legacy lib/sync/ deleted except imports.server.ts (which manages the
sync_imports table, untouched by this change).
DB migration verified locally (task 1.3): pnpm db:migrate-data renamed
the table and backfilled credentials JSONB; pnpm db:push then dropped
the legacy access_token/refresh_token/expires_at columns. Final shape
matches the schema; check constraints + unique index in place.
Test status (task 6.1):
- pnpm typecheck: green across all 15 workspaces
- pnpm lint: green
- @trails-cool/journal: 112 passed, 31 skipped — 12 fewer tests than
before because pushes.server.test.ts and the legacy wahoo.test.ts
were deleted. Their coverage is replaced by the new contract tests
(importer/pusher/webhook) plus manager.test.ts + oauth.test.ts.
Remaining: 6.2 (e2e), 6.3-6.4 (manual smoke + staging migration test),
7.1-7.3 (followups + spec deltas at archive).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:25:33 +02:00
Ullrich Schäfer
9aaa17c898
Fix Wahoo import crash for workouts without FIT files
...
Some Wahoo workouts (indoor, manual entries) don't have a FIT file URL.
Both the manual import action and webhook handler called downloadFile
unconditionally, throwing "No file URL for workout". Now skips the
download and creates the activity without GPX when no file is available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:41:28 +01:00
Ullrich Schäfer
b6711d23d6
Add Wahoo activity sync with provider-agnostic framework
...
Provider-agnostic sync framework + Wahoo as first implementation:
Framework (apps/journal/app/lib/sync/):
- SyncProvider interface for OAuth2, webhooks, import, conversion
- Provider registry for settings UI iteration
- Generic sync_connections + sync_imports tables
- Token storage with auto-refresh
Wahoo provider:
- OAuth2 with workouts_read, user_read, offline_data scopes
- Webhook-based auto-import (workout_summary events)
- Manual import page with pagination
- FIT→GPX conversion via fit-file-parser
- Webhook token verification
Routes:
- /api/sync/connect/:provider — OAuth redirect
- /api/sync/callback/:provider — OAuth callback
- /api/sync/disconnect/:provider — remove connection
- /api/sync/webhook/:provider — webhook receiver
- /sync/import/:provider — manual import page
Settings: Connected Services section with per-provider connect/disconnect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:22:02 +01:00