From e20824905cd20ec98abbc671731a423011973ba7 Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 27 Feb 2026 12:36:31 -0500 Subject: [PATCH 1/2] fix(): update posthog calls --- claim-db-worker/app/api/analytics/route.ts | 39 ++++----- .../app/api/auth/callback/route.ts | 47 ++-------- claim-db-worker/app/api/claim/route.ts | 14 ++- .../components/PageViewTracker.tsx | 28 +++--- .../components/PostHogProvider.tsx | 39 ++------- claim-db-worker/lib/analytics-client.ts | 56 ++++++++++++ claim-db-worker/lib/analytics.ts | 85 ++++++++++++++++--- claim-db-worker/lib/env.ts | 16 ++-- 8 files changed, 190 insertions(+), 134 deletions(-) create mode 100644 claim-db-worker/lib/analytics-client.ts diff --git a/claim-db-worker/app/api/analytics/route.ts b/claim-db-worker/app/api/analytics/route.ts index 7a48827..48b875c 100644 --- a/claim-db-worker/app/api/analytics/route.ts +++ b/claim-db-worker/app/api/analytics/route.ts @@ -1,14 +1,13 @@ import { NextRequest, NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; +import { sendAnalyticsEvent } from "@/lib/analytics"; import { buildRateLimitKey } from "@/lib/server/ratelimit"; export async function POST(request: NextRequest) { const env = getEnv(); - const url = new URL(request.url); const key = buildRateLimitKey(request); - // --- Simple rate limiting --- const { success } = await env.CLAIM_DB_RATE_LIMITER.limit({ key }); if (!success) { return NextResponse.json( @@ -21,16 +20,13 @@ export async function POST(request: NextRequest) { ); } - if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { - return NextResponse.json({ success: true }); - } - try { - const { - event, - properties, - }: { event: string; properties: Record } = - await request.json(); + const body = (await request.json()) as { + event?: string; + properties?: Record; + }; + + const event = body.event?.trim(); if (!event) { return NextResponse.json( @@ -39,19 +35,14 @@ export async function POST(request: NextRequest) { ); } - await fetch(`${env.POSTHOG_API_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: env.POSTHOG_API_KEY, - event, - properties: properties || {}, - distinct_id: "web-claim", - }), - }); + if (!event.startsWith("create_db:")) { + return NextResponse.json( + { error: "Event must start with create_db:" }, + { status: 400 } + ); + } + + await sendAnalyticsEvent(event, body.properties || {}); return NextResponse.json({ success: true }); } catch (error) { diff --git a/claim-db-worker/app/api/auth/callback/route.ts b/claim-db-worker/app/api/auth/callback/route.ts index b8ac49f..f0f017d 100644 --- a/claim-db-worker/app/api/auth/callback/route.ts +++ b/claim-db-worker/app/api/auth/callback/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; import { exchangeCodeForToken, validateProject } from "@/lib/auth-utils"; +import { sendAnalyticsEvent } from "@/lib/analytics"; import { redirectToError, redirectToSuccess, @@ -9,41 +10,6 @@ import { import { transferProject } from "@/lib/project-transfer"; import { buildRateLimitKey } from "@/lib/server/ratelimit"; -async function sendServerAnalyticsEvent( - event: string, - properties: Record, - request: NextRequest -) { - const env = getEnv(); - - if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { - return; - } - - try { - await fetch(`${env.POSTHOG_API_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: env.POSTHOG_API_KEY, - event, - properties: { - ...properties, - $current_url: request.url, - $user_agent: request.headers.get("user-agent"), - }, - distinct_id: "server-claim", - timestamp: new Date().toISOString(), - }), - }); - } catch (error) { - console.error("Failed to send server analytics event:", error); - } -} - export async function GET(request: NextRequest) { try { const env = getEnv(); @@ -98,7 +64,7 @@ export async function GET(request: NextRequest) { } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; - await sendServerAnalyticsEvent( + await sendAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, @@ -115,13 +81,12 @@ export async function GET(request: NextRequest) { } // Validate project exists and get project data - let projectData; try { - projectData = await validateProject(projectID); + await validateProject(projectID); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; - await sendServerAnalyticsEvent( + await sendAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, @@ -176,7 +141,7 @@ export async function GET(request: NextRequest) { }; const databaseId = (databases.data?.[0]?.id ?? "").replace(/^db_/, ""); - await sendServerAnalyticsEvent( + await sendAnalyticsEvent( "create_db:claim_successful", { "project-id": projectID, @@ -194,7 +159,7 @@ export async function GET(request: NextRequest) { databaseId ); } else { - await sendServerAnalyticsEvent( + await sendAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, diff --git a/claim-db-worker/app/api/claim/route.ts b/claim-db-worker/app/api/claim/route.ts index c0c9220..1ab66d0 100644 --- a/claim-db-worker/app/api/claim/route.ts +++ b/claim-db-worker/app/api/claim/route.ts @@ -23,14 +23,22 @@ export async function GET(request: NextRequest) { } const projectID = url.searchParams.get("projectID"); + const utmSource = url.searchParams.get("utm_source"); + const utmMedium = url.searchParams.get("utm_medium"); if (!projectID || projectID === "undefined") { return NextResponse.json({ error: "Missing project ID" }, { status: 400 }); } - await sendAnalyticsEvent("create_db:claim_viewed", { - "project-id": projectID, - }); + await sendAnalyticsEvent( + "create_db:claim_viewed", + { + "project-id": projectID, + utm_source: utmSource || undefined, + utm_medium: utmMedium || undefined, + }, + request + ); return NextResponse.json({ success: true }); } diff --git a/claim-db-worker/components/PageViewTracker.tsx b/claim-db-worker/components/PageViewTracker.tsx index 3294c37..a19abe0 100644 --- a/claim-db-worker/components/PageViewTracker.tsx +++ b/claim-db-worker/components/PageViewTracker.tsx @@ -2,29 +2,25 @@ import { Suspense, useEffect } from "react"; import { usePathname, useSearchParams } from "next/navigation"; -import { sendAnalyticsEvent } from "@/lib/analytics"; +import { sendAnalyticsEvent } from "@/lib/analytics-client"; function PageViewTrackerContent() { const pathname = usePathname(); const searchParams = useSearchParams(); + const search = searchParams?.toString() ?? ""; useEffect(() => { - if (typeof window === "undefined") return; + if (!pathname || typeof window === "undefined") return; - if (pathname) { - const url = window.location.href; - const search = searchParams?.toString(); - const fullPath = search ? `${pathname}?${search}` : pathname; - - sendAnalyticsEvent("create_db:claim_page_viewed", { - path: pathname, - full_path: fullPath, - url: url, - referrer: document.referrer || "", - timestamp: new Date().toISOString(), - }); - } - }, [pathname, searchParams]); + const fullPath = search ? `${pathname}?${search}` : pathname; + void sendAnalyticsEvent("create_db:claim_page_viewed", { + path: pathname, + full_path: fullPath, + url: window.location.href, + referrer: document.referrer || "", + timestamp: new Date().toISOString(), + }); + }, [pathname, search]); return null; } diff --git a/claim-db-worker/components/PostHogProvider.tsx b/claim-db-worker/components/PostHogProvider.tsx index d699f65..98444f4 100644 --- a/claim-db-worker/components/PostHogProvider.tsx +++ b/claim-db-worker/components/PostHogProvider.tsx @@ -1,37 +1,12 @@ "use client"; -import posthog, { PostHog } from "posthog-js"; -import { PostHogProvider as PHProvider } from "posthog-js/react"; - -declare global { - interface Window { - posthog: PostHog; - } -} +import { PageViewTracker } from "@/components/PageViewTracker"; export function PostHogProvider({ children }: { children: React.ReactNode }) { - if ( - typeof window !== "undefined" && - process.env.NEXT_PUBLIC_POSTHOG_API_KEY - ) { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "", { - api_host: - process.env.NEXT_PUBLIC_POSTHOG_API_HOST || - "https://proxyhog.prisma-data.net", - person_profiles: "identified_only", - capture_pageview: true, - capture_pageleave: true, - autocapture: true, - debug: process.env.NODE_ENV === "development", // Enable debug in development - loaded: (posthog) => { - if (process.env.NODE_ENV === "development") { - console.log("PostHog initialized in debug mode"); - // Make posthog available globally for debugging - window.posthog = posthog; - } - }, - }); - } - - return {children}; + return ( + <> + + {children} + + ); } diff --git a/claim-db-worker/lib/analytics-client.ts b/claim-db-worker/lib/analytics-client.ts new file mode 100644 index 0000000..1901c34 --- /dev/null +++ b/claim-db-worker/lib/analytics-client.ts @@ -0,0 +1,56 @@ +"use client"; + +const DISTINCT_ID_STORAGE_KEY = "create_db:distinct_id"; + +function getDistinctId(): string { + if (typeof window === "undefined") { + return "claim-db-worker"; + } + + try { + const existing = window.localStorage.getItem(DISTINCT_ID_STORAGE_KEY); + if (existing) { + return existing; + } + + const nextId = crypto.randomUUID(); + window.localStorage.setItem(DISTINCT_ID_STORAGE_KEY, nextId); + return nextId; + } catch { + return "claim-db-worker"; + } +} + +export async function sendAnalyticsEvent( + event: string, + properties: Record = {} +): Promise { + if (!event.startsWith("create_db:")) { + return; + } + + try { + const response = await fetch("/api/analytics", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + keepalive: true, + body: JSON.stringify({ + event, + properties: { + ...properties, + distinct_id: getDistinctId(), + }, + }), + }); + + if (!response.ok && process.env.NODE_ENV === "development") { + console.error("Failed to send analytics event:", response.status); + } + } catch (error) { + if (process.env.NODE_ENV === "development") { + console.error("Failed to send analytics event:", error); + } + } +} diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index 438400c..61e4ca0 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -1,18 +1,77 @@ -"use client"; +import type { NextRequest } from "next/server"; +import { getEnv } from "@/lib/env"; -export const sendAnalyticsEvent = async ( +type AnalyticsProperties = Record; + +function isCreateDbEvent(event: string) { + return event.startsWith("create_db:"); +} + +export async function sendAnalyticsEvent( event: string, - properties: Record -) => { - const response = await fetch(`${window.location.origin}/api/analytics`, { - method: "POST", - headers: { - "Content-Type": "application/json", + properties: AnalyticsProperties = {}, + request?: NextRequest +): Promise { + if (!isCreateDbEvent(event)) { + return; + } + + const env = getEnv(); + const host = env.POSTHOG_API_HOST?.replace(/\/+$/, ""); + const key = env.POSTHOG_API_KEY; + + if (!host || !key) { + return; + } + + const { distinct_id: distinctIdProp, ...restProperties } = properties; + const projectId = + typeof restProperties["project-id"] === "string" + ? restProperties["project-id"] + : undefined; + const distinctId = + typeof distinctIdProp === "string" && distinctIdProp.length > 0 + ? distinctIdProp + : projectId + ? `project:${projectId}` + : "claim-db-worker"; + + const payload = { + api_key: key, + event, + distinct_id: distinctId, + properties: { + $process_person_profile: false, + ...restProperties, + ...(request + ? { + $current_url: request.url, + $user_agent: request.headers.get("user-agent") ?? undefined, + } + : {}), }, - body: JSON.stringify({ event, properties }), - }); + timestamp: new Date().toISOString(), + }; + + try { + const response = await fetch(`${host}/e`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${key}`, + }, + body: JSON.stringify(payload), + }); - if (!response.ok) { - console.error("Failed to send analytics event:", response); + if (!response.ok) { + console.error( + `Failed to send PostHog event '${event}': ${response.status} ${response.statusText}` + ); + } + } catch (error) { + console.error( + `Failed to send PostHog event '${event}':`, + error instanceof Error ? error.message : String(error) + ); } -}; +} diff --git a/claim-db-worker/lib/env.ts b/claim-db-worker/lib/env.ts index fea0c92..8c97766 100644 --- a/claim-db-worker/lib/env.ts +++ b/claim-db-worker/lib/env.ts @@ -4,8 +4,9 @@ export interface Env { CLIENT_SECRET: string; CLIENT_ID: string; CREATE_DB_DATASET: any; - POSTHOG_API_KEY: string; - POSTHOG_API_HOST: string; + POSTHOG_API_KEY?: string; + POSTHOG_API_HOST?: string; + POSTHOG_PROXY_HOST?: string; } export function getEnv(): Env { @@ -18,7 +19,10 @@ export function getEnv(): Env { CLIENT_ID: (globalThis as any).CLIENT_ID, CREATE_DB_DATASET: (globalThis as any).CREATE_DB_DATASET, POSTHOG_API_KEY: (globalThis as any).POSTHOG_API_KEY, - POSTHOG_API_HOST: (globalThis as any).POSTHOG_API_HOST, + POSTHOG_API_HOST: + (globalThis as any).POSTHOG_API_HOST || + (globalThis as any).POSTHOG_PROXY_HOST, + POSTHOG_PROXY_HOST: (globalThis as any).POSTHOG_PROXY_HOST, }; } @@ -33,7 +37,9 @@ export function getEnv(): Env { CREATE_DB_DATASET: (process.env.CREATE_DB_DATASET as any) || { writeDataPoint: async () => {}, // No-op analytics for development }, - POSTHOG_API_KEY: process.env.POSTHOG_API_KEY!, - POSTHOG_API_HOST: process.env.POSTHOG_API_HOST!, + POSTHOG_API_KEY: process.env.POSTHOG_API_KEY, + POSTHOG_API_HOST: + process.env.POSTHOG_API_HOST || process.env.POSTHOG_PROXY_HOST, + POSTHOG_PROXY_HOST: process.env.POSTHOG_PROXY_HOST, }; } From 831a53a4662a4f40d31c93561a4db068fae394be Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 27 Feb 2026 12:49:47 -0500 Subject: [PATCH 2/2] chore(): `POSTHOG_API_HOST` -> `POSTHOG_PROXY_HOST` --- claim-db-worker/__tests__/callback-api.test.ts | 2 +- claim-db-worker/lib/analytics.ts | 2 +- claim-db-worker/lib/env.ts | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/claim-db-worker/__tests__/callback-api.test.ts b/claim-db-worker/__tests__/callback-api.test.ts index b1b175b..7bcd007 100644 --- a/claim-db-worker/__tests__/callback-api.test.ts +++ b/claim-db-worker/__tests__/callback-api.test.ts @@ -9,7 +9,7 @@ vi.mock("@/lib/env", () => ({ limit: vi.fn(() => Promise.resolve({ success: true })), }, POSTHOG_API_KEY: "test-key", - POSTHOG_API_HOST: "https://app.posthog.com", + POSTHOG_PROXY_HOST: "https://proxyhog.prisma-data.net", })), })); diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index 61e4ca0..ed72293 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -17,7 +17,7 @@ export async function sendAnalyticsEvent( } const env = getEnv(); - const host = env.POSTHOG_API_HOST?.replace(/\/+$/, ""); + const host = env.POSTHOG_PROXY_HOST?.replace(/\/+$/, ""); const key = env.POSTHOG_API_KEY; if (!host || !key) { diff --git a/claim-db-worker/lib/env.ts b/claim-db-worker/lib/env.ts index 8c97766..23a6e74 100644 --- a/claim-db-worker/lib/env.ts +++ b/claim-db-worker/lib/env.ts @@ -5,7 +5,6 @@ export interface Env { CLIENT_ID: string; CREATE_DB_DATASET: any; POSTHOG_API_KEY?: string; - POSTHOG_API_HOST?: string; POSTHOG_PROXY_HOST?: string; } @@ -19,9 +18,6 @@ export function getEnv(): Env { CLIENT_ID: (globalThis as any).CLIENT_ID, CREATE_DB_DATASET: (globalThis as any).CREATE_DB_DATASET, POSTHOG_API_KEY: (globalThis as any).POSTHOG_API_KEY, - POSTHOG_API_HOST: - (globalThis as any).POSTHOG_API_HOST || - (globalThis as any).POSTHOG_PROXY_HOST, POSTHOG_PROXY_HOST: (globalThis as any).POSTHOG_PROXY_HOST, }; } @@ -38,8 +34,6 @@ export function getEnv(): Env { writeDataPoint: async () => {}, // No-op analytics for development }, POSTHOG_API_KEY: process.env.POSTHOG_API_KEY, - POSTHOG_API_HOST: - process.env.POSTHOG_API_HOST || process.env.POSTHOG_PROXY_HOST, POSTHOG_PROXY_HOST: process.env.POSTHOG_PROXY_HOST, }; }