chatelet/__init__.py
Padreug 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

112 lines
3.8 KiB
Python

import asyncio
from fastapi import APIRouter
from .crud import db
from .tasks import expire_holds_loop, wait_for_paid_invoices
from .views import chatelet_generic_router
from .views_api import chatelet_api_router
chatelet_static_files = [
{
"path": "/chatelet/static",
"name": "chatelet_static",
}
]
chatelet_ext: APIRouter = APIRouter(prefix="/chatelet", tags=["chatelet"])
chatelet_ext.include_router(chatelet_generic_router)
chatelet_ext.include_router(chatelet_api_router)
scheduled_tasks: list[asyncio.Task] = []
def chatelet_stop():
for task in scheduled_tasks:
task.cancel()
def chatelet_start():
from lnbits.tasks import create_permanent_unique_task
scheduled_tasks.append(
create_permanent_unique_task("chatelet_invoices", wait_for_paid_invoices)
)
scheduled_tasks.append(
create_permanent_unique_task("chatelet_hold_expiry", expire_holds_loop)
)
# Inbound Nostr: answer kind:22000 availability queries over relays via
# nostrclient (client-agnostic availability path). Self-backs-off if
# nostrclient isn't installed.
from .nostr.service import subscribe_inbound
scheduled_tasks.append(
create_permanent_unique_task("chatelet_availability_sub", subscribe_inbound)
)
# Expose the booking flow over the core LNbits nostr transport (kind-21000
# RPC) so an HTTP-allergic client can drive Chatelet over relays. Also wire
# the booking-owner resolver so the operator can stream booking
# settlements via subscribe_payments({tag:"chatelet", link_id:<booking_id>}).
# No-op if the core transport module isn't present in this LNbits build.
try:
from lnbits.core.services.nostr_transport.dispatcher import (
AUTH_ACCOUNT,
AUTH_NONE,
AUTH_WALLET,
register_rpc,
)
from lnbits.core.services.nostr_transport.subscriptions import (
register_link_owner_resolver,
)
except ImportError:
return
from .transport_rpcs import (
handle_availability,
handle_block_create,
handle_booking_get,
handle_booking_request,
handle_operator_get,
handle_operator_update,
handle_room_create,
handle_room_get,
handle_room_list,
handle_room_list_mine,
handle_room_publish,
handle_room_unavailable,
handle_room_update,
resolve_chatelet_owner,
)
# operator (wallet-scoped)
register_rpc("chatelet_room_create", handle_room_create, AUTH_WALLET)
register_rpc("chatelet_room_update", handle_room_update, AUTH_WALLET)
register_rpc("chatelet_room_publish", handle_room_publish, AUTH_WALLET)
register_rpc("chatelet_block_create", handle_block_create, AUTH_WALLET)
register_rpc("chatelet_room_list_mine", handle_room_list_mine, AUTH_ACCOUNT)
register_rpc("chatelet_operator_get", handle_operator_get, AUTH_WALLET)
register_rpc("chatelet_operator_update", handle_operator_update, AUTH_WALLET)
# public (discovery + guest booking)
register_rpc("chatelet_room_list", handle_room_list, AUTH_NONE)
register_rpc("chatelet_room_get", handle_room_get, AUTH_NONE)
register_rpc("chatelet_room_unavailable", handle_room_unavailable, AUTH_NONE)
register_rpc("chatelet_availability", handle_availability, AUTH_NONE)
register_rpc("chatelet_booking_request", handle_booking_request, AUTH_NONE)
register_rpc("chatelet_booking_get", handle_booking_get, AUTH_NONE)
# tasks.py stamps extra["booking_id"] on settlement (see on_invoice_paid),
# so override the default link_extra_key ("link") to match.
register_link_owner_resolver(
"chatelet", resolve_chatelet_owner, link_extra_key="booking_id"
)
__all__ = [
"db",
"chatelet_ext",
"chatelet_start",
"chatelet_static_files",
"chatelet_stop",
]