mirror of
https://github.com/Such-Software/wowcheckout.git
synced 2026-10-17 05:51:45 +02:00
ProfileResponse schema's default_qr_logo literal was inherited from xmrcheckout: Literal["monero", "none", "custom"]. The DB column (models.py) defaults to "wownero" and main.py's migration backfills NULL → ... but the backfill also wrote "monero", contradicting the column default. End result: new users got default_qr_logo="wownero" (from the column default), but GET /api/core/profile blew up with a Pydantic ResponseValidationError on serialization because "wownero" isn't in the literal. UI showed "Unavailable" for both Store ID and Primary Address because the entire profile response 500'd, leaving profileData null on the dashboard. API key + webhook secret rendered fine because those come from cookies, not profileData. Fix: - schemas.py: QrLogoMode now Literal["wownero", "monero", "none", "custom"] (keep "monero" for backward compat with any rows from before the migration ran) - schemas.py: ProfileResponse.default_qr_logo default → "wownero" - main.py: backfill UPDATE → 'wownero' (was 'monero'); migration column DEFAULT already correct as 'wownero' - ui/dashboard/page.tsx: TS type for default_qr_logo widened to include "wownero"; fallback default → "wownero" - ui/dashboard/actions.ts: DefaultQrLogoState.logo type widened; validation in updateDefaultQrLogo + initialMetadataPayload now accepts "wownero" Suchshop didn't hit this because their wowcheckout user row was created back when this UI path wasn't being used (they were provisioned via direct SQL/API). The new shop.wowne.ro merchant created via the UI today triggered the column default → exposed the schema mismatch.
343 lines
12 KiB
Python
343 lines
12 KiB
Python
import logging
|
|
import os
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.openapi.utils import get_openapi
|
|
from sqlalchemy import text
|
|
|
|
from .db import Base, engine
|
|
from .config import DONATIONS_ENABLED
|
|
from .admin_routes import router as admin_router
|
|
from .btcpay_routes import router as btcpay_router
|
|
from .routes import router
|
|
|
|
app = FastAPI(title="xmrcheckout.com API", version="0.1.0")
|
|
|
|
logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO").upper())
|
|
|
|
|
|
def custom_openapi():
|
|
if app.openapi_schema:
|
|
return app.openapi_schema
|
|
openapi_schema = get_openapi(
|
|
title=app.title,
|
|
version=app.version,
|
|
description=app.description,
|
|
routes=app.routes,
|
|
)
|
|
openapi_schema.get("paths", {}).pop("/api/core/donations", None)
|
|
if not DONATIONS_ENABLED:
|
|
openapi_schema.get("paths", {}).pop("/api/core/public/donation/{invoice_id}", None)
|
|
openapi_schema.get("paths", {}).pop("/api/core/auth/validate", None)
|
|
openapi_schema.get("paths", {}).pop("/api/core/profile", None)
|
|
app.openapi_schema = openapi_schema
|
|
return app.openapi_schema
|
|
|
|
|
|
app.openapi = custom_openapi
|
|
|
|
|
|
@app.on_event("startup")
|
|
def startup():
|
|
lock_acquired = False
|
|
lock_id = 894221741
|
|
with engine.begin() as connection:
|
|
try:
|
|
connection.execute(text("SELECT pg_advisory_lock(:lock_id)"), {"lock_id": lock_id})
|
|
lock_acquired = True
|
|
except Exception:
|
|
lock_acquired = False
|
|
try:
|
|
Base.metadata.create_all(bind=connection)
|
|
connection.execute(
|
|
text("ALTER TABLE invoices ADD COLUMN IF NOT EXISTS user_id UUID")
|
|
)
|
|
connection.execute(
|
|
text("ALTER TABLE invoices ADD COLUMN IF NOT EXISTS wallet_address VARCHAR")
|
|
)
|
|
connection.execute(
|
|
text("ALTER TABLE invoices ADD COLUMN IF NOT EXISTS subaddress_index INTEGER")
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE invoices "
|
|
"ADD COLUMN IF NOT EXISTS confirmations INTEGER DEFAULT 0"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE invoices "
|
|
"ADD COLUMN IF NOT EXISTS total_paid_atomic BIGINT"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE invoices "
|
|
"ADD COLUMN IF NOT EXISTS paid_after_expiry BOOLEAN DEFAULT false"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE invoices "
|
|
"ADD COLUMN IF NOT EXISTS paid_after_expiry_at TIMESTAMP WITH TIME ZONE"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE invoices "
|
|
"ADD COLUMN IF NOT EXISTS archived_at TIMESTAMP WITH TIME ZONE"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text("UPDATE invoices SET confirmations = 0 WHERE confirmations IS NULL")
|
|
)
|
|
connection.execute(
|
|
text("CREATE INDEX IF NOT EXISTS ix_invoices_user_id ON invoices (user_id)")
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"CREATE INDEX IF NOT EXISTS ix_invoices_wallet_address ON invoices (wallet_address)"
|
|
)
|
|
)
|
|
connection.execute(text("ALTER TABLE webhooks ADD COLUMN IF NOT EXISTS user_id UUID"))
|
|
connection.execute(
|
|
text("ALTER TABLE webhooks ADD COLUMN IF NOT EXISTS event_urls JSON")
|
|
)
|
|
connection.execute(
|
|
text("ALTER TABLE webhooks ALTER COLUMN url DROP NOT NULL")
|
|
)
|
|
connection.execute(
|
|
text("CREATE INDEX IF NOT EXISTS ix_webhooks_user_id ON webhooks (user_id)")
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"CREATE INDEX IF NOT EXISTS ix_webhook_deliveries_user_id "
|
|
"ON webhook_deliveries (user_id)"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"CREATE INDEX IF NOT EXISTS ix_webhook_deliveries_webhook_id "
|
|
"ON webhook_deliveries (webhook_id)"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS invoice_id UUID"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS invoice_address VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS invoice_subaddress_index INTEGER"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS invoice_amount_xmr NUMERIC(18, 12)"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS invoice_status VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE webhook_deliveries "
|
|
"ADD COLUMN IF NOT EXISTS payload JSON"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text("ALTER TABLE users ADD COLUMN IF NOT EXISTS payment_address VARCHAR")
|
|
)
|
|
connection.execute(
|
|
text("ALTER TABLE users ADD COLUMN IF NOT EXISTS view_key_encrypted VARCHAR")
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS webhook_secret_encrypted VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS next_subaddress_index INTEGER DEFAULT 1"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS subaddress_start_index INTEGER DEFAULT 0"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS default_confirmation_target INTEGER DEFAULT 1"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ALTER COLUMN default_confirmation_target SET DEFAULT 1"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS default_qr_logo VARCHAR DEFAULT 'wownero'"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS default_qr_logo_data_url VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS btcpay_checkout_style VARCHAR DEFAULT 'btcpay_classic'"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ALTER COLUMN btcpay_checkout_style SET DEFAULT 'btcpay_classic'"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"UPDATE users SET btcpay_checkout_style = 'btcpay_classic' "
|
|
"WHERE btcpay_checkout_style IS NULL"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"UPDATE users "
|
|
"SET btcpay_checkout_style = 'btcpay_classic' "
|
|
"WHERE btcpay_checkout_style = 'standard' "
|
|
"AND NOT EXISTS ("
|
|
" SELECT 1 FROM profile_history ph "
|
|
" WHERE ph.user_id = users.id AND ph.field_name = 'btcpay_checkout_style'"
|
|
")"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"UPDATE users SET default_confirmation_target = 1 "
|
|
"WHERE default_confirmation_target IS NULL"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"UPDATE users SET default_qr_logo = 'wownero' "
|
|
"WHERE default_qr_logo IS NULL"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text("CREATE INDEX IF NOT EXISTS ix_users_payment_address ON users (payment_address)")
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"CREATE TABLE IF NOT EXISTS system_status ("
|
|
"name VARCHAR PRIMARY KEY, "
|
|
"last_reconcile_started_at TIMESTAMP WITH TIME ZONE, "
|
|
"last_reconcile_completed_at TIMESTAMP WITH TIME ZONE, "
|
|
"last_reconcile_error VARCHAR, "
|
|
"wallet_rpc VARCHAR, "
|
|
"daemon VARCHAR, "
|
|
"daemon_height INTEGER, "
|
|
"checked_at TIMESTAMP WITH TIME ZONE, "
|
|
"updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()"
|
|
")"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS last_reconcile_started_at TIMESTAMP WITH TIME ZONE"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS last_reconcile_completed_at TIMESTAMP WITH TIME ZONE"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS last_reconcile_error VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS wallet_rpc VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS daemon VARCHAR"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS daemon_height INTEGER"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS checked_at TIMESTAMP WITH TIME ZONE"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"ALTER TABLE system_status "
|
|
"ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"INSERT INTO system_status (name) VALUES ('reconciler') "
|
|
"ON CONFLICT (name) DO NOTHING"
|
|
)
|
|
)
|
|
connection.execute(
|
|
text(
|
|
"INSERT INTO system_status (name) VALUES ('monero_connectivity') "
|
|
"ON CONFLICT (name) DO NOTHING"
|
|
)
|
|
)
|
|
finally:
|
|
if lock_acquired:
|
|
connection.execute(
|
|
text("SELECT pg_advisory_unlock(:lock_id)"), {"lock_id": lock_id}
|
|
)
|
|
|
|
|
|
app.include_router(router)
|
|
app.include_router(btcpay_router)
|
|
app.include_router(admin_router)
|