Skip to content

feat: implement cursor-based pagination for event tickets and ticket orders - #174

Merged
DioChuks merged 1 commit into
BuidlZone-Labs:mainfrom
kingjosmel:Inefficient-Pagination-using-skip
Jul 29, 2026
Merged

feat: implement cursor-based pagination for event tickets and ticket orders#174
DioChuks merged 1 commit into
BuidlZone-Labs:mainfrom
kingjosmel:Inefficient-Pagination-using-skip

Conversation

@kingjosmel

@kingjosmel kingjosmel commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

feat: implement cursor-based pagination for event tickets and ticket orders

close #165

Description

This PR implements cursor-based (keyset) pagination for EventTicketService.getEventTickets and TicketOrderService (getUserOrders and getOrganizerOrders).

Using .skip(offset) degrades query performance to $O(N)$ as page numbers increase on large datasets. Keyset pagination using _id and indexed timestamp fields (createdAt / datePurchased) enables efficient $O(1)$ constant-time page fetches.

Backward compatibility with existing page and limit parameters is maintained as a fallback when no cursor query parameter is provided.

Key Changes

  • Services (src/services/event-ticket.service.ts, src/services/ticket-order.service.ts):

    • Added keyset filter logic using cursor.sortValue and cursor.id.
    • Encoded next pagination cursor (nextCursor) in response when additional results exist.
    • Preserved .skip() pagination when cursor is omitted.
    • Handled query builder chaining and lean document typing.
  • Controllers (src/controllers/event-ticket.controller.ts, src/controllers/ticket-order.controller.ts):

    • Added parsing and validation for optional cursor query parameters (decodePaginationCursor).
    • Returns 400 Bad Request on malformed cursor strings.
    • Forwarded decoded cursor parameters to underlying service methods.
  • Models (src/models/event-ticket.ts):

    • Added optional createdAt and updatedAt properties to IEventTicket interface for schema timestamp tracking.
  • Tests (tests/):

    • Added unit tests for cursor generation, page fetching, and fallback behavior across service and controller test suites.

Acceptance Criteria

  • EventTicketService.getEventTickets updated to support cursor-based pagination.
  • TicketOrderService updated to support cursor-based pagination.
  • Tests added and passing to verify cursor logic.
  • Backward compatibility preserved for page query parameter fallback.

How to Test

Run the targeted Jest test suite:

npx jest tests/event-ticket.service.test.ts tests/ticket-order.service.test.ts tests/event-ticket.controller.test.ts tests/ticket-order.controller.test.ts --runInBand

result 

PASS tests/event-ticket.service.test.ts
PASS tests/ticket-order.service.test.ts
PASS tests/ticket-order.controller.test.ts
PASS tests/event-ticket.controller.test.ts

Test Suites: 4 passed, 4 total
Tests:       24 passed, 24 total

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Added cursor-based pagination for event tickets and ticket orders.
  * Responses now include a cursor for loading the next page when more results are available.
  * Existing page-based pagination remains available as a fallback.
  * Invalid pagination cursors now return a clear `400 Invalid cursor` response.

* **Documentation**
  * Updated endpoint documentation to describe cursor pagination and next-page cursors.

* **Tests**
  * Added coverage for valid and invalid cursors across ticket and order listings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Cursor-based pagination is added to event ticket and ticket order listings. Controllers validate cursors, services apply cursor-aware queries and return nextCursor, existing page pagination remains supported, route documentation is updated, and tests cover both flows.

Changes

Cursor Pagination

Layer / File(s) Summary
Cursor encoding and validation
src/utils/pagination-cursor.ts
Defines cursor data and encodes or validates cursor strings containing a date and MongoDB identifier.
Event ticket pagination
src/models/event-ticket.ts, src/controllers/event-ticket.controller.ts, src/services/event-ticket.service.ts, src/routes/event-ticket.route.ts, tests/event-ticket.*
Adds cursor validation and service integration, cursor-filtered ticket queries, nextCursor responses, updated route documentation, and pagination tests.
Ticket order pagination
src/controllers/ticket-order.controller.ts, src/services/ticket-order.service.ts, src/routes/ticket-order.route.ts, tests/ticket-order.*
Adds cursor validation for user and organizer order endpoints, cursor-filtered queries ordered by purchase date and identifier, nextCursor responses, documentation, and tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: diochuks

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: cursor-based pagination for event tickets and ticket orders.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@kingjosmel 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

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (3)
src/services/event-ticket.service.ts (1)

209-213: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential awaits instead of Promise.all.

total and tickets are fetched with two sequential round trips. The sibling method getEventTicketsByCategory (unchanged) parallelizes the equivalent calls via Promise.all; this rework dropped that pattern here.

⚡ Parallelize the count and fetch
-      // Get total count
-      const total = await EventTicket.countDocuments();
-
-      // Fetch tickets with pagination
-      const tickets = await query.lean(); // Use lean() for better performance
+      // Get total count and fetch tickets concurrently
+      const [total, tickets] = await Promise.all([
+        EventTicket.countDocuments(),
+        query.lean(), // Use lean() for better performance
+      ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/event-ticket.service.ts` around lines 209 - 213, Update the
ticket retrieval flow around EventTicket.countDocuments and query.lean in the
enclosing method to execute both independent queries concurrently with
Promise.all, preserving the existing total and tickets assignments and
pagination behavior.
src/services/ticket-order.service.ts (1)

36-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same keyset-pagination logic copy-pasted across three call sites. The cursor-filter build, sort, limit-and-slice, and nextCursor computation are identical apart from the sort field name and base filter; extracting a shared helper would remove the duplication and prevent drift (e.g. the Promise.all fix needed in event-ticket.service.ts would then only need to land once).

  • src/services/ticket-order.service.ts#L36-L84: extract the $or cursor-filter build + sort/limit/slice/nextCursor logic from getUserOrders into a shared helper parameterized by sort field name (datePurchased) and base filter.
  • src/services/ticket-order.service.ts#L137-L185: reuse the same helper in getOrganizerOrders instead of re-implementing the identical block.
  • src/services/event-ticket.service.ts#L185-L235: reuse the same helper in getEventTickets, parameterized with createdAt as the sort field.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/ticket-order.service.ts` around lines 36 - 84, Extract the
shared keyset-pagination flow from getUserOrders into a reusable helper
parameterized by the base filter and sort field. In
src/services/ticket-order.service.ts lines 36-84, use the helper for
datePurchased; in lines 137-185, replace the duplicated getOrganizerOrders logic
with the helper; and in src/services/event-ticket.service.ts lines 185-235,
reuse it with createdAt, including cursor filtering, sorting, limiting/slicing,
and nextCursor generation.
src/utils/pagination-cursor.ts (1)

32-38: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use a strict ObjectId check for cursors.

mongoose.Types.ObjectId.isValid(parsed.id) also accepts any 12-character string, so a crafted cursor can pass validation and be used in new mongoose.Types.ObjectId(cursor.id). Use mongoose.isObjectIdOrHexString(parsed.id) when that check is needed for generated cursor ObjectIds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/pagination-cursor.ts` around lines 32 - 38, Update the ObjectId
validation in the parsed cursor checks to use
mongoose.isObjectIdOrHexString(parsed.id) instead of
mongoose.Types.ObjectId.isValid(parsed.id), while preserving the existing
string-type and null-return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/services/event-ticket.service.ts`:
- Around line 209-213: Update the ticket retrieval flow around
EventTicket.countDocuments and query.lean in the enclosing method to execute
both independent queries concurrently with Promise.all, preserving the existing
total and tickets assignments and pagination behavior.

In `@src/services/ticket-order.service.ts`:
- Around line 36-84: Extract the shared keyset-pagination flow from
getUserOrders into a reusable helper parameterized by the base filter and sort
field. In src/services/ticket-order.service.ts lines 36-84, use the helper for
datePurchased; in lines 137-185, replace the duplicated getOrganizerOrders logic
with the helper; and in src/services/event-ticket.service.ts lines 185-235,
reuse it with createdAt, including cursor filtering, sorting, limiting/slicing,
and nextCursor generation.

In `@src/utils/pagination-cursor.ts`:
- Around line 32-38: Update the ObjectId validation in the parsed cursor checks
to use mongoose.isObjectIdOrHexString(parsed.id) instead of
mongoose.Types.ObjectId.isValid(parsed.id), while preserving the existing
string-type and null-return behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1850e4f-d179-43b5-827c-5ebfa94e3103

📥 Commits

Reviewing files that changed from the base of the PR and between 4c17c60 and 89f8cc5.

📒 Files selected for processing (12)
  • src/controllers/event-ticket.controller.ts
  • src/controllers/ticket-order.controller.ts
  • src/models/event-ticket.ts
  • src/routes/event-ticket.route.ts
  • src/routes/ticket-order.route.ts
  • src/services/event-ticket.service.ts
  • src/services/ticket-order.service.ts
  • src/utils/pagination-cursor.ts
  • tests/event-ticket.controller.test.ts
  • tests/event-ticket.service.test.ts
  • tests/ticket-order.controller.test.ts
  • tests/ticket-order.service.test.ts

@DioChuks
DioChuks self-requested a review July 29, 2026 10:02

@DioChuks DioChuks 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.

LGTM! 🚀
Well done!
Thanks for your contribution.

@DioChuks
DioChuks merged commit 3334a41 into BuidlZone-Labs:main Jul 29, 2026
4 of 5 checks 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.

Inefficient Pagination using .skip() for Large Datasets

2 participants