Skip to content

Closes #196 Add pagination and stable ordering to expense and settlement lists - #325

Merged
K1NGD4VID merged 1 commit into
mergepay:mainfrom
Fury03:issue-196-pagination-settlement-lists
Sep 1, 2026
Merged

Closes #196 Add pagination and stable ordering to expense and settlement lists#325
K1NGD4VID merged 1 commit into
mergepay:mainfrom
Fury03:issue-196-pagination-settlement-lists

Conversation

@Fury03

@Fury03 Fury03 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #196

Problem Statement

Settlement records within a group have no dedicated list endpoint with cursor-based pagination. While the ledger endpoint merges expenses, settlements, and treasury transactions, clients that specifically need to list settlements for a group must either use the merged ledger (which interleaves different record types) or the cross-group history endpoint (which scopes to the authenticated user rather than the group).

Solution Comparison and Decision

Option A: Add pagination to a new GET /settlements endpoint — This would list all settlements across all groups, which is not what the issue describes. The issue asks for pagination on "group expenses or settlement records," meaning per-group scoping.

Option B: Rely on the ledger endpoint — The ledger merges expenses, settlements, and treasury transactions. It already has pagination, but a client that only needs settlements must fetch and filter mixed results, wasting bandwidth and making cursor-based pagination impossible across types.

Option C: Add GET /groups/:id/settlements with shared pagination (chosen) — A dedicated endpoint scoped to the group, using the same cursor-based pagination contract as every other list endpoint. This gives clients a stable, deterministic way to page through settlements without mixing types or relying on cross-group history.

The Change

New route in src/routes/settlements.ts:

app.get("/groups/:id/settlements", async (req) => {
  const auth = requireUser(req);
  const { id: groupId } = idParamSchema.parse(req.params);
  const { cursor, limit, order } = paginationQuerySchema.parse(req.query ?? {});
  await requireMembership(groupId, auth.id);

  const position = requireCursor(cursor);
  const settlements = await prisma.settlement.findMany({
    where: { groupId, ...cursorFilter(position, order) },
    include: settlementInclude,
    orderBy: cursorOrderBy(order),
    take: takeForPage(limit),
  });

  const { items, meta } = buildPage(settlements, limit, order);
  return { settlements: items.map(serializeSettlement), meta };
});
Endpoint Auth Pagination Membership
GET /groups/:id/settlements Required cursor, limit, order Checked before any row read

Key behaviors:

  • Deterministic (createdAt, id) ordering handles equal timestamps safely
  • limit + 1 row fetch computes hasMore without a count query
  • Maximum page size enforced server-side
  • Cursor carries no membership authority — groupId filter always scopes the query

Compatibility Note

No INTERFACE_VERSION change. This is an additive new endpoint that does not modify existing routes or response shapes. Existing clients are unaffected.

Incidental Fixes

  1. Added GET /groups/:id/settlements to the shared pagination contract documentation in docs/api-contract.md.

Testing

New tests (in tests/pagination-contract.test.ts):

  • Contract test: returns consistent metadata on an empty page
  • Contract test: rejects page size over maximum
  • Contract test: rejects malformed cursor
  • First page returns items and cursor pointing at last row
  • Cursor resume with tie-breaking on id
  • Bounded query (limit + 1 rows, no skip)
  • Non-member rejection (403 before any row read)
  • Cursor scoping: groupId always present, not derived from cursor

Test results: All 757 tests pass (57 test files), including 46 pagination-contract tests.

Additional Notes

Settlement records were previously only accessible through the merged ledger endpoint (GET /groups/:id/ledger) or the cross-group history endpoint (GET /history). This PR adds the dedicated per-group list endpoint that settlement-specific clients need.

@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@Fury03 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@mergekeeper

mergekeeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

MergeKeeper review unavailable

AI provider review response did not contain valid JSON

No approval or merge action was taken.

@Fury03
Fury03 force-pushed the issue-196-pagination-settlement-lists branch from 6949f4f to 226eec4 Compare August 28, 2026 11:51
@mergekeeper

mergekeeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

MergeKeeper review

Scope: in scope for linked issue #196.
Verdict: clean

Successfully added the paginated GET /groups/:id/settlements endpoint with proper membership checks, cursor handling, documentation, and comprehensive test coverage.

Reviewed commit: 226eec47af0bd0834f499f3e744dcd929bc8bfbf.
CI and merge eligibility are checked separately.

@mergekeeper mergekeeper Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

Successfully added the paginated GET /groups/:id/settlements endpoint with proper membership checks, cursor handling, documentation, and comprehensive test coverage.

@mergekeeper

mergekeeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

MergeKeeper merge status

Status: blocked
PR state: open
Mergeability: mergeable
Checked commit: 226eec47af0bd0834f499f3e744dcd929bc8bfbf.

Reason:
GitHub pull request merge request failed with 405: Base branch was modified. Review and try the merge again.

Next steps:

  1. Check the linked GitHub status and repository-rule details.
  2. Fix the reported issue or update the repository rule.
  3. Push a new commit or retry after the requirement becomes eligible.

Closes mergepay#196

Adds `GET /groups/:id/settlements` with cursor-based pagination and
deterministic (createdAt, id) ordering, matching the shared pagination
contract used by every other list endpoint.

- New route scoped to authenticated group membership
- Uses existing pagination primitives (cursorFilter, cursorOrderBy,
  takeForPage, buildPage) for bounded, consistent queries
- Enforces maximum page size server-side
- Membership checked before any row is read
- 3 contract tests added to shared pagination suite
- 5 dedicated settlement-list tests (first page, cursor resume,
  bounded query, non-member rejection, cursor-scoping)
- API contract docs updated

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@Fury03
Fury03 force-pushed the issue-196-pagination-settlement-lists branch from 226eec4 to 69d3650 Compare August 31, 2026 16:04
@K1NGD4VID
K1NGD4VID merged commit 41de183 into mergepay:main Sep 1, 2026
1 check passed
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.

Add pagination and stable ordering to expense and settlement lists

2 participants