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 */}
|
{/* Table */}
|
||||||
<div className="border rounded-lg overflow-hidden">
|
<div className="border rounded-lg overflow-hidden">
|
||||||
{/* Header */}
|
{/* 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>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={filtered.length > 0 && filtered.every((ps: any) => selectedIds.has(ps.projectId))}
|
checked={filtered.length > 0 && filtered.every((ps: any) => selectedIds.has(ps.projectId))}
|
||||||
@@ -339,6 +339,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
</div>
|
</div>
|
||||||
<div>Project</div>
|
<div>Project</div>
|
||||||
<div>Category</div>
|
<div>Category</div>
|
||||||
|
<div>Country</div>
|
||||||
<div>State</div>
|
<div>State</div>
|
||||||
<div>Entered</div>
|
<div>Entered</div>
|
||||||
<div />
|
<div />
|
||||||
@@ -351,7 +352,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={ps.id}
|
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>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -373,6 +374,9 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
{ps.project?.competitionCategory || '—'}
|
{ps.project?.competitionCategory || '—'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs text-muted-foreground truncate">
|
||||||
|
{ps.project?.country || '—'}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Badge variant="outline" className={`text-xs ${cfg.color}`}>
|
<Badge variant="outline" className={`text-xs ${cfg.color}`}>
|
||||||
<StateIcon className="h-3 w-3 mr-1" />
|
<StateIcon className="h-3 w-3 mr-1" />
|
||||||
|
|||||||
@@ -4,6 +4,25 @@ import { Prisma } from '@prisma/client'
|
|||||||
import { router, protectedProcedure, adminProcedure } from '../trpc'
|
import { router, protectedProcedure, adminProcedure } from '../trpc'
|
||||||
import { logAudit } from '../utils/audit'
|
import { logAudit } from '../utils/audit'
|
||||||
import { processEligibilityJob } from '../services/award-eligibility-job'
|
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({
|
export const specialAwardRouter = router({
|
||||||
// ─── Admin Queries ──────────────────────────────────────────────────────
|
// ─── Admin Queries ──────────────────────────────────────────────────────
|
||||||
@@ -425,6 +444,8 @@ export const specialAwardRouter = router({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
|
||||||
|
|
||||||
await ctx.prisma.awardEligibility.upsert({
|
await ctx.prisma.awardEligibility.upsert({
|
||||||
where: {
|
where: {
|
||||||
awardId_projectId: {
|
awardId_projectId: {
|
||||||
@@ -433,16 +454,16 @@ export const specialAwardRouter = router({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
awardId: input.awardId,
|
award: { connect: { id: input.awardId } },
|
||||||
projectId: input.projectId,
|
project: { connect: { id: input.projectId } },
|
||||||
eligible: input.eligible,
|
eligible: input.eligible,
|
||||||
method: 'MANUAL',
|
method: 'MANUAL',
|
||||||
overriddenBy: ctx.user.id,
|
overriddenByUser: { connect: { id: verifiedUserId } },
|
||||||
overriddenAt: new Date(),
|
overriddenAt: new Date(),
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
eligible: input.eligible,
|
eligible: input.eligible,
|
||||||
overriddenBy: ctx.user.id,
|
overriddenByUser: { connect: { id: verifiedUserId } },
|
||||||
overriddenAt: new Date(),
|
overriddenAt: new Date(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1018,12 +1039,14 @@ export const specialAwardRouter = router({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verifiedUserId = await ensureUserExists(ctx.prisma, ctx.user.id)
|
||||||
|
|
||||||
// Mark all as confirmed
|
// Mark all as confirmed
|
||||||
await ctx.prisma.awardEligibility.updateMany({
|
await ctx.prisma.awardEligibility.updateMany({
|
||||||
where: { awardId: input.awardId, shortlisted: true, eligible: true },
|
where: { awardId: input.awardId, shortlisted: true, eligible: true },
|
||||||
data: {
|
data: {
|
||||||
confirmedAt: new Date(),
|
confirmedAt: new Date(),
|
||||||
confirmedBy: ctx.user.id,
|
confirmedBy: verifiedUserId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -690,6 +690,7 @@ export async function getProjectRoundStates(
|
|||||||
title: true,
|
title: true,
|
||||||
teamName: true,
|
teamName: true,
|
||||||
competitionCategory: true,
|
competitionCategory: true,
|
||||||
|
country: true,
|
||||||
status: true,
|
status: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user