Commit Graph

2 Commits

Author SHA1 Message Date
mivanchenko 2f6e0c1459 Availability engine + booking API: create/cancel/reschedule (#16)
Adds the plumbing that makes "can a customer actually get booked" true
end to end at the API layer, on top of #15's schema/tenancy layer.

- resource_hours table + min_notice_minutes/max_advance_days/buffer_minutes
  on resources -- config #15 didn't include but #16 depends on.
- availability.py: pure slot-generation function, correct across a
  Europe/Berlin DST transition (tested both directions).
- booking_api.py: JSON blueprint for slot listing, booking creation
  (auto_confirm -> confirmed/pending), and signed-JWT cancel/reschedule,
  registered into app.py.
- booking_db.py gains resource-hours CRUD, a tenant-scoped busy-bookings
  query for buffer/slot validation, and a read-only client lookup.

A true concurrent-threads test (not just sequential requests) surfaced a
real gap: Postgres can raise DeadlockDetected instead of ExclusionViolation
when two overlapping inserts race the exclusion constraint directly, which
went uncaught and would have 500'd instead of giving the clean 4xx the
ticket requires -- now caught alongside ExclusionViolation.

Also fixed: reschedule used the request's raw UTC offset to pick the
business day instead of the client's own timezone (could pick the wrong
day's hours/bookings near local midnight); the cancel/reschedule JWT no
longer falls back to reusing CRM_API_TOKEN as its signing secret.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:52:33 +02:00
mivanchenko b5c0fc8a5a Booking schema + tenancy-safe data-access module (#15)
Adds the plumbing every other booking ticket builds on: resources,
services, users, and password_reset_tokens tables, plus the clients
columns (slug, timezone, auto_confirm, ics_token) and the bookings
resource_id/EXCLUDE-constraint double-booking protection described in #14.

booking_db.py is the only place raw SQL runs against these tables --
every function takes client_id and injects the tenant filter itself,
and create/update_booking additionally verify the resource_id belongs
to that client before writing, closing a guessed-ID cross-tenant hole.

Tests spin up a real throwaway Postgres 16 container (matching prod) and
exercise the EXCLUDE constraint, tenancy isolation, and password-reset
single-use semantics end to end, per #14's "real Postgres, no mocking"
testing decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:28:06 +02:00