MOPC-App/src/server/trpc.ts

160 lines
4.1 KiB
TypeScript

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<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)