160 lines
4.1 KiB
TypeScript
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)
|