Fix award eligibility FK constraint + add country column to round projects
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m13s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m13s
- specialAward.setEligibility: add ensureUserExists() guard and use Prisma connect syntax to prevent FK violation on stale session user IDs - specialAward.confirmShortlist: same ensureUserExists() guard for confirmedBy - Round projects table: add Country column showing project origin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -330,7 +330,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
||||
{/* Table */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="grid grid-cols-[40px_1fr_140px_120px_100px_48px] gap-2 px-4 py-2.5 bg-muted/40 text-xs font-medium text-muted-foreground border-b">
|
||||
<div className="grid grid-cols-[40px_1fr_140px_160px_120px_100px_48px] gap-2 px-4 py-2.5 bg-muted/40 text-xs font-medium text-muted-foreground border-b">
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={filtered.length > 0 && filtered.every((ps: any) => selectedIds.has(ps.projectId))}
|
||||
@@ -339,6 +339,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
||||
</div>
|
||||
<div>Project</div>
|
||||
<div>Category</div>
|
||||
<div>Country</div>
|
||||
<div>State</div>
|
||||
<div>Entered</div>
|
||||
<div />
|
||||
@@ -351,7 +352,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
||||
return (
|
||||
<div
|
||||
key={ps.id}
|
||||
className="grid grid-cols-[40px_1fr_140px_120px_100px_48px] gap-2 px-4 py-3 items-center border-b last:border-b-0 hover:bg-muted/30 text-sm"
|
||||
className="grid grid-cols-[40px_1fr_140px_160px_120px_100px_48px] gap-2 px-4 py-3 items-center border-b last:border-b-0 hover:bg-muted/30 text-sm"
|
||||
>
|
||||
<div>
|
||||
<Checkbox
|
||||
@@ -373,6 +374,9 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
||||
{ps.project?.competitionCategory || '—'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{ps.project?.country || '—'}
|
||||
</div>
|
||||
<div>
|
||||
<Badge variant="outline" className={`text-xs ${cfg.color}`}>
|
||||
<StateIcon className="h-3 w-3 mr-1" />
|
||||
|
||||
@@ -4,6 +4,25 @@ import { Prisma } from '@prisma/client'
|
||||
import { router, protectedProcedure, adminProcedure } from '../trpc'
|
||||
import { logAudit } from '../utils/audit'
|
||||
import { processEligibilityJob } from '../services/award-eligibility-job'
|
||||
import type { PrismaClient } from '@prisma/client'
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
export const specialAwardRouter = router({
|
||||
// ─── Admin Queries ──────────────────────────────────────────────────────
|
||||
@@ -425,6 +444,8 @@ export const specialAwardRouter = router({
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
|
||||
|
||||
await ctx.prisma.awardEligibility.upsert({
|
||||
where: {
|
||||
awardId_projectId: {
|
||||
@@ -433,16 +454,16 @@ export const specialAwardRouter = router({
|
||||
},
|
||||
},
|
||||
create: {
|
||||
awardId: input.awardId,
|
||||
projectId: input.projectId,
|
||||
award: { connect: { id: input.awardId } },
|
||||
project: { connect: { id: input.projectId } },
|
||||
eligible: input.eligible,
|
||||
method: 'MANUAL',
|
||||
overriddenBy: ctx.user.id,
|
||||
overriddenByUser: { connect: { id: verifiedUserId } },
|
||||
overriddenAt: new Date(),
|
||||
},
|
||||
update: {
|
||||
eligible: input.eligible,
|
||||
overriddenBy: ctx.user.id,
|
||||
overriddenByUser: { connect: { id: verifiedUserId } },
|
||||
overriddenAt: new Date(),
|
||||
},
|
||||
})
|
||||
@@ -1018,12 +1039,14 @@ export const specialAwardRouter = router({
|
||||
})
|
||||
}
|
||||
|
||||
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
|
||||
|
||||
// Mark all as confirmed
|
||||
await ctx.prisma.awardEligibility.updateMany({
|
||||
where: { awardId: input.awardId, shortlisted: true, eligible: true },
|
||||
data: {
|
||||
confirmedAt: new Date(),
|
||||
confirmedBy: ctx.user.id,
|
||||
confirmedBy: verifiedUserId,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -690,6 +690,7 @@ export async function getProjectRoundStates(
|
||||
title: true,
|
||||
teamName: true,
|
||||
competitionCategory: true,
|
||||
country: true,
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user