fix: analytics and deprecated warning

This commit is contained in:
Michael Freno
2026-01-21 13:58:34 -05:00
parent 7b60494d6d
commit 955c856a85
9 changed files with 95 additions and 55 deletions

View File

@@ -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);
};

View File

@@ -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<string, string | string[] | undefined> | 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<string, string | string[] | undefined>)[
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,

View File

@@ -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"

View File

@@ -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({

View File

@@ -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<string, string | string[] | undefined> | 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<string, string | string[] | undefined>)[
name.toLowerCase()
];
if (Array.isArray(value)) return value[0];
return value;
}
async function createContextInner(event: APIEvent): Promise<Context> {
const payload = await getAuthPayloadFromEvent(event.nativeEvent);
@@ -27,19 +47,16 @@ async function createContextInner(event: APIEvent): Promise<Context> {
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;