openvibers/openvibe.directory AGENTS.md

AGENTS.md

main 7.7 KB · 146 lines · markdown

1# AGENTS.md — OpenVibe.Directory
2
3## Project overview
4
5Source-linked directories for the OpenVibe network: OpenVibe sites, tools, AI models and providers,
6open-source projects, local businesses from OpenStreetMap, and opt-in people listings. Every entry
7carries a source and a last-checked date. Node.js/Express, server-rendered pages, one small
8PostgreSQL table for people listings.
9
10- Origin `https://openvibe.directory`, port `5281`, unit `openvibe-directory.service`,
11 release root `/opt/openvibe.directory`.
12- Public sources refresh at startup and every six hours; a failed source keeps its last successful
13 in-memory snapshot and the failure is visible in `/api/v1/kinds`.
14
15## What it owns
16
17- `GET /`, `/kinds/:kind`, `/search`, `/entries/:kind/:slug` — HTML, with `Accept: text/markdown` on
18 entry pages.
19- `GET /api/v1/kinds`, `/api/v1/entries`, `/api/v1/entries/:kind/:slug` — the public read API.
20- `/api/v1/people/me` and `/people/me` — a signed-in person's own listing (get, put, delete).
21- `robots.txt`, `sitemap.xml`, `llms.txt`, JSON-LD, `/release.json`, `/shared/*`, `/api/health`,
22 `/api/ready`.
23- The `directory_people` table and its validation.
24
25It does not own the sources: it reads Network's frame, Tools, Exchange models, GitHub's public search
26and OpenStreetMap. It does not publish anything outside this site.
27
28## Where the data comes from
29
30[server/sources.js](server/sources.js) `URLS`/`kinds`:
31
32- `https://openvibe.network/api/frame?host=openvibe.network` — OpenVibe sites.
33- `https://openvibe.tools/api/v1/tools` — tools.
34- `https://openvibe.exchange/api/v1/models` — AI models and providers.
35- `https://api.github.com/search/repositories?q=org%3AOpenVibers+archived%3Afalse` — projects.
36- `https://api.openstreetmap.org/api/0.6/map?bbox=…` — a **bounded San Francisco pilot** under
37 ODbL 1.0.
38- People come from PostgreSQL, not a feed.
39
40Every entry is normalised (`entry()`, `slug()`, `safeUrl()`): a missing id/name/source or a non-https
41source is dropped rather than guessed.
42
43## Repository map
44
45- **Entry:** [server/index.js](server/index.js) — builds the source store, opens the people store when
46 `DATABASE_URL` is set, listens on `PORT || 5281`.
47- **App/routes:** [server/app.js](server/app.js) — security headers, static `/public`, `/shared`,
48 `/auth`, health/ready, release, the v1 API, pages, people form, 404.
49- **Sources:** [server/sources.js](server/sources.js) — fetch/refresh, normalisation, search.
50- **People:** [server/people.js](server/people.js) — `createPeopleStore` (migration + CRUD), `validate`,
51 `publicEntry`, `writeGuard`, `createAuth`.
52- **Views:** [server/views.js](server/views.js) — HTML/markdown, escaping, JSON-LD.
53- **Browser:** [public/people.js](public/people.js).
54- **Schema:** [migrations/0001_people.sql](migrations/0001_people.sql).
55- **Deploy:** [deploy/systemd/openvibe-directory.service](deploy/systemd/openvibe-directory.service),
56 [deploy/nginx/openvibe.directory.conf](deploy/nginx/openvibe.directory.conf).
57
58## Commands
59
60```bash
61npm ci
62node server/index.js # http://127.0.0.1:5281; start script is `npm start`
63npm test # node --test test/*.test.js
64ov test # the harness's sharded run of what the change reaches
65node --check <file.js>
66```
67
68There is no `dev` script and no build step. Without `DATABASE_URL` the directories still serve from
69the feeds; the people routes answer `503 people.unavailable`.
70
71## Database and migrations
72
73One table, `directory_people`, created from [migrations/0001_people.sql](migrations/0001_people.sql)
74**at startup** by [server/people.js](server/people.js), not by a boot migration runner: the file is
75read and applied through the **owner** connection (`DATABASE_DIRECT_URL`), while reads and writes use
76the runtime connection (`DATABASE_URL`). The statement is `CREATE TABLE IF NOT EXISTS`, so startup is
77idempotent. Keep that split: the test asserts exactly one owner call (the DDL) and then a runtime
78`SELECT`. Listing text is capped at 500 characters, names at 100, websites must be `https:` with no
79credentials, and `visible` defaults to **false**.
80
81## Auth and the people listing
82
83- Sign-in is OpenVibe.Network SSO via `openvibe-sdk/sso` ([server/people.js](server/people.js)
84 `createAuth`); without `OV_OAUTH_CLIENT_ID`/`SECRET` the auth object is absent and the people routes
85 are disabled.
86- Only the person's own row: the subject comes from the session (`usr_…`); never take an id from the
87 query or body.
88- `writeGuard` requires the same-origin `Origin` (or no Origin plus a Bearer token) and
89 `application/json` (DELETE excepted); invalid requests answer 403/415.
90- An entry is private until the person explicitly publishes it (`visible: true`).
91
92## Frontend loading model
93
94Server-rendered HTML from [server/views.js](server/views.js) with `openvibe-shared/kit`,
95`/thread`, `/shell` and `/serve`; **no SPA, no bundler**. Static files come from `public/`;
96`/shared/*` is served from the pinned `openvibe-shared` node_modules by `openvibe-shared/serve`.
97The only page script is [public/people.js](public/people.js). The home page redirects a signed-out
98browser to silent SSO unless `?sso=none` or the guest hint cookie is set — keep that for crawlers
99(`bot|crawl|spider|slurp` must not be redirected).
100
101## Deploy
102
103Unit [deploy/systemd/openvibe-directory.service](deploy/systemd/openvibe-directory.service) runs
104`/opt/openvibe.directory/server/index.js` on `127.0.0.1:5281`, env `/etc/openvibe/directory.env`;
105vhost [deploy/nginx/openvibe.directory.conf](deploy/nginx/openvibe.directory.conf). The repository does
106not document an `ovhost deploy` command — change the unit/vhost with the host's own installer rather
107than inventing one. No secret belongs in a file or a log.
108
109## Tests
110
111`npm test` runs Node's built-in runner over [test/directory.test.js](test/directory.test.js) and
112[test/people.test.js](test/people.test.js). They pin: OSM phone-tag omission and no age/city/phone
113fields; unlisted by default; the owner-connection DDL / runtime-read split; entry validation; the
114public pages, markdown negotiation, sitemap and llms output. `ov test` runs the same suite.
115
116## Conventions
117
118- CommonJS; **compact single-line JS** is the style in `server/app.js`, `server/sources.js` and
119 `server/people.js`; the newer view helpers use 4 spaces. Match the file you edit.
120- Escape every interpolated value (`views.esc`); embed JSON with the `<`-escaping `json()`.
121- Errors are small JSON bodies (`{ error: 'entry.not_found' }`) or problem-style statuses; do not leak
122 upstream text.
123- Source URLs are `https:` only; never invent a source, a checked date or a phone number.
124
125## Pitfalls
126
127- **Unverified OSM phone tags are omitted.** A business number needs a business-owned source before
128 publication; do not re-add phone fields.
129- **No private-person searches or reverse lookups.** People listings are claim/edit/unpublish/delete
130 of one's own row only.
131- **The SF business pilot is bounded and ODbL 1.0.** Do not widen the bounding box or change the
132 licence without the owner.
133- **A failed source keeps its last snapshot** and reports the failure; it never empties the directory
134 or silently serves stale data as fresh.
135- **The startup migration uses the owner connection**; using `DATABASE_URL` for DDL will fail in
136 production and breaks the test.
137- **`visible` defaults false.** Publishing is an explicit act by the person.
138- **Crawler behavior:** home may redirect signed-out browsers to silent SSO; bots must not be
139 redirected and must still see the page.
140
141## Documentation
142
143- [README.md](README.md) — the product brief, sources, licence and the database split.
144- [migrations/0001_people.sql](migrations/0001_people.sql) — the only table.
145- OpenVibe.Contracts, if a directory manifest is added, is the place for the public schema.
146