Add 8 proposed changes and old trails analysis
Proposed changes (all with proposal, design, specs, tasks): - app-navigation (9 tasks) — nav bars for both apps - planner-landing-page (7 tasks) — standalone landing page - planner-multiplayer-awareness (13 tasks) — participants, cursors, names - changelog (13 tasks) — public changelog with "what's new" - transactional-emails (15 tasks) — magic link + welcome emails - planner-features (18 tasks) — no-go areas, notes, recovery, rate limits - komoot-import (23 tasks) — Komoot tour import - route-features (37 tasks) — sharing, multi-day, spatial, photos Also adds docs/old-trails-analysis.md with feature analysis from the older trails project. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
57141cdfab
commit
c7c1c275df
55 changed files with 1704 additions and 0 deletions
2
openspec/changes/komoot-import/.openspec.yaml
Normal file
2
openspec/changes/komoot-import/.openspec.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-03-25
|
||||
72
openspec/changes/komoot-import/design.md
Normal file
72
openspec/changes/komoot-import/design.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
## 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`).
|
||||
|
||||
## 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:
|
||||
1. Fetches all tour pages from Komoot
|
||||
2. For each tour, fetches GPX and creates activity + route
|
||||
3. Updates the batch row with progress
|
||||
4. Client polls `/api/integrations/komoot/import-status` for 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.
|
||||
32
openspec/changes/komoot-import/proposal.md
Normal file
32
openspec/changes/komoot-import/proposal.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
## Why
|
||||
|
||||
Users switching to trails.cool have years of tours on Komoot. Without import,
|
||||
they start with an empty Journal — no history, no motivation to switch. A
|
||||
one-click Komoot import lets users bring their existing tours and start using
|
||||
trails.cool immediately.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add Komoot account connection flow (email + password → API credentials)
|
||||
- Import Komoot tours as Journal activities with route data
|
||||
- Batch import with progress tracking, deduplication, and error handling
|
||||
- Integration settings page to manage connected accounts and trigger imports
|
||||
- Fetch full tour GPX (not just metadata) so imported routes have geometry
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `komoot-import`: Connect a Komoot account, import tours as activities with routes, track import progress, deduplicate on re-import
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `route-management`: Routes can now be created via import (not just manual creation), with an external source tracking field
|
||||
|
||||
## Impact
|
||||
|
||||
- **Database**: New tables for integration connections and import batches in `journal` schema
|
||||
- **Files**: New routes (`/integrations`, `/api/integrations/*`), new server utilities for Komoot API, new DB schema tables
|
||||
- **Dependencies**: No new packages needed — uses fetch for Komoot API, existing Drizzle for DB
|
||||
- **Privacy**: Komoot credentials stored encrypted. Must be documented in privacy manifest.
|
||||
- **External**: Komoot API (api.komoot.de) — basic auth, no official developer program
|
||||
63
openspec/changes/komoot-import/specs/komoot-import/spec.md
Normal file
63
openspec/changes/komoot-import/specs/komoot-import/spec.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Connect Komoot account
|
||||
Users SHALL be able to connect their Komoot account by providing email and password. Credentials are validated against the Komoot API and stored encrypted.
|
||||
|
||||
#### Scenario: Successful connection
|
||||
- **WHEN** user enters valid Komoot email and password on the integrations page
|
||||
- **THEN** the system validates credentials via `api.komoot.de`, stores them encrypted, and shows the connection as active
|
||||
|
||||
#### Scenario: Invalid credentials
|
||||
- **WHEN** user enters incorrect Komoot credentials
|
||||
- **THEN** the system shows an error message and does not store credentials
|
||||
|
||||
#### Scenario: Disconnect account
|
||||
- **WHEN** user disconnects their Komoot integration
|
||||
- **THEN** stored credentials are deleted and no further imports can occur
|
||||
|
||||
### Requirement: Import Komoot tours
|
||||
Users SHALL be able to import all their Komoot tours as Journal activities with linked routes.
|
||||
|
||||
#### Scenario: Import all tours
|
||||
- **WHEN** user clicks "Import" on the integrations page
|
||||
- **THEN** the system fetches all tours (paginated), 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/geometry 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** 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", and 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 encryption
|
||||
Komoot credentials SHALL be encrypted at rest using AES-256-GCM.
|
||||
|
||||
#### Scenario: Credentials stored securely
|
||||
- **WHEN** a user connects their Komoot account
|
||||
- **THEN** the password is encrypted 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: what credentials are stored, what data is imported, and that credentials are encrypted
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Route creation
|
||||
The system SHALL support creating routes via import from external services, in addition to manual creation and GPX upload.
|
||||
|
||||
#### Scenario: Route created from import
|
||||
- **WHEN** a Komoot tour is imported
|
||||
- **THEN** a route is created with name, distance, elevation, GPX geometry, and a source field indicating "komoot"
|
||||
|
||||
#### Scenario: Route links to activity
|
||||
- **WHEN** a tour is imported as an activity
|
||||
- **THEN** the activity is linked to the created route
|
||||
51
openspec/changes/komoot-import/tasks.md
Normal file
51
openspec/changes/komoot-import/tasks.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
## 1. Database Schema
|
||||
|
||||
- [ ] 1.1 Add `journal.integrations` table (id, userId, provider enum, encryptedCredentials, apiUsername, status, lastSyncedAt, createdAt)
|
||||
- [ ] 1.2 Add `journal.import_batches` table (id, userId, integrationId, status enum, totalFound, importedCount, duplicateCount, errorMessage, startedAt, completedAt)
|
||||
- [ ] 1.3 Add `dedupeKey` column to `journal.activities` table with unique constraint on (ownerId, dedupeKey)
|
||||
- [ ] 1.4 Add `source` column to `journal.routes` table (nullable, e.g. "komoot", "manual", "gpx-upload")
|
||||
- [ ] 1.5 Run `pnpm db:push` and verify schema locally
|
||||
|
||||
## 2. Credential Encryption
|
||||
|
||||
- [ ] 2.1 Create `apps/journal/app/lib/crypto.server.ts` with AES-256-GCM encrypt/decrypt using `INTEGRATION_SECRET` env var
|
||||
- [ ] 2.2 Write unit tests for encrypt/decrypt roundtrip
|
||||
|
||||
## 3. Komoot API Client
|
||||
|
||||
- [ ] 3.1 Create `apps/journal/app/lib/komoot.server.ts` with login function (email + password → username + token)
|
||||
- [ ] 3.2 Add fetchTours function (paginated, fetches all pages)
|
||||
- [ ] 3.3 Add fetchTourGpx function (fetch GPX geometry for a single tour)
|
||||
- [ ] 3.4 Write unit tests for API response parsing (mock fetch)
|
||||
|
||||
## 4. Import Logic
|
||||
|
||||
- [ ] 4.1 Create `apps/journal/app/lib/import.server.ts` with importKomootTours function that: creates batch, pages through tours, fetches GPX, creates activities + routes, deduplicates, updates batch
|
||||
- [ ] 4.2 Write integration test for import with mock Komoot responses
|
||||
|
||||
## 5. API Routes
|
||||
|
||||
- [ ] 5.1 Create `POST /api/integrations/komoot/connect` — validate credentials, store encrypted
|
||||
- [ ] 5.2 Create `POST /api/integrations/komoot/disconnect` — delete credentials
|
||||
- [ ] 5.3 Create `POST /api/integrations/komoot/import` — trigger import, return batch ID
|
||||
- [ ] 5.4 Create `GET /api/integrations/komoot/import-status` — return current batch progress
|
||||
|
||||
## 6. UI
|
||||
|
||||
- [ ] 6.1 Create `/integrations` route with Komoot connection form (email + password)
|
||||
- [ ] 6.2 Show connected status, last sync time, and import button when connected
|
||||
- [ ] 6.3 Import progress UI — poll import-status, show counts (found, imported, duplicated)
|
||||
- [ ] 6.4 Add link to integrations page from Journal navigation
|
||||
- [ ] 6.5 Add i18n keys for all integration strings (en + de)
|
||||
|
||||
## 7. Privacy & Config
|
||||
|
||||
- [ ] 7.1 Update /privacy page to document Komoot integration (credentials stored encrypted, what data is imported)
|
||||
- [ ] 7.2 Add `INTEGRATION_SECRET` env var to docker-compose.yml and CI
|
||||
- [ ] 7.3 Add `INTEGRATION_SECRET` to deploy secrets documentation
|
||||
|
||||
## 8. Verify
|
||||
|
||||
- [ ] 8.1 Test full flow locally: connect Komoot → import tours → verify activities + routes created
|
||||
- [ ] 8.2 Verify deduplication: re-import and confirm no duplicates
|
||||
- [ ] 8.3 Verify disconnect removes credentials
|
||||
Loading…
Add table
Add a link
Reference in a new issue