feat(finale): deliberation jury identity resolution, rankable projects, score-revision path, session sync
- submitVote resolves the caller's JuryGroupMember participant row server-side (was comparing JuryGroupMember id to User id — every juror got FORBIDDEN) - getSessionWithVotes now includes category projects so the ranking form has data before finalize - liveVoting.vote accepts any finale-ordered project (revision during deliberation); timed window still applies to the live project - live.sendToScreens keeps LiveVotingSession.currentProjectId/status in sync Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,7 +110,6 @@ export const deliberationRouter = router({
|
||||
.input(
|
||||
z.object({
|
||||
sessionId: z.string(),
|
||||
juryMemberId: z.string(),
|
||||
projectId: z.string(),
|
||||
rank: z.number().int().min(1).optional(),
|
||||
isWinnerPick: z.boolean().optional(),
|
||||
@@ -118,15 +117,26 @@ export const deliberationRouter = router({
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Enforce that jury members can only vote as themselves
|
||||
if (input.juryMemberId !== ctx.user.id) {
|
||||
// Resolve the caller's participant row server-side: DeliberationParticipant
|
||||
// and DeliberationVote both reference JuryGroupMember ids, which the
|
||||
// client has no business knowing. A juror can only ever vote as themself.
|
||||
const participant = await ctx.prisma.deliberationParticipant.findFirst({
|
||||
where: {
|
||||
sessionId: input.sessionId,
|
||||
user: { userId: ctx.user.id },
|
||||
},
|
||||
})
|
||||
if (!participant) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You can only submit votes as yourself',
|
||||
message: 'You are not a participant in this deliberation',
|
||||
})
|
||||
}
|
||||
|
||||
const vote = await submitVote(input, ctx.prisma)
|
||||
const vote = await submitVote(
|
||||
{ ...input, juryMemberId: participant.userId },
|
||||
ctx.prisma
|
||||
)
|
||||
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
|
||||
@@ -616,22 +616,29 @@ export const liveVotingRouter = router({
|
||||
}
|
||||
}
|
||||
|
||||
if (session.status !== 'IN_PROGRESS') {
|
||||
if (session.status !== 'IN_PROGRESS' && session.status !== 'PAUSED') {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Voting is not currently active',
|
||||
})
|
||||
}
|
||||
|
||||
if (session.currentProjectId !== input.projectId) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Cannot vote for this project right now',
|
||||
})
|
||||
const isCurrentProject = session.currentProjectId === input.projectId
|
||||
if (!isCurrentProject) {
|
||||
// Revision path (deliberation / catching up): any project in the
|
||||
// finale run order may be (re)scored while the session is open.
|
||||
const ordered = await getOrderedFinaleProjects(ctx.prisma, session)
|
||||
if (!ordered.some((p) => p.id === input.projectId)) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Cannot vote for this project right now',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check if voting window is still open
|
||||
if (session.votingEndsAt && new Date() > session.votingEndsAt) {
|
||||
// The timed voting window only applies to the live flow for the
|
||||
// currently presented project
|
||||
if (isCurrentProject && session.votingEndsAt && new Date() > session.votingEndsAt) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Voting window has closed',
|
||||
|
||||
@@ -432,6 +432,12 @@ export const liveRouter = router({
|
||||
: {}),
|
||||
},
|
||||
})
|
||||
// Keep the voting session in lockstep so jury votes target this project
|
||||
// (updateMany: a session may not exist yet — that's fine).
|
||||
await ctx.prisma.liveVotingSession.updateMany({
|
||||
where: { roundId: input.roundId },
|
||||
data: { currentProjectId: input.projectId, status: 'IN_PROGRESS' },
|
||||
})
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
|
||||
Reference in New Issue
Block a user