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.
6.6 KiB
6.6 KiB
Trip Plan — Project Overview
A self-hosted, multi-user web tool for collaboratively planning trips. Runs as a single Docker container.
Core Idea
Multiple people log in to the same instance. Anyone can create a trip by picking a name and a date range. The tool generates a day-by-day calendar for that range, and every member of the trip can fill in what happens on each day — flights, hotel stays, border crossings, drives, activities. The trip is visualized on an interactive map with the route drawn between stops, distances per leg, and an overall summary (days, nights, flights, total km).
Features
1. Users & Access (privacy-friendly, Mullvad-style)
- No registration form, no email, no password. Click "Create account" and you get a random account number (e.g.
7K2M-9QX4-TT8B-3WPF) — that token IS your login. It's shown once; only its hash is stored server-side. - Every account gets an auto-generated, editable display name (e.g.
brave-otter) so members and cost splits stay human-readable. - Trips are shared via a join code: every trip has a short code (e.g.
M4TH-8RK2); anyone with an account can enter it under "Join a trip" to become an editor. The owner can regenerate the code to cut off further joins.
2. Trips
- Create a trip with name + start date + end date.
- Editing the date range regenerates the calendar (existing day entries outside the new range are kept but flagged).
- Trip list dashboard showing all trips you're a member of.
3. Day-by-Day Calendar
- Calendar grid generated from the trip's date range (weeks as rows, like a real calendar).
- Click any day to open the day editor and add entries:
| Entry type | Typical fields |
|---|---|
| ✈️ Flight | flight no., from/to airports, departure/arrival time |
| 🛂 Immigration / border | location, notes (visa, documents) |
| 🚗 Travel / transfer | mode (car/train/bus/boat), from → to |
| 🏨 Hotel stay | hotel name, check-in/check-out, booking ref |
| 📍 Activity / sightseeing | place, time, notes |
| 📝 Note | free text |
- Every entry can have a location (searched via OpenStreetMap geocoding — type "Chiang Mai" and pick from suggestions; lat/lng stored automatically).
- Entries show as compact chips inside the day cell; multiple entries per day, ordered.
4. Map Visualization
- Interactive map (Leaflet + OpenStreetMap tiles — free, no API key required, unlike Google Maps).
- All located entries plotted as markers, numbered in chronological order.
- Route polyline connecting the stops in order.
- Distance per leg (km, great-circle) shown on the route and in a leg-by-leg list.
5. Costs & Splitting
- Every entry can carry a price (flights, hotels, car rental, train tickets, activities…), in the trip's currency (one currency per trip, no FX conversion).
- Each priced entry records who paid and how it's split:
equal— total split equally among selected participants (e.g. rental car 50/50)own— price is per person, everyone pays their own (e.g. flights)payer— personal expense of the payer
- Cost summary per trip: total cost, breakdown by type, per-user share vs. paid vs. net balance, and settle-up suggestions (who transfers how much to whom).
6. Summary
- Total days and nights.
- Number of flights, hotel stays, travel legs.
- Total distance in km across the whole trip.
- Countries/locations visited (from entry locations).
Tech Stack
| Layer | Choice | Why |
|---|---|---|
| Backend | Node.js 20 + Express | Small, ubiquitous, one runtime in the container |
| Database | SQLite (better-sqlite3) | Zero-config single file; persisted via Docker volume |
| Auth | cookie sessions + bcrypt | Simple and adequate for a self-hosted tool |
| Frontend | Vanilla JS SPA (no build step) | Keeps image small and the stack simple |
| Map | Leaflet + OpenStreetMap | Free, no API key, offline-friendly tiles options |
| Geocoding | Nominatim (proxied server-side) | Free location search, no key |
| Packaging | Dockerfile + docker-compose | docker compose up and you're running |
Architecture
trip_plan/
├── src/server/ # Express app
│ ├── index.js # bootstrap, static serving
│ ├── db.js # SQLite schema + connection
│ ├── auth.js # session middleware
│ ├── routes/ # auth, trips, entries, geocode
│ └── util/distance.js # haversine km calc
├── public/ # SPA served statically
│ ├── index.html
│ ├── css/styles.css
│ └── js/ # api client, views (trips, calendar, map, summary)
├── tests/ # API tests (node:test + supertest)
├── data/ # SQLite file (gitignored, volume-mounted)
├── docs/ # this file + API.md
├── Dockerfile
└── docker-compose.yml
Data Model
users (id, username, password_hash, created_at)
trips (id, name, start_date, end_date, owner_id, created_at)
trip_members (trip_id, user_id, role) -- owner | editor
entries (id, trip_id, date, type, title, details,
start_time, end_time, location_name, lat, lng, sort_order)
Route + km are derived: located entries ordered by (date, sort_order) form the route; each consecutive pair is a leg with haversine distance.
API (REST, JSON, under /api)
POST /api/auth/account(create token account) ·POST /api/auth/login·POST /api/auth/logout·GET/PATCH /api/auth/meGET/POST /api/trips·GET/PATCH/DELETE /api/trips/:idPOST /api/trips/join(by join code) ·POST /api/trips/:id/join-code(regenerate)GET/POST /api/trips/:id/entries·PATCH/DELETE /api/entries/:idGET /api/trips/:id/route(legs + km + summary, computed server-side)GET /api/geocode?q=...(Nominatim proxy)
Running It
# development
npm install
npm start # http://localhost:3000
# production
docker compose up -d # builds image, mounts ./data for the SQLite file
Non-Goals (for now)
- Real-time collaborative editing (last-write-wins is fine for this scale)
- Driving-route distances via a routing engine (great-circle km first; OSRM could be added later)
- Email invites / password reset (admin resets via CLI)
Build Plan
- Scaffold — repo, package.json, this doc, API contract. ✅
- Swarm build (parallel agents): backend API + DB · frontend SPA (calendar, map, summary) · Docker/infra + tests.
- Integrate & verify — run tests, boot server, smoke-test in browser, build Docker image.
- Initial commit.