# Trip Plan A self-hosted, multi-user web tool for collaboratively planning trips. Multiple people log in to the same instance, create trips with a name and date range, and fill in a day-by-day calendar together — flights (with multi-leg connections), hotels, border crossings, drives, rental cars, and activities. Each trip is drawn on an interactive map with the route between stops, per-leg distances, cost splitting between members, and an overall summary. It runs as a single Docker container with a SQLite database. ## Features - **Accounts without passwords** — Mullvad-style: one click generates a random account number (shown once, stored only as a hash). No email, no password, no personal data. - **Sharing via join codes** — every trip has a short code; anyone with an account enters it to become a co-editor. The owner can regenerate the code to stop further joins. - **Day-by-day calendar** — a real calendar grid; click a day to add any number of entries (activity, hotel, travel, flight, rental car, immigration, note). - **Multi-leg flights** — type `CNX-BKK-DXB-FRA` and it expands into segments with per-leg flight number and times; every airport becomes a stop on the map (bundled offline IATA airport database). - **Rental cars** — brand/model/type, booking ref, pickup/dropoff with dates and times, and included km — compared against the trip's estimated driven km. - **Locations** — geocoded via OpenStreetMap (Nominatim, English results); lat/lng stored per entry. - **Map** — Leaflet + OpenStreetMap; stops plotted in order, air legs dashed / ground legs solid, per-leg great-circle km. - **Costs & splitting** — price per entry with "split equally", "everyone pays their own", or "payer's expense"; per-person share/paid/net table and minimal settle-up transfers, in the trip's currency. - **Summary** — days/nights, entry counts, total km, **≈ km driven** (rental-car figure) vs km flown, and locations visited. ## Deployment (Docker + external Caddy for TLS) The app serves plain HTTP on port 3000 and is designed to sit behind a reverse proxy that terminates TLS — e.g. an external [Caddy](https://caddyserver.com/). ### 1. Run the container Using the prebuilt image from the registry: ```yaml # docker-compose.yml services: tripplan: image: git.b4l.co.th/grabowski/trip-plan:latest restart: unless-stopped environment: SESSION_SECRET: "" volumes: - ./data:/app/data # database lives next to this compose file # No public port mapping needed when Caddy shares a Docker network with # the app (recommended). For a host-level Caddy, map localhost only: ports: - "127.0.0.1:3000:3000" ``` The entrypoint chowns `./data` to the app's unprivileged `node` user on startup, so a root-created bind-mount directory works out of the box. (Images older than this note need a one-time `sudo chown -R 1000:1000 ./data`.) ```bash docker compose up -d ``` (Or build locally instead of pulling: clone this repo and use `build: .` — the checked-in `docker-compose.yml` does exactly that.) ### 2. Point Caddy at it If Caddy runs on the same host (host-level Caddy, app bound to `127.0.0.1:3000`): ```caddyfile trips.example.com { reverse_proxy 127.0.0.1:3000 } ``` If Caddy runs as a container, attach both services to a shared Docker network and proxy by service name instead: ```caddyfile trips.example.com { reverse_proxy tripplan:3000 } ``` That's all — Caddy obtains and renews the certificate automatically; the app needs no TLS configuration of its own. Session cookies are `SameSite=Lax` and `httpOnly`, and work as-is behind the proxy. ### 3. Updating ```bash docker compose pull && docker compose up -d ``` Schema migrations run automatically at startup; existing data in `./data` is preserved (back up that directory to back up all trips). ## Quick start (local, no proxy) ```bash docker compose up -d # builds the image from source # open http://localhost:3000 and create the first account ``` > Before exposing the app beyond localhost, set your own random `SESSION_SECRET` > in `docker-compose.yml`. ## Local development ```bash npm install npm start # http://localhost:3000 npm test # API tests (node:test + supertest) ``` `npm run dev` starts the server with `--watch` for auto-reload. ## Environment variables | Variable | Default | Description | |---|---|---| | `PORT` | `3000` | HTTP port the server listens on. | | `DATA_DIR` | `./data` (`/app/data` in Docker) | Directory holding the SQLite database file. | | `SESSION_SECRET` | dev value (warns) | Secret used to sign session cookies. **Set this in production.** | | `OSRM_URL` | `https://router.project-osrm.org` | Base URL for the driving-directions proxy (`/api/directions`). The default is the public OSRM demo server — fine for light personal use. Self-hosters can point this at their own OSRM instance. | ## Data All state lives in a single SQLite file under `DATA_DIR`. In Docker this is `/app/data`, bind-mounted from `./data` next to the compose file — back up that directory to back up all trips. No external database or services are required (geocoding calls OpenStreetMap's Nominatim, which needs no API key; airport lookups use a bundled offline dataset; road directions call the public OSRM demo server, also no API key). ## Documentation - [docs/PROJECT_OVERVIEW.md](docs/PROJECT_OVERVIEW.md) — architecture, data model, and design decisions. - [docs/API.md](docs/API.md) — the REST API contract.