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

@ -7,7 +7,7 @@ endpoints (room/block CRUD, settings) are HTTP-only and back the admin UI;
the guest-facing surface (availability, booking) is what also rides the RPC.
"""
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from lnbits.core.models import WalletTypeInfo
from lnbits.decorators import require_admin_key, require_invoice_key
@ -24,6 +24,7 @@ from .models import (
CreateRoomData,
Room,
RoomStatus,
UnavailableRanges,
public_booking_dict,
public_room_dict,
)
@ -212,6 +213,20 @@ async def api_public_room(room_id: str) -> dict:
return public_room_dict(room)
@chatelet_api_router.get("/api/v1/public/rooms/{room_id}/unavailable")
async def api_public_room_unavailable(
room_id: str,
start: str | None = Query(None, description="YYYY-MM-DD, default today"),
end: str | None = Query(None, description="YYYY-MM-DD exclusive, default +365d"),
) -> UnavailableRanges:
"""Nights a guest cannot book, merged and anonymous — what a calendar
greys out. Keyless, like the room read it hangs off."""
try:
return await services.get_unavailable_ranges(room_id, start, end)
except ValueError as exc:
raise _to_http(exc) from exc
# --- availability (public read) --------------------------------------------