refactor(v2): hoist LP state (wallet, mode, autoforward) into dca_lp table
LP-level preferences were denormalised across every `dca_clients` row
of a given user. Every LP enrolment carried its own wallet_id /
dca_mode / fixed_mode_daily_limit / autoforward_ln_address /
autoforward_enabled — and satmachineclient's `update_lp_autoforward`
did a multi-row UPDATE to keep them in sync. That sync dance was the
smell: user-level intent stored at machine-enrolment granularity.
New shape:
dca_lp (user_id PK, dca_wallet_id, default_dca_mode,
fixed_mode_daily_limit, autoforward_ln_address,
autoforward_enabled, ...)
dca_clients (id, machine_id, user_id, username, status, ...)
// pure (machine, LP) enrolment — wallet/mode/autoforward gone
Authority split:
- LP writes dca_lp via satmachineclient (Phase 2, separate commit).
- Operator writes dca_clients via satmachineadmin. They cannot
choose the LP's destination wallet — it's resolved from dca_lp
at distribution time. Better trust hygiene.
Onboarding gate:
- `api_create_deposit` refuses (HTTP 422) when the target LP has
no dca_lp row. Forces every LP through a "yes, I am here and
this is where I want my sats" gesture via satmachineclient
before any fiat starts accumulating against them.
Schema:
- m001 canonical schema updated: slim `dca_clients`, new `dca_lp`.
Fresh installs land here directly.
- m004 idempotent migration for installs that already have the
legacy `dca_clients.wallet_id` column: creates dca_lp,
backfills from the latest dca_clients row per user (window
function), then DROP COLUMN on the moved fields. Greg's live
test data survives the upgrade.
Distribution:
- `get_flow_mode_clients_for_machine` INNER JOINs dca_lp so
un-onboarded LPs are filtered out (no destination wallet).
- `_pay_one_dca_leg`, `_attempt_autoforward`, `settle_lp_balance`
all fetch `dca_lp` via the new `get_dca_lp(user_id)` helper.
Wallet + autoforward read from prefs, not from client.
Models:
- `DcaClient` loses 5 fields. `CreateDcaClientData` reduces to
(machine_id, user_id, username). `UpdateDcaClientData` keeps
only operator-controlled fields (username, status).
- New `DcaLpPreferences` + `UpsertDcaLpData` models for the
per-user surface (satmachineclient writes these in Phase 2).
CRUD:
- New: `get_dca_lp`, `lp_is_onboarded`, `upsert_dca_lp` (the
latter takes a `fallback_wallet_id` for first-onboarding when
satmachineclient auto-seeds from the LP's default LNbits wallet).
- `create_dca_client` insert reduces to the new column set.
Tests: 86 unit tests still green.
Next:
- Phase 1c (this repo): UI simplification for operator's
Add/Edit LP dialogs + deposit-gating UX.
- Phase 2 (satmachineclient): own dca_lp writes + auto-init with
the LP's default LNbits wallet on first dashboard visit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1feaba80ed
commit
80b5a6d785
5 changed files with 307 additions and 49 deletions
58
models.py
58
models.py
|
|
@ -80,40 +80,70 @@ class UpdateMachineData(BaseModel):
|
|||
|
||||
|
||||
class CreateDcaClientData(BaseModel):
|
||||
"""Operator enrols an LP at one of their machines.
|
||||
|
||||
Pure (machine, LP) tuple — no wallet, no mode, no autoforward. Those
|
||||
live on the per-user `dca_lp` row, written by the LP themselves via
|
||||
satmachineclient. An LP must have onboarded (have a `dca_lp` row)
|
||||
before deposits can be recorded against this enrolment; enrolment
|
||||
itself works either way.
|
||||
"""
|
||||
|
||||
machine_id: str
|
||||
user_id: str
|
||||
wallet_id: str
|
||||
username: Optional[str] = None
|
||||
dca_mode: str = "flow" # 'flow' | 'fixed'
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
# Auto-forward DCA distributions to an external LN address (best-effort;
|
||||
# sats stay in LNbits wallet on forward failure — see satmachineadmin#8).
|
||||
autoforward_ln_address: Optional[str] = None
|
||||
autoforward_enabled: bool = False
|
||||
|
||||
|
||||
class DcaClient(BaseModel):
|
||||
id: str
|
||||
machine_id: str
|
||||
user_id: str
|
||||
wallet_id: str
|
||||
username: Optional[str]
|
||||
dca_mode: str
|
||||
fixed_mode_daily_limit: Optional[float]
|
||||
autoforward_ln_address: Optional[str]
|
||||
autoforward_enabled: bool
|
||||
status: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class UpdateDcaClientData(BaseModel):
|
||||
"""Operator-side updates to an enrolment. The operator can only edit
|
||||
fields that aren't LP-controlled (username display, status). Wallet
|
||||
/ mode / autoforward changes go through satmachineclient against
|
||||
`dca_lp` instead."""
|
||||
|
||||
username: Optional[str] = None
|
||||
dca_mode: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class DcaLpPreferences(BaseModel):
|
||||
"""Per-user DCA preferences, owned by the LP.
|
||||
|
||||
Created on first satmachineclient dashboard access (the extension
|
||||
auto-seeds `dca_wallet_id` with the LP's first/default LNbits wallet
|
||||
— they can change it from the dashboard). All distribution decisions
|
||||
(where do the sats go, do we forward to an LN address, what's the
|
||||
default mode) read from here, joined onto `dca_clients` by user_id.
|
||||
"""
|
||||
|
||||
user_id: str
|
||||
dca_wallet_id: str
|
||||
default_dca_mode: str # 'flow' | 'fixed'
|
||||
fixed_mode_daily_limit: Optional[float]
|
||||
autoforward_ln_address: Optional[str]
|
||||
autoforward_enabled: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class UpsertDcaLpData(BaseModel):
|
||||
"""satmachineclient writes this on first onboarding / when the LP
|
||||
edits their preferences. All fields optional on update — pass only
|
||||
the ones being changed."""
|
||||
|
||||
dca_wallet_id: Optional[str] = None
|
||||
default_dca_mode: Optional[str] = None
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
autoforward_ln_address: Optional[str] = None
|
||||
autoforward_enabled: Optional[bool] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class ClientBalanceSummary(BaseModel):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue