Skip to content

Conversation

aidankmcalister
Copy link
Member

@aidankmcalister aidankmcalister commented Sep 5, 2025

Summary by CodeRabbit

  • New Features

    • Added an analytics API endpoint with per-route rate limiting and graceful no-op when analytics is unavailable.
    • Introduced automatic page-view tracking across the app.
  • Improvements

    • Centralized client-side analytics through a single endpoint.
    • Updated server-side flows to emit analytics events without impacting redirects.
    • Standardized event names and streamlined event properties.

Copy link

coderabbitai bot commented Sep 5, 2025

Walkthrough

Introduces a client-to-server analytics pipeline: a new API route relays events to PostHog with rate limiting; a client analytics helper posts to this route; a PageViewTracker component emits page-view events and is wired in layout; claim API switches to the new helper; auth callback sends server-side PostHog events.

Changes

Cohort / File(s) Summary
Analytics API Endpoint
claim-db-worker/app/api/analytics/route.ts
Adds POST handler enforcing per-URL rate limiting; validates body for required event; short-circuits with success if PostHog config missing; otherwise forwards to {POSTHOG_API_HOST}/e with Bearer auth; returns 429/400/500 on limit/validation/error.
Client Analytics Library
claim-db-worker/lib/analytics.ts
Replaces PostHog-specific client logic with sendAnalyticsEvent(event, properties) that POSTs to /api/analytics; removes env handling and old helpers (trackClaimSuccess, trackClaimFailure, sendPosthogEvent).
Page View Tracking (Client + Layout)
claim-db-worker/components/PageViewTracker.tsx, claim-db-worker/app/layout.tsx
Adds PageViewTracker client component that emits create_db:claim_page_viewed with path/url/referrer/timestamp on navigation; integrates component into RootLayout to run on page renders.
Claim API Instrumentation
claim-db-worker/app/api/claim/route.ts
Switches to centralized sendAnalyticsEvent; removes local PostHog sender; renames event to create_db:claim_viewed; reduces properties to project-id only.
Auth Callback Instrumentation (Server-side)
claim-db-worker/app/api/auth/callback/route.ts
Replaces prior tracking with internal server-only sender to PostHog; logs success/failure events (create_db:claim_successful/create_db:claim_failed) with project-id and error context; respects PostHog env presence; no change to redirect flow.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Browser as Client (Browser)
  participant Tracker as PageViewTracker
  participant Lib as sendAnalyticsEvent
  participant API as /api/analytics
  participant RL as Rate Limiter
  participant PH as PostHog API

  User->>Browser: Navigate / page view
  Browser->>Tracker: Render
  Tracker->>Lib: sendAnalyticsEvent("create_db:claim_page_viewed", {...})
  Lib->>API: POST /api/analytics {event, properties}
  API->>RL: Check limit for request URL
  alt Over limit
    RL-->>API: Limited
    API-->>Lib: 429
  else Allowed
    API->>API: Validate event present
    alt Missing event
      API-->>Lib: 400
    else Config missing
      API-->>Lib: 200 {success:true}
    else PostHog configured
      API->>PH: POST {api_key,event,properties,distinct_id:"web-claim"}
      alt PH success
        PH-->>API: 200
        API-->>Lib: 200 {success:true}
      else PH error
        PH-->>API: 4xx/5xx
        API-->>Lib: 500 {error:"Analytics failed"}
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  participant User as User Agent
  participant Auth as GET /api/auth/callback
  participant PH as PostHog API

  User->>Auth: Callback request
  alt Token exchange fails
    Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
    Auth-->>User: Redirect (failure path)
  else Project validation fails
    Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
    Auth-->>User: Redirect (failure path)
  else Transfer succeeds
    Auth->>PH: POST event "create_db:claim_successful" {project-id}
    Auth-->>User: Redirect (success path)
  else Transfer fails
    Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
    Auth-->>User: Redirect (failure path)
  end
  note over Auth,PH: Server-side sender is a no-op if PostHog config is absent
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch DC-5180-analytics-fixed

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

github-actions bot commented Sep 5, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17504196954:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17504196954.

Copy link

cloudflare-workers-and-pages bot commented Sep 5, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
claim-db-worker 41fa7d7 Commit Preview URL

Branch Preview URL
Sep 08 2025, 02:07 PM

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
claim-db-worker/app/api/auth/callback/route.ts (3)

21-31: Emit analytics for rate-limit and missing-parameter branches

To keep failure telemetry comprehensive, also send events in these early-return paths.

     if (!rateLimitResult.success) {
+      void sendAnalyticsEvent("create_db:claim_failed", {
+        "project-id": projectID ?? "unknown",
+        "error-type": "rate_limited",
+      }).catch(() => {});
       return redirectToError(
         request,
         "Rate Limited",
         "We're experiencing high demand. Please try again later."
       );
     }
@@
     if (!state) {
+      void sendAnalyticsEvent("create_db:claim_failed", {
+        "project-id": projectID ?? "unknown",
+        "error-type": "missing_state",
+      }).catch(() => {});
       return redirectToError(
         request,
         "Missing State Parameter",
         "Please try again.",
         "The state parameter is required for security purposes."
       );
     }
@@
     if (!projectID) {
+      void sendAnalyticsEvent("create_db:claim_failed", {
+        "project-id": "unknown",
+        "error-type": "missing_project_id",
+      }).catch(() => {});
       return redirectToError(
         request,
         "Missing Project ID",
         "Please ensure you are accessing this page with a valid project ID.",
         "The project ID parameter is required to claim your database."
       );
     }

Also applies to: 33-41, 42-49


33-41: Validate OAuth “state” against the original value
In claim-db-worker/app/api/auth/callback/route.ts (around line 34), you only check that state is non-empty. You must also compare it to the value generated in app/api/auth/url/route.ts (e.g. stored in a secure cookie or session) and reject (via redirectToError) if it doesn’t match. Presence alone does not prevent CSRF.


51-59: Add code null-check before token exchange

Insert before the exchangeCodeForToken call in claim-db-worker/app/api/auth/callback/route.ts:

     // Exchange authorization code for access token
     const baseUrl = getBaseUrl(request);
     const redirectUri = new URL("/api/auth/callback", baseUrl);
     redirectUri.searchParams.set("projectID", projectID);

+    if (!code) {
+      void sendAnalyticsEvent("create_db:claim_failed", {
+        "project-id": projectID ?? "unknown",
+        "error-type": "missing_oauth_code",
+      }).catch(() => {});
+      return redirectToError(
+        request,
+        "Missing Code Parameter",
+        "Please try again.",
+        "The OAuth code parameter is required."
+      );
+    }

     let tokenData;
     try {
       tokenData = await exchangeCodeForToken(code!, redirectUri.toString());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ad03c04 and 8be4556.

📒 Files selected for processing (6)
  • claim-db-worker/app/api/analytics/route.ts (1 hunks)
  • claim-db-worker/app/api/auth/callback/route.ts (4 hunks)
  • claim-db-worker/app/api/claim/route.ts (2 hunks)
  • claim-db-worker/app/layout.tsx (2 hunks)
  • claim-db-worker/components/PageViewTracker.tsx (1 hunks)
  • claim-db-worker/lib/analytics.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
🧬 Code graph analysis (6)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
  • sendAnalyticsEvent (1-19)
claim-db-worker/app/layout.tsx (1)
claim-db-worker/components/PageViewTracker.tsx (1)
  • PageViewTracker (7-20)
claim-db-worker/app/api/analytics/route.ts (2)
claim-db-worker/lib/env.ts (1)
  • getEnv (11-39)
create-db-worker/src/index.ts (1)
  • fetch (15-160)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/analytics.ts (1)
  • sendAnalyticsEvent (1-19)
claim-db-worker/lib/response-utils.ts (1)
  • redirectToSuccess (27-38)
claim-db-worker/app/api/claim/route.ts (1)
claim-db-worker/lib/analytics.ts (1)
  • sendAnalyticsEvent (1-19)
claim-db-worker/lib/analytics.ts (1)
create-db-worker/src/index.ts (1)
  • fetch (15-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: claim-db-worker
  • GitHub Check: Workers Builds: create-db-worker
🔇 Additional comments (3)
claim-db-worker/lib/analytics.ts (1)

1-19: Analytics property keys use correct kebab-case. No snake_case keys found in any sendAnalyticsEvent call sites.

claim-db-worker/app/api/claim/route.ts (1)

23-23: Kebab-case property key — good consistency.

"project-id" follows the established kebab-case convention for analytics properties.

claim-db-worker/app/layout.tsx (1)

51-51: LGTM: PageViewTracker mounted at layout scope

Mounting the tracker in the root layout ensures coverage across routes; effect runs post-paint so it won’t block render.

Copy link

github-actions bot commented Sep 5, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17504473700:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17504473700.

Copy link

github-actions bot commented Sep 5, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17504576962:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17504576962.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
claim-db-worker/app/api/auth/callback/route.ts (1)

68-76: Validate the OAuth "code" param before token exchange

Currently only state/projectID are checked. Guard code to fail fast with a clear error.

   // Validate required parameters
   if (!state) {
@@
   }
+  const code = searchParams.get("code");
+  if (!code) {
+    return redirectToError(
+      request,
+      "Missing Code Parameter",
+      "Please try again.",
+      "The authorization code is required to continue."
+    );
+  }
-  const code = searchParams.get("code");
♻️ Duplicate comments (6)
claim-db-worker/app/api/auth/callback/route.ts (4)

97-105: Make analytics non-blocking and use kebab-case keys

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

119-126: Same: fire-and-forget + kebab-case

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

151-158: Add status-code and kebab-case; make non-blocking

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: transferResult.error!,
+          "status-code": transferResult.status,
+          "error-message": transferResult.error!,
         },
         request
-      );
+      ).catch(() => {});

11-45: Centralize via /api/analytics and add timeout; drop direct PostHog coupling

Route already has a unified analytics endpoint. Avoid duplicating ingestion logic here; post to your own /api/analytics using a short timeout and keep it server-derived from request.nextUrl.origin.

 async function sendServerAnalyticsEvent(
   event: string,
   properties: Record<string, any>,
   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(),
-      }),
-    });
+    const ctrl = new AbortController();
+    const t = setTimeout(() => ctrl.abort(), 1500);
+    await fetch(`${request.nextUrl.origin}/api/analytics`, {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({
+        event,
+        properties: {
+          ...properties,
+          $current_url: request.url,
+          $user_agent: request.headers.get("user-agent"),
+          $ip:
+            request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
+            undefined,
+          timestamp: new Date().toISOString(),
+        },
+      }),
+      keepalive: true as any,
+      signal: ctrl.signal,
+    }).finally(() => clearTimeout(t));
   } catch (error) {
     console.error("Failed to send server analytics event:", error);
   }
 }
claim-db-worker/lib/analytics.ts (1)

3-18: Harden client calls: optional props, base-URL fallback, keepalive, and error swallow

-export const sendAnalyticsEvent = async (
-  event: string,
-  properties: Record<string, any>
-) => {
-  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);
-  }
-};
+export const sendAnalyticsEvent = async (
+  event: string,
+  properties?: Record<string, unknown>
+): Promise<void> => {
+  const base = process.env.NEXT_PUBLIC_BASE_URL;
+  const url = base ? `${base}/api/analytics` : "/api/analytics";
+  const payload = JSON.stringify({ event, properties: properties ?? {} });
+  // Prefer sendBeacon when available (non-blocking on unload)
+  if (typeof navigator !== "undefined" && "sendBeacon" in navigator) {
+    try {
+      const blob = new Blob([payload], { type: "application/json" });
+      if ((navigator as any).sendBeacon(url, blob)) return;
+    } catch {}
+  }
+  try {
+    const res = await fetch(url, {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: payload,
+      // helps reliability in browser navigations
+      ...(typeof window !== "undefined" ? ({ keepalive: true } as any) : {}),
+    });
+    if (!res.ok) {
+      const t = await res.text().catch(() => "");
+      console.error("Failed to send analytics event:", res.status, t);
+    }
+  } catch (e) {
+    console.error("Failed to send analytics event:", e);
+  }
+};
claim-db-worker/components/PageViewTracker.tsx (1)

17-23: Kebab-case properties and include project-id; make call fire-and-forget

-      sendAnalyticsEvent("create_db:claim_page_viewed", {
-        path: pathname,
-        full_path: fullPath,
-        url: url,
-        referrer: document.referrer || "",
-        timestamp: new Date().toISOString(),
-      });
+      const projectId = searchParams?.get("projectID") || undefined;
+      void sendAnalyticsEvent("create_db:claim_page_viewed", {
+        path: pathname,
+        "full-path": fullPath,
+        url,
+        referrer: document.referrer || "",
+        "project-id": projectId,
+        timestamp: new Date().toISOString(),
+      }).catch?.(() => {});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8be4556 and 615a60b.

📒 Files selected for processing (3)
  • claim-db-worker/app/api/auth/callback/route.ts (4 hunks)
  • claim-db-worker/components/PageViewTracker.tsx (1 hunks)
  • claim-db-worker/lib/analytics.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
📚 Learning: 2025-08-27T16:39:21.271Z
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.

Applied to files:

  • claim-db-worker/components/PageViewTracker.tsx
  • claim-db-worker/app/api/auth/callback/route.ts
🧬 Code graph analysis (3)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
  • sendAnalyticsEvent (3-18)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/env.ts (1)
  • getEnv (11-39)
create-db-worker/src/index.ts (1)
  • fetch (15-160)
claim-db-worker/lib/analytics.ts (1)
create-db-worker/src/index.ts (1)
  • fetch (15-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: create-db-worker
  • GitHub Check: Workers Builds: claim-db-worker

Copy link

github-actions bot commented Sep 5, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17504723298:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17504723298.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
claim-db-worker/app/api/auth/callback/route.ts (1)

51-54: Validate code param before use

code! can be null at runtime. Add a guard and send a failure event.

     const code = searchParams.get("code");
@@
-    // Validate required parameters
+    // Validate required parameters
     if (!state) {
@@
     }
+    if (!code) {
+      void sendServerAnalyticsEvent(
+        "create_db:claim_failed",
+        { "project-id": projectID, "error-message": "Missing authorization code" },
+        request
+      ).catch(() => {});
+      return redirectToError(
+        request,
+        "Missing Authorization Code",
+        "Please try again.",
+        "Authorization code was not provided."
+      );
+    }

Also applies to: 67-83

♻️ Duplicate comments (6)
claim-db-worker/components/PageViewTracker.tsx (2)

19-25: Make analytics fire-and-forget and fix key casing to kebab-case

Avoid unhandled promise rejections and align with repo analytics conventions. Change full_path to full-path, call fire-and-forget, and swallow errors locally.

-      sendAnalyticsEvent("create_db:claim_page_viewed", {
-        path: pathname,
-        full_path: fullPath,
-        url: url,
-        referrer: document.referrer || "",
-        timestamp: new Date().toISOString(),
-      });
+      void sendAnalyticsEvent("create_db:claim_page_viewed", {
+        path: pathname,
+        "full-path": fullPath,
+        url,
+        referrer: document.referrer || "",
+        timestamp: new Date().toISOString(),
+      }).catch(() => {});

8-10: Optional: include project ID when present for correlation

Read projectID from the query and include it as "project-id".

   const pathname = usePathname();
   const searchParams = useSearchParams();
+  const projectId = searchParams?.get("projectID");
@@
-      void sendAnalyticsEvent("create_db:claim_page_viewed", {
+      void sendAnalyticsEvent("create_db:claim_page_viewed", {
         path: pathname,
         "full-path": fullPath,
         url,
         referrer: document.referrer || "",
         timestamp: new Date().toISOString(),
+        "project-id": projectId ?? undefined,
       }).catch(() => {});

Also applies to: 19-25

claim-db-worker/app/api/auth/callback/route.ts (4)

96-103: Do not await analytics on failure path + kebab-case keys

Make analytics non-blocking and align key casing.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

118-125: Same as above for project validation failure

Non-blocking + kebab-case.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

141-147: Do not await analytics on success path

Fire-and-forget to keep the redirect fast.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_successful",
         {
           "project-id": projectID,
         },
         request
-      );
+      ).catch(() => {});

150-157: Non-blocking on transfer failure + add status code and kebab-case

Include "status-code" for observability and don’t await.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: transferResult.error!,
+          "error-message": transferResult.error!,
+          "status-code": transferResult.status,
         },
         request
-      );
+      ).catch(() => {});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77a3c06 and 9be1411.

📒 Files selected for processing (2)
  • claim-db-worker/app/api/auth/callback/route.ts (4 hunks)
  • claim-db-worker/components/PageViewTracker.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
📚 Learning: 2025-08-27T16:39:21.271Z
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.

Applied to files:

  • claim-db-worker/app/api/auth/callback/route.ts
  • claim-db-worker/components/PageViewTracker.tsx
🧬 Code graph analysis (2)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/env.ts (1)
  • getEnv (11-39)
create-db-worker/src/index.ts (1)
  • fetch (15-160)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
  • sendAnalyticsEvent (3-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: create-db-worker
  • GitHub Check: Workers Builds: claim-db-worker

Copy link

github-actions bot commented Sep 8, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17553104249:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17553104249.

Copy link

github-actions bot commented Sep 8, 2025

Preview CLIs & Workers are live!

Test the CLIs locally under tag pr55-DC-5180-analytics-fixed-17553357895:

npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55

Worker URLs
• Create-DB Worker:
• Claim-DB Worker:

These will live as long as this PR exists under tag pr55-DC-5180-analytics-fixed-17553357895.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (11)
claim-db-worker/app/api/analytics/route.ts (4)

7-12: Rate limit by client fingerprint, not just URL.

Keying by request.url buckets all clients together. Include IP for fairness.

-  const rateLimitResult = await env.CLAIM_DB_RATE_LIMITER.limit({
-    key: request.url,
-  });
+  const ip =
+    request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
+    request.headers.get("x-real-ip") ??
+    "unknown";
+  const rateLimitResult = await env.CLAIM_DB_RATE_LIMITER.limit({
+    key: `${request.url}:${ip}`,
+  });

14-16: Return 204 when analytics disabled.

Avoid implying a captured event when gated off.

-  if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) {
-    return NextResponse.json({ success: true });
-  }
+  if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) {
+    return NextResponse.json(null, { status: 204 });
+  }

18-30: Validate/guard JSON and types; return 400 on client errors.

Current parse errors bubble to 500. Add guarded parsing and type checks.

-  try {
-    const {
-      event,
-      properties,
-    }: { event: string; properties: Record<string, any> } =
-      await request.json();
-
-    if (!event) {
-      return NextResponse.json(
-        { error: "Event name required" },
-        { status: 400 }
-      );
-    }
+  try {
+    let body: unknown;
+    try {
+      body = await request.json();
+    } catch {
+      return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
+    }
+    const event = (body as any)?.event;
+    const properties = (body as any)?.properties;
+    if (typeof event !== "string") {
+      return NextResponse.json({ error: "Event name required" }, { status: 400 });
+    }
+    if (properties != null && typeof properties !== "object") {
+      return NextResponse.json({ error: "Invalid properties" }, { status: 400 });
+    }

32-44: Add timeout, check upstream status, and prefer project-id for distinct_id.

Prevents hangs and surfaces upstream failures as 502.

-    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",
-      }),
-    });
+    const ac = new AbortController();
+    const t = setTimeout(() => ac.abort(), 3000);
+    const resp = await fetch(`${env.POSTHOG_API_HOST}/e`, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        // Authorization header likely not required for public ingestion; see verification note below.
+        Authorization: `Bearer ${env.POSTHOG_API_KEY}`,
+      },
+      body: JSON.stringify({
+        api_key: env.POSTHOG_API_KEY,
+        event,
+        properties: properties ?? {},
+        distinct_id: (properties && (properties as any)["project-id"]) ?? "web-claim",
+      }),
+      signal: ac.signal,
+    }).finally(() => clearTimeout(t));
+    if (!resp.ok) {
+      const text = await resp.text().catch(() => "");
+      console.error(`PostHog proxy responded ${resp.status}`, text);
+      return NextResponse.json({ error: "Upstream analytics failed" }, { status: 502 });
+    }
claim-db-worker/app/api/auth/callback/route.ts (5)

11-44: Prefer centralizing via /api/analytics; add short timeout; better distinct_id.

Avoid duplicating PostHog wiring here and keep requests snappy.

Option A (preferred): forward to internal analytics route.

 async function sendServerAnalyticsEvent(
   event: string,
   properties: Record<string, any>,
   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(),
-      }),
-    });
+  try {
+    const origin = request.nextUrl.origin;
+    const ac = new AbortController();
+    const t = setTimeout(() => ac.abort(), 800);
+    await fetch(`${origin}/api/analytics`, {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({
+        event,
+        properties: {
+          ...properties,
+          $current_url: request.url,
+          $user_agent: request.headers.get("user-agent"),
+        },
+      }),
+      signal: ac.signal,
+      cache: "no-store",
+    }).finally(() => clearTimeout(t));
   } catch (error) {
     console.error("Failed to send server analytics event:", error);
   }
 }

Option B (if staying direct): add AbortController, remove Bearer if using public ingestion, and set distinct_id from project-id. (See analytics route comment for endpoint/auth verification.)


96-103: Make analytics non-blocking and align property keys (kebab-case).

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

118-125: Same: non-blocking and kebab-case.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: errorMessage,
+          "error-message": errorMessage,
         },
         request
-      );
+      ).catch(() => {});

141-147: Do not await analytics on success path.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_successful",
         {
           "project-id": projectID,
         },
         request
-      );
+      ).catch(() => {});

150-157: Non-blocking, include status-code, and kebab-case.

-      await sendServerAnalyticsEvent(
+      void sendServerAnalyticsEvent(
         "create_db:claim_failed",
         {
           "project-id": projectID,
-          error: transferResult.error!,
+          "status-code": transferResult.status,
+          "error-message": transferResult.error!,
         },
         request
-      );
+      ).catch(() => {});
claim-db-worker/lib/analytics.ts (1)

3-18: Harden client helper: relative URL, keepalive, and error swallowing; better typing.

Prevents failures on undefined origins and avoids breaking UX on network errors.

-export const sendAnalyticsEvent = async (
-  event: string,
-  properties: Record<string, any>
-) => {
-  const response = await fetch(`${window.location.origin}/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);
-  }
-};
+export const sendAnalyticsEvent = async (
+  event: string,
+  properties?: Record<string, unknown>
+): Promise<void> => {
+  const url = "/api/analytics"; // works client-side; server must not import this module
+  const init: RequestInit = {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify({ event, properties: properties ?? {} }),
+  };
+  try {
+    // keepalive helps during navigations/unload
+    (init as any).keepalive = true;
+    const resp = await fetch(url, init);
+    if (!resp.ok) {
+      const text = await resp.text().catch(() => "");
+      console.error("Failed to send analytics event:", resp.status, text);
+    }
+  } catch (e) {
+    // Never break UX due to analytics failures
+    console.error("Failed to send analytics event:", e);
+  }
+};
claim-db-worker/components/PageViewTracker.tsx (1)

11-27: Avoid unhandled rejections; stabilize deps; redact sensitive params; optionally include project-id.

   useEffect(() => {
     if (typeof window === "undefined") return;
 
     if (pathname) {
-      const url = window.location.href;
-      const search = searchParams?.toString();
-      const fullPath = search ? `${pathname}?${search}` : pathname;
+      const url = window.location.href;
+      const search = searchParams?.toString();
+      // redact sensitive keys
+      const redactSearch = (s: string | null) => {
+        if (!s) return null;
+        const p = new URLSearchParams(s);
+        for (const k of ["code", "state", "token", "access_token", "id_token", "refresh_token", "password", "secret"]) {
+          p.delete(k);
+        }
+        const out = p.toString();
+        return out.length ? out : null;
+      };
+      const redacted = redactSearch(search);
+      const fullPath = redacted ? `${pathname}?${redacted}` : pathname;
 
-      sendAnalyticsEvent("create_db:claim_page_viewed", {
-        path: pathname,
-        full_path: fullPath,
-        url: url,
-        referrer: document.referrer || "",
-        timestamp: new Date().toISOString(),
-      });
+      const params = new URLSearchParams(search ?? "");
+      const projectId = params.get("projectID") ?? undefined;
+      void sendAnalyticsEvent("create_db:claim_page_viewed", {
+        path: pathname,
+        full_path: fullPath,
+        url,
+        referrer: document.referrer || "",
+        "project-id": projectId,
+        timestamp: new Date().toISOString(),
+      }).catch(() => {});
     }
-  }, [pathname, searchParams]);
+  }, [pathname, searchParams?.toString()]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad03c04 and 41fa7d7.

📒 Files selected for processing (6)
  • claim-db-worker/app/api/analytics/route.ts (1 hunks)
  • claim-db-worker/app/api/auth/callback/route.ts (4 hunks)
  • claim-db-worker/app/api/claim/route.ts (2 hunks)
  • claim-db-worker/app/layout.tsx (2 hunks)
  • claim-db-worker/components/PageViewTracker.tsx (1 hunks)
  • claim-db-worker/lib/analytics.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: claim-db-worker
  • GitHub Check: Workers Builds: create-db-worker
🔇 Additional comments (4)
claim-db-worker/app/api/claim/route.ts (2)

22-24: Event-name consistency check.

You emit "create_db:claim_page_viewed" from PageViewTracker; confirm this distinct name is intentional vs unifying on one.


22-24: Fire-and-forget server analytics via internal route; don’t block response.

Also guards against network errors.

-  await sendAnalyticsEvent("create_db:claim_viewed", {
-    "project-id": projectID,
-  });
+  try {
+    const origin = request.nextUrl.origin;
+    const ac = new AbortController();
+    const t = setTimeout(() => ac.abort(), 800);
+    void fetch(`${origin}/api/analytics`, {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({
+        event: "create_db:claim_viewed",
+        properties: { "project-id": projectID },
+      }),
+      signal: ac.signal,
+      cache: "no-store",
+    })
+      .catch(() => {})
+      .finally(() => clearTimeout(t));
+  } catch {
+    // swallow
+  }

Likely an incorrect or invalid review comment.

claim-db-worker/app/layout.tsx (1)

51-52: LGTM: tracking wired in layout.

Placement before Toaster is fine.

claim-db-worker/components/PageViewTracker.tsx (1)

32-38: LGTM: Suspense wrapper and null UI.

Component is non-visual and safe for layout use.

@aidankmcalister aidankmcalister merged commit f3ae481 into main Sep 8, 2025
4 checks passed
@aidankmcalister aidankmcalister deleted the DC-5180-analytics-fixed branch September 8, 2025 15:10
@coderabbitai coderabbitai bot mentioned this pull request Sep 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants