Public: anyone can read this; the crew can change it.

GET /api/v1/users/:username/repos carries an ETag and answers a repeat with 304 when none of that person's repositories it lists has changed #9154

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`GET /api/v1/users/:username/repos` now carries a weak ETag and answers a matching `If-None-Match` with an empty 304.

shipped production is serving it deploy b070674

opened this

GET /api/v1/users/:username/repos now carries a weak ETag and answers a matching If-None-Match with an empty 304.

What changed

  • Owner filter: ListReposOptions in src/core/repos.ts gains an owner field, which visibleClause applies. visibleReposMarker therefore takes the owner filter directly, and there is no second marker helper.
  • listReposByOwner: it now calls listReposVisibleTo with that owner, which removes a hand-copied duplicate of the same WHERE-clause builder. The rows it returns are unchanged.
  • Route in src/http/api.ts: the tag is built from the owner id, the viewer (or anon), the limit, the cursor and the marker. Responses carry cache-control: private, no-cache and vary: authorization, cookie. The marker is computed in a tag phase, and a match returns 304 before listReposByOwner runs, so a 304 has no repos phase. An unknown or hidden user is still a 404 with no tag, because that check runs first.
  • OpenAPI: the route summary now names the ETag, If-None-Match and the 304.
  • Tests: test/user-repos-etag.test.ts covers each case the issue lists. I did not run it against main to confirm each case fails there, but the tag, 304 and tag-phase assertions can only pass with this change.
  • Existing test changed: test/api-personrepos-timing.test.ts expects the phase order session, tag, repos, render now. The old order was correct only before this change.

Checks: tsc --noEmit is clean. The new ETag test, the existing repos-ETag test, the timing tests, contract.test.ts, openapi-coverage.test.ts and admin-repos.test.ts pass. I did not run the full suite: scripts/fast.ts could not name a bounded set (it reported 1775 of 2066 test files), so the forge's check on the push is the full run. The output of scripts/gate.ts was cut off, so I can't say it passed. The full suite and gate run once in the forge's check on this push.

Not done: the marker covers only the listed page, as /api/v1/repos does, so a change to a repository on a later page doesn't alter this page's tag.

Try it: curl -i $URL/api/v1/users/alice/repos for the etag, then repeat with -H 'if-none-match: <tag>' to get the 304.

🤖 Generated with Claude Code

Closes #9147

commented

GET /api/v1/users/:username/repos now carries a weak ETag and answers a matching If-None-Match with an empty 304.

What changed

  • Owner filter: ListReposOptions in src/core/repos.ts gains an owner field, which visibleClause applies. visibleReposMarker therefore takes the owner filter directly, and there is no second marker helper.
  • listReposByOwner: it now calls listReposVisibleTo with that owner, which removes a hand-copied duplicate of the same WHERE-clause builder. The rows it returns are unchanged.
  • Route in src/http/api.ts: the tag is built from the owner id, the viewer (or anon), the limit, the cursor and the marker. Responses carry cache-control: private, no-cache and vary: authorization, cookie. The marker is computed in a tag phase, and a match returns 304 before listReposByOwner runs, so a 304 has no repos phase. An unknown or hidden user is still a 404 with no tag, because that check runs first.
  • OpenAPI: the route summary now names the ETag, If-None-Match and the 304.
  • Tests: test/user-repos-etag.test.ts covers each case the issue lists. I did not run it against main to confirm each case fails there, but the tag, 304 and tag-phase assertions can only pass with this change.
  • Existing test changed: test/api-personrepos-timing.test.ts expects the phase order session, tag, repos, render now. The old order was correct only before this change.

Checks: tsc --noEmit is clean. The new ETag test, the existing repos-ETag test, the timing tests, contract.test.ts, openapi-coverage.test.ts and admin-repos.test.ts pass. I did not run the full suite: scripts/fast.ts could not name a bounded set (it reported 1775 of 2066 test files), so the forge's check on the push is the full run. The output of scripts/gate.ts was cut off, so I can't say it passed. The full suite and gate run once in the forge's check on this push.

Not done: the marker covers only the listed page, as /api/v1/repos does, so a change to a repository on a later page doesn't alter this page's tag.

Try it: curl -i $URL/api/v1/users/alice/repos for the etag, then repeat with -H 'if-none-match: <tag>' to get the 304.

🤖 Generated with Claude Code

commented

GET /api/v1/users/:username/repos now carries an ETag. A repeat request that sends it back gets a 304 until one of that owner's repositories changes (#9147).

The check failed only because no commit in the pull had a Try: line, which the gate requires when src/http/api.ts changes. I added an empty commit with that line and pushed it. I didn't run the gate, tsc or the tests, because no code changed. I haven't seen the forge's check result for the new push.

Try: curl -si -H "authorization: Bearer $FORGE_TOKEN" https://lab--9154--forge-trevato-apps.open-platform.sh/api/v1/users/trevato/repos. Then repeat it with If-None-Match set to the returned ETag and expect a 304.

🤖 Generated with Claude Code

Approved

Clean and small: owner filter folded into visibleClause, listReposByOwner now shares it (duplicate SQL removed), marker reused, 304 returned before the repos phase, tag keyed on owner id, hashed viewer, page and marker, 404 precedes the tag. Tests cover each case. Page-only marker is the same trade-off as /api/v1/repos.

commented

Left #9147 open: 1 unticked item remain (Tests in test/user-repos-etag.test.ts: a repeat is a 304; a new repository under that owner makes the next answer a 200 with a new tag; a push changes the tag; that owner's repository turning private changes an anonymous viewer's tag; a repository created under a different owner does not change it; the owner and a stranger never share a tag. Each fails on main.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs