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:
@@ -6,7 +6,9 @@
|
||||
//! changes between targets.
|
||||
|
||||
use anyhow::Context;
|
||||
use keysat::{api, btcpay, config, crypto, db, license_self, payment, reconcile, webhooks};
|
||||
use keysat::{
|
||||
analytics, api, btcpay, config, crypto, db, license_self, payment, reconcile, webhooks,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
@@ -80,6 +82,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Spawn background loops before handing state to the router.
|
||||
reconcile::spawn(state.clone());
|
||||
webhooks::spawn_delivery_worker(state.clone());
|
||||
// Opt-in community analytics — every tick checks the toggle
|
||||
// and short-circuits if disabled (default), so spawning is safe
|
||||
// unconditionally.
|
||||
analytics::spawn(state.clone());
|
||||
|
||||
// Hourly session reaper — drops sessions whose expires_at < now.
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user