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).
This commit is contained in:
@@ -69,6 +69,7 @@ pub mod self_license;
|
||||
pub mod session_layer;
|
||||
pub mod tier;
|
||||
pub mod validate;
|
||||
pub mod community;
|
||||
pub mod db_info;
|
||||
pub mod recover;
|
||||
pub mod webhook;
|
||||
@@ -324,6 +325,16 @@ pub fn router(state: AppState) -> Router {
|
||||
// Database health snapshot — operator-facing sanity check
|
||||
// against the catastrophic-loss risk; see db_info.rs.
|
||||
.route("/v1/admin/db-info", get(db_info::get))
|
||||
// Opt-in community analytics. Off by default; toggling on
|
||||
// requires the operator to confirm a collector URL.
|
||||
.route(
|
||||
"/v1/admin/community-analytics",
|
||||
get(community::get).post(community::set),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/community-analytics/reset",
|
||||
post(community::reset),
|
||||
)
|
||||
// Discount / referral codes.
|
||||
.route(
|
||||
"/v1/admin/discount-codes",
|
||||
|
||||
Reference in New Issue
Block a user