feat(api): keyless GET /api/v1/public/bookings/{id} for guests

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>
This commit is contained in:
Padreug 2026-09-15 23:43:21 +02:00
commit 94542ad0f9
4 changed files with 106 additions and 2 deletions

View file

@ -7,7 +7,13 @@ import pytest
from fastapi import HTTPException
from .. import crud, views_api
from ..models import RoomStatus, public_room_dict
from ..models import (
Booking,
BookingStatus,
RoomStatus,
public_booking_dict,
public_room_dict,
)
from .conftest import make_room
@ -58,3 +64,66 @@ def test_public_room_returns_stripped_when_active(monkeypatch):
assert out["id"] == "a"
assert "checkin_instructions" not in out
assert "wallet" not in out
def _booking(**overrides) -> Booking:
base = {
"id": "bk_1234567",
"room_id": "a",
"guest_pubkey": "ab" * 32,
"guest_contact": "guest@example.com",
"check_in": "2026-10-05",
"check_out": "2026-10-07",
"nights": 2,
"num_guests": 1,
"currency": "EUR",
"price_fiat": 200.0,
"amount_sat": 300000,
"deposit_sat": 300000,
"status": BookingStatus.awaiting_payment,
"payment_hash": "ph_1",
"request_event_id": "req_ev",
"reservation_event_id": "res_ev",
}
base.update(overrides)
return Booking(**base)
def test_public_booking_dict_strips_identity_and_plumbing():
d = public_booking_dict(_booking())
for private in (
"guest_pubkey",
"guest_contact",
"payment_hash",
"request_event_id",
"reservation_event_id",
):
assert private not in d
# What a guest client needs to render "waiting" / "booked" / "expired".
assert d["id"] == "bk_1234567"
assert d["status"] == "awaiting_payment"
assert d["check_in"] == "2026-10-05"
assert d["nights"] == 2
assert d["deposit_sat"] == 300000
assert "expires_at" in d
def test_public_booking_404_when_missing(monkeypatch):
async def gb(_):
return None
monkeypatch.setattr(crud, "get_booking", gb)
with pytest.raises(HTTPException) as e:
asyncio.run(views_api.api_public_booking("nope"))
assert e.value.status_code == 404
def test_public_booking_returns_stripped(monkeypatch):
async def gb(_):
return _booking(status=BookingStatus.confirmed)
monkeypatch.setattr(crud, "get_booking", gb)
out = asyncio.run(views_api.api_public_booking("bk_1234567"))
assert out["status"] == "confirmed"
assert "guest_contact" not in out
assert "payment_hash" not in out