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:
parent
feaaef93e3
commit
9ab52f2c69
17 changed files with 531 additions and 42 deletions
40
models.py
40
models.py
|
|
@ -65,6 +65,28 @@ OCCUPYING_STATUSES = {
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Per-operator (LNbits user) choices that apply to all of that user's rooms.
|
||||
HOUSE_RULE_FIELDS = ("checkin_time", "checkout_time", "cancellation_policy")
|
||||
|
||||
|
||||
class UpdateOperatorSettings(BaseModel):
|
||||
accept_fiat: bool = False
|
||||
checkin_time: str = "15:00"
|
||||
checkout_time: str = "11:00"
|
||||
cancellation_policy: str = "" # shown to guests + in the check-in DM
|
||||
|
||||
|
||||
class OperatorSettings(UpdateOperatorSettings):
|
||||
"""One row per operator user, created on first read. `accept_fiat` is the
|
||||
operator's *wish*; whether card is actually offered also depends on LNbits
|
||||
core having a fiat provider for that user (services.payment_methods_for_room)
|
||||
— chatelet never holds provider credentials."""
|
||||
|
||||
user_id: str
|
||||
created_at: datetime = Field(default_factory=_now)
|
||||
updated_at: datetime = Field(default_factory=_now)
|
||||
|
||||
|
||||
class ChateletSettings(BaseModel):
|
||||
# LNbits account whose Nostr signer publishes listings/receipts on the
|
||||
# castle's behalf. Resolved via lnbits.core.signers.resolve_signer so
|
||||
|
|
@ -74,9 +96,11 @@ class ChateletSettings(BaseModel):
|
|||
relays: list[str] = Field(default_factory=list) # where we publish/subscribe
|
||||
default_hold_minutes: int = 30 # how long a `held` booking survives unpaid
|
||||
deposit_percent: int = 100 # 100 = full prepay; <100 = deposit + balance
|
||||
# Legacy (pre-m003): house rules are per operator now — see OperatorSettings.
|
||||
# Columns kept so old rows load; nothing reads them.
|
||||
checkin_time: str = "15:00"
|
||||
checkout_time: str = "11:00"
|
||||
cancellation_policy: str = "" # free-form markdown, surfaced in listings/DMs
|
||||
cancellation_policy: str = ""
|
||||
publish_availability: bool = True # mirror blocked dates to a public NIP-52 calendar
|
||||
created_at: datetime = Field(default_factory=_now)
|
||||
updated_at: datetime = Field(default_factory=_now)
|
||||
|
|
@ -195,14 +219,24 @@ class Block(BaseModel):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def public_room_dict(room: Room) -> dict:
|
||||
def public_room_dict(
|
||||
room: Room,
|
||||
*,
|
||||
house_rules: dict | None = None,
|
||||
payment_methods: list[str] | None = None,
|
||||
) -> dict:
|
||||
"""A Room as public JSON for guests — strips operator-private fields: the
|
||||
wallet id, and the check-in instructions (address/gate code, delivered only
|
||||
in the encrypted post-payment DM). Shared by the HTTP and Nostr-RPC guest
|
||||
doors so neither can leak them."""
|
||||
doors so neither can leak them. `house_rules` / `payment_methods` come from
|
||||
the owner's OperatorSettings (services.public_room_view resolves them)."""
|
||||
d = json.loads(room.json())
|
||||
d.pop("wallet", None)
|
||||
d.pop("checkin_instructions", None)
|
||||
if house_rules is not None:
|
||||
d["house_rules"] = house_rules
|
||||
if payment_methods is not None:
|
||||
d["payment_methods"] = payment_methods
|
||||
return d
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue