fix(equipment): DB-enforced reservation no-overlap (D1, P0)

reserveEquipment was check-then-insert with no DB guard: two concurrent
requests both passed the in-memory overlap check and double-booked a
machine. Migration 042 adds btree_gist + a GiST EXCLUDE constraint
(equipment_id =, tstzrange && ) WHERE status='reserved', making the DB
the arbiter; the action maps the 23P01 violation to the existing
friendly overlap message. App-side check kept as a fast pre-check.
Schema + DB maps updated.
This commit is contained in:
Moheeb Zara
2026-05-18 18:26:19 -07:00
parent 247e966446
commit a513ad0fab
5 changed files with 49 additions and 1 deletions
+9 -1
View File
@@ -290,7 +290,15 @@ export async function reserveEquipment(input: unknown) {
})
.select('id')
.single()
if (error) return { error: error.message }
if (error) {
// The GiST exclusion constraint is the concurrency-safe arbiter: a
// simultaneous request that passed the in-memory pre-check still loses
// here. Surface the same friendly message as the pre-check.
if (/exclusion constraint|equipment_reservations_no_overlap|23P01/i.test(error.message)) {
return { error: 'That time overlaps an existing reservation.' }
}
return { error: error.message }
}
await logActivity(supabase, member, 'reserved', 'equipment', r.equipmentId)
revalidatePath('/equipment')