feat: paginate list endpoints with X-Total-Count (#99) #170

Merged
claude-bot merged 1 commit from feat/99-pagination into main 2026-07-16 01:09:09 +00:00
Contributor

Summary

No backend list endpoint paginated — unbounded response sizes/query times as campaigns accumulate data. Adds limit/offset + an X-Total-Count header to the list endpoints, uniformly.

Design

  • Shared app/pagination.py dependency so the convention is identical everywhere: limit default 200 (ge=1 → 422 on 0/negative), clamped in code to max 500 (not le=, which would 422 — the spec wants clamp); offset default 0 (ge=0).
  • X-Total-Count header (chosen over an envelope — fully backward compatible; the body stays a plain JSON array). Each endpoint runs a count() for the total and a page query with an explicit ORDER BY … , id unique tiebreaker (no shuffle/overlap).
  • Documented in docs/API.md.

Endpoints paginated (9)

sessions, members, npcs, lore, lore/proposals, planning/arcs, planning/threads, session votes, and /api/bot/sessions/upcoming (default 200 keeps the bot's get_upcoming_sessions working unchanged).

Deliberately skipped (with reasons): milestones — it appends synthesized "NPC reappeared" entries after the DB query, so a DB count()/limit would give a wrong X-Total-Count and overlapping pages (paginating it would be incorrect, not just harder); analytics — aggregate object, not a list; plus bounded GM-only/search/graph lists left for focus.

Frontend

"Load more" (accumulate-append) on the two lists that can realistically exceed 200: session list (CampaignDetail.jsx) and wiki entry list (CampaignStoryline.jsx); new API helpers read X-Total-Count.

Behaviour change

Any of the 9 endpoints that previously returned >200 rows by default is now capped at 200 unless the caller raises limit. Under 200 rows, no-param behaviour is identical.

Verification

Backend 388 passed (+6 pagination tests: two-page no-overlap + header on sessions & bot/upcoming, clamp-not-reject, default-under-cap, a 422 matrix, dependency clamp unit). Frontend 67 passed (one unrelated pre-existing react-markdown stale-dep failure in an untouched file). ruff 0.4.4 clean.

Closes #99

🤖 Generated with Claude Code

## Summary No backend list endpoint paginated — unbounded response sizes/query times as campaigns accumulate data. Adds `limit`/`offset` + an `X-Total-Count` header to the list endpoints, uniformly. ## Design - Shared `app/pagination.py` dependency so the convention is identical everywhere: `limit` default **200** (`ge=1` → 422 on 0/negative), **clamped in code** to max **500** (not `le=`, which would 422 — the spec wants clamp); `offset` default 0 (`ge=0`). - **`X-Total-Count` header** (chosen over an envelope — fully backward compatible; the body stays a plain JSON array). Each endpoint runs a `count()` for the total and a page query with an explicit `ORDER BY … , id` unique tiebreaker (no shuffle/overlap). - Documented in `docs/API.md`. ## Endpoints paginated (9) sessions, members, npcs, lore, lore/proposals, planning/arcs, planning/threads, session votes, and `/api/bot/sessions/upcoming` (default 200 keeps the bot's `get_upcoming_sessions` working unchanged). **Deliberately skipped (with reasons):** `milestones` — it appends *synthesized* "NPC reappeared" entries after the DB query, so a DB `count()`/`limit` would give a wrong `X-Total-Count` and overlapping pages (paginating it would be *incorrect*, not just harder); `analytics` — aggregate object, not a list; plus bounded GM-only/search/graph lists left for focus. ## Frontend "Load more" (accumulate-append) on the two lists that can realistically exceed 200: session list (`CampaignDetail.jsx`) and wiki entry list (`CampaignStoryline.jsx`); new API helpers read `X-Total-Count`. ## Behaviour change Any of the 9 endpoints that previously returned >200 rows by default is now capped at 200 unless the caller raises `limit`. Under 200 rows, no-param behaviour is identical. ## Verification Backend **388 passed** (+6 pagination tests: two-page no-overlap + header on sessions & bot/upcoming, clamp-not-reject, default-under-cap, a 422 matrix, dependency clamp unit). Frontend **67 passed** (one unrelated pre-existing `react-markdown` stale-dep failure in an untouched file). ruff 0.4.4 clean. Closes #99 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(api): paginate list endpoints with X-Total-Count header
All checks were successful
CI / Frontend tests, audit, and build (pull_request) Successful in 1m5s
CI / Backend lint (ruff) (pull_request) Successful in 2m35s
CI / Docker image build (pull_request) Successful in 2m44s
CI / Backend migration, tests, and audit (pull_request) Successful in 2m56s
CI / Bot tests and audit (pull_request) Successful in 3m36s
6965ffcbb5
Add optional limit/offset query params and an X-Total-Count response
header to the campaign-scoped list endpoints. The response body stays a
plain JSON array, so existing clients that ignore the header keep working
unchanged.

limit defaults to 200 and is clamped to 500 when higher (not rejected);
negative/zero limit or non-integer limit/offset return 422 via FastAPI
Query validation. Every paginated query gets an explicit ORDER BY with a
unique id tiebreaker so pages never overlap or shuffle.

Endpoints paginated: sessions, members, npcs, lore, lore/proposals,
planning/arcs, planning/threads, session votes, and bot/sessions/upcoming.
A shared app.pagination dependency keeps the convention uniform.

Behaviour change: any of these endpoints that previously returned more
than 200 rows by default is now capped at 200 unless the caller raises
limit. The Discord bot's get_upcoming_sessions keeps working unchanged
because the default limit of 200 preserves the array response shape.

Frontend: add limit/offset pass-through and a "Load more" button (reading
X-Total-Count) to the session list (CampaignDetail) and the wiki entry
list (CampaignStoryline), the two lists that can realistically exceed 200.

Docs: document the params and the X-Total-Count convention in docs/API.md.

Closes #99

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-07-16 01:06:09 +00:00
claude-bot deleted branch feat/99-pagination 2026-07-16 01:09:09 +00:00
Sign in to join this conversation.
No description provided.