fix(v2): m005-m007 idempotency + SQLite CREATE INDEX syntax; template self-closing tags
Three live-test bugs caught while wiring the v2 frontend to a real
LNbits regtest instance.
1. **SQLite parse error on CREATE INDEX with schema-prefixed table.**
`CREATE INDEX foo ON satoshimachine.bar (col)` errors with
"near '.': syntax error" on SQLite. PG accepts the prefix on the
table; SQLite expects the schema prefix on the INDEX NAME only,
not on the table. Cleanest portable fix (libra extension pattern):
drop `satoshimachine.` from the table reference inside CREATE INDEX.
The index lands in the same schema as the table regardless.
2. **m005 non-idempotent after partial failure.** The previous bug
above tripped m005 mid-flight (CREATE TABLE super_config + CREATE
TABLE dca_machines succeeded, then the first CREATE INDEX errored
and aborted). LNbits doesn't mark partial migrations done, so the
next boot re-ran m005 — and CREATE TABLE super_config now errored
with "table already exists". To make recovery clean:
- CREATE TABLE IF NOT EXISTS on every table (13 tables)
- CREATE INDEX IF NOT EXISTS on every index (10 indexes)
- super_config seed INSERT wrapped in check-then-insert so the
PK conflict on 'default' on re-run is avoided
3. **Vue compiler error code 30 — self-closing tags on non-void
elements in templates/satmachineadmin/index.html.** The previous
commit `98f82be` on satmachineclient called this out as a known
LNbits UMD gotcha: Vue 3 UMD's compiler doesn't auto-expand `<q-icon />`
the way SFCs do — the browser HTML parser sees the malformed self-
closing tag and aborts compilation. 118 tags expanded from
`<q-foo … />` to `<q-foo …></q-foo>` via mechanical rewrite.
Verified end-to-end against docker regtest-lnbits-1:
- All three migrations (m005, m006, m007) ran cleanly
- Schema has all 8 v2 tables + 10 indexes
- "satmachineadmin v2 loaded" + invoice listener registered
- /satmachineadmin/ returns 200; JS loads; super-config + machines
endpoints respond
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b96837164e
commit
cb19ba3675
2 changed files with 160 additions and 154 deletions
|
|
@ -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)"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue