Files
trip-plan/docs/PROJECT_OVERVIEW.md
T
grabowski d393e16ae1 Rework entry types: merge hotel into stay, transport with modes, auto-transport
- Type set is now activity/stay/transport/flight/rental/note; hotel, travel
  and immigration are removed with idempotent startup data migrations
  (hotel->stay, travel->transport, immigration->activity with flag prefix)
- Transport entries carry an optional mode (train/bus/ferry/taxi/drive/other)
  that drives the chip/map icon; route stops expose transport_mode
- Creating a stay auto-creates a bridging transport to its neighbouring
  stays unless a transport/flight already covers the gap (one-shot)
- Summary: transports count replaces hotels/travelLegs
2026-07-19 17:47:21 +07:00

136 lines
6.8 KiB
Markdown

# 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, stays, transports, 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 |
|---|---|
| 📍 Activity / sightseeing | place, time, notes |
| 🏙️ Stay (area block / accommodation) | location, from → until dates, hotel name in title, price |
| 🚆 Transport | mode (train/bus/ferry/taxi/drive), from → to; auto-created between consecutive stays |
| ✈️ Flight | flight no., from/to airports, departure/arrival time, multi-leg segments |
| 🚙 Rental car | pickup/dropoff, included km |
| 📝 Note | free text |
(Legacy `hotel`/`travel`/`immigration` entries are migrated automatically at startup: hotel → stay, travel → transport, immigration → activity with a 🛂 title prefix.)
- 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, stays, 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, stays, transports.
- **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/me`
- `GET/POST /api/trips` · `GET/PATCH/DELETE /api/trips/:id`
- `POST /api/trips/join` (by join code) · `POST /api/trips/:id/join-code` (regenerate)
- `GET/POST /api/trips/:id/entries` · `PATCH/DELETE /api/entries/:id`
- `GET /api/trips/:id/route` (legs + km + summary, computed server-side)
- `GET /api/geocode?q=...` (Nominatim proxy)
## Running It
```bash
# 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
1. **Scaffold** — repo, package.json, this doc, API contract. ✅
2. **Swarm build** (parallel agents): backend API + DB · frontend SPA (calendar, map, summary) · Docker/infra + tests.
3. **Integrate & verify** — run tests, boot server, smoke-test in browser, build Docker image.
4. Initial commit.