fix: security hardening + performance refactoring (code review batch 1)

- IDOR fix: deliberation vote now verifies juryMemberId === ctx.user.id
- Rate limiting: tRPC middleware (100/min), AI endpoints (5/hr), auth IP-based (10/15min)
- 6 compound indexes added to Prisma schema
- N+1 eliminated in processRoundClose (batch updateMany/createMany)
- N+1 eliminated in batchCheckRequirementsAndTransition (3 batch queries)
- Service extraction: juror-reassignment.ts (578 lines)
- Dead code removed: award.ts, cohort.ts, decision.ts (680 lines)
- 35 bare catch blocks replaced across 16 files
- Fire-and-forget async calls fixed
- Notification false positive bug fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 16:18:24 +01:00
parent a8b8643936
commit b85a9b9a7b
32 changed files with 1032 additions and 1355 deletions

View File

@@ -1,4 +1,4 @@
import { router, adminProcedure } from '../trpc'
import { router, adminProcedure, withAIRateLimit } from '../trpc'
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import type { Prisma } from '@prisma/client'
@@ -69,6 +69,7 @@ export const rankingRouter = router({
* RANK-05, RANK-06, RANK-08.
*/
executeRanking: adminProcedure
.use(withAIRateLimit)
.input(
z.object({
roundId: z.string(),
@@ -260,6 +261,7 @@ export const rankingRouter = router({
* Reads ranking criteria from round configJson and executes quickRank.
*/
triggerAutoRank: adminProcedure
.use(withAIRateLimit)
.input(z.object({ roundId: z.string() }))
.mutation(async ({ ctx, input }) => {
const { roundId } = input