Repository navigation
Generate an OpenAPI document for the node API - #68
Conversation
Purple10101
left a comment
There was a problem hiding this comment.
I checked this out by hand and with Claude.
Verified against my desk node (blah2-api v0.5.0): the route table in the running
container matches the spec 24/24, and all 19 documented GETs return 200.
That said, no operation documents a response body, so what we publish is an accurate
route map rather than a reference. The types are as follows if we want to detail them:
| Route | Content-Type | Body |
|---|---|---|
/api/timestamp |
text/html |
1789753049651 |
/api/tracker |
text/html |
{"timestamp":...,"n":7,"nTentative":7,...} |
/api/detection |
text/html |
{"timestamp":...,"delay":[],"doppler":[],...} |
/api/map, /api/timing, /api/iqdata |
text/html |
all JSON |
I am not sure we need to do this at all, but it is something I want to make sure is
deliberate?
One ask before merge
The workflow runs npm install with api/package-lock.json gitignored.
swagger-autogen is pinned, but acorn, deepmerge, glob and json5 resolve by
caret range on every run. Pairing a byte-exact comparison with a floating dependency
tree means a patch release upstream can turn every api/** PR red with no code change
here.
Could we commit the lockfile and switch to npm ci?
Two optional one-liners while you're in there
...restonapi/openapi.js:105is spread aftertags, so it would silently clobber
the curated tag list if swagger-autogen ever emitted a top-leveltagskey.operation.tags || [tagFor(route)]onapi/openapi.js:78skips the fallback on an
empty array.
Both are inert today.
The RETINA API reference at api.retina.fm shows blah2's node-local API next to retina-server's and tower-finder-service's, reading it from api/openapi.json on main. Express carries no schema, so the document is generated from the routes in server.js, where the code lives: api/openapi.js runs swagger-autogen (a pinned devDependency) over it and adds the info block, the node-local server address and a tag per route family. CI regenerates it on every PR that touches api/ and fails when the committed copy differs, so the reference stays current with the code. A byte-exact check needs the generator's whole dependency tree fixed, not only swagger-autogen, or an upstream patch release could fail every api/ PR with no change here. So api/package-lock.json is committed rather than ignored, and CI and the image both install from it with npm ci. swagger-autogen is needed only to generate the document, so the image installs with --omit=dev rather than shipping it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
158bf1a to
ecb7c62
Compare
|
Rebased onto main (the tracker route went in #69, so the spec is now 23 operations on 20 paths) and amended:
On response bodies: deliberate. This PR is the route map, and filling bodies in means |
/api/tracker went out in #69, but the api tag description still listed "tracks" among what the tag covers, so the reference advertised a route that no longer exists. openapi:check cannot catch this: the tag text is hand-written and lands identically on both sides of the comparison, so it stays green while the prose goes stale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I ended up taking this PR due to some merge conflicts that I had the best context to resolve.
Why
We're putting a browsable API reference at
https://api.retina.fm(Scalar, the same three-column layout as ClickUp's docs), with one OpenAPI document per service: retina-server, tower-finder-service and blah2's node API. retina-server and tower-finder are FastAPI and generate theirs for free; Express carries no schema, so blah2's has to be generated fromserver.js.This PR generates it here, where the routes live, rather than from outside the repo, so it stays yours to shape and can't drift from the code without CI saying so. api.retina.fm will read
api/openapi.jsonfrommainvia jsDelivr once this lands.What
api/openapi.jsruns swagger-autogen (MIT, pinned2.23.7as a devDependency) overserver.jsand adds the info block, the node-local server (http://{node}:3000, defaulting tolocalhost) and a tag per route family (api,stash,capture,health). A route under a new first path segment fails generation until it gets a tag.api/openapi.jsonis the committed output: 23 operations on 20 paths, deterministic byte for byte.npm run openapiregenerates;npm run openapi:checkexits 1 when the committed copy is stale..github/workflows/api.ymlruns the check on PRs touchingapi/**and on pushes tomain. No existing workflow ran anything forapi/.api/package-lock.jsonis committed (it was gitignored) and both CI and the image install withnpm ci. A byte-exact check needs the generator's whole dependency tree fixed, not just swagger-autogen. For the image this also pins express (4.22.3) and js-yaml (4.3.2), which until now resolved afresh on every build.api/Dockerfileinstalls withnpm ci --omit=dev, so swagger-autogen (and 13 transitive packages, about 2.3 MB) stays out of the image.server.jsis untouched. Summaries, descriptions and request/response schemas can be added per route later with#swaggercomments, which the generator picks up; that's your call.Worth knowing
POST /capture/retune/ack,/reject,/overload-statusand/rf-statusare the blah2 core talking to this API. LikePOST /capture/retunethey are unauthenticated on all interfaces, so anyone on the node's LAN can already post to them; the code is public, so documenting them discloses nothing new, but it does make them easier to find. The Cloudflare tunnel route in node-infra also covers/capture, behind Access.#swaggercomments inserver.js. The JSON routes go out astext/htmlbecauseres.sendis handed a string, so a schema written against today's responses would have to saytext/html."any"placeholders on the retune JSON fields, and the emptydefaultresponse onGET /capture/retune. All fixable with#swaggercomments.build: ./apidoesn't match the Dockerfile'sCOPY api/...paths (which need the repo root as context, as CI uses), and.dockerignoredoesn't excludeapi/node_modules, so a local build copies a developer'snode_modulesinto the image.Verified
npm ci && npm run openapi:checkpasses on a cleangit archiveinnode:20-slim(npm 10.8.2), and on Node 26 locally./apiroute; a route under an untagged segment) and passes on a comment-only change toserver.js.openapi.jskeeps the curated tag list if swagger-autogen emits a top-leveltags, and falls back ontags: []; checked against a stubbed generator.api/test_*.jspass.openapi-spec-validator; actionlint is clean on the workflow.ClickUp: API reference at api.retina.fm
🤖 Generated with Claude Code