Tito duplicate check fix#93
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reduces unnecessary Tito invitation creation requests by preloading existing RSVP release invitations and reusing known invite URLs, aiming to avoid duplicate-email errors and lower risk of hitting Tito request limits.
Changes:
- Added
getRsvpInvitationsMapto fetch all existing RSVP invitations (paginated) and build an email→inviteURL map. - Updated
bulkCreateInvitationsto preload invites, skip creates for known emails, and reuse preloaded URLs during duplicate-error recovery. - Updated bulk invitation tests to mock the new preload function and assert skipping behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| app/(api)/_utils/tito/getRsvpInvitationsMap.ts | New helper to page through Tito RSVP invitations and return a normalized email→URL map. |
| app/(api)/_utils/tito/bulkCreateInvitations.ts | Uses preloaded invites to skip creates and attempts reuse on duplicates; adds new logging and counters. |
| tests/titoBulkCreateInvitations.test.ts | Switches mocks from per-email lookup to preloaded map and adjusts expectations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Recovery logic for duplicate ticket errors | ||
| if (isDuplicateTicketError(createError)) { | ||
| const existingInviteUrl = preloadedInviteMap.get(normalizedEmail); | ||
| if (existingInviteUrl) { | ||
| inviteMap.set(app.email.toLowerCase(), existingInviteUrl); | ||
| inviteMap.set(normalizedEmail, existingInviteUrl); | ||
| autoFixedCount += 1; | ||
| autoFixedNotesMap[app.email.toLowerCase()] = | ||
| autoFixedNotesMap[normalizedEmail] = | ||
| 'Existing Tito invite was reused due to duplicate email.'; | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| const deleteResult = await deleteRsvpInvitationByEmail({ | ||
| rsvpListSlug, | ||
| email: app.email, | ||
| }); | ||
|
|
||
| if (deleteResult.ok) { | ||
| const retryResult = await createRsvpInvitation({ | ||
| firstName: app.firstName, | ||
| lastName: app.lastName, | ||
| email: app.email, | ||
| const deleteResult = await deleteRsvpInvitationByEmail({ | ||
| rsvpListSlug, | ||
| releaseIds, | ||
| discountCode, | ||
| email: app.email, | ||
| }); |
There was a problem hiding this comment.
On duplicate-ticket errors, this now only attempts reuse from the initial preloadedInviteMap. If the duplicate was caused by an invitation created after preload (or if preload was incomplete), the code will fall through to deleteRsvpInvitationByEmail and potentially delete a valid existing invitation that could have been reused. To preserve the previous safety behavior, add a fallback live lookup (e.g., getRsvpInvitationByEmail) before deleting, and only delete+retry if no invitation URL can be found.
There was a problem hiding this comment.
Not doing, The goal here is to minimize API calls and request time, so I’d prefer preload-first with delete+retry only as a fallback if the preload data is stale.
|
closes #95 |
Current code:
Tito req made -> throws error of duplicate -> do retry
Fix:
Gets a list of pre-existing invites.
This list of preloaded invites are used to skip known existing invites, only sending requests for those that don't have invites.
This leads to fewer unnecessary Tito requests and less risk of hitting the 300s request limit.