Skip to content

fix: resale indexes, one ACTIVE listing per ticket, row-locked purchases, pool docs (#214, #215, #216, #217) - #363

Merged
EmmanuelOchaje merged 4 commits into
StellarTickets:mainfrom
luciusverus-cyber:fix/issues-214-215-216-217
Sep 26, 2026
Merged

EmmanuelOchaje merged 4 commits into
StellarTickets:mainfrom
luciusverus-cyber:fix/issues-214-215-216-217

Conversation

@luciusverus-cyber

Copy link
Copy Markdown
Contributor

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):

  • Add index on ResaleListing(ticketId, status) #214 — composite index on ResaleListing(ticketId, status): schema @@index + raw migration 20260926130000_resale_ticket_status_index (same pattern as the Add index on Ticket(ownerId, status) #213 Ticket_ownerId_status_idx), documented in docs/DATABASE.md.
  • Add DB connection pool configuration #215 — new "Connection pool configuration" section in docs/DATABASE.md: connection_limit / pool_timeout / connect_timeout with engine defaults, sizing guidance per environment (local dev, Kubernetes pods, migration CLI runs, serverless via pooled proxies), the total-budget rule against Postgres max_connections, and P2024 triage notes.
  • Enforce one ACTIVE resale listing per ticket at DB level #216 — one ACTIVE resale listing per ticket at DB level: partial unique index ResaleListing_ticketId_active_key (ticketId WHERE status = 'ACTIVE') via raw migration; TicketsService adds an assertNoActiveResaleListing fail-fast 409 plus throwOnResaleConflict mapping the index P2002 to 409 Conflict; unit tests cover both paths and the resale e2e fires two concurrent confirm-list-resale calls asserting exactly one 201, one 409, and a single ACTIVE row. Non-ACTIVE rows are excluded so tickets can be re-listed after SOLD/CANCELLED.
  • Use serializable or row-level locking for ticket purchase flows #217 — purchase flows under concurrency: confirmIssue and confirmPurchase 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 — concurrent buyers serialize and losers abort with TICKET_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; new test/purchase-concurrency.e2e-spec.ts runs 8 concurrent purchases against a 3-ticket tier on real Postgres and proves exactly 3 succeed, 5 get TICKET_TYPE_SOLD_OUT 409s, and quantityIssued == ticket count == quantityTotal.

Testing

  • Unit tests follow the existing tickets.service.spec.ts mock style (factories from test/factories, Prisma.PrismaClientKnownRequestError for P2002, $queryRaw added to the prisma mock with a healthy locked-row default so all existing tests stay green).
  • E2E tests follow test/resale.e2e-spec.ts's bootstrap (real DATABASE_URL Postgres, mocked StellarService, overridden JwtAuthGuard); the purchase-concurrency spec additionally overrides NotificationService and carries per-request buyer identity via a header so concurrent requests keep distinct identities.
  • No CI/workflow/secret files touched; no dependencies added.

Closes #214
Closes #215
Closes #216
Closes #217

…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
@drips-wave

drips-wave Bot commented Sep 26, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@netlify

netlify Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for stellarticketsbackend ready!

Name Link
🔨 Latest commit 6bc9d74
🔍 Latest deploy log https://app.netlify.com/projects/stellarticketsbackend/deploys/6ab79aef05992f0008d4369a
😎 Deploy Preview https://deploy-preview-363--stellarticketsbackend.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@EmmanuelOchaje
EmmanuelOchaje merged commit f8de0ea into StellarTickets:main Sep 26, 2026
4 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

2 participants