GET /api/v1/bookings/mine (LNbits account auth; identity = the account's
Nostr pubkey, the same value the booking request carried) and RPC twin
chatelet_booking_list_mine (scoped by the signed sender_pubkey). Rows come
back newest check-in first via the m003 guest index, as guest_booking_dict:
the guest's own contact and counts, minus the Lightning/Nostr plumbing.
Declared ahead of /bookings/{booking_id} so 'mine' is not read as an id.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
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>
v0.4.0's merge of public_booking_dict landed inside AvailabilityQuery and
left the two date fields orphaned after the helper's return, so
POST /api/v1/availability raised AttributeError on q.check_in (500) on
every install of that version. The RPC door reads the raw body and was
unaffected, which is why nothing noticed.
Put the helper next to public_room_dict and add a regression test that
constructs the query model and drives the endpoint through the
services layer.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guest cannot subscribe to the operator's wallet and the existing
booking read needs a wallet invoice key, so a client had no way to wait
for awaiting_payment -> confirmed over HTTP short of polling the invoice
on LNbits core. Add the HTTP twin of the RPC door's chatelet_booking_get:
the 10-char booking id from the quote is the capability, and the response
is public_booking_dict — lifecycle, dates and money only, with the guest's
pubkey/contact and the Lightning/Nostr plumbing stripped.
Closes#18
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Asserts public_room_dict drops wallet + checkin_instructions, public list
shows active-only + stripped, and public get 404s on inactive. 32 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
Runs the full m0NN migration chain against a fresh temp SQLite via the
lnbits Database, then round-trips a room + booking through crud (exercising
the m002 checkin_instructions column, big_int amounts, and is_available on
real rows). Closes the gap that let the #13 SQLite-index bug ship: the rest
of the suite monkeypatches crud, so migrations were never executed.
Isolation is import-order-independent: monkeypatch settings.lnbits_data_folder
to tmp_path, build a fresh ext_chatelet Database, swap it into crud for the
test. Verified it fails (sqlite3 OperationalError) if the #13 bad-index syntax
is reintroduced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
Assert the wrap is kind 1059, p-tagged to the guest, authored by an
ephemeral key (not the operator), and that plaintext doesn't leak. Round-
trip: decrypt the wrap with the guest key via core nip44_decrypt to recover
the operator-authored seal (kind 13) — proves the ephemeral NIP-44 v2 layer
is real + interoperable, not just structural. Soft-fail case returns None.
24 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
16 tests, run under the LNbits pytest env (spirekeeper pattern: monkeypatch
crud/invoice, drive async via asyncio.run — no live DB/wallet):
- test_availability: half-open overlap semantics (back-to-back stays OK),
and is_available blocking on held/confirmed bookings + manual blocks.
- test_booking_flow: canonical amount_sat, awaiting_payment transition,
min-nights guard, and hold-release (declined) on InvoiceError.
- test_atomic_hold: the #4 regression — two concurrent same-date requests
yield exactly one hold + one conflict; non-overlapping both succeed. The
fakes yield mid-check to open the race window, so the test fails without
the per-room lock (verified by neutering it) and passes with it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD