The old streak was non-functional: streaks.current_days was always 0 (never computed/incremented), and the profile page read me.streak (0) + account created_at as the "since" date — showing "0 days protected since <signup>" for everyone. This is the DiGA key metric, so it had to be rebuilt. New model: optimistic protection-coverage based on actual VPN/MDM protection state, never resets to 0. - backend: append-only protection_state_log + migration; POST /api/protection/event (ingestion, deduped) + GET /api/protection/coverage (read-time compute, no cron); server-side cooldown_disable event on cooldown resolve. Generous >6h-off/day rule. - frontend: report protection on/off transitions (initial + flips, deduped) from useProtectionState; rewrote profile StreakSection → half-donut (protected vs unprotected) + progress bar (current streak → personal record) + empty state. - coverage starts fresh from deploy (no historical backfill — clean data for DiGA). - spec: docs/specs/protection-coverage-streak.md (shared contract). - old streaks/streak_events/profiles.streak left intact (coach/scores consumers). Also adds go-to-market one-pagers under docs/marketing/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
1.4 KiB
SQL
34 lines
1.4 KiB
SQL
-- Migration: protection_state_log
|
|
-- Adds append-only protection-state transition log (DiGA-Kernmetrik).
|
|
--
|
|
-- Purpose:
|
|
-- Replaces the broken streaks.current_days=0 metric with a factual,
|
|
-- optimistic coverage model based on actual VPN/MDM protection state.
|
|
-- Drives GET /api/protection/coverage → protectedDays, currentStreakDays,
|
|
-- longestStreakDays, etc.
|
|
--
|
|
-- Design decisions:
|
|
-- - Append-only: never UPDATE rows, only INSERT new transitions.
|
|
-- - Dedup enforced at application layer (server + client both deduplicate).
|
|
-- - source column is VARCHAR (not enum) for forward-compatibility.
|
|
-- - Index on (user_id, occurred_at) covers the primary query pattern:
|
|
-- all events for a user ordered by time.
|
|
--
|
|
-- Non-breaking: purely additive, no existing columns modified.
|
|
--
|
|
-- Deploy: pnpm prisma migrate deploy (on server via GitHub Actions pipeline)
|
|
|
|
CREATE TABLE "rebreak"."protection_state_log" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"user_id" UUID NOT NULL,
|
|
"active" BOOLEAN NOT NULL,
|
|
"source" VARCHAR(64) NOT NULL,
|
|
"occurred_at" TIMESTAMPTZ NOT NULL,
|
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
CONSTRAINT "protection_state_log_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE INDEX "protection_state_log_user_id_occurred_at_idx"
|
|
ON "rebreak"."protection_state_log" ("user_id", "occurred_at");
|