diff --git a/migrations.py b/migrations.py index fbe3c88..354d006 100644 --- a/migrations.py +++ b/migrations.py @@ -10,7 +10,7 @@ async def m001_initial_dca_schema(db): # DCA Clients table await db.execute( f""" - CREATE TABLE satoshimachine.dca_clients ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_clients ( id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, wallet_id TEXT NOT NULL, @@ -27,7 +27,7 @@ async def m001_initial_dca_schema(db): # DCA Deposits table await db.execute( f""" - CREATE TABLE satoshimachine.dca_deposits ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_deposits ( id TEXT PRIMARY KEY NOT NULL, client_id TEXT NOT NULL, amount INTEGER NOT NULL, @@ -43,7 +43,7 @@ async def m001_initial_dca_schema(db): # DCA Payments table await db.execute( f""" - CREATE TABLE satoshimachine.dca_payments ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_payments ( id TEXT PRIMARY KEY NOT NULL, client_id TEXT NOT NULL, amount_sats INTEGER NOT NULL, @@ -61,7 +61,7 @@ async def m001_initial_dca_schema(db): # Lamassu Configuration table await db.execute( f""" - CREATE TABLE satoshimachine.lamassu_config ( + CREATE TABLE IF NOT EXISTS satoshimachine.lamassu_config ( id TEXT PRIMARY KEY NOT NULL, host TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 5432, @@ -90,7 +90,7 @@ async def m001_initial_dca_schema(db): # Lamassu Transactions table (for audit trail) await db.execute( f""" - CREATE TABLE satoshimachine.lamassu_transactions ( + CREATE TABLE IF NOT EXISTS satoshimachine.lamassu_transactions ( id TEXT PRIMARY KEY NOT NULL, lamassu_transaction_id TEXT NOT NULL UNIQUE, fiat_amount INTEGER NOT NULL, @@ -200,7 +200,7 @@ async def m005_satmachine_v2_overhaul(db): # The only thing the LNbits super has direct DB control over in this extension. await db.execute( f""" - CREATE TABLE satoshimachine.super_config ( + CREATE TABLE IF NOT EXISTS satoshimachine.super_config ( id TEXT PRIMARY KEY, super_fee_pct DECIMAL(10,4) NOT NULL DEFAULT 0.0000, super_fee_wallet_id TEXT, @@ -208,17 +208,23 @@ async def m005_satmachine_v2_overhaul(db): ); """ ) - await db.execute( - "INSERT INTO satoshimachine.super_config (id, super_fee_pct) " - "VALUES ('default', 0.0000)" + # Idempotent seed: check before insert so re-runs after a partial- + # failure recovery don't trip the PK conflict. + existing = await db.fetchone( + "SELECT id FROM satoshimachine.super_config WHERE id = 'default'" ) + if not existing: + await db.execute( + "INSERT INTO satoshimachine.super_config (id, super_fee_pct) " + "VALUES ('default', 0.0000)" + ) # dca_machines — one row per bitSpire ATM, owned by exactly one operator. # fallback_commission_pct kicks in only when bitSpire's settlement Payment.extra # is missing the (net_sats, fee_sats) split — see plan's lamassu-next ask #1. await db.execute( f""" - CREATE TABLE satoshimachine.dca_machines ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_machines ( id TEXT PRIMARY KEY, operator_user_id TEXT NOT NULL, machine_npub TEXT NOT NULL UNIQUE, @@ -234,15 +240,15 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE INDEX dca_machines_operator_idx " - "ON satoshimachine.dca_machines (operator_user_id)" + "CREATE INDEX IF NOT EXISTS dca_machines_operator_idx " + "ON dca_machines (operator_user_id)" ) # dca_clients — LP registrations scoped per (machine, user). One LP can hold # positions across many machines (and many operators) on the same instance. await db.execute( f""" - CREATE TABLE satoshimachine.dca_clients ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_clients ( id TEXT PRIMARY KEY, machine_id TEXT NOT NULL, user_id TEXT NOT NULL, @@ -259,19 +265,19 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE UNIQUE INDEX dca_clients_machine_user_uq " - "ON satoshimachine.dca_clients (machine_id, user_id)" + "CREATE UNIQUE INDEX IF NOT EXISTS dca_clients_machine_user_uq " + "ON dca_clients (machine_id, user_id)" ) await db.execute( - "CREATE INDEX dca_clients_user_idx " - "ON satoshimachine.dca_clients (user_id)" + "CREATE INDEX IF NOT EXISTS dca_clients_user_idx " + "ON dca_clients (user_id)" ) # dca_deposits — fiat the operator (or super) records against an LP at a machine. # creator_user_id preserves audit trail (resolves a v1 tech-debt finding). await db.execute( f""" - CREATE TABLE satoshimachine.dca_deposits ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_deposits ( id TEXT PRIMARY KEY, client_id TEXT NOT NULL, machine_id TEXT NOT NULL, @@ -286,8 +292,8 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE INDEX dca_deposits_client_idx " - "ON satoshimachine.dca_deposits (client_id, created_at DESC)" + "CREATE INDEX IF NOT EXISTS dca_deposits_client_idx " + "ON dca_deposits (client_id, created_at DESC)" ) # dca_settlements — idempotency table for bitSpire-driven settlements. @@ -306,7 +312,7 @@ async def m005_satmachine_v2_overhaul(db): # section "Customer discounts". await db.execute( f""" - CREATE TABLE satoshimachine.dca_settlements ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_settlements ( id TEXT PRIMARY KEY, machine_id TEXT NOT NULL, payment_hash TEXT NOT NULL UNIQUE, @@ -332,8 +338,8 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE INDEX dca_settlements_machine_idx " - "ON satoshimachine.dca_settlements (machine_id, created_at DESC)" + "CREATE INDEX IF NOT EXISTS dca_settlements_machine_idx " + "ON dca_settlements (machine_id, created_at DESC)" ) # payment_hash UNIQUE already creates a lookup index — no extra index needed. @@ -344,7 +350,7 @@ async def m005_satmachine_v2_overhaul(db): # scope must equal 1.0 — enforced at write-time in crud.py. await db.execute( f""" - CREATE TABLE satoshimachine.dca_commission_splits ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_commission_splits ( id TEXT PRIMARY KEY, machine_id TEXT, operator_user_id TEXT NOT NULL, @@ -357,8 +363,8 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE INDEX dca_commission_splits_lookup_idx " - "ON satoshimachine.dca_commission_splits (operator_user_id, machine_id)" + "CREATE INDEX IF NOT EXISTS dca_commission_splits_lookup_idx " + "ON dca_commission_splits (operator_user_id, machine_id)" ) # dca_payments — every leg of every distribution. The leg_type discriminator @@ -367,7 +373,7 @@ async def m005_satmachine_v2_overhaul(db): # autoforward (see satmachineadmin#8) | refund. await db.execute( f""" - CREATE TABLE satoshimachine.dca_payments ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_payments ( id TEXT PRIMARY KEY, settlement_id TEXT, client_id TEXT, @@ -388,16 +394,16 @@ async def m005_satmachine_v2_overhaul(db): """ ) await db.execute( - "CREATE INDEX dca_payments_client_idx " - "ON satoshimachine.dca_payments (client_id, created_at DESC)" + "CREATE INDEX IF NOT EXISTS dca_payments_client_idx " + "ON dca_payments (client_id, created_at DESC)" ) await db.execute( - "CREATE INDEX dca_payments_settlement_idx " - "ON satoshimachine.dca_payments (settlement_id)" + "CREATE INDEX IF NOT EXISTS dca_payments_settlement_idx " + "ON dca_payments (settlement_id)" ) await db.execute( - "CREATE INDEX dca_payments_operator_idx " - "ON satoshimachine.dca_payments (operator_user_id, leg_type)" + "CREATE INDEX IF NOT EXISTS dca_payments_operator_idx " + "ON dca_payments (operator_user_id, leg_type)" ) # dca_telemetry — latest replaceable kind-30078 (public availability beacon) @@ -408,7 +414,7 @@ async def m005_satmachine_v2_overhaul(db): # lands. Ingest opportunistically; render absent fields gracefully in the UI. await db.execute( """ - CREATE TABLE satoshimachine.dca_telemetry ( + CREATE TABLE IF NOT EXISTS satoshimachine.dca_telemetry ( machine_id TEXT PRIMARY KEY, beacon_cash_in BOOLEAN, beacon_cash_out BOOLEAN, @@ -473,6 +479,6 @@ async def m007_settlement_claim_and_machine_wallet_unique(db): "ADD COLUMN processing_claim TEXT" ) await db.execute( - "CREATE UNIQUE INDEX dca_machines_wallet_id_uq " - "ON satoshimachine.dca_machines (wallet_id)" + "CREATE UNIQUE INDEX IF NOT EXISTS dca_machines_wallet_id_uq " + "ON dca_machines (wallet_id)" ) diff --git a/templates/satmachineadmin/index.html b/templates/satmachineadmin/index.html index eb763e2..8faf730 100644 --- a/templates/satmachineadmin/index.html +++ b/templates/satmachineadmin/index.html @@ -63,19 +63,19 @@ active-color="primary" indicator-color="primary" narrow-indicator> - - - - + + + + ${ worklistCount } - + - + @@ -95,13 +95,13 @@ + @click="openAddMachineDialog"> You haven't registered any machines yet. Click Add machine to register a bitSpire ATM by its Nostr npub. @@ -187,13 +187,13 @@ + @click="openAddClientDialog"> Register at least one machine before adding LPs — an LP is scoped to a specific machine. @@ -201,7 +201,7 @@ No LPs yet. Use Register LP to add one at any of your machines. @@ -228,7 +228,7 @@ + :label="props.row.dca_mode"> + :label="props.row.status"> - + Edit - + Settle balance… - + Delete @@ -295,7 +295,7 @@ + @click="openAddDepositDialog"> @@ -307,18 +307,18 @@ {label: 'Pending', value: 'pending'}, {label: 'Confirmed', value: 'confirmed'}, {label: 'Rejected', value: 'rejected'}]" - label="Status" emit-value map-options dense outlined /> + label="Status" emit-value map-options dense outlined>
+ label="LP" emit-value map-options dense outlined clearable>
Register at least one LP before recording deposits. @@ -326,7 +326,7 @@ No deposits yet. Use Record deposit to log a new one. @@ -347,7 +347,7 @@ + :label="props.row.status"> @@ -381,7 +381,7 @@ clickable v-close-popup @click="confirmDepositStatus(props.row, 'confirmed')"> - + Confirm @@ -389,7 +389,7 @@ clickable v-close-popup @click="openRejectDepositDialog(props.row)"> - + Reject… @@ -397,7 +397,7 @@ clickable v-close-popup @click="openEditDepositDialog(props.row)"> - + Edit @@ -405,7 +405,7 @@ clickable v-close-popup @click="confirmDeleteDeposit(props.row)"> - + Delete @@ -435,7 +435,7 @@ label="Scope being edited" emit-value map-options dense outlined - @update:model-value="loadCommissionSplits" /> + @update:model-value="loadCommissionSplits">
Default ruleset — applies to every machine without an @@ -464,7 +464,7 @@ v-text="(commissionSum * 100).toFixed(2) + '%'"> + class="q-ml-xs"> @@ -475,14 +475,14 @@
+ @click="addCommissionLeg">
No default rules. Without a default, all operator @@ -499,12 +499,12 @@ + emit-value map-options dense outlined>
+ dense outlined>
+ @click="commissionLegs.splice(idx, 1)">
Preview against @@ -543,15 +543,15 @@ + @click="confirmDeleteCommissionOverride"> + @click="loadCommissionSplits"> + @click="saveCommissionSplits"> @@ -569,17 +569,17 @@ + type="number" min="1"> + @click="loadWorklist"> All clear — no errored or stuck settlements. @@ -589,7 +589,7 @@ class="q-mb-lg">
+ class="q-mr-sm"> + @click="downloadMachinesCsv">
@@ -681,7 +681,7 @@ + @click="downloadClientsCsv"> @@ -696,7 +696,7 @@ + @click="downloadDepositsCsv"> @@ -712,7 +712,7 @@ + @click="downloadPaymentsCsv"> @@ -729,8 +729,8 @@
Add bitSpire machine
- - + +

@@ -744,14 +744,14 @@ label="Machine name" hint="Operator-friendly label (e.g. ATM-Antigua-1)" class="q-mb-md" - dense outlined /> + dense outlined> + dense outlined> + dense outlined> + dense outlined> - + + @click="submitAddMachine"> @@ -809,7 +809,7 @@ v-text="machineDetail.machine.name || 'Unnamed machine'"> - + @@ -844,7 +844,7 @@ - +

@@ -873,7 +873,7 @@ + :label="props.row.status"> @@ -915,7 +915,7 @@ - + Add note @@ -923,7 +923,7 @@ clickable v-close-popup @click="confirmRetrySettlement(props.row)"> - + Retry distribution @@ -932,7 +932,7 @@ clickable v-close-popup @click="openPartialDispense(props.row)"> - + Partial dispense… @@ -941,7 +941,7 @@ clickable v-close-popup @click="confirmForceReset(props.row)"> - + Force-reset (stuck)… @@ -968,13 +968,13 @@
Apply partial dispense
- - + +
Original gross: . @@ -984,8 +984,8 @@ - - + + @@ -993,7 +993,7 @@ label="Dispensed fraction" hint="e.g. 0.6 means 60% of the original tx was dispensed" type="number" step="0.01" min="0" max="1" - dense outlined /> + dense outlined> + dense outlined> + dense outlined> - + + @click="submitPartialDispense">
@@ -1027,8 +1027,8 @@
Add note to settlement
- - + +

@@ -1042,10 +1042,10 @@ dense outlined /> - + + @click="submitNote"> @@ -1057,8 +1057,8 @@

Platform fee (super-only)
- - + +

@@ -1070,17 +1070,17 @@ label="Fee % (decimal, 0..1)" hint="0.30 = 30% of every operator's commission" type="number" step="0.0001" min="0" max="1" - class="q-mb-md" dense outlined /> + class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> - + + @click="submitSuperFee"> @@ -1093,8 +1093,8 @@

- - + +
+ class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> - + + @click="submitDeposit">
@@ -1137,8 +1137,8 @@
Reject deposit
- - + +

@@ -1148,13 +1148,13 @@ + dense outlined> - + + @click="submitRejectDeposit"> @@ -1166,8 +1166,8 @@

- - + +

+ class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> + class="q-mb-md"> + class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> - + + @click="submitClient"> @@ -1251,13 +1251,13 @@

Settle LP balance
- - + +
Pay the LP's remaining fiat balance in sats from your wallet at the rate you choose. Useful to zero out small balances that would @@ -1287,18 +1287,18 @@ + class="q-mb-md" dense outlined> + class="q-mb-md" dense outlined> - + + @click="submitSettleBalance">
@@ -1310,36 +1310,36 @@
Edit machine
- - + +
+ label="Machine name" class="q-mb-md" dense outlined> + label="Location" class="q-mb-md" dense outlined> + dense outlined> + class="q-mb-md" dense outlined> + label="Fiat code" class="q-mb-md" dense outlined> + label="Active (receives settlements)" class="q-mb-md"> - + + @click="submitEditMachine">