From 52b2baaf58e1e858c9762211153a876400c6488e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Mon, 23 Mar 2026 00:11:43 +0100 Subject: [PATCH] Add local dev setup, fix BRouter Dockerfile, archive change (#12) --- CLAUDE.md | 4 ++ README.md | 25 +++++++- docker-compose.dev.yml | 10 ++- docker/brouter/Dockerfile | 9 ++- .../2026-03-23-local-dev-setup/.openspec.yaml | 2 + .../2026-03-23-local-dev-setup/design.md | 63 +++++++++++++++++++ .../2026-03-23-local-dev-setup/proposal.md | 31 +++++++++ .../specs/local-dev-environment/spec.md | 37 +++++++++++ .../2026-03-23-local-dev-setup/tasks.md | 26 ++++++++ openspec/specs/local-dev-environment/spec.md | 37 +++++++++++ package.json | 6 +- packages/db/drizzle.config.ts | 12 +++- scripts/dev.sh | 55 ++++++++++++++++ scripts/download-dev-segments.sh | 21 +++++++ 14 files changed, 329 insertions(+), 9 deletions(-) create mode 100644 openspec/changes/archive/2026-03-23-local-dev-setup/.openspec.yaml create mode 100644 openspec/changes/archive/2026-03-23-local-dev-setup/design.md create mode 100644 openspec/changes/archive/2026-03-23-local-dev-setup/proposal.md create mode 100644 openspec/changes/archive/2026-03-23-local-dev-setup/specs/local-dev-environment/spec.md create mode 100644 openspec/changes/archive/2026-03-23-local-dev-setup/tasks.md create mode 100644 openspec/specs/local-dev-environment/spec.md create mode 100755 scripts/dev.sh create mode 100755 scripts/download-dev-segments.sh diff --git a/CLAUDE.md b/CLAUDE.md index 4825ab9..89726a9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,6 +62,10 @@ pnpm test # Run unit tests (vitest) pnpm test:watch # Run unit tests in watch mode pnpm test:e2e # Run E2E tests (playwright, requires dev servers) pnpm test:e2e:ui # Run E2E tests with Playwright UI +pnpm dev:full # Start full stack (Docker + DB + BRouter + apps) +pnpm dev:services # Start Docker services only (PostgreSQL + BRouter) +pnpm db:push # Push Drizzle schema to local PostgreSQL +pnpm db:studio # Open Drizzle Studio (DB browser) ``` ## Testing Strategy diff --git a/README.md b/README.md index 669e65c..af848bf 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ packages/ ## Getting Started -Prerequisites: Node.js 20+, pnpm +Prerequisites: Node.js 20+, pnpm, Docker ```bash # Clone @@ -45,8 +45,29 @@ cd trails # Install dependencies pnpm install -# Start development +# Start development (apps only, no database or routing) pnpm dev + +# Start full stack (PostgreSQL + BRouter + apps) +pnpm dev:full +``` + +### Full Local Dev Setup + +`pnpm dev:full` starts everything needed to test the Planner end-to-end: + +1. **PostgreSQL + PostGIS** on port 5432 (via Docker) +2. **BRouter** routing engine on port 17777 (via Docker) +3. **Database schema** pushed automatically via Drizzle +4. **BRouter segment** downloaded for Berlin area (~124MB, cached) +5. **Journal** on http://localhost:3000 +6. **Planner** on http://localhost:3001 + +Other useful commands: +```bash +pnpm dev:services # Start Docker services only (DB + BRouter) +pnpm db:push # Push database schema changes +pnpm db:studio # Open Drizzle Studio (DB browser) ``` ## Development Tools diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 14b6984..f65f243 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,6 +1,6 @@ services: postgres: - image: postgis/postgis:16-3.4 + image: imresamu/postgis:16-3.4 # multi-arch (amd64 + arm64) ports: - "5432:5432" environment: @@ -15,5 +15,13 @@ services: timeout: 5s retries: 5 + brouter: + build: docker/brouter + ports: + - "17777:17777" + volumes: + - brouter_segments:/data/segments + volumes: pgdata: + brouter_segments: diff --git a/docker/brouter/Dockerfile b/docker/brouter/Dockerfile index e6ed7a7..8ff91d5 100644 --- a/docker/brouter/Dockerfile +++ b/docker/brouter/Dockerfile @@ -4,17 +4,20 @@ WORKDIR /brouter # Download latest BRouter release ARG BROUTER_VERSION=1.7.8 -RUN apt-get update && apt-get install -y --no-install-recommends wget unzip \ +RUN apt-get update && apt-get install -y --no-install-recommends wget unzip curl \ && wget -q "https://github.com/abrensch/brouter/releases/download/v${BROUTER_VERSION}/brouter-${BROUTER_VERSION}.zip" \ && unzip "brouter-${BROUTER_VERSION}.zip" \ + && mv "brouter-${BROUTER_VERSION}"/* . \ + && rmdir "brouter-${BROUTER_VERSION}" \ && rm "brouter-${BROUTER_VERSION}.zip" \ && apt-get purge -y wget unzip && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* # Create directories for segments and profiles RUN mkdir -p /data/segments /data/profiles -# Copy default profiles from release -RUN cp -r profiles2/* /data/profiles/ 2>/dev/null || true +# Copy default profiles from release and rename JAR for simpler CMD +RUN cp -r profiles2/* /data/profiles/ 2>/dev/null || true \ + && mv brouter-*-all.jar brouter.jar EXPOSE 17777 diff --git a/openspec/changes/archive/2026-03-23-local-dev-setup/.openspec.yaml b/openspec/changes/archive/2026-03-23-local-dev-setup/.openspec.yaml new file mode 100644 index 0000000..caac517 --- /dev/null +++ b/openspec/changes/archive/2026-03-23-local-dev-setup/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-22 diff --git a/openspec/changes/archive/2026-03-23-local-dev-setup/design.md b/openspec/changes/archive/2026-03-23-local-dev-setup/design.md new file mode 100644 index 0000000..de4555d --- /dev/null +++ b/openspec/changes/archive/2026-03-23-local-dev-setup/design.md @@ -0,0 +1,63 @@ +## Context + +The Planner app has server-side dependencies (PostgreSQL for sessions, BRouter +for routing) that aren't available when running `pnpm dev`. The app starts but +session creation and route computation fail. We need a local dev environment +that mirrors production dependencies. + +## Goals / Non-Goals + +**Goals:** +- One command to start the full local stack (DB + BRouter + apps) +- Database migrations run automatically +- BRouter has at least one segment for local route testing +- Works on macOS (primary dev platform) + +**Non-Goals:** +- Full Germany segment coverage (too large for dev, one tile is enough) +- Production-like Caddy/Garage setup locally +- CI integration (CI doesn't need BRouter for current tests) + +## Decisions + +### D1: Extend docker-compose.dev.yml with BRouter + +Add BRouter to the existing dev compose file. Use a named volume for segments +so they persist across restarts. + +**Alternative**: Run BRouter natively (requires JVM). Rejected — Docker is +simpler and consistent. + +### D2: Use a small test segment + +Download one BRouter segment covering Berlin area (E10_N50.rd5, ~124MB) for +local testing. This is small enough to download quickly but covers a useful +area for route testing. + +**Alternative**: Download all Germany (~750MB). Rejected — too slow for first +dev setup. Can always download more tiles manually. + +### D3: Drizzle push for dev, migrations for production + +Use `drizzle-kit push` in development (applies schema directly, no migration +files needed). Use `drizzle-kit migrate` in production. + +**Alternative**: Always use migrations. Rejected — adds friction during rapid +schema iteration in dev. + +### D4: Script-based orchestration + +A shell script (`scripts/dev.sh`) starts Docker services, waits for health +checks, runs migrations, and starts the apps. The `pnpm dev:full` command +wraps this script. + +**Alternative**: Use Docker Compose for everything including apps. Rejected — +we want Vite HMR for the apps, which works better running natively. + +## Risks / Trade-offs + +**[Docker required]** → Developers need Docker Desktop or OrbStack running. +Mitigation: Document in README prerequisites. + +**[Segment download time]** → First run downloads ~124MB. Mitigation: Only +one tile, and it's cached in a Docker volume. diff --git a/openspec/changes/archive/2026-03-23-local-dev-setup/proposal.md b/openspec/changes/archive/2026-03-23-local-dev-setup/proposal.md new file mode 100644 index 0000000..9d7d2a2 --- /dev/null +++ b/openspec/changes/archive/2026-03-23-local-dev-setup/proposal.md @@ -0,0 +1,31 @@ +## Why + +We can't test the Planner end-to-end locally. The Planner needs PostgreSQL +for session persistence and BRouter for route computation. Without a one-command +local dev setup, developers must manually run Docker containers and download +BRouter segments. This blocks iteration on Groups 5-6. + +## What Changes + +- Add a `pnpm dev:full` command that starts PostgreSQL, BRouter, and both apps +- Download a minimal BRouter segment for local testing (one tile) +- Run database migrations automatically on dev startup +- Seed data for quick testing (sample session with waypoints) +- Document local dev setup in README + +## Capabilities + +### New Capabilities + +- `local-dev-environment`: One-command local development setup with PostgreSQL, BRouter, and both apps running together + +### Modified Capabilities + +(None) + +## Impact + +- **docker-compose.dev.yml**: Add BRouter service with segment volume +- **package.json**: New `dev:full` script +- **packages/db**: Add Drizzle migration runner +- **README.md**: Document local dev prerequisites and setup diff --git a/openspec/changes/archive/2026-03-23-local-dev-setup/specs/local-dev-environment/spec.md b/openspec/changes/archive/2026-03-23-local-dev-setup/specs/local-dev-environment/spec.md new file mode 100644 index 0000000..d943ad8 --- /dev/null +++ b/openspec/changes/archive/2026-03-23-local-dev-setup/specs/local-dev-environment/spec.md @@ -0,0 +1,37 @@ +## ADDED Requirements + +### Requirement: One-command dev startup +The project SHALL provide a single command that starts all services needed for local development. + +#### Scenario: Start full dev stack +- **WHEN** a developer runs `pnpm dev:full` +- **THEN** PostgreSQL, BRouter, and both apps start and are reachable at their respective ports + +#### Scenario: Services are healthy before apps start +- **WHEN** the dev script starts +- **THEN** it waits for PostgreSQL health check and BRouter readiness before starting the apps + +### Requirement: Automatic database setup +The dev environment SHALL automatically create schemas and tables on startup. + +#### Scenario: First run database setup +- **WHEN** `pnpm dev:full` runs for the first time +- **THEN** the planner and journal PostgreSQL schemas and tables are created automatically + +### Requirement: BRouter segment for local testing +The dev environment SHALL include at least one BRouter segment for route computation testing. + +#### Scenario: Download segment on first run +- **WHEN** the BRouter Docker volume has no segments +- **THEN** the dev script downloads E10_N50.rd5 (Berlin area, ~124MB) + +#### Scenario: Skip download if segment exists +- **WHEN** the BRouter Docker volume already has segments +- **THEN** the dev script skips the download + +### Requirement: Local route computation +The Planner SHALL be able to compute routes locally in the dev environment. + +#### Scenario: Compute a route in Berlin +- **WHEN** a developer sends a route request with two Berlin waypoints to the local Planner +- **THEN** the Planner proxies to local BRouter and returns a valid GeoJSON route diff --git a/openspec/changes/archive/2026-03-23-local-dev-setup/tasks.md b/openspec/changes/archive/2026-03-23-local-dev-setup/tasks.md new file mode 100644 index 0000000..6481fb8 --- /dev/null +++ b/openspec/changes/archive/2026-03-23-local-dev-setup/tasks.md @@ -0,0 +1,26 @@ +## 1. Docker Compose Dev Services + +- [x] 1.1 Add BRouter service to docker-compose.dev.yml with segment volume +- [x] 1.2 Add BRouter profiles volume (included in BRouter Dockerfile) +- [x] 1.3 Verify PostgreSQL + BRouter start with `docker compose -f docker-compose.dev.yml up -d` + +## 2. Segment Download + +- [x] 2.1 Create scripts/download-dev-segments.sh that downloads E10_N50.rd5 to BRouter volume +- [x] 2.2 Skip download if segment already exists in volume + +## 3. Database Setup + +- [x] 3.1 Add `pnpm db:push` script that runs `drizzle-kit push` against local PostgreSQL +- [x] 3.2 Verify planner and journal schemas are created + +## 4. Dev Orchestration + +- [x] 4.1 Create scripts/dev.sh that starts Docker services, waits for health, pushes schema, starts apps +- [x] 4.2 Add `pnpm dev:full` script to root package.json +- [x] 4.3 Add `pnpm dev:services` script to start only Docker services (for running apps separately) + +## 5. Documentation & Verification + +- [x] 5.1 Update README.md with local dev prerequisites (Docker, pnpm) and setup instructions +- [x] 5.2 Test full flow: start dev stack → create session → compute route with Berlin waypoints diff --git a/openspec/specs/local-dev-environment/spec.md b/openspec/specs/local-dev-environment/spec.md new file mode 100644 index 0000000..d943ad8 --- /dev/null +++ b/openspec/specs/local-dev-environment/spec.md @@ -0,0 +1,37 @@ +## ADDED Requirements + +### Requirement: One-command dev startup +The project SHALL provide a single command that starts all services needed for local development. + +#### Scenario: Start full dev stack +- **WHEN** a developer runs `pnpm dev:full` +- **THEN** PostgreSQL, BRouter, and both apps start and are reachable at their respective ports + +#### Scenario: Services are healthy before apps start +- **WHEN** the dev script starts +- **THEN** it waits for PostgreSQL health check and BRouter readiness before starting the apps + +### Requirement: Automatic database setup +The dev environment SHALL automatically create schemas and tables on startup. + +#### Scenario: First run database setup +- **WHEN** `pnpm dev:full` runs for the first time +- **THEN** the planner and journal PostgreSQL schemas and tables are created automatically + +### Requirement: BRouter segment for local testing +The dev environment SHALL include at least one BRouter segment for route computation testing. + +#### Scenario: Download segment on first run +- **WHEN** the BRouter Docker volume has no segments +- **THEN** the dev script downloads E10_N50.rd5 (Berlin area, ~124MB) + +#### Scenario: Skip download if segment exists +- **WHEN** the BRouter Docker volume already has segments +- **THEN** the dev script skips the download + +### Requirement: Local route computation +The Planner SHALL be able to compute routes locally in the dev environment. + +#### Scenario: Compute a route in Berlin +- **WHEN** a developer sends a route request with two Berlin waypoints to the local Planner +- **THEN** the Planner proxies to local BRouter and returns a valid GeoJSON route diff --git a/package.json b/package.json index 6127dff..be4d546 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,11 @@ "test:watch": "vitest", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", - "typecheck": "turbo typecheck" + "typecheck": "turbo typecheck", + "db:push": "drizzle-kit push --config packages/db/drizzle.config.ts --force", + "db:studio": "drizzle-kit studio --config packages/db/drizzle.config.ts", + "dev:services": "docker compose -f docker-compose.dev.yml up -d", + "dev:full": "./scripts/dev.sh" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 700e8a4..fe0568f 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -1,9 +1,17 @@ import { defineConfig } from "drizzle-kit"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ - schema: ["./src/schema/planner.ts", "./src/schema/journal.ts"], - out: "./migrations", + schema: [ + path.resolve(__dirname, "src/schema/planner.ts"), + path.resolve(__dirname, "src/schema/journal.ts"), + ], + out: path.resolve(__dirname, "migrations"), dialect: "postgresql", + schemaFilter: ["planner", "journal"], dbCredentials: { url: process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails", }, diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..9d6d1e0 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$PROJECT_DIR" + +echo "=== trails.cool dev environment ===" +echo "" + +# 1. Start Docker services +echo "Starting Docker services..." +docker compose -f docker-compose.dev.yml up -d + +# 2. Wait for PostgreSQL +echo "Waiting for PostgreSQL..." +until docker compose -f docker-compose.dev.yml exec -T postgres pg_isready -U trails > /dev/null 2>&1; do + sleep 1 +done +echo "✓ PostgreSQL is ready" + +# 3. Push database schema +echo "Pushing database schema..." +pnpm db:push 2>&1 | tail -3 +echo "✓ Database schema up to date" + +# 4. Download BRouter segments if needed +echo "Checking BRouter segments..." +"$SCRIPT_DIR/download-dev-segments.sh" + +# 5. Wait for BRouter (if segments are available) +if docker compose -f docker-compose.dev.yml ps brouter --format '{{.State}}' 2>/dev/null | grep -q "running"; then + echo "Waiting for BRouter..." + for i in $(seq 1 30); do + if curl -sf http://localhost:17777/brouter?lonlats=13.4,52.5\|13.5,52.5\&profile=trekking\&format=geojson > /dev/null 2>&1; then + echo "✓ BRouter is ready" + break + fi + if [ "$i" = "30" ]; then + echo "⚠ BRouter not responding (segments may still be loading). Continuing..." + fi + sleep 2 + done +fi + +echo "" +echo "=== Starting apps ===" +echo " Journal: http://localhost:3000" +echo " Planner: http://localhost:3001" +echo " BRouter: http://localhost:17777" +echo "" + +# 6. Start both apps with turbo +exec pnpm dev diff --git a/scripts/download-dev-segments.sh b/scripts/download-dev-segments.sh new file mode 100755 index 0000000..e486d95 --- /dev/null +++ b/scripts/download-dev-segments.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail + +# Download a minimal BRouter segment for local development. +# E10_N50 covers the Berlin area (~124MB) — enough for route testing. + +VOLUME_NAME="trails-cool_brouter_segments" +SEGMENT="E10_N50.rd5" +URL="https://brouter.de/brouter/segments4/${SEGMENT}" + +# Check if segment already exists in the Docker volume +if docker run --rm -v "${VOLUME_NAME}:/data" alpine test -f "/data/${SEGMENT}" 2>/dev/null; then + echo "✓ Segment ${SEGMENT} already exists, skipping download" + exit 0 +fi + +echo "Downloading ${SEGMENT} (~124MB) for local development..." +docker run --rm -v "${VOLUME_NAME}:/data" alpine/curl \ + -L -o "/data/${SEGMENT}" "${URL}" + +echo "✓ Segment downloaded to Docker volume ${VOLUME_NAME}"