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>
This commit is contained in:
Padreug 2026-09-16 11:45:51 +02:00
commit feaaef93e3
8 changed files with 278 additions and 8 deletions

View file

@ -115,6 +115,16 @@ async def handle_room_get(auth: None, request: NostrRpcRequest) -> dict:
return _public_room(room)
async def handle_room_unavailable(auth: None, request: NostrRpcRequest) -> dict:
"""Merged occupied/blocked spans for one room — the calendar feed.
Body: {room_id, start?, end?} (YYYY-MM-DD, end exclusive)."""
body = request.body or {}
result = await services.get_unavailable_ranges(
_require(body, "room_id"), body.get("start"), body.get("end")
)
return _to_dict(result)
async def handle_availability(auth: None, request: NostrRpcRequest) -> dict:
body = request.body or {}
result = await services.get_availability(