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>
This commit is contained in:
Padreug 2026-09-16 12:16:14 +02:00
commit 9ab52f2c69
17 changed files with 531 additions and 42 deletions

25
crud.py
View file

@ -19,6 +19,7 @@ from .models import (
ChateletSettings,
CreateBlockData,
CreateRoomData,
OperatorSettings,
Room,
RoomStatus,
)
@ -48,6 +49,30 @@ async def update_settings(settings: ChateletSettings) -> ChateletSettings:
return settings
# ---------------------------------------------------------------------------
# Operator settings (per LNbits user — multi-tenant)
# ---------------------------------------------------------------------------
async def get_or_create_operator_settings(user_id: str) -> OperatorSettings:
row = await db.fetchone(
"SELECT * FROM chatelet.operator_settings WHERE user_id = :uid",
{"uid": user_id},
OperatorSettings,
)
if row:
return row
ops = OperatorSettings(user_id=user_id)
await db.insert("chatelet.operator_settings", ops)
return ops
async def update_operator_settings(ops: OperatorSettings) -> OperatorSettings:
ops.updated_at = datetime.now(timezone.utc)
await db.update("chatelet.operator_settings", ops, "WHERE user_id = :user_id")
return ops
# ---------------------------------------------------------------------------
# Rooms
# ---------------------------------------------------------------------------