openvibers/openvibe.shoes AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Shoes |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Shoes is the network's shoe resource at `https://open.shoes`: one page per model with every |
| 6 | store's all-in offer, Shoes' own daily price history and size-price curve, cited specs/fit/care notes, |
| 7 | and an official drops calendar. A person can watch a price or paste a listing link, which Shoes hands |
| 8 | to OpenVibe.Watch. |
| 9 | |
| 10 | - **Entry:** `server/index.js` (Express); app wiring in `server/http/router.js`; port `5180`, |
| 11 | service id `shoes`, env prefix `SHOES`. |
| 12 | - **Pages:** `server/http/pages.js` (server-rendered HTML via `server/render/layout.js`/`html.js`, |
| 13 | `server/views/index.js`); own CSS in `public/css/`. No SPA and no build step. |
| 14 | - **API:** `server/http/api.js` (`/api/v1`), OpenAPI in `server/http/openapi.js`, discovery in |
| 15 | `server/http/discovery.js` (robots, sitemap, llms.txt), SSO in `server/auth/`. |
| 16 | - **Domain:** `server/domain/parse.js` (pure source parsers), `server/domain/drops.js` (release |
| 17 | calendar); jobs in `server/jobs/` (`offers.js`, `drops.js`, `ebay.js`). |
| 18 | - **Identity:** `server/identity/` and `server/auth/` for Network sessions; the person's own token is |
| 19 | forwarded to Watch and Today. |
| 20 | |
| 21 | ## Owns, does not own |
| 22 | |
| 23 | Owns the shoe catalogue, offers and daily snapshots, drops, watches stored on Shoes, cited facts and |
| 24 | job-run records (`shoe_models`, `shoe_offers`, `shoe_snapshots`, `shoe_drops`, `shoe_watches`, |
| 25 | `shoe_facts`, `shoe_job_runs`, `account_data_events`, `telemetry_samples`). It publishes a search |
| 26 | index document and its share of account export/deletion. |
| 27 | |
| 28 | Does not own: store catalogues or prices (Deals and the official APIs are the sources), a person's |
| 29 | general watch list (OpenVibe.Watch — Shoes forwards the link and keeps only its own `shw_` row), the |
| 30 | calendar (OpenVibe.Today), or any checkout. **No raffle, draw, reservation or checkout is automated, |
| 31 | ever; no affiliate links, no sponsored ordering and no Marketplace scraping.** |
| 32 | |
| 33 | ## Who calls it, what it calls |
| 34 | |
| 35 | Public pages and `GET /api/v1/*` need no token; a signed-in person's writes (watch, remind) use their |
| 36 | Network session. Shoes calls OpenVibe.Deals' public offer/product APIs, the eBay Browse API only |
| 37 | when a developer key pair is configured, the official release calendar/rSS feeds, and the person's |
| 38 | own Network token to call OpenVibe.Watch (`shw_` ↔ remote watch) and OpenVibe.Today (drop |
| 39 | reminders). A missing source key turns that source off with a plain note, never an error. |
| 40 | |
| 41 | ## Database and migrations |
| 42 | |
| 43 | PostgreSQL through `openvibe-sdk/db`; migrations `migrations/NNNN_*.sql` (0001 initial, 0002 search |
| 44 | index, 0003 shares, 0004 outbox, 0005 product catalog), applied at boot with the owner role. Tests use |
| 45 | PGlite; `npm run test:pg` runs the same suite through PgBouncer. Never edit a migration that has run; |
| 46 | a schema removal waits for a contract migration. Each job writes one `shoe_job_runs` row (counts and |
| 47 | errors) and refuses to start under `SHOES_DRILL=1`; tests call `runOnce()`. |
| 48 | |
| 49 | ## Frontend loading model |
| 50 | |
| 51 | Every page is server-rendered HTML with the shared Frame (`openvibe-shared/shell`, navbar/footer, |
| 52 | `/shared/*`); page-specific CSS is in `public/css`. There is no bundler and no route-feature loader. |
| 53 | Interactive bits (watch form, reminder, the small price chart) are inline server output plus small |
| 54 | scripts; keep pages usable without JavaScript and phone-first. |
| 55 | |
| 56 | ## Auth |
| 57 | |
| 58 | Network SSO (RS256 JWT + session cookie) using the `shoes` OAuth client; public reads are anonymous. |
| 59 | A person's Watch/Today call uses their own Network token — never a service token on their behalf. A |
| 60 | session without a usable token can still save the watch on Shoes, marked "not connected". Forms are |
| 61 | origin-checked; `/internal/events` is HMAC-verified and loopback-only. |
| 62 | |
| 63 | ## Deploy |
| 64 | |
| 65 | Install `deploy/systemd/openvibe-shoes.service` and `deploy/nginx/open.shoes.conf`; the unit reads |
| 66 | `/etc/openvibe/shoes.env`, works in `/opt/openvibe.shoes`, runs as `ubuntu:ubuntu` on |
| 67 | `127.0.0.1:5180`. The vhost proxies `/api/`, `/auth/` and the forms with their own limits. |
| 68 | `sudo ovhost deploy shoes` is the production path (git-checkout, install on lockfile change, |
| 69 | restart, wait for `/api/ready`); rollback with `sudo ovhost rollback shoes --to <sha>`. |
| 70 | |
| 71 | ## Commands and tests |
| 72 | |
| 73 | ```bash |
| 74 | npm ci --include=dev |
| 75 | npm start # http://127.0.0.1:5180 |
| 76 | npm test # every test/*.test.js, PGlite, mock Network |
| 77 | npm test -- importer # only matching files |
| 78 | npm run test:pg # the same through PgBouncer |
| 79 | npm run seed # one run of every job against live sources (needs the network) |
| 80 | ``` |
| 81 | |
| 82 | `test/run.js` uses `openvibe-shared/test-runner` (90 s, serial); files get a temp PGlite database and |
| 83 | an in-process Network mock, never the network. Tests cover the parsers and all-in price, snapshots |
| 84 | (first check per offer per day wins), the jobs' stop/error behavior, the API and pages, Watch/Today |
| 85 | forwarding, account export/deletion and the shared discovery artifacts. `VERBOSE=1` shows each file's |
| 86 | output. |
| 87 | |
| 88 | ## Conventions and pitfalls |
| 89 | |
| 90 | - CommonJS, 4-space indent, single quotes, semicolons; no linter. Paths and domain logic stay in |
| 91 | `server/domain/` so a parser is testable without HTTP. |
| 92 | - **Prices are all-in** (price + stated shipping); keep currency and the source's own check time, and |
| 93 | mark a stale offer rather than hiding it. |
| 94 | - **The first snapshot of an offer per day wins** (unique key); do not overwrite history. |
| 95 | - **Cite or say "not known yet":** never invent a spec, fit note or release date; a parser that |
| 96 | cannot read a date leaves it out. |
| 97 | - **Respect the source rules:** Deals public pages, eBay's official API only with keys, official |
| 98 | release pages; no scraping of Marketplace or Facebook, no affiliate URLs. |
| 99 | - New environment variables go in `.env.example` and the README list; secrets live only in |
| 100 | `/etc/openvibe/shoes.env`. |
| 101 |