Task 7.2: when komoot-import is implemented, it must use the connected_services + web-login credential kind shape, not a separate journal.integrations table. Note added to komoot-import/design.md referencing ADR-0001 and CONTEXT.md. Also marks tasks 6.2 (no Wahoo e2e tests exist; nothing to run) and 7.1 (no CONTEXT.md term changes during impl) complete in tasks.md. Remaining: 6.3 (manual smoke), 6.4 (staging migration test), 7.3 (spec deltas at archive). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.3 KiB
Context
trails.cool Journal supports manual route creation and GPX import. Users coming
from Komoot have hundreds of tours they'd lose by switching. The old trails
project had a working Komoot integration using basic auth against Komoot's
undocumented API (api.komoot.de).
Note (added 2026-05-08, post
deepen-connected-services): Earlier drafts of this design proposed a separatejournal.integrationstable for Komoot credentials. That has been superseded by the connected-services architecture introduced inopenspec/changes/deepen-connected-services/. When this change is revisited, Komoot must implement:
- A row in
journal.connected_serviceswithcredential_kind = 'web-login'and acredentialsJSONB blob carrying{ email, encrypted_password, session_jar }.- A
web-loginCredentialAdapteratapps/journal/app/lib/connected-services/credential-adapters/web-login.tsimplementingrelogin(creds) → creds | InvalidCredentials. Web-login breakage (form changes, captcha, password rotation) surfaces at the import layer, not the credential layer (see ADR-0001 / CONTEXT.md).- A
KomootImporterinapps/journal/app/lib/connected-services/providers/komoot/importer.tsthat goes throughctx.withFreshCredentialslike the Wahoo importer. Komoot does not have webhooks or push, so its manifest declares only theImportercapability — noroutePusher, nowebhookReceiver.- A manifest at
providers/komoot/manifest.tsregistered viaproviders/index.ts.Don't add a
journal.integrationstable. The user-facing "Connected Services" list at/settings/connectionsshould show Komoot alongside Wahoo, which only works if both shareconnected_services.
Goals / Non-Goals
Goals:
- Connect Komoot account via email + password
- Import all tours (paginated) as activities + routes
- Track import progress with batch status
- Deduplicate on re-import (same tour never imported twice)
- Fetch GPX geometry per tour (not just metadata)
Non-Goals:
- OAuth flow (Komoot has no public OAuth — basic auth only)
- Real-time sync or webhook-based updates
- Strava/other providers (future iteration, but design the schema generically)
- Background job queue like Inngest (keep it simple — synchronous import with progress)
Decisions
D1: Generic integrations table with provider column
Store connections in a journal.integrations table with a provider enum
(komoot, and later strava etc). Credentials encrypted at rest. This avoids
a separate table per provider.
D2: Import batches for progress tracking
Each import creates an import_batches row tracking: status (running,
completed, failed), total found, imported count, duplicate count, error message.
The UI polls this for progress.
D3: Deduplication via composite key
Activities get a dedupeKey column. For Komoot: komoot:{tourId}. Combined
with ownerId, a unique constraint prevents duplicates. Insert uses
onConflictDoNothing.
D4: Synchronous import with streaming progress
No background job queue. The import runs in an API route action that:
- Fetches all tour pages from Komoot
- For each tour, fetches GPX and creates activity + route
- Updates the batch row with progress
- Client polls
/api/integrations/komoot/import-statusfor live updates
This keeps the architecture simple. If imports are too slow (>100 tours), we can add a background worker later.
D5: Encrypt credentials with AES-256-GCM
Use Node's crypto.createCipheriv with a server-side key derived from
INTEGRATION_SECRET env var. Decrypt on use, never log plaintext.
Alternative considered: Store only the API token (not email+password). Rejected because the token may expire and re-auth requires the original credentials.
Risks / Trade-offs
- Komoot API is undocumented → Could break without notice. Mitigation: wrap all API calls in error handling, mark integration as "needs reauth" on 401.
- Storing user passwords for third-party service → Security risk.
Mitigation: AES-256-GCM encryption, separate
INTEGRATION_SECRET, document in privacy manifest. - Synchronous import may timeout for large accounts → Mitigation: paginate and commit per-page. If a page fails, the batch is marked partial and can be resumed.