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:
parent
013e3d5f6b
commit
937749f149
4 changed files with 798 additions and 969 deletions
69
tasks.py
69
tasks.py
|
|
@ -1,53 +1,32 @@
|
|||
import asyncio
|
||||
from datetime import datetime
|
||||
# Satoshi Machine v2 — task placeholders.
|
||||
#
|
||||
# The v1 SSH/PostgreSQL polling + invoice listener are intentionally absent.
|
||||
# They will be replaced in P1 (Nostr subscription manager: subscribes via
|
||||
# lnbits.core.services.nostr_transport to kind-21000 settlements + kind-30078
|
||||
# beacons + kind-30079 telemetry per registered machine, with auto-reconnect).
|
||||
#
|
||||
# These no-op stubs keep __init__.py importable in the interim so the
|
||||
# extension can be activated even before P1 lands.
|
||||
|
||||
import asyncio
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.core.services import websocket_updater
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
from loguru import logger
|
||||
|
||||
from .transaction_processor import poll_lamassu_transactions
|
||||
|
||||
#######################################
|
||||
########## RUN YOUR TASKS HERE ########
|
||||
#######################################
|
||||
|
||||
# The usual task is to listen to invoices related to this extension
|
||||
|
||||
|
||||
async def wait_for_paid_invoices():
|
||||
"""Invoice listener for DCA-related payments"""
|
||||
invoice_queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue, "ext_satmachineadmin")
|
||||
async def wait_for_paid_invoices() -> None:
|
||||
"""No-op placeholder pending P1 Nostr subscription manager."""
|
||||
logger.debug(
|
||||
"satmachineadmin v2: invoice listener stub running. "
|
||||
"Real Nostr-transport subscription pending P1."
|
||||
)
|
||||
# Sleep forever; the task system expects a long-lived coroutine.
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
await on_invoice_paid(payment)
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
|
||||
async def hourly_transaction_polling():
|
||||
"""Background task that polls Lamassu database every hour for new transactions"""
|
||||
logger.info("Starting hourly Lamassu transaction polling task")
|
||||
|
||||
async def hourly_transaction_polling() -> None:
|
||||
"""No-op placeholder. The v1 Lamassu PostgreSQL poller is gone — bitSpire
|
||||
settlements arrive push-based via Nostr kind-21000 in v2."""
|
||||
logger.debug("satmachineadmin v2: legacy polling stub (no-op).")
|
||||
while True:
|
||||
try:
|
||||
logger.info(f"Running Lamassu transaction poll at {datetime.now()}")
|
||||
await poll_lamassu_transactions()
|
||||
logger.info("Completed Lamassu transaction poll, sleeping for 1 hour")
|
||||
|
||||
# Sleep for 1 hour (3600 seconds)
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in hourly polling task: {e}")
|
||||
# Sleep for 5 minutes before retrying on error
|
||||
await asyncio.sleep(300)
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
"""Handle DCA-related invoice payments"""
|
||||
# DCA payments are handled internally by the transaction processor
|
||||
# This function can be extended if needed for additional payment processing
|
||||
if payment.extra.get("tag") in ["dca_distribution", "dca_commission"]:
|
||||
logger.info(f"DCA payment processed: {payment.checking_id} - {payment.amount} sats")
|
||||
# Could add websocket notifications here if needed
|
||||
pass
|
||||
await asyncio.sleep(3600)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue