import { initTRPC, TRPCError } from '@trpc/server' import superjson from 'superjson' import { ZodError } from 'zod' import type { Context } from './context' import type { UserRole } from '@prisma/client' /** * Initialize tRPC with context type and configuration */ const t = initTRPC.context().create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, } }, }) /** * Export reusable router and procedure helpers */ export const router = t.router export const publicProcedure = t.procedure export const middleware = t.middleware export const createCallerFactory = t.createCallerFactory // ============================================================================= // Middleware // ============================================================================= /** * Middleware to require authenticated user */ const isAuthenticated = middleware(async ({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You must be logged in to perform this action', }) } return next({ ctx: { ...ctx, user: ctx.session.user, }, }) }) /** * Middleware to require specific role(s) */ const hasRole = (...roles: UserRole[]) => middleware(async ({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You must be logged in to perform this action', }) } if (!roles.includes(ctx.session.user.role)) { throw new TRPCError({ code: 'FORBIDDEN', message: 'You do not have permission to perform this action', }) } return next({ ctx: { ...ctx, user: ctx.session.user, }, }) }) /** * Middleware for audit logging */ const withAuditLog = middleware(async ({ ctx, next, path }) => { const result = await next() // Log successful mutations if (result.ok && path.includes('.')) { const [, action] = path.split('.') const mutationActions = ['create', 'update', 'delete', 'import', 'submit', 'grant', 'revoke'] if (mutationActions.some((a) => action?.toLowerCase().includes(a))) { // Audit logging would happen here // We'll implement this in the audit service } } return result }) // ============================================================================= // Procedure Types // ============================================================================= /** * Protected procedure - requires authenticated user */ export const protectedProcedure = t.procedure.use(isAuthenticated) /** * Admin procedure - requires SUPER_ADMIN or PROGRAM_ADMIN role */ export const adminProcedure = t.procedure.use( hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN') ) /** * Super admin procedure - requires SUPER_ADMIN role */ export const superAdminProcedure = t.procedure.use(hasRole('SUPER_ADMIN')) /** * Jury procedure - requires JURY_MEMBER role */ export const juryProcedure = t.procedure.use(hasRole('JURY_MEMBER')) /** * Mentor procedure - requires MENTOR role (or admin) */ export const mentorProcedure = t.procedure.use( hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'MENTOR') ) /** * Observer procedure - requires OBSERVER role (read-only access) */ export const observerProcedure = t.procedure.use( hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'OBSERVER') ) /** * Award master procedure - requires AWARD_MASTER role (or admin) */ export const awardMasterProcedure = t.procedure.use( hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'AWARD_MASTER') ) /** * Audience procedure - requires any authenticated user */ export const audienceProcedure = t.procedure.use(isAuthenticated) /** * Protected procedure with audit logging */ export const auditedProcedure = t.procedure .use(isAuthenticated) .use(withAuditLog)