MOPC-App/src/server/routers/gracePeriod.ts

206 lines
5.2 KiB
TypeScript
Raw Normal View History

import { z } from 'zod'
import { router, adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
export const gracePeriodRouter = router({
/**
* Grant a grace period to a juror
*/
grant: adminProcedure
.input(
z.object({
stageId: z.string(),
userId: z.string(),
projectId: z.string().optional(),
extendedUntil: z.date(),
reason: z.string().optional(),
})
)
.mutation(async ({ ctx, input }) => {
const gracePeriod = await ctx.prisma.gracePeriod.create({
data: {
...input,
grantedById: ctx.user.id,
},
})
// Audit log
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'GRANT_GRACE_PERIOD',
entityType: 'GracePeriod',
entityId: gracePeriod.id,
detailsJson: {
stageId: input.stageId,
userId: input.userId,
projectId: input.projectId,
extendedUntil: input.extendedUntil.toISOString(),
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return gracePeriod
}),
/**
* List grace periods for a stage
*/
listByStage: adminProcedure
.input(z.object({ stageId: z.string() }))
.query(async ({ ctx, input }) => {
return ctx.prisma.gracePeriod.findMany({
where: { stageId: input.stageId },
include: {
user: { select: { id: true, name: true, email: true } },
grantedBy: { select: { id: true, name: true } },
},
orderBy: { createdAt: 'desc' },
})
}),
/**
* List active grace periods for a stage
*/
listActiveByStage: adminProcedure
.input(z.object({ stageId: z.string() }))
.query(async ({ ctx, input }) => {
return ctx.prisma.gracePeriod.findMany({
where: {
stageId: input.stageId,
extendedUntil: { gte: new Date() },
},
include: {
user: { select: { id: true, name: true, email: true } },
grantedBy: { select: { id: true, name: true } },
},
orderBy: { extendedUntil: 'asc' },
})
}),
/**
* Get grace periods for a specific user in a stage
*/
getByUser: adminProcedure
.input(
z.object({
stageId: z.string(),
userId: z.string(),
})
)
.query(async ({ ctx, input }) => {
return ctx.prisma.gracePeriod.findMany({
where: {
stageId: input.stageId,
userId: input.userId,
},
orderBy: { createdAt: 'desc' },
})
}),
/**
* Update a grace period
*/
update: adminProcedure
.input(
z.object({
id: z.string(),
extendedUntil: z.date().optional(),
reason: z.string().optional(),
})
)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input
const gracePeriod = await ctx.prisma.gracePeriod.update({
where: { id },
data,
})
// Audit log
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'UPDATE_GRACE_PERIOD',
entityType: 'GracePeriod',
entityId: id,
detailsJson: data,
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return gracePeriod
}),
/**
* Revoke a grace period
*/
revoke: adminProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
const gracePeriod = await ctx.prisma.gracePeriod.delete({
where: { id: input.id },
})
// Audit log
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'REVOKE_GRACE_PERIOD',
entityType: 'GracePeriod',
entityId: input.id,
detailsJson: {
userId: gracePeriod.userId,
stageId: gracePeriod.stageId,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return gracePeriod
}),
/**
* Bulk grant grace periods
*/
bulkGrant: adminProcedure
.input(
z.object({
stageId: z.string(),
userIds: z.array(z.string()),
extendedUntil: z.date(),
reason: z.string().optional(),
})
)
.mutation(async ({ ctx, input }) => {
const created = await ctx.prisma.gracePeriod.createMany({
data: input.userIds.map((userId) => ({
stageId: input.stageId,
userId,
extendedUntil: input.extendedUntil,
reason: input.reason,
grantedById: ctx.user.id,
})),
skipDuplicates: true,
})
// Audit log
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'BULK_GRANT_GRACE_PERIOD',
entityType: 'GracePeriod',
detailsJson: {
stageId: input.stageId,
userCount: input.userIds.length,
created: created.count,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return { created: created.count }
}),
})