/admin redirected to the invoice list, which stopped making sense once
the admin also ran the bingo. Make it a dashboard with one tile per
area: invoice totals, outstanding amount per currency, overdue count and
the five latest invoices; the bingo score, reserve size, latest incident
and card cells still missing a photo. A shared nav links the sections.
Tiles are per area so they can follow user permissions later.
Assisted-by: Claude:opus-5
Add a second public site to the same process: a 5x5 card of the 25
largest Russian datacenters where a cell is crossed out once an incident
with a public source is recorded (Yandex Sasovo and Kaluga drone strikes
on 2026-10-08/09 are seeded). config.ru dispatches by Host, so it rides
the existing container, DB and bucket instead of a new deploy.
The card is localized ru/en/uk with per-locale columns falling back to
ru. Content is managed from /admin/bingo on asxp.io, reusing AdminAuth
and CSRF. Photos go to the existing bucket under bingo/ and are proxied
by the app, so the bucket stays private and storage needs no change.
The seed data was fact-checked against operator sites and press on
2026-10-10; several entries in the original list were wrong or vague.
Assisted-by: Claude:opus-5
One asset per invoice from a registry (lib/crypto_asset.rb): BTC, LTC,
ETH, XMR, SOL, ALGO, USDT/USDC on ERC-20/TRC-20/BEP-20/Solana/Algorand.
Migration 003 generalizes the ltc_* columns to crypto_* + crypto_coin
(existing LTC invoices backfilled). QR payload adapts per asset: BIP21
amount URIs where supported, bare address for tokens with a network
hint on the PDF. Default addresses come from the CRYPTO_ADDRESSES
secret (JSON code=>address); LTC_ADDRESS still works as legacy.
Mounts AdminAuth (HTTP Basic on /admin/*) and adds two route groups:
- Admin (/admin/invoices): list, new form, create, show, toggle-paid.
POST /admin/invoices builds the Invoice, renders the PDF, uploads to
MinIO, then persists the row with the resulting pdf_key.
- Public (/i/:uuid): an HTML landing page showing client name, number,
total, and status badge, plus /i/:uuid/pdf which 302s to a short-lived
MinIO presigned URL. Anyone with the UUID can fetch the PDF — that's
the whole point of the link-based delivery.
The Invoice model is required only when DATABASE_URL is set, so the
contact-form site still boots in dev without Postgres. Invoicing routes
return 503 in that mode rather than crashing on first request.