Fix FK constraint error on filtering override — verify user exists
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

The overriddenBy FK to User was failing when the session contained a
stale user ID (e.g. after database reseed). Added ensureUserExists()
guard to all override/reinstate mutations and switched single-record
updates to use Prisma connect syntax for safer FK resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 22:23:16 +01:00
parent d02b0b91b9
commit 04c54b6794

View File

@@ -12,6 +12,24 @@ import {
NotificationTypes, NotificationTypes,
} from '../services/in-app-notification' } from '../services/in-app-notification'
/**
* Verify the current session user exists in the database.
* Guards against stale JWT sessions (e.g., after database reseed).
*/
async function ensureUserExists(db: PrismaClient, userId: string): Promise<string> {
const user = await db.user.findUnique({
where: { id: userId },
select: { id: true },
})
if (!user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Your session refers to a user that no longer exists. Please log out and log back in.',
})
}
return user.id
}
/** /**
* Extract a numeric confidence/quality score from aiScreeningJson. * Extract a numeric confidence/quality score from aiScreeningJson.
* Looks for common keys: overallScore, confidenceScore, score, qualityScore. * Looks for common keys: overallScore, confidenceScore, score, qualityScore.
@@ -908,18 +926,20 @@ export const filteringRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
const result = await ctx.prisma.filteringResult.update({ const result = await ctx.prisma.filteringResult.update({
where: { id: input.id }, where: { id: input.id },
data: { data: {
finalOutcome: input.finalOutcome, finalOutcome: input.finalOutcome,
overriddenBy: ctx.user.id, overriddenByUser: { connect: { id: verifiedUserId } },
overriddenAt: new Date(), overriddenAt: new Date(),
overrideReason: input.reason, overrideReason: input.reason,
}, },
}) })
await logAudit({ await logAudit({
userId: ctx.user.id, userId: verifiedUserId,
action: 'UPDATE', action: 'UPDATE',
entityType: 'FilteringResult', entityType: 'FilteringResult',
entityId: input.id, entityId: input.id,
@@ -946,18 +966,20 @@ export const filteringRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
await ctx.prisma.filteringResult.updateMany({ await ctx.prisma.filteringResult.updateMany({
where: { id: { in: input.ids } }, where: { id: { in: input.ids } },
data: { data: {
finalOutcome: input.finalOutcome, finalOutcome: input.finalOutcome,
overriddenBy: ctx.user.id, overriddenBy: verifiedUserId,
overriddenAt: new Date(), overriddenAt: new Date(),
overrideReason: input.reason, overrideReason: input.reason,
}, },
}) })
await logAudit({ await logAudit({
userId: ctx.user.id, userId: verifiedUserId,
action: 'BULK_UPDATE_STATUS', action: 'BULK_UPDATE_STATUS',
entityType: 'FilteringResult', entityType: 'FilteringResult',
detailsJson: { detailsJson: {
@@ -983,6 +1005,8 @@ export const filteringRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
const currentRound = await ctx.prisma.round.findUniqueOrThrow({ const currentRound = await ctx.prisma.round.findUniqueOrThrow({
where: { id: input.roundId }, where: { id: input.roundId },
select: { id: true, competitionId: true, sortOrder: true, name: true }, select: { id: true, competitionId: true, sortOrder: true, name: true },
@@ -1101,7 +1125,7 @@ export const filteringRouter = router({
where: { id: { in: demotedIds } }, where: { id: { in: demotedIds } },
data: { data: {
finalOutcome: 'FLAGGED', finalOutcome: 'FLAGGED',
overriddenBy: ctx.user.id, overriddenBy: verifiedUserId,
overriddenAt: new Date(), overriddenAt: new Date(),
overrideReason: 'Demoted by category target enforcement', overrideReason: 'Demoted by category target enforcement',
}, },
@@ -1112,7 +1136,7 @@ export const filteringRouter = router({
await ctx.prisma.$transaction(operations) await ctx.prisma.$transaction(operations)
await logAudit({ await logAudit({
userId: ctx.user.id, userId: verifiedUserId,
action: 'UPDATE', action: 'UPDATE',
entityType: 'Stage', entityType: 'Stage',
entityId: input.roundId, entityId: input.roundId,
@@ -1149,6 +1173,8 @@ export const filteringRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
await ctx.prisma.filteringResult.update({ await ctx.prisma.filteringResult.update({
where: { where: {
roundId_projectId: { roundId_projectId: {
@@ -1158,7 +1184,7 @@ export const filteringRouter = router({
}, },
data: { data: {
finalOutcome: 'PASSED', finalOutcome: 'PASSED',
overriddenBy: ctx.user.id, overriddenByUser: { connect: { id: verifiedUserId } },
overriddenAt: new Date(), overriddenAt: new Date(),
overrideReason: 'Reinstated by admin', overrideReason: 'Reinstated by admin',
}, },
@@ -1170,7 +1196,7 @@ export const filteringRouter = router({
}) })
await logAudit({ await logAudit({
userId: ctx.user.id, userId: verifiedUserId,
action: 'UPDATE', action: 'UPDATE',
entityType: 'FilteringResult', entityType: 'FilteringResult',
detailsJson: { detailsJson: {
@@ -1192,6 +1218,8 @@ export const filteringRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
await ctx.prisma.$transaction([ await ctx.prisma.$transaction([
...input.projectIds.map((projectId) => ...input.projectIds.map((projectId) =>
ctx.prisma.filteringResult.update({ ctx.prisma.filteringResult.update({
@@ -1203,7 +1231,7 @@ export const filteringRouter = router({
}, },
data: { data: {
finalOutcome: 'PASSED', finalOutcome: 'PASSED',
overriddenBy: ctx.user.id, overriddenByUser: { connect: { id: verifiedUserId } },
overriddenAt: new Date(), overriddenAt: new Date(),
overrideReason: 'Bulk reinstated by admin', overrideReason: 'Bulk reinstated by admin',
}, },
@@ -1216,7 +1244,7 @@ export const filteringRouter = router({
]) ])
await logAudit({ await logAudit({
userId: ctx.user.id, userId: verifiedUserId,
action: 'BULK_UPDATE_STATUS', action: 'BULK_UPDATE_STATUS',
entityType: 'FilteringResult', entityType: 'FilteringResult',
detailsJson: { detailsJson: {