mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
fix(bookings): enforce no-overlap atomically via exclusion constraints
Check-then-insert left a race: two concurrent requests for the same slot could both confirm. Migration 00014 adds three partial btree_gist exclusion constraints mirroring resourcesCollide semantics (same staff, same location, both-unassigned), excluding cancelled rows and class bookings. SQLSTATE 23P01 maps to ErrBookingConflict at all three write paths so a lost race returns a clean 409. Validated on a local Postgres 17 container: constraint matrix at SQL level, repo-level 23P01 detection, concurrent burst yielding exactly one confirmed booking. Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e4d95bf504
commit
9f92a2026f
6 files changed
+63
No files matched your search
@@ -121,6 +121,12 @@
|
||||
- capacity states read honestly: spots-left in success/warning tones, `Obsazeno`/`Full` when empty, and the action switches to `Na čekací listinu`/`Join waitlist` (previously a full session still said "Book now" while silently producing a waitlisted booking)
|
||||
- reminder arming extracted into a shared `armReminderJobs` helper (email + opt-in SMS) used by booking creation and waitlist promotion — a booking promoted after its reminder already dispatched-as-skipped now gets re-armed instead of going unreminded
|
||||
- remaining: live staging validation
|
||||
- `[done]` Milestone 19: Double-booking race hardening (validated on local Postgres)
|
||||
- migration `00014_booking_no_overlap.sql`: `btree_gist` extension plus three partial exclusion constraints on `bookings` mirroring `resourcesCollide` semantics — same staff, same location, or both-unassigned appointments may not overlap (`tstzrange`, `[)` bounds so abutting slots are legal); `cancelled` rows and `booking_mode='class'` are excluded
|
||||
- `db.IsExclusionViolation` maps SQLSTATE 23P01 to `ErrBookingConflict` at all three write paths (public create, customer reschedule, dashboard manual create) — a lost race now surfaces as a proper 409 instead of a silent double-booking
|
||||
- validated end-to-end against a local `postgres:17` container: SQL-level matrix (staff/location/unassigned overlap blocked, abutting allowed, cancel frees the slot), repo-level 23P01 detection, and a 10-request concurrent burst on one slot producing exactly one 201 + 409s for the rest; down/up migration roundtrip clean
|
||||
- local test container: `docker run -d --name bookra-pg-local -p 5439:5432 -e POSTGRES_PASSWORD=bookra-local postgres:17-alpine`, migrations via `npm run db:migrate:up` with `BOOKRA_DATABASE_DIRECT_URL` pointed at it
|
||||
- remaining: live staging validation on Neon
|
||||
|
||||
## Overview
|
||||
Bookra is a Czech-first booking SaaS for small local service businesses. It targets salons, massage therapists, small clinics, repair shops, and small studios/clubs that need a simple public booking flow, internal schedule management, reminders, and basic business reporting without becoming a full ERP.
|
||||
|
||||
Reference in new issue
Block a user