import { initTRPC, TRPCError } from "@trpc/server"; import type { TrpcContext } from "./context.js"; import { isSubscriptionActive } from "../db.js"; import { childLogger } from "./logger.js"; const trpcLog = childLogger("trpc"); const t = initTRPC.context().create({ errorFormatter({ shape }) { return shape; }, }); export const router = t.router; export const middleware = t.middleware; // Timing middleware: logs every procedure call with its duration. Errors are // re-thrown so the existing error handlers in the express adapter still fire. const timing = t.middleware(async ({ path, type, next, ctx }) => { const start = Date.now(); try { const result = await next(); const durationMs = Date.now() - start; if (result.ok) { trpcLog.debug({ path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure ok"); } else { trpcLog.warn( { path, type, durationMs, userId: ctx.user?.id ?? null, code: result.error.code }, "procedure error" ); } return result; } catch (err) { const durationMs = Date.now() - start; trpcLog.error({ err, path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure threw"); throw err; } }); export const publicProcedure = t.procedure.use(timing); const isAuthed = t.middleware(({ ctx, next }) => { if (!ctx.user) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" }); } if (ctx.user.disabled) { throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" }); } return next({ ctx: { ...ctx, user: ctx.user, }, }); }); export const protectedProcedure = t.procedure.use(isAuthed); const isAdmin = t.middleware(({ ctx, next }) => { if (!ctx.user) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" }); } if (ctx.user.disabled) { throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" }); } if (ctx.user.role !== "admin") { throw new TRPCError({ code: "FORBIDDEN", message: "Accès réservé aux administrateurs" }); } return next({ ctx: { ...ctx, user: ctx.user, }, }); }); export const adminProcedure = t.procedure.use(isAdmin); const requireActiveSubscription = t.middleware(async ({ ctx, next }) => { if (!ctx.user) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" }); } const active = await isSubscriptionActive(ctx.user.id); if (!active) { throw new TRPCError({ code: "FORBIDDEN", message: "Subscription expired or inactive", }); } return next({ ctx: { ...ctx, user: ctx.user } }); }); export const subscriptionProcedure = t.procedure.use(requireActiveSubscription);