Initial release: collaborative trip planner
Multi-user trip planning web app in a single Docker container. Mullvad-style token accounts, trip sharing via join codes, day-by-day calendar with typed entries (activity, hotel, travel, flight, rental car, immigration, note), multi-leg flight segments with bundled IATA airport dataset, Leaflet/OSM map with per-leg great-circle km (air vs ground), rough km-driven vs rental included-km comparison, cost splitting with settle-up suggestions, flip-clock departure countdown. Node 20 + Express + SQLite (WAL, additive migrations), vanilla JS SPA, 44 API tests.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# 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: "<long random string — e.g. openssl rand -hex 32>"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
# 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"
|
||||
```
|
||||
|
||||
```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.** |
|
||||
|
||||
## Data
|
||||
|
||||
All state lives in a single SQLite file under `DATA_DIR`. In Docker this is `/app/data`, mounted from `./data` on the host. No external database or services are required (geocoding calls OpenStreetMap's Nominatim, which needs no API key; airport lookups use a bundled offline dataset).
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user