trails/openspec/specs/komoot-import/spec.md
Ullrich Schäfer 0cf87b72ab
Fix medium-severity spec drift across 17 specs
- authentication-methods: document completeAuth mode param ("redirect"|"json");
  clarify add-passkey nudge (no dismiss mechanism, disappears on passkey add)
- journal-auth: session maxAge is 30 days; terms allow-list uses /legal/ prefix
  matching (broader than fixed list of paths)
- session-notes: mark awareness isolation and UndoManager isolation as not yet
  implemented (shared instances in current code)
- activity-feed: add fan-out scenario for visibility change to public
- explore: note that ?perPage is not yet implemented (hardcoded page size)
- multi-day-routes: add per-day GPX track split scenario (splitByDays option);
  document overnight vs isDayBreak naming gap
- osm-poi-overlays: debounce is 800ms + 2000ms min interval (not 500ms);
  retry is not automatic (fires on next viewport change)
- brouter-integration: rate limit corrected to 300/hour; add segment-cache
  requirement (client caches per-pair segments)
- wahoo-route-push: OAuth state shape uses camelCase (returnTo, pushAfter
  object) not snake_case with push_after boolean
- komoot-import: document noop adapter / ConnectedServiceManager bypass;
  note four Komoot-specific routes that bypass the generic OAuth framework
- background-jobs: exponential backoff not wired (retryLimit only); add SIGINT
- connected-services: add revoked status; name ConnectionNotActiveError
- infrastructure: add INTEGRATION_SECRET and SENTRY_DSN to env var lists;
  split secret decryption scenario by workflow (cd-apps vs cd-infra)
- secret-management: correct CD decryption — cd-apps only decrypts app.env;
  cd-infra decrypts both
- transactional-emails: welcome email is async (pg-boss job); magic-link email
  includes 6-digit numeric code
- journal-route-detail: websites are https: links (not mailto:); opening_hours
  is also displayed
- local-dev-environment: add mobile app (Expo) dev commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:11:31 +02:00

5.3 KiB

Purpose

Komoot integration for the Journal app: connect a Komoot account (public bio-verification or authenticated with credentials) and import tours as Journal activities with linked routes.

Requirements

Requirement: Connect Komoot account — public mode

Users SHALL be able to connect their Komoot account without providing credentials by verifying ownership via their Komoot bio field.

Scenario: Initiate public connection

  • WHEN a user enters their Komoot profile URL on the integrations page
  • THEN the system displays their trails.cool profile URL and instructs them to add it to their Komoot bio ("Über dich" field)

Scenario: Verify public ownership

  • WHEN the user clicks Verify after updating their Komoot bio
  • THEN the system fetches the public Komoot profile, checks that content_text contains the user's trails.cool profile URL, and on success marks the connection as active with mode "public"

Scenario: Bio not yet updated

  • WHEN verification is attempted but the trails.cool URL is not found in the Komoot bio
  • THEN the system shows an error explaining that changes may take a few minutes to propagate, with a retry button

Scenario: Disconnect public account

  • WHEN a user disconnects their public Komoot integration
  • THEN the stored Komoot username is removed and no further imports can occur

Requirement: Connect Komoot account — authenticated mode

Users SHALL be able to connect their Komoot account by providing email and password to access all tours including private ones.

Scenario: Successful authenticated connection

  • WHEN a user enters valid Komoot email and password
  • THEN the system validates credentials via the Komoot API, stores them encrypted, and shows the connection as active with mode "authenticated"

Scenario: Invalid credentials

  • WHEN a user enters incorrect Komoot credentials
  • THEN the system shows an error and does not store credentials

Scenario: Disconnect authenticated account

  • WHEN a user disconnects their authenticated Komoot integration
  • THEN stored credentials are deleted and no further imports can occur

Requirement: Import Komoot tours

Users SHALL be able to import their Komoot tours as Journal activities with linked routes. Public mode imports public tours only; authenticated mode imports all tours.

Scenario: Import public tours

  • WHEN a user with a public-mode connection triggers import
  • THEN the system fetches all public tours from the Komoot API without credentials, creates an activity and route for each, and shows progress

Scenario: Import all tours (authenticated)

  • WHEN a user with an authenticated-mode connection triggers import
  • THEN the system fetches all tours (public and private, paginated) using stored credentials, creates an activity and route for each, and shows progress

Scenario: Tour with GPX geometry

  • WHEN a Komoot tour is imported
  • THEN the system fetches the tour's GPX and creates a route with the full track data

Scenario: Import progress

  • WHEN an import is running
  • THEN the UI shows: total tours found, imported so far, duplicates skipped, and current status

Requirement: Deduplication

The system SHALL NOT create duplicate activities when re-importing tours that were previously imported.

Scenario: Re-import skips existing tours

  • WHEN a user runs import and a tour was already imported previously
  • THEN the tour is skipped and counted as a duplicate in the import summary

Requirement: Import batch tracking

Each import run SHALL be tracked as a batch with status and statistics.

Scenario: Batch lifecycle

  • WHEN an import starts
  • THEN a batch record is created with status "running", updated to "completed" or "failed" when done

Scenario: Batch statistics

  • WHEN an import completes
  • THEN the batch shows: totalFound, importedCount, duplicateCount, and duration

Requirement: Credential storage

Both connection modes store credentials in connected_services with credential_kind = 'web-login'. Public mode stores only the verified Komoot username; authenticated mode stores an encrypted email + password in the credentials JSONB blob. The Komoot provider uses a noop CredentialAdapter — it does not go through ConnectedServiceManager's withFreshCredentials lifecycle (no token refresh needed for web-login credentials). Credential decryption for API calls happens inside the Komoot importer directly.

The four Komoot-specific routes (/api/sync/komoot/connect, /api/sync/komoot/verify, /api/sync/komoot/import, /api/sync/komoot/import-status) intentionally bypass the generic /api/sync/connect/:provider / /api/sync/callback/:provider framework — Komoot's bio-verification flow is too different from OAuth to fit the generic shape.

Scenario: Credentials stored securely

  • WHEN a user connects via authenticated mode
  • THEN the password is encrypted with AES-256-GCM before storage and only decrypted when making API calls

Requirement: Privacy disclosure

The Komoot integration SHALL be documented in the privacy manifest.

Scenario: Privacy manifest updated

  • WHEN Komoot import is available
  • THEN the /privacy page documents: that public mode stores only the Komoot username, that authenticated mode stores an encrypted password, and what tour data is imported