openvibers/openvibe.tours AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Tours |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Tours (`https://openvibe.tours`) is the network's travel room: cities and places from open |
| 6 | data, IRL live tours and stays from the network's public APIs, self-guided routes with cited stops, |
| 7 | and a signed-in person's trips. Everything is server-rendered and works without JavaScript. |
| 8 | |
| 9 | - **Entry:** `server/index.js`; app in `server/http/router.js`; port `5170`, service id `tours`. |
| 10 | - **Sources:** `server/sources/` — `osm.js` (Overpass), `wikidata.js`, `wikivoyage.js`/ |
| 11 | `wikivoyage-client.js`, `live.js` (Live's public list), `rent.js` (Rent's public listings), |
| 12 | `openverse.js`, `http.js`. |
| 13 | - **Places:** `server/places/` — `discover.js`, `is-place.js`, `narrative.js` (AI summary + attribution), |
| 14 | `photo-cache.js` (licensed images to Media), `clip.js`. |
| 15 | - **Jobs:** `server/jobs/` — `cities.js`, `places.js`, `routes.js`, `live.js`, `stays.js`, `index.js`. |
| 16 | - **API/pages:** `server/http/api.js` (`/api/v1`), `principal.js`, `pages.js`, `discovery.js`; |
| 17 | views in `server/views/index.js`/`labels.js`, rendering in `server/render/layout.js`/`html.js`. |
| 18 | `public/js/tours.js` is the only own browser script (MapLibre only on the route page); own CSS in |
| 19 | `public/css`. |
| 20 | - **Seeds:** `seed/`, `scripts/seed.js`; status and counts in `STATUS.json`. |
| 21 | |
| 22 | ## Owns, does not own |
| 23 | |
| 24 | Owns its cached open data: `tours_cities` (`cty_`), `tours_city_sources`, `tours_places` (`plc_`), |
| 25 | `tours_routes` (`rte_`), `tours_route_stops`, the trips/shares tables and its outbox. Every row keeps |
| 26 | the source URL and the time fetched. |
| 27 | |
| 28 | Does not own: Live, Rent, Media and AI — Tours reads Live's and Rent's public pages/lists; licensed |
| 29 | place images are copied to Media with the `media.object.upload` service scope; sourced summaries use |
| 30 | AI (`ai.run.create`, `ai.run.read`) when `OV_AI_INTERNAL_URL` is configured. Both keep the original |
| 31 | source and attribution on the place page. Identity/roles are Network's (OAuth client `tours`, PKCE |
| 32 | S256, canonical `usr_…`). OpenStreetMap, Wikivoyage, Wikidata and Commons are external open data, |
| 33 | cached with source and fetched time — never scraped past their terms. Tours never takes a payment and |
| 34 | carries **no affiliate link**. |
| 35 | |
| 36 | ## Who calls it, what it calls |
| 37 | |
| 38 | People use the pages/API with the `tours_at` session cookie or a Network Bearer token. Trips and saved |
| 39 | places are the person's own; a share link is read-only. Tours calls Network (SSO, JWKS, identity), |
| 40 | Live's public stream/VOD list, Rent's public listings, Media (image upload), AI (summaries when |
| 41 | configured), and the open-data endpoints (Wikidata, Wikivoyage, Overpass, Commons, Openverse) through |
| 42 | `server/sources/`. It calls no service with a person's write; the public reads are cacheable, a person's |
| 43 | page is `private, no-store`. |
| 44 | |
| 45 | ## Database and migrations |
| 46 | |
| 47 | PostgreSQL through `openvibe-sdk/db`; migrations `migrations/0001..0011` (initial, place sources, |
| 48 | search index, English names, shares/defaults, outbox, discoverable/notable places, photo retries) are |
| 49 | applied at boot with the owner role. Tests use PGlite; `npm run test:pg` runs them on PostgreSQL. |
| 50 | Never edit a migration that has run. A job records what it fetched; a failed source leaves the cached |
| 51 | row and its fetched time rather than inventing content. |
| 52 | |
| 53 | ## Auth |
| 54 | |
| 55 | Network SSO (RS256 JWT + `tours_at` cookie) and offline JWKS verification. A write made with the |
| 56 | session cookie must come from `https://openvibe.tours`, else `403 tours.cross_site`. Every person |
| 57 | acts only on their own trips and saved places; a share link is an unguessable read-only token. |
| 58 | `/internal/events` is HMAC-verified and loopback-only. |
| 59 | |
| 60 | ## Deploy |
| 61 | |
| 62 | `deploy/systemd/openvibe-tours.service` (port `5170`, env `/etc/openvibe/tours.env`, checkout |
| 63 | `/opt/openvibe.tours`) and `deploy/nginx/openvibe.tours.conf` (the forms zone covers `/trips`); |
| 64 | `sudo ovhost deploy tours` is the production path and `sudo ovhost rollback tours --to <sha>` the |
| 65 | rollback. |
| 66 | |
| 67 | ## Commands and tests |
| 68 | |
| 69 | ```bash |
| 70 | npm ci |
| 71 | npm run dev # http://127.0.0.1:5170 |
| 72 | npm test # every test/*.test.js, PGlite + in-process mocks |
| 73 | npm test -- places routes # only matching files |
| 74 | npm run test:pg # the same on PostgreSQL |
| 75 | npm run seed # seed cities/places from the open sources |
| 76 | ``` |
| 77 | |
| 78 | `test/run.js` uses `openvibe-shared/test-runner` (90 s, serial); temp PGlite plus in-process Network, |
| 79 | Live, Chat, Today and OSM/Ticketmaster fixtures, never the network. Tests cover the sources and |
| 80 | parsers, place discovery, routes and stops, trips/shares, the pages, accent contrast, account data, |
| 81 | agent twins and discovery artifacts. |
| 82 | |
| 83 | ## Conventions and pitfalls |
| 84 | |
| 85 | - CommonJS, 4-space indent, single quotes, semicolons; problem+json with a stable code. No linter. |
| 86 | - **Every fact carries its source and fetched time** (city, place, route, stop, photo licence). A |
| 87 | missing or failed source is shown honestly; never invent a place, a date, an opening time or a route. |
| 88 | - **No affiliate and no payment.** Flights are a link to the official search; v1 had no flight API key. |
| 89 | The Duffel/Kiwi seam stays off, and a key alone does not turn it on. |
| 90 | - **TTS is off** until AI has a speech route: every stop says so instead of shipping a dead player. |
| 91 | - **Attribution is part of the data:** copying a photo to Media or summarising with AI keeps the |
| 92 | original source and licence on the place page. |
| 93 | - **Routes are generated and marked generated**, every stop cited; the no-JavaScript version is a |
| 94 | complete SVG + ordered list, not an empty map. |
| 95 | - **Respect the open-data terms:** no bulk scraping past Overpass/Wikivoyage/Wikidata/Commons limits; |
| 96 | use the cached row while a source is unavailable. |
| 97 | - New env names go into `.env.example` and the README; secret values live only in |
| 98 | `/etc/openvibe/tours.env`. |
| 99 |