fix: resale indexes, one ACTIVE listing per ticket, row-locked purchases, pool docs (#214, #215, #216, #217) - #363
Merged
EmmanuelOchaje merged 4 commits intoSep 26, 2026
Conversation
…StellarTickets#214) Resale lookups filter listings by ticket and status (active listing per ticket, ticket-scoped feeds, expiry sweeps). Add the composite index as a schema @@index plus a raw-SQL migration that mirrors the Ticket owner/status index pattern from StellarTickets#213, and document it in docs/DATABASE.md.
…ance (StellarTickets#215) Add a Connection pool configuration section to docs/DATABASE.md: the Prisma engine pool parameters (connection_limit / pool_timeout / connect_timeout) with their defaults, sizing guidance per environment (local dev, Kubernetes API pods, migration CLI runs, serverless via pooled proxies), the total-budget rule against Postgres max_connections, and the P2024 pool-timeout triage note.
StellarTickets#216) - partial unique index ResaleListing_ticketId_active_key (ticketId WHERE status = 'ACTIVE') via raw migration — Prisma cannot express WHERE; concurrent creates for the same ticket cannot both succeed, non-ACTIVE rows stay excluded so tickets can be re-listed after SOLD/CANCELLED - TicketsService: assertNoActiveResaleListing pre-check fails fast with a 409 before the chain round-trip; throwOnResaleConflict translates the P2002 from the index into 409 Conflict when a concurrent create wins - tests: unit spec covers the pre-check fail-fast and the P2002 -> 409 mapping; resale e2e fires two concurrent confirm-list-resale calls and asserts exactly one 201, one 409, and a single ACTIVE listing row - documented in docs/DATABASE.md
…kets#217) confirmIssue and confirmPurchase incremented quantityIssued with only a pre-transaction capacity check — concurrent purchases could both pass the check and oversell quantityTotal. - both flows now take a row-level lock inside the same interactive transaction (SELECT ... FOR UPDATE on the TicketType row) and re-check quantityIssued < quantityTotal FROM the locked row before incrementing, so concurrent buyers serialize on the row and losers abort with TICKET_TYPE_SOLD_OUT (409 via the domain exception filter) - unit tests: the FOR UPDATE lock precedes the increment, and the locked row (not the stale pre-transaction read) is the capacity authority - new concurrency e2e (purchase-concurrency.e2e-spec.ts, real Postgres like the other e2e specs): 8 concurrent purchases against a 3-ticket tier — exactly 3 succeed, 5 get TICKET_TYPE_SOLD_OUT 409s, and quantityIssued == ticket count == quantityTotal afterwards - documented in docs/DATABASE.md
|
@luciusverus-cyber 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! 🚀 |
✅ Deploy Preview for stellarticketsbackend ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the four self-contained issues, one commit per issue, based on
main(mirroring the migration/service/e2e patterns established by PR #360 and the e2e wiring from #357/#358):ResaleListing(ticketId, status): schema@@index+ raw migration20260926130000_resale_ticket_status_index(same pattern as the Add index on Ticket(ownerId, status) #213Ticket_ownerId_status_idx), documented indocs/DATABASE.md.docs/DATABASE.md:connection_limit/pool_timeout/connect_timeoutwith engine defaults, sizing guidance per environment (local dev, Kubernetes pods, migration CLI runs, serverless via pooled proxies), the total-budget rule against Postgresmax_connections, andP2024triage notes.ResaleListing_ticketId_active_key(ticketIdWHEREstatus= 'ACTIVE') via raw migration;TicketsServiceadds anassertNoActiveResaleListingfail-fast 409 plusthrowOnResaleConflictmapping the indexP2002to409 Conflict; unit tests cover both paths and the resale e2e fires two concurrentconfirm-list-resalecalls asserting exactly one201, one409, and a single ACTIVE row. Non-ACTIVE rows are excluded so tickets can be re-listed after SOLD/CANCELLED.confirmIssueandconfirmPurchasenow take a row-level lock inside the same interactive transaction (SELECT … FOR UPDATEon theTicketTyperow) and re-checkquantityIssued < quantityTotalfrom the locked row before incrementing — concurrent buyers serialize and losers abort withTICKET_TYPE_SOLD_OUT(409). Unit tests assert the lock precedes the increment and that the locked row (not the stale pre-transaction read) is the capacity authority; newtest/purchase-concurrency.e2e-spec.tsruns 8 concurrent purchases against a 3-ticket tier on real Postgres and proves exactly 3 succeed, 5 getTICKET_TYPE_SOLD_OUT409s, andquantityIssued == ticket count == quantityTotal.Testing
tickets.service.spec.tsmock style (factories fromtest/factories,Prisma.PrismaClientKnownRequestErrorfor P2002,$queryRawadded to the prisma mock with a healthy locked-row default so all existing tests stay green).test/resale.e2e-spec.ts's bootstrap (realDATABASE_URLPostgres, mockedStellarService, overriddenJwtAuthGuard); the purchase-concurrency spec additionally overridesNotificationServiceand carries per-request buyer identity via a header so concurrent requests keep distinct identities.Closes #214
Closes #215
Closes #216
Closes #217