Skip to content

feat(gateway): forward /api/time to the time service - #151

Merged
Bakhomious merged 1 commit into
mainfrom
step/time-gateway
Oct 29, 2026
Merged

Bakhomious merged 1 commit into
mainfrom
step/time-gateway

Conversation

@Bakhomious

Copy link
Copy Markdown
Contributor

Brief

Goal. The gateway forwards /api/time/* to the time service behind both of its locks, the Tailscale identity and the session, and hides the session cookie from time. The Time page (time-page) can then call GET /api/time/report. The sixth of time-tracking's twelve steps (decision 181); the homelab already names the peer (codedogapp/homelab#42).

Scope.

  • services/gateway: time becomes a required peer, and /api/time is registered with the other services' routes.
  • The gateway's tests: time in every hand-built BAKHYARD_PEERS, and in the forwarding, lock and hidden-cookie tables.
  • process-compose.yaml: the gateway names time on 8088 and waits for it to be healthy.
  • apps/web/playwright.config.ts: the E2E gateway names time.
  • Docs: plan §0 row, §4.3, §4.9 and §8.2 (homelab-time and time-gateway marked done); setup.md.

Out of scope.

  • The Time page (time-page) and the time service itself.
  • home's peers in process-compose (home-week or time-attention).

Acceptance criteria.

  1. The gateway refuses to start without a time peer, and names it among the missing peers in name order.
  2. A signed-in /api/time/report reaches time's /time/report unchanged.
  3. Without a session, the request gets 401, and time never sees the session cookie.
  4. mise run up starts the gateway once time is healthy.
  5. The Playwright gateway starts with the new peer, and the E2E suite stays green.
  6. mise run check is green.

What changed

  • services/gateway/main.go: upstreams gains time, and peers() reads it between pulls and todos, so its error falls in name order. The local is timeURL, so that it doesn't shadow the time package's name.
  • services/gateway/router.go: proxy.Register(api, "time", …) in the API group, behind both locks and session.Hide. The E2E build's openRoutes shares wire, so it forwards time too.
  • Tests:
    • peers_test.go: time in the forwarding table, a withPeers("time") row, and a peerNames() helper. The full list is now written once rather than four times; with time, it no longer fit in 100 columns.
    • lock_test.go: the 401 row, and the row that checks time never gets the cookie.
    • open_test.go: the open gateway's forwarding row.
    • headers_test.go, main_test.go: time in their peers.
  • process-compose.yaml: the gateway's BAKHYARD_PEERS names time=tcp://127.0.0.1:8088, and depends_on waits for it.
  • apps/web/playwright.config.ts: the E2E gateway names time (nowhere, like the others), and the comment's port range reads 8080 to 8088.
  • docs/plan.md: the §0 row; §8.2 marks homelab-time (codedogapp/homelab#42) and time-gateway done. §4.3's peers and §4.9's depends_on now list every peer: notify and nudge had been missing since they landed.
  • docs/setup.md: the gateway waits for time too.

QA

  • Mutations: 13 of 13 caught (files/qa-time-gateway/mutate.py):

    • Go, by the gateway's tests: no route; time behind the Tailscale lock only; time with the agents (identity only); time without session.Hide; registered under another name; forwarded to todos; peer optional; its error out of order; read as todos; kept as pulls;
    • Playwright, by an E2E spec: the E2E gateway without the peer exits 1 (BAKHYARD_PEERS has no peer "time"), and Playwright's web server fails;
    • process-compose, by the live run below: without the peer, the gateway never gets healthy; without the depends_on, it doesn't wait for time. process-compose up --dry-run checks only the file's shape, so nothing in mise run check pins these two; plan §0 already has the idea of a check that would.
  • Live (live.py, 21 checks, all passed), with the real gateway and time from mise run build, a QA session in the dev database (deleted after), and Jira off:

    • AC1: bin/gateway with every peer but time exits 1 and names time alone.
    • AC4: process-compose logs "gateway is waiting for time to be healthy" once, then starts the gateway once.
    • AC2: GET /api/time/report gets time's own 404 "Jira is not set up", with the gateway's security headers. HEAD gets 404 with no body, and POST gets time's own 405 and Allow. Another path under /api/time gets time's 404, /api/times doesn't reach time, and a cross-site POST gets 403.
    • AC3: no session gets 401, a wrong one 401, and no Tailscale login 403.
    • The stack stops cleanly.

    The local dev stack doesn't start on this Mac today, for reasons outside this step: notes' vault folder (BAKHYARD_VAULT) is gone, and notify and nudge have no databases or VAPID key here. The live run therefore merged a second process-compose file that replaces only those three commands with stand-ins answering /healthz. The gateway's and time's blocks ran as committed.

Review

No blocking findings. Non-blocking:

  • timeURL breaks its neighbours' naming pattern, for the reason above.
  • The -tags e2e build fails TestOnlyTheEndToEndBuildIsOpen by design: the test pins the default build as locked, and mise run test runs only the default build.
  • Rollout: the laptop's gateway already names time (codedogapp/homelab#42), so the new image starts there.

Gates

mise run check passed locally:

  • Go fmt; golangci-lint (0 issues); buf; jactionlint; process-compose dry run; gen:check; govulncheck (none);
  • Go tests with -race, Go coverage 95.9% total (the gateway package 91.8%);
  • Biome, svelte-check, tsc, knip, pnpm audit;
  • Vitest, 1,082 tests;
  • Playwright E2E, 319 passed and 7 skipped.

SonarQube Cloud runs on demand (decision 132).

Merged under the user's standing OK (2026-10-26): merge once CI's check is green, and review everything at the end.

The Time page needs GET /api/time/report. The gateway now requires a
time peer and forwards /api/time behind both locks, hiding the session
cookie as for every service. mise run up starts the gateway once time
is healthy, and the end-to-end gateway names the peer too.
@Bakhomious
Bakhomious merged commit 7bb4bf7 into main Oct 29, 2026
2 checks passed
@Bakhomious
Bakhomious deleted the step/time-gateway branch October 29, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant