Commit Graph

5 Commits

Author SHA1 Message Date
Grant 356d17fdde Multi-currency Phase 2 — admin write path (currency picker)
Backend:
- POST /v1/admin/products accepts both forms:
  - legacy: { price_sats: 50000 }
  - typed:  { price_currency: 'USD', price_value: 4900 }
  Whitelist enforced (SAT|USD|EUR). Mismatched legacy + typed → 400
  to catch half-migrated clients sending stale price_sats alongside
  fresh price_value.
- repo::create_product_with_currency: SAT → dual-write price_sats =
  price_value; USD/EUR → price_sats = 0 until first invoice creation
  triggers a rate lookup (Phase 4 + 5).
- Test admin_create_product_accepts_legacy_and_typed_currency_forms
  pins 6 happy/sad paths.

Frontend (Products page):
- Create-product form has a currency picker (sats / USD / EUR).
  Picker swaps the unit hint + step in place.
- Decimal entry on USD/EUR is converted to cents on the way out.
- Products table renders prices via formatProductPrice(): USD
  products show "$49.00" with optional "≈ 75k sats" hint.

Test count: 34 (was 33).
2026-05-08 12:11:36 -05:00
Grant d827b1aaab Opt-in community analytics + admin UI surface
Closes the last T2 plan item. Off by default; toggling on requires
the operator to confirm a collector URL (an empty URL is "armed but
silent"). The toggle lives on the admin Overview page next to the
public-key card — the right place for a privacy-affecting choice
since it's where operators actually live.

What's sent (per the in-card "Show me exactly what gets sent"
disclosure, and pinned by the test):
- install_uuid: random UUIDv4 generated on first opt-in. NOT
  derived from operator_name, store id, public URL, or any
  other identifier. Wipeable via the Reset button.
- daemon_version (CARGO_PKG_VERSION).
- tier (creator/pro/patron/unlicensed) — the same string the
  admin tier endpoint already exposes.
- counts: products, active_licenses, settled_invoices — each
  floored to the nearest 5 (anti-fingerprinting; an exact license
  count uniquely identifies an operator over time).
- uptime_bucket: <1d / 1-7d / 1-4w / >4w (bucketed, not exact).

What's NOT sent (test asserts none of these strings appear in the
preview heartbeat): operator_name, public_url, store_id, api_key,
buyer_email, btcpay_url. Also no product/policy slugs or names, no
license/invoice ids, no fingerprints, no webhook secrets.

Backend:
- src/analytics.rs — heartbeat builder, opt-in check, daily
  background tick (5min initial grace period after boot).
- src/api/community.rs — GET / POST / reset admin endpoints.
- main.rs spawns the background tick unconditionally; the tick
  is a no-op if disabled OR no collector URL configured.

Frontend (web/index.html, Overview page):
- Toggle + collector URL input + privacy disclosure showing the
  EXACT JSON shape that would be sent (renders the live preview
  heartbeat from /v1/admin/community-analytics).
- "Reset install_uuid" button so an operator who's been beaconing
  under one identifier can start fresh.

Also includes the configureBtcpay.ts idempotency change from
v0.1.0:46 (already committed; touched again here only because the
diff includes the .ts file in the same dirty-tree push).

Test count: 32 (was 31; +1 community_analytics_opt_in_and_privacy_contract
which seeds 23 licenses and verifies the heartbeat reports 20 —
proves the floor-to-5 anti-fingerprinting is in effect).
2026-05-08 11:35:50 -05:00
Grant 4adf5a8593 Admin SPA: surface webhook delivery history (DLQ visible)
The /v1/admin/webhook-deliveries endpoints from v0.1.0:43 were
operator-actionable via curl but invisible in the dashboard. Adds a
"Delivery history" section to the Webhooks page showing recent
deliveries with a status filter (defaults to "Failed (DLQ)" so the
problem case is what an operator sees first).

Each row shows created-at, event type, status badge (delivered /
failed / pending), attempt count, last status code, and last_error
inline beneath the status when present (so operators don't have to
chase a separate "details" view to know why a delivery failed).
Non-delivered rows get a Retry button that re-queues via the
existing POST /v1/admin/webhook-deliveries/:id/retry; the worker
picks up the retried row on its next 5s tick.

No backend changes. The endpoints landed in :43; this commit is
just the front-end surface.
2026-05-08 10:41:44 -05:00
Grant beedd07f07 v0.1.0:25–40 — tier model, edit forms, force-delete, license counts, migration 0009 (and hotfix); KEYSAT_INTEGRATION.md merged with downstream-LLM revisions 2026-05-07 23:35:22 -05:00
Grant 6ac118ae70 v0.1.0:24 — Keysat licensing service end-to-end
Daemon, StartOS wrapper, admin SPA, public buy/thank-you pages,
discount codes, free-license redemption, Apply-discount UX,
self-licensing, and v0.1.0 release notes.
2026-05-07 10:33:39 -05:00