feat(v2): operator-scoped CRUD + stub legacy entry points

Replaces v1's super-only single-config CRUD with the v2 operator-scoped data
layer that matches the m005 schema:

- Machines: create/get/get_by_npub/list_for_operator/update/delete
- Clients: scoped per (machine, user). Adds list_for_operator (across an
  operator's fleet) and list_for_user (LP cross-operator view), plus
  get_flow_mode_clients_for_machine for the distribution algorithm.
- Deposits: now carry machine_id and creator_user_id; per-operator listing.
- Settlements: create_settlement_idempotent treats bitspire_event_id as the
  uniqueness key, returning the existing row on replay so subscription
  re-delivery is safe by construction. mark_settlement_status drives the
  pending → processed/partial/refunded/errored lifecycle.
- Commission splits: replace_commission_splits is an atomic per-scope
  replace; the SetCommissionSplitsData model already validates legs sum
  to 1.0 at the boundary. get_effective_commission_splits handles the
  per-machine-override-or-operator-default precedence.
- Payments: leg-typed (dca / super_fee / operator_split / settlement /
  autoforward / refund) with helpers for settlement/client/operator scopes.
- Balance summary: sums confirmed deposits minus completed dca legs.
- Telemetry: upsert_beacon_snapshot uses COALESCE so today's sparse
  kind-30078 payload doesn't clobber post-#43 fields when they start
  arriving. upsert_fleet_snapshot stores raw JSON until lamassu-next#42
  fixes the kind-30079 schema.
- Super config: singleton get/update.

Also stubs three legacy entry points so __init__.py imports cleanly while
the rest of P0/P1 is in flight:

- tasks.py: no-op stubs for wait_for_paid_invoices + hourly_transaction_polling.
  Real Nostr subscription manager lands in P1.
- views_api.py: a single /api/v1/dca/{...} catch-all returns 503 with a
  precise message. v2 endpoints land in P1+.
- views.py: drops the super-only check on the index page (v2 is
  operator-installable); platform-fee config moves to a super-only API in P1.

transaction_processor.py is left untouched but is now orphaned (no one
imports it) — gets a full rewrite in P1.

Refs: plan at ~/.claude/plans/snug-gliding-shamir.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-14 14:37:48 +02:00
commit 937749f149
4 changed files with 798 additions and 969 deletions

View file

@ -1,8 +1,10 @@
# Description: DCA Admin page endpoints.
# Satoshi Machine v2 — page route.
#
# v2 is operator-installable (any LNbits user, not super-only). The super-only
# check in v1's index() is gone. Super-only controls (platform fee config)
# move to a dedicated API endpoint protected by check_super_user in P1.
from http import HTTPStatus
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
@ -15,13 +17,9 @@ def satmachineadmin_renderer():
return template_renderer(["satmachineadmin/templates"])
# DCA Admin page - Requires superuser access
@satmachineadmin_generic_router.get("/", response_class=HTMLResponse)
async def index(req: Request, user: User = Depends(check_user_exists)):
if not user.super_user:
raise HTTPException(
HTTPStatus.FORBIDDEN, "User not authorized. No super user privileges."
)
return satmachineadmin_renderer().TemplateResponse(
"satmachineadmin/index.html", {"request": req, "user": user.json()}
"satmachineadmin/index.html",
{"request": req, "user": user.json()},
)