feat(analytics): Umami website-analytics suite — world map, realtime, sessions, heatmap, pixel tracking, tracked links
Adds the read-side Umami integration queued in last week's website-analytics plan (Phases 1–6 of `docs/website-analytics-flesh-out-plan.md`): - Realtime panel polls Umami at 5s intervals; world map renders visitor origins via echarts + `public/world-map/echarts-world.json` topo. - Sessions list + session-detail-sheet drill-down (per-session event timeline pulled from `/api/v1/website-analytics`). - Weekly heatmap (day-of-week × hour-of-day) for engagement timing. - Metric-detail pages under `/[portSlug]/website-analytics/[metric]` for pageviews / referrers / events deep-dives. - Email-pixel write path: `/api/public/email-pixel/[sendId]` 1×1 GIF beacon backed by `email_open_tracking` (migration 0076); resolves inline on render in inbox. - Tracked-link redirect: `/q/[slug]` routes through `tracked_links` (migration 0077) and forwards to the canonical destination after logging the click. - Dashboard `website-glance-tile` now reads from the live Umami service instead of placeholder data. Deps: `@umami/node`, `echarts`, `echarts-for-react`, `@types/geojson`, `@types/topojson-client`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
src/lib/db/migrations/0076_email_open_tracking.sql
Normal file
19
src/lib/db/migrations/0076_email_open_tracking.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Phase 4b — email open tracking via a 1×1 pixel endpoint.
|
||||
-- Adds a per-send open log + cached aggregates on document_sends.
|
||||
|
||||
ALTER TABLE "document_sends"
|
||||
ADD COLUMN IF NOT EXISTS "track_opens" boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS "first_opened_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "open_count" integer NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "document_send_opens" (
|
||||
"id" text PRIMARY KEY,
|
||||
"port_id" text NOT NULL REFERENCES "ports"("id"),
|
||||
"send_id" text NOT NULL REFERENCES "document_sends"("id") ON DELETE CASCADE,
|
||||
"opened_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"user_agent" text,
|
||||
"referer" text
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "idx_dso_send" ON "document_send_opens" ("send_id", "opened_at" DESC);
|
||||
CREATE INDEX IF NOT EXISTS "idx_dso_port" ON "document_send_opens" ("port_id", "opened_at" DESC);
|
||||
36
src/lib/db/migrations/0077_tracked_links.sql
Normal file
36
src/lib/db/migrations/0077_tracked_links.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- Phase 4c — tracked redirect links for email click-through tracking.
|
||||
-- A short URL at /q/<slug> redirects to the target and records the
|
||||
-- click against the originating send. Cross-posted to Umami as a
|
||||
-- `link-clicked` event.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "tracked_links" (
|
||||
"id" text PRIMARY KEY,
|
||||
"port_id" text NOT NULL REFERENCES "ports"("id"),
|
||||
"slug" text NOT NULL,
|
||||
"target_url" text NOT NULL,
|
||||
"send_id" text REFERENCES "document_sends"("id") ON DELETE SET NULL,
|
||||
"click_count" integer NOT NULL DEFAULT 0,
|
||||
"first_clicked_at" timestamptz,
|
||||
"last_clicked_at" timestamptz,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"created_by_user_id" text REFERENCES "user"("id") ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Slugs are scoped to a port; an admin can rotate them per-port. Global
|
||||
-- uniqueness isn't required because /q/<slug> is gated by tenancy in
|
||||
-- the route handler.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "uniq_tracked_links_slug" ON "tracked_links" ("slug");
|
||||
CREATE INDEX IF NOT EXISTS "idx_tracked_links_send" ON "tracked_links" ("send_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_tracked_links_port" ON "tracked_links" ("port_id", "created_at" DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "tracked_link_clicks" (
|
||||
"id" text PRIMARY KEY,
|
||||
"tracked_link_id" text NOT NULL REFERENCES "tracked_links"("id") ON DELETE CASCADE,
|
||||
"port_id" text NOT NULL REFERENCES "ports"("id"),
|
||||
"clicked_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"user_agent" text,
|
||||
"referer" text
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "idx_tlc_link" ON "tracked_link_clicks" ("tracked_link_id", "clicked_at" DESC);
|
||||
CREATE INDEX IF NOT EXISTS "idx_tlc_port" ON "tracked_link_clicks" ("port_id", "clicked_at" DESC);
|
||||
Reference in New Issue
Block a user