import { initTRPC, TRPCError } from "@trpc/server"; import type { APIEvent } from "@solidjs/start/server"; import { getCookie } from "vinxi/http"; import { env } from "~/env/server"; import { logVisit, enrichAnalyticsEntry } from "~/server/analytics"; import { getRequestIP } from "vinxi/http"; import { getAuthSession } from "~/server/session-helpers"; export type Context = { event: APIEvent; userId: string | null; privilegeLevel: "anonymous" | "user" | "admin"; }; async function createContextInner(event: APIEvent): Promise { // Get auth session from Vinxi encrypted session const session = await getAuthSession(event.nativeEvent); let userId: string | null = null; let privilegeLevel: "anonymous" | "user" | "admin" = "anonymous"; if (session && session.userId) { userId = session.userId; privilegeLevel = session.isAdmin ? "admin" : "user"; } const req = event.nativeEvent.node?.req || event.nativeEvent; 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; const referrer = req.headers?.referer || req.headers?.referrer || event.request?.headers?.get("referer") || undefined; const ipAddress = getRequestIP(event.nativeEvent) || undefined; const sessionId = getCookie(event.nativeEvent, "session_id") || undefined; // Don't log the performance logging endpoint itself to avoid circular tracking if (!path.includes("analytics.logPerformance")) { logVisit( enrichAnalyticsEntry({ userId, path, method, userAgent, referrer, ipAddress, sessionId }) ); } return { event, userId, privilegeLevel }; } export const createTRPCContext = (event: APIEvent) => { return createContextInner(event); }; export const t = initTRPC.context().create(); export const createTRPCRouter = t.router; export const publicProcedure = t.procedure; const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { if (!ctx.userId || ctx.privilegeLevel === "anonymous") { throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" }); } return next({ ctx: { ...ctx, userId: ctx.userId } }); }); const enforceUserIsAdmin = t.middleware(({ ctx, next }) => { if (ctx.privilegeLevel !== "admin") { throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" }); } return next({ ctx: { ...ctx, userId: ctx.userId! } }); }); export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); export const adminProcedure = t.procedure.use(enforceUserIsAdmin);