Commit graph

4 commits

Author SHA1 Message Date
9ab52f2c69 feat: per-operator settings — house rules + card acceptance (multi-tenant)
Chatelet is multi-tenant: any LNbits user can host rooms. What an operator
decides for all their rooms now lives in chatelet.operator_settings, keyed
by user id and created lazily (m003, which also indexes bookings by guest):
check-in/out times, cancellation policy, and accept_fiat.

Guests see it: the public room view (both doors) gains house_rules and
payment_methods, and the kind:30402 listing carries payment_methods,
checkin_time and checkout_time tags so a generic Nostr client can render
the right pay buttons and rules without our RPC. The check-in DM reads the
room owner's rules instead of the instance row.

Card is offered only when the operator opted in, the room is fiat-priced,
and LNbits core has a fiat provider for that user — resolved through
settings.get_fiat_providers_for_user(owner), the one seam lnbits#67's
per-user Stripe credentials will plug into; chatelet never sees creds.

Operator endpoints: GET/PUT /api/v1/operator (admin key → wallet user) and
RPC twins chatelet_operator_get/update (AUTH_WALLET); saving re-publishes
the owner's active listings. Admin UI moves the house-rule inputs into a
per-operator card with the card toggle and a provider hint. The old
house-rule columns on settings stay for old rows but are no longer read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 12:16:14 +02:00
feaaef93e3 feat(api): keyless unavailable-ranges feed for the guest calendar
GET /api/v1/public/rooms/{id}/unavailable?start=&end= and its RPC twin
chatelet_room_unavailable return the nights a guest cannot book over a
window (default today → +365 d, capped at 400 d): occupying bookings —
live holds included, so the feed always agrees with POST /availability —
and manual blocks, clipped, sorted and coalesced into anonymous half-open
spans. Adjacent spans merge on purpose so a guest cannot tell where one
occupant's dates end and the next begin, nor a block from a stay.

is_available now uses the OCCUPYING_STATUSES constant it was inlining.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:45:51 +02:00
9d87c129ad fix: make check-then-hold atomic against concurrent bookings (#4)
Two simultaneous requests for the same nights could both pass is_available()
before either wrote its held row, double-booking the dates. Wrap the
is_available -> create_booking pair in a per-room asyncio.Lock
(_room_locks[room_id]) in services.request_booking, which both the HTTP and
RPC doors funnel through. FX + invoice creation stay outside the lock, so it
covers only the DB critical section.

Single-loop scope (LNbits runs one worker); documented the multi-worker
caveat (needs a DB-level guard) in event-flow.md and the crud.is_available
note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
2026-07-19 17:08:43 +02:00
5cfc9b893d refactor: extract booking flow into services.py
Move availability quoting + the check-then-hold + invoice orchestration out
of views_api.py into a transport-agnostic services.py, with typed errors
(NotFound/Unavailable/ValueError/BookingError). views_api becomes a thin
HTTP door that maps those to status codes. No behavior change — this is so
the incoming nostr-transport door can share one booking flow instead of
duplicating it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
2026-07-19 01:30:13 +02:00