ReviewOS

stacks/bench-review

scale: paginate review and listing endpoints

#28
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Current state: /api/reviews returns a fixed 6 latest; /api/judges/{id}/reviews and /api/court-houses/{id}/reviews return everything; /admin/reviews and /admin/users paginate locally via in-memory slicing.

At 100+ reviews per popular judge, these become a real UX cliff (long lists, slow renders, big payloads).

Scope

  • Once the upstream pagination umbrella (stacksjs/stacks#1910) is fully wired in our vendored framework, migrate the read endpoints to Model.paginate() so they return the canonical { data, current_page, per_page, total, last_page, prev_page_url, next_page_url, ... } shape with the auto-generated Link header.
  • Until then: hand-roll a minimal paginate() shim on bench-review's side (sibling task) and migrate progressively.
  • Frontend: <Pagination> component from @stacksjs/components once upstream releases it, or a local equivalent.
  • /my-reviews already filters client-side from a single fetch — once it grows past ~50 rows per user, this needs paginating too.

Acceptance

  • /api/reviews?page=2&per_page=20 returns canonical paginator shape.
  • Same for /api/judges/{id}/reviews, /api/court-houses/{id}/reviews, /api/admin/reviews, /api/admin/users, /api/me/reviews.
  • UI shows page controls + page count, query params survive across nav.
  • Tracks upstream pagination umbrella stacksjs/stacks#1910 (closed, but our vendored framework needs a resync).
9 days ago

First slice of #28 shipped.

Helper — app/Helpers/paginate.ts now exposes two building blocks:

  • paginate({ table, where, orderBy, perPage, page }) — convenience wrapper, fetches + counts + builds canonical paginator. Use when the action is a straight `SELECT` over one table.
  • resolvePaginatorArgs() + buildPaginatorMeta(data, total, page, perPage) — the meta-build half on its own. Use when the action needs custom hydration (joins, per-row enrichment, like-count hydration, role hydration) before wrapping.

Migrated — ReviewsByJudgeAction (/api/judges/:id/reviews) now returns the canonical Laravel-style shape:

```json { "data": [...], "current_page": 1, "per_page": 25, "total": 18, "last_page": 1, "from": 1, "to": 18, "has_more_pages": false, "prev_page_url": null, "next_page_url": null, "first_page_url": "...", "last_page_url": "...", "path": "..." } ```

Reads `?page=N&per_page=M` from the request automatically.

Consumer — resources/stores/reviews.ts fetchByJudge(judgeId, page=1, perPage=25) now passes pagination params and unwraps `.data`. Backward-compat fallback if the endpoint ever returns a raw array.

Smoke test — End-to-end against the live DB with judge 16 (3 published reviews): total: 3, last_page: 1, data: 3, from: 1, to: 3 ✓

Deferred to follow-up

  • MyReviewsAction — needs server-side status filtering + pre-computed per-status counts (the current pill labels are derived client-side from the full list). Migrating this requires also reworking MyReviewsView.stx to fetch on filter change instead of slicing. Bigger scope; will tackle as a sibling issue.
  • Admin/ReviewIndexAction / Admin/UserIndexAction — they already paginate with an ad-hoc { data, total, page, perPage } shape. The admin tables consume those keys. Migrating purely for the canonical shape is churn without user-visible value right now. Best done when the upstream paginator wiring is fully functional in our vendored framework (then we get the URL fields + Link header for free).
  • /api/court-houses/:id/reviews — endpoint doesn't exist yet (the courthouse profile aggregates judge stats, doesn't list reviews directly). Will file as a separate issue if/when the courthouse profile grows a reviews tab.
  • UI page controls — <Pagination> component from upstream P5. The store now supports page params; the UI surfacing comes after.

The shim's the foundation; the rest is mechanical migration as we go.

Sign in to comment on this issue.