From 8be4556c8751175f150500e18acfc9ef8e8a811b Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 5 Sep 2025 16:50:41 -0400 Subject: [PATCH 1/6] fix: analytics --- claim-db-worker/app/api/analytics/route.ts | 54 +++++++++++++++ .../app/api/auth/callback/route.ts | 24 ++++--- claim-db-worker/app/api/claim/route.ts | 27 +------- claim-db-worker/app/layout.tsx | 2 + .../components/PageViewTracker.tsx | 20 ++++++ claim-db-worker/lib/analytics.ts | 65 ++++--------------- 6 files changed, 105 insertions(+), 87 deletions(-) create mode 100644 claim-db-worker/app/api/analytics/route.ts create mode 100644 claim-db-worker/components/PageViewTracker.tsx diff --git a/claim-db-worker/app/api/analytics/route.ts b/claim-db-worker/app/api/analytics/route.ts new file mode 100644 index 0000000..17ae07b --- /dev/null +++ b/claim-db-worker/app/api/analytics/route.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEnv } from "@/lib/env"; + +export async function POST(request: NextRequest) { + const env = getEnv(); + + const rateLimitResult = await env.CLAIM_DB_RATE_LIMITER.limit({ + key: request.url, + }); + if (!rateLimitResult.success) { + return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 }); + } + + const POSTHOG_API_KEY = env.POSTHOG_API_KEY; + const POSTHOG_PROXY_HOST = env.POSTHOG_API_HOST; + + if (!POSTHOG_API_KEY || !POSTHOG_PROXY_HOST) { + return NextResponse.json({ success: true }); + } + + try { + const { + event, + properties, + }: { event: string; properties: Record } = + await request.json(); + + if (!event) { + return NextResponse.json( + { error: "Event name required" }, + { status: 400 } + ); + } + + await fetch(`${POSTHOG_PROXY_HOST}/e`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${POSTHOG_API_KEY}`, + }, + body: JSON.stringify({ + api_key: POSTHOG_API_KEY, + event, + properties: properties || {}, + distinct_id: "web-claim", + }), + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Failed to send PostHog event:", error); + return NextResponse.json({ error: "Analytics failed" }, { status: 500 }); + } +} diff --git a/claim-db-worker/app/api/auth/callback/route.ts b/claim-db-worker/app/api/auth/callback/route.ts index 574b34d..b693082 100644 --- a/claim-db-worker/app/api/auth/callback/route.ts +++ b/claim-db-worker/app/api/auth/callback/route.ts @@ -1,7 +1,7 @@ import { NextRequest } from "next/server"; import { getEnv } from "@/lib/env"; import { exchangeCodeForToken, validateProject } from "@/lib/auth-utils"; -import { trackClaimSuccess, trackClaimFailure } from "@/lib/analytics"; +import { sendAnalyticsEvent } from "@/lib/analytics"; import { redirectToError, redirectToSuccess, @@ -59,7 +59,10 @@ export async function GET(request: NextRequest) { } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; - await trackClaimFailure(projectID, 0, errorMessage); + await sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID, + error: errorMessage, + }); return redirectToError( request, "Authentication Failed", @@ -74,6 +77,10 @@ export async function GET(request: NextRequest) { } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; + await sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID, + error: errorMessage, + }); return redirectToError( request, "Project Not Found", @@ -89,14 +96,15 @@ export async function GET(request: NextRequest) { ); if (transferResult.success) { - await trackClaimSuccess(projectID); + await sendAnalyticsEvent("create_db:claim_successful", { + "project-id": projectID, + }); return redirectToSuccess(request, projectID); } else { - await trackClaimFailure( - projectID, - transferResult.status!, - transferResult.error! - ); + await sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID, + error: transferResult.error!, + }); return redirectToError( request, "Transfer Failed", diff --git a/claim-db-worker/app/api/claim/route.ts b/claim-db-worker/app/api/claim/route.ts index d869cca..4dbdcb8 100644 --- a/claim-db-worker/app/api/claim/route.ts +++ b/claim-db-worker/app/api/claim/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; +import { sendAnalyticsEvent } from "@/lib/analytics"; export async function GET(request: NextRequest) { const env = getEnv(); @@ -12,38 +13,14 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 }); } - async function sendPosthogEvent( - event: string, - properties: Record - ) { - const POSTHOG_API_KEY = env.POSTHOG_API_KEY; - const POSTHOG_PROXY_HOST = env.POSTHOG_API_HOST; - - await fetch(`${POSTHOG_PROXY_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: POSTHOG_API_KEY, - event, - properties, - distinct_id: "web-claim", - }), - }); - } - const projectID = searchParams.get("projectID"); if (!projectID || projectID === "undefined") { return NextResponse.json({ error: "Missing project ID" }, { status: 400 }); } - await sendPosthogEvent("create_db:claim_page_viewed", { + await sendAnalyticsEvent("create_db:claim_viewed", { "project-id": projectID, - "utm-source": searchParams.get("utm_source") || "unknown", - "utm-medium": searchParams.get("utm_medium") || "unknown", }); return NextResponse.json({ success: true }); diff --git a/claim-db-worker/app/layout.tsx b/claim-db-worker/app/layout.tsx index e0aca9e..a56b53b 100644 --- a/claim-db-worker/app/layout.tsx +++ b/claim-db-worker/app/layout.tsx @@ -5,6 +5,7 @@ import { Navbar } from "../components/Navbar"; import { Footer } from "../components/Footer"; import { DropProvider } from "./contexts/DropContext"; import { Toaster } from "react-hot-toast"; +import { PageViewTracker } from "@/components/PageViewTracker"; const barlow = Barlow({ weight: ["400", "500", "700", "800", "900"], @@ -47,6 +48,7 @@ export default function RootLayout({ +
diff --git a/claim-db-worker/components/PageViewTracker.tsx b/claim-db-worker/components/PageViewTracker.tsx new file mode 100644 index 0000000..7c61fd4 --- /dev/null +++ b/claim-db-worker/components/PageViewTracker.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useEffect } from "react"; +import { usePathname } from "next/navigation"; +import { sendAnalyticsEvent } from "@/lib/analytics"; + +export function PageViewTracker() { + const pathname = usePathname(); + + useEffect(() => { + if (pathname) { + sendAnalyticsEvent("create_db:claim_page_viewed", { + path: pathname, + timestamp: new Date().toISOString(), + }); + } + }, [pathname]); + + return null; +} diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index ecd2aa4..bdd1687 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -1,62 +1,19 @@ -import { getEnv } from "./env"; - -export async function sendPosthogEvent( +export const sendAnalyticsEvent = async ( event: string, properties: Record -) { - const env = getEnv(); - const POSTHOG_API_KEY = env.POSTHOG_API_KEY; - const POSTHOG_PROXY_HOST = env.POSTHOG_API_HOST; - - // Skip analytics if PostHog is not configured - if (!POSTHOG_API_KEY || !POSTHOG_PROXY_HOST) { - return; - } - - try { - await fetch(`${POSTHOG_PROXY_HOST}/e`, { +) => { + const response = await fetch( + `${process.env.NEXT_PUBLIC_BASE_URL}/api/analytics`, + { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${POSTHOG_API_KEY}`, }, - body: JSON.stringify({ - api_key: POSTHOG_API_KEY, - event, - properties, - distinct_id: "web-claim", - }), - }); - } catch (error) { - console.error("Failed to send PostHog event:", error); - } -} - -export async function trackClaimSuccess(projectID: string) { - const env = getEnv(); + body: JSON.stringify({ event, properties }), + } + ); - try { - env.CREATE_DB_DATASET.writeDataPoint({ - blobs: ["database_claimed"], - indexes: ["claim_db"], - }); - } catch (error) { - console.error("Failed to write analytics data point:", error); + if (!response.ok) { + console.error("Failed to send analytics event:", response); } - - await sendPosthogEvent("create_db:claim_successful", { - "project-id": projectID, - }); -} - -export async function trackClaimFailure( - projectID: string, - status: number, - error: string -) { - await sendPosthogEvent("create_db:claim_failed", { - "project-id": projectID, - status, - error, - }); -} +}; From 615a60bfee2ea163ee690485a31aa67617d9e513 Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 5 Sep 2025 17:06:48 -0400 Subject: [PATCH 2/6] fix: api routes fixed --- .../app/api/auth/callback/route.ts | 83 +++++++++++++++---- .../components/PageViewTracker.tsx | 12 ++- claim-db-worker/lib/analytics.ts | 19 ++--- 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/claim-db-worker/app/api/auth/callback/route.ts b/claim-db-worker/app/api/auth/callback/route.ts index b693082..0d751d5 100644 --- a/claim-db-worker/app/api/auth/callback/route.ts +++ b/claim-db-worker/app/api/auth/callback/route.ts @@ -1,7 +1,6 @@ import { NextRequest } from "next/server"; import { getEnv } from "@/lib/env"; import { exchangeCodeForToken, validateProject } from "@/lib/auth-utils"; -import { sendAnalyticsEvent } from "@/lib/analytics"; import { redirectToError, redirectToSuccess, @@ -9,6 +8,42 @@ import { } from "@/lib/response-utils"; import { transferProject } from "@/lib/project-transfer"; +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, + $ip: request.ip || request.headers.get("x-forwarded-for"), + $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(); @@ -59,10 +94,14 @@ export async function GET(request: NextRequest) { } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; - await sendAnalyticsEvent("create_db:claim_failed", { - "project-id": projectID, - error: errorMessage, - }); + await sendServerAnalyticsEvent( + "create_db:claim_failed", + { + "project-id": projectID, + error: errorMessage, + }, + request + ); return redirectToError( request, "Authentication Failed", @@ -77,10 +116,14 @@ export async function GET(request: NextRequest) { } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; - await sendAnalyticsEvent("create_db:claim_failed", { - "project-id": projectID, - error: errorMessage, - }); + await sendServerAnalyticsEvent( + "create_db:claim_failed", + { + "project-id": projectID, + error: errorMessage, + }, + request + ); return redirectToError( request, "Project Not Found", @@ -96,15 +139,23 @@ export async function GET(request: NextRequest) { ); if (transferResult.success) { - await sendAnalyticsEvent("create_db:claim_successful", { - "project-id": projectID, - }); + await sendServerAnalyticsEvent( + "create_db:claim_successful", + { + "project-id": projectID, + }, + request + ); return redirectToSuccess(request, projectID); } else { - await sendAnalyticsEvent("create_db:claim_failed", { - "project-id": projectID, - error: transferResult.error!, - }); + await sendServerAnalyticsEvent( + "create_db:claim_failed", + { + "project-id": projectID, + error: transferResult.error!, + }, + request + ); return redirectToError( request, "Transfer Failed", diff --git a/claim-db-worker/components/PageViewTracker.tsx b/claim-db-worker/components/PageViewTracker.tsx index 7c61fd4..58e97c1 100644 --- a/claim-db-worker/components/PageViewTracker.tsx +++ b/claim-db-worker/components/PageViewTracker.tsx @@ -1,20 +1,28 @@ "use client"; import { useEffect } from "react"; -import { usePathname } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { sendAnalyticsEvent } from "@/lib/analytics"; export function PageViewTracker() { const pathname = usePathname(); + const searchParams = useSearchParams(); useEffect(() => { 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]); + }, [pathname, searchParams]); return null; } diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index bdd1687..4296549 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -1,17 +1,16 @@ +"use client"; + export const sendAnalyticsEvent = async ( event: string, properties: Record ) => { - const response = await fetch( - `${process.env.NEXT_PUBLIC_BASE_URL}/api/analytics`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ event, properties }), - } - ); + const response = await fetch(`/api/analytics`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ event, properties }), + }); if (!response.ok) { console.error("Failed to send analytics event:", response); From 77a3c06ea3f9d414447a7cca3971438d9e93859b Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 5 Sep 2025 17:11:59 -0400 Subject: [PATCH 3/6] fix: page viewed --- claim-db-worker/lib/analytics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index 4296549..438400c 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -4,7 +4,7 @@ export const sendAnalyticsEvent = async ( event: string, properties: Record ) => { - const response = await fetch(`/api/analytics`, { + const response = await fetch(`${window.location.origin}/api/analytics`, { method: "POST", headers: { "Content-Type": "application/json", From 9be141152e32b63b20946687bbb761c488219cb5 Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Fri, 5 Sep 2025 17:19:08 -0400 Subject: [PATCH 4/6] fix: build error --- claim-db-worker/app/api/auth/callback/route.ts | 1 - claim-db-worker/components/PageViewTracker.tsx | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/claim-db-worker/app/api/auth/callback/route.ts b/claim-db-worker/app/api/auth/callback/route.ts index 0d751d5..d0f1d2b 100644 --- a/claim-db-worker/app/api/auth/callback/route.ts +++ b/claim-db-worker/app/api/auth/callback/route.ts @@ -32,7 +32,6 @@ async function sendServerAnalyticsEvent( properties: { ...properties, $current_url: request.url, - $ip: request.ip || request.headers.get("x-forwarded-for"), $user_agent: request.headers.get("user-agent"), }, distinct_id: "server-claim", diff --git a/claim-db-worker/components/PageViewTracker.tsx b/claim-db-worker/components/PageViewTracker.tsx index 58e97c1..3294c37 100644 --- a/claim-db-worker/components/PageViewTracker.tsx +++ b/claim-db-worker/components/PageViewTracker.tsx @@ -1,14 +1,16 @@ "use client"; -import { useEffect } from "react"; +import { Suspense, useEffect } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import { sendAnalyticsEvent } from "@/lib/analytics"; -export function PageViewTracker() { +function PageViewTrackerContent() { const pathname = usePathname(); const searchParams = useSearchParams(); useEffect(() => { + if (typeof window === "undefined") return; + if (pathname) { const url = window.location.href; const search = searchParams?.toString(); @@ -26,3 +28,11 @@ export function PageViewTracker() { return null; } + +export function PageViewTracker() { + return ( + + + + ); +} From 56d7a2ab68edf4afad7f84f17e06ae310f1b0cac Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Mon, 8 Sep 2025 09:50:33 -0400 Subject: [PATCH 5/6] fix: env vars set up different --- claim-db-worker/app/api/analytics/route.ts | 11 ++++------- claim-db-worker/lib/analytics.ts | 2 ++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/claim-db-worker/app/api/analytics/route.ts b/claim-db-worker/app/api/analytics/route.ts index 17ae07b..fde1a48 100644 --- a/claim-db-worker/app/api/analytics/route.ts +++ b/claim-db-worker/app/api/analytics/route.ts @@ -11,10 +11,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 }); } - const POSTHOG_API_KEY = env.POSTHOG_API_KEY; - const POSTHOG_PROXY_HOST = env.POSTHOG_API_HOST; - - if (!POSTHOG_API_KEY || !POSTHOG_PROXY_HOST) { + if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { return NextResponse.json({ success: true }); } @@ -32,14 +29,14 @@ export async function POST(request: NextRequest) { ); } - await fetch(`${POSTHOG_PROXY_HOST}/e`, { + await fetch(`${env.POSTHOG_API_HOST}/e`, { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${POSTHOG_API_KEY}`, + Authorization: `Bearer ${env.POSTHOG_API_KEY}`, }, body: JSON.stringify({ - api_key: POSTHOG_API_KEY, + api_key: env.POSTHOG_API_KEY, event, properties: properties || {}, distinct_id: "web-claim", diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index 438400c..4676536 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -15,4 +15,6 @@ export const sendAnalyticsEvent = async ( if (!response.ok) { console.error("Failed to send analytics event:", response); } + + console.log(response); }; From 41fa7d73bdbbb0dbb95017b1924aecf23b75f708 Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Mon, 8 Sep 2025 10:00:11 -0400 Subject: [PATCH 6/6] chore: console log removed --- claim-db-worker/lib/analytics.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/claim-db-worker/lib/analytics.ts b/claim-db-worker/lib/analytics.ts index 4676536..438400c 100644 --- a/claim-db-worker/lib/analytics.ts +++ b/claim-db-worker/lib/analytics.ts @@ -15,6 +15,4 @@ export const sendAnalyticsEvent = async ( if (!response.ok) { console.error("Failed to send analytics event:", response); } - - console.log(response); };