From 955c856a858fd8dbacd6553cafdc406d44afda30 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Wed, 21 Jan 2026 13:58:34 -0500 Subject: [PATCH] fix: analytics and deprecated warning --- src/routes/api/auth/callback/github.ts | 6 +-- src/routes/api/auth/callback/google.ts | 6 +-- src/routes/api/auth/email-login-callback.ts | 6 +-- .../api/auth/email-verification-callback.ts | 20 +++++----- src/server/api/root.ts | 21 +++++++++-- src/server/api/routers/analytics.ts | 33 +++++++++++++---- .../api/routers/apple-notifications.test.ts | 9 +++-- src/server/api/routers/downloads.test.ts | 12 +++--- src/server/api/utils.ts | 37 ++++++++++++++----- 9 files changed, 95 insertions(+), 55 deletions(-) diff --git a/src/routes/api/auth/callback/github.ts b/src/routes/api/auth/callback/github.ts index 5788871..1ce182e 100644 --- a/src/routes/api/auth/callback/github.ts +++ b/src/routes/api/auth/callback/github.ts @@ -1,6 +1,5 @@ import type { APIEvent } from "@solidjs/start/server"; -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/utils"; +import { createServerCaller } from "~/server/api/root"; export async function GET(event: APIEvent) { const url = new URL(event.request.url); @@ -31,8 +30,7 @@ export async function GET(event: APIEvent) { try { console.log("[GitHub OAuth Callback] Creating tRPC caller..."); - const ctx = await createTRPCContext(event); - const caller = appRouter.createCaller(ctx); + const caller = await createServerCaller(event); console.log("[GitHub OAuth Callback] Calling githubCallback procedure..."); const result = await caller.auth.githubCallback({ code }); diff --git a/src/routes/api/auth/callback/google.ts b/src/routes/api/auth/callback/google.ts index 1171269..19ccdb2 100644 --- a/src/routes/api/auth/callback/google.ts +++ b/src/routes/api/auth/callback/google.ts @@ -1,6 +1,5 @@ import type { APIEvent } from "@solidjs/start/server"; -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/utils"; +import { createServerCaller } from "~/server/api/root"; export async function GET(event: APIEvent) { const url = new URL(event.request.url); @@ -31,8 +30,7 @@ export async function GET(event: APIEvent) { try { console.log("[Google OAuth Callback] Creating tRPC caller..."); - const ctx = await createTRPCContext(event); - const caller = appRouter.createCaller(ctx); + const caller = await createServerCaller(event); console.log("[Google OAuth Callback] Calling googleCallback procedure..."); const result = await caller.auth.googleCallback({ code }); diff --git a/src/routes/api/auth/email-login-callback.ts b/src/routes/api/auth/email-login-callback.ts index 7a61390..56f388d 100644 --- a/src/routes/api/auth/email-login-callback.ts +++ b/src/routes/api/auth/email-login-callback.ts @@ -1,6 +1,5 @@ import type { APIEvent } from "@solidjs/start/server"; -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/utils"; +import { createServerCaller } from "~/server/api/root"; export async function GET(event: APIEvent) { const url = new URL(event.request.url); @@ -27,8 +26,7 @@ export async function GET(event: APIEvent) { try { console.log("[Email Login Callback] Creating tRPC caller..."); // Create tRPC caller to invoke the emailLogin procedure - const ctx = await createTRPCContext(event); - const caller = appRouter.createCaller(ctx); + const caller = await createServerCaller(event); console.log("[Email Login Callback] Calling emailLogin procedure..."); // Call the email login handler - rememberMe will be read from JWT payload diff --git a/src/routes/api/auth/email-verification-callback.ts b/src/routes/api/auth/email-verification-callback.ts index e70e197..f75801a 100644 --- a/src/routes/api/auth/email-verification-callback.ts +++ b/src/routes/api/auth/email-verification-callback.ts @@ -1,6 +1,5 @@ import type { APIEvent } from "@solidjs/start/server"; -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/utils"; +import { createServerCaller } from "~/server/api/root"; export async function GET(event: APIEvent) { const url = new URL(event.request.url); @@ -57,20 +56,19 @@ export async function GET(event: APIEvent) { `, { status: 400, - headers: { "Content-Type": "text/html" }, + headers: { "Content-Type": "text/html" } } ); } try { // Create tRPC caller to invoke the emailVerification procedure - const ctx = await createTRPCContext(event); - const caller = appRouter.createCaller(ctx); + const caller = await createServerCaller(event); // Call the email verification handler const result = await caller.auth.emailVerification({ email, - token, + token }); if (result.success) { @@ -129,7 +127,7 @@ export async function GET(event: APIEvent) { `, { status: 200, - headers: { "Content-Type": "text/html" }, + headers: { "Content-Type": "text/html" } } ); } else { @@ -139,8 +137,10 @@ export async function GET(event: APIEvent) { console.error("Email verification callback error:", error); // Check if it's a token expiration error - const errorMessage = error instanceof Error ? error.message : "server_error"; - const isTokenError = errorMessage.includes("expired") || errorMessage.includes("invalid"); + const errorMessage = + error instanceof Error ? error.message : "server_error"; + const isTokenError = + errorMessage.includes("expired") || errorMessage.includes("invalid"); return new Response( ` @@ -192,7 +192,7 @@ export async function GET(event: APIEvent) { `, { status: 400, - headers: { "Content-Type": "text/html" }, + headers: { "Content-Type": "text/html" } } ); } diff --git a/src/server/api/root.ts b/src/server/api/root.ts index c0cc0d9..409b944 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -13,8 +13,9 @@ import { accountRouter } from "./routers/account"; import { downloadsRouter } from "./routers/downloads"; import { cairnDbRouter } from "./routers/cairn"; import { appleNotificationsRouter } from "./routers/apple-notifications"; -import { createTRPCRouter, createTRPCContext } from "./utils"; +import { createTRPCRouter, createTRPCContext, t } from "./utils"; import type { H3Event } from "h3"; +import type { APIEvent } from "@solidjs/start/server"; export const appRouter = createTRPCRouter({ auth: authRouter, @@ -36,12 +37,24 @@ export const appRouter = createTRPCRouter({ export type AppRouter = typeof appRouter; +/** Server-side caller factory using the modern tRPC pattern */ +export const createCallerFactory = t.createCallerFactory(appRouter); + /** - * Create a server-side caller for tRPC procedures - * This allows calling tRPC procedures directly on the server with proper context + * Create a server-side caller for tRPC procedures from H3Event (vinxi/http getEvent) + * Used in server functions within route files */ export const createCaller = async (event: H3Event) => { const apiEvent = { nativeEvent: event, request: event.node.req } as any; const ctx = await createTRPCContext(apiEvent); - return appRouter.createCaller(ctx); + return createCallerFactory(ctx); +}; + +/** + * Create a server-side caller for tRPC procedures from APIEvent + * Used in API route handlers + */ +export const createServerCaller = async (event: APIEvent) => { + const ctx = await createTRPCContext(event); + return createCallerFactory(ctx); }; diff --git a/src/server/api/routers/analytics.ts b/src/server/api/routers/analytics.ts index f62f671..87b01b4 100644 --- a/src/server/api/routers/analytics.ts +++ b/src/server/api/routers/analytics.ts @@ -5,7 +5,6 @@ import { getAnalyticsSummary, getPathAnalytics, cleanupOldAnalytics, - logVisit, getPerformanceStats, enrichAnalyticsEntry } from "~/server/analytics"; @@ -13,6 +12,26 @@ import { ConnectionFactory } from "~/server/database"; import { v4 as uuid } from "uuid"; import { getRequestIP } from "vinxi/http"; +/** Safely get a header value from either Fetch API Headers or Node.js IncomingHttpHeaders */ +function getHeader( + headers: Record | Headers | undefined, + name: string +): string | undefined { + if (!headers) return undefined; + + // Check if it's a Fetch API Headers object (has .get method) + if (typeof (headers as Headers).get === "function") { + return (headers as Headers).get(name) || undefined; + } + + // Otherwise treat as Node.js IncomingHttpHeaders (plain object) + const value = (headers as Record)[ + name.toLowerCase() + ]; + if (Array.isArray(value)) return value[0]; + return value; +} + export const analyticsRouter = createTRPCRouter({ logPerformance: publicProcedure .input( @@ -71,14 +90,12 @@ export const analyticsRouter = createTRPCRouter({ } else { const req = ctx.event.nativeEvent.node?.req || ctx.event.nativeEvent; const userAgent = - req.headers?.["user-agent"] || - ctx.event.request?.headers?.get("user-agent") || - undefined; + getHeader(req.headers, "user-agent") || + getHeader(ctx.event.request?.headers, "user-agent"); const referrer = - req.headers?.referer || - req.headers?.referrer || - ctx.event.request?.headers?.get("referer") || - undefined; + getHeader(req.headers, "referer") || + getHeader(req.headers, "referrer") || + getHeader(ctx.event.request?.headers, "referer"); const ipAddress = getRequestIP(ctx.event.nativeEvent) || undefined; const enriched = enrichAnalyticsEntry({ userId: ctx.userId, diff --git a/src/server/api/routers/apple-notifications.test.ts b/src/server/api/routers/apple-notifications.test.ts index 583a3c3..aa5f0d5 100644 --- a/src/server/api/routers/apple-notifications.test.ts +++ b/src/server/api/routers/apple-notifications.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { appRouter } from "~/server/api/root"; +import { createCallerFactory, appRouter } from "~/server/api/root"; import { createTRPCContext } from "~/server/api/utils"; vi.mock("~/server/apple-notification", () => ({ @@ -17,9 +17,10 @@ vi.mock("~/server/apple-notification-store", () => ({ describe("apple notification router", () => { it("verifies and stores notifications", async () => { - const caller = appRouter.createCaller( - await createTRPCContext({ nativeEvent: { node: { req: {} } } } as any) - ); + const ctx = await createTRPCContext({ + nativeEvent: { node: { req: {} } } + } as any); + const caller = createCallerFactory(ctx); const result = await caller.appleNotifications.verifyAndStore.mutate({ signedPayload: "test" diff --git a/src/server/api/routers/downloads.test.ts b/src/server/api/routers/downloads.test.ts index e369fad..68ff370 100644 --- a/src/server/api/routers/downloads.test.ts +++ b/src/server/api/routers/downloads.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { appRouter } from "~/server/api/root"; +import { createCallerFactory } from "~/server/api/root"; import { createTRPCContext } from "~/server/api/utils"; // Mock the S3 client and getSignedUrl function @@ -33,9 +33,8 @@ process.env.VITE_DOWNLOAD_BUCKET_STRING = "test-bucket"; describe("downloads router", () => { it("should return a signed URL for valid asset names", async () => { - const caller = appRouter.createCaller( - await createTRPCContext({ nativeEvent: {} } as any) - ); + const ctx = await createTRPCContext({ nativeEvent: {} } as any); + const caller = createCallerFactory(ctx); const result = await caller.downloads.getDownloadUrl.query({ asset_name: "lineage" @@ -46,9 +45,8 @@ describe("downloads router", () => { }); it("should throw NOT_FOUND for invalid asset names", async () => { - const caller = appRouter.createCaller( - await createTRPCContext({ nativeEvent: {} } as any) - ); + const ctx = await createTRPCContext({ nativeEvent: {} } as any); + const caller = createCallerFactory(ctx); try { await caller.downloads.getDownloadUrl.query({ diff --git a/src/server/api/utils.ts b/src/server/api/utils.ts index 20847c9..a4c8bc8 100644 --- a/src/server/api/utils.ts +++ b/src/server/api/utils.ts @@ -12,6 +12,26 @@ export type Context = { cairnUserId: string | null; }; +/** Safely get a header value from either Fetch API Headers or Node.js IncomingHttpHeaders */ +function getHeader( + headers: Record | Headers | undefined, + name: string +): string | undefined { + if (!headers) return undefined; + + // Check if it's a Fetch API Headers object (has .get method) + if (typeof (headers as Headers).get === "function") { + return (headers as Headers).get(name) || undefined; + } + + // Otherwise treat as Node.js IncomingHttpHeaders (plain object) + const value = (headers as Record)[ + name.toLowerCase() + ]; + if (Array.isArray(value)) return value[0]; + return value; +} + async function createContextInner(event: APIEvent): Promise { const payload = await getAuthPayloadFromEvent(event.nativeEvent); @@ -27,19 +47,16 @@ async function createContextInner(event: APIEvent): Promise { const path = req.url || event.request?.url || "unknown"; const method = req.method || event.request?.method || "GET"; const userAgent = - req.headers?.["user-agent"] || - event.request?.headers?.get("user-agent") || - undefined; + getHeader(req.headers, "user-agent") || + getHeader(event.request?.headers, "user-agent"); const referrer = - req.headers?.referer || - req.headers?.referrer || - event.request?.headers?.get("referer") || - undefined; + getHeader(req.headers, "referer") || + getHeader(req.headers, "referrer") || + getHeader(event.request?.headers, "referer"); const ipAddress = getRequestIP(event.nativeEvent) || undefined; const authHeader = - event.request?.headers?.get("authorization") || - req.headers?.authorization || - req.headers?.Authorization || + getHeader(req.headers, "authorization") || + getHeader(event.request?.headers, "authorization") || null; let cairnUserId: string | null = null;