feat: implement cursor-based pagination for event tickets and ticket orders - #174
Conversation
📝 WalkthroughWalkthroughCursor-based pagination is added to event ticket and ticket order listings. Controllers validate cursors, services apply cursor-aware queries and return ChangesCursor Pagination
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@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! 🚀 |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/services/event-ticket.service.ts (1)
209-213: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSequential awaits instead of
Promise.all.
totalandticketsare fetched with two sequential round trips. The sibling methodgetEventTicketsByCategory(unchanged) parallelizes the equivalent calls viaPromise.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 winSame keyset-pagination logic copy-pasted across three call sites. The cursor-filter build, sort, limit-and-slice, and
nextCursorcomputation are identical apart from the sort field name and base filter; extracting a shared helper would remove the duplication and prevent drift (e.g. thePromise.allfix needed inevent-ticket.service.tswould then only need to land once).
src/services/ticket-order.service.ts#L36-L84: extract the$orcursor-filter build + sort/limit/slice/nextCursorlogic fromgetUserOrdersinto a shared helper parameterized by sort field name (datePurchased) and base filter.src/services/ticket-order.service.ts#L137-L185: reuse the same helper ingetOrganizerOrdersinstead of re-implementing the identical block.src/services/event-ticket.service.ts#L185-L235: reuse the same helper ingetEventTickets, parameterized withcreatedAtas 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 winUse 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 innew mongoose.Types.ObjectId(cursor.id). Usemongoose.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
📒 Files selected for processing (12)
src/controllers/event-ticket.controller.tssrc/controllers/ticket-order.controller.tssrc/models/event-ticket.tssrc/routes/event-ticket.route.tssrc/routes/ticket-order.route.tssrc/services/event-ticket.service.tssrc/services/ticket-order.service.tssrc/utils/pagination-cursor.tstests/event-ticket.controller.test.tstests/event-ticket.service.test.tstests/ticket-order.controller.test.tstests/ticket-order.service.test.ts
DioChuks
left a comment
There was a problem hiding this comment.
LGTM! 🚀
Well done!
Thanks for your contribution.
feat: implement cursor-based pagination for event tickets and ticket orders
close #165
Description
This PR implements cursor-based (keyset) pagination for
EventTicketService.getEventTicketsandTicketOrderService(getUserOrdersandgetOrganizerOrders).Using$O(N)$ as page numbers increase on large datasets. Keyset pagination using $O(1)$ constant-time page fetches.
.skip(offset)degrades query performance to_idand indexed timestamp fields (createdAt/datePurchased) enables efficientBackward compatibility with existing
pageandlimitparameters is maintained as a fallback when nocursorquery parameter is provided.Key Changes
Services (
src/services/event-ticket.service.ts,src/services/ticket-order.service.ts):cursor.sortValueandcursor.id.nextCursor) in response when additional results exist..skip()pagination whencursoris omitted.Controllers (
src/controllers/event-ticket.controller.ts,src/controllers/ticket-order.controller.ts):cursorquery parameters (decodePaginationCursor).400 Bad Requeston malformed cursor strings.Models (
src/models/event-ticket.ts):createdAtandupdatedAtproperties toIEventTicketinterface for schema timestamp tracking.Tests (
tests/):Acceptance Criteria
EventTicketService.getEventTicketsupdated to support cursor-based pagination.TicketOrderServiceupdated to support cursor-based pagination.pagequery parameter fallback.How to Test
Run the targeted Jest test suite: