Fix round deletion FK constraint with migration and defensive code
Build and Push Docker Image / build (push) Failing after 7m3s Details

- Add SQL migration to CASCADE Evaluation.formId and SET NULL ProjectFile.roundId
- Explicitly delete evaluations in round delete transaction as defensive measure
- Make sidebar Apply Page link dynamic using current edition context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-10 20:42:33 +01:00
parent 5c4200158f
commit 573785e440
3 changed files with 30 additions and 1 deletions

View File

@ -0,0 +1,13 @@
-- Fix round deletion FK constraint errors
-- Add CASCADE on Evaluation.formId so deleting EvaluationForm cascades to Evaluations
-- Add SET NULL on ProjectFile.roundId so deleting Round nullifies the reference
-- AlterTable: Evaluation.formId -> onDelete CASCADE
ALTER TABLE "Evaluation" DROP CONSTRAINT "Evaluation_formId_fkey";
ALTER TABLE "Evaluation" ADD CONSTRAINT "Evaluation_formId_fkey"
FOREIGN KEY ("formId") REFERENCES "EvaluationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AlterTable: ProjectFile.roundId -> onDelete SET NULL
ALTER TABLE "ProjectFile" DROP CONSTRAINT "ProjectFile_roundId_fkey";
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_roundId_fkey"
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -38,6 +38,7 @@ import {
import { getInitials } from '@/lib/utils'
import { Logo } from '@/components/shared/logo'
import { EditionSelector } from '@/components/shared/edition-selector'
import { useEdition } from '@/contexts/edition-context'
import { UserAvatar } from '@/components/shared/user-avatar'
import { NotificationBell } from '@/components/shared/notification-bell'
import { trpc } from '@/lib/trpc/client'
@ -145,10 +146,19 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
const pathname = usePathname()
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const { data: avatarUrl } = trpc.avatar.getUrl.useQuery()
const { currentEdition } = useEdition()
const isSuperAdmin = user.role === 'SUPER_ADMIN'
const roleLabel = roleLabels[user.role || ''] || 'User'
// Build dynamic admin nav with current edition's apply page
const dynamicAdminNav = adminNavigation.map((item) => {
if (item.name === 'Apply Page' && currentEdition?.id) {
return { ...item, href: `/admin/programs/${currentEdition.id}/apply-settings` }
}
return item
})
return (
<>
{/* Mobile menu button */}
@ -235,7 +245,7 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
<p className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
Administration
</p>
{adminNavigation.map((item) => {
{dynamicAdminNav.map((item) => {
let isActive = pathname.startsWith(item.href)
if (item.activeMatch) {
isActive = pathname.includes(item.activeMatch)

View File

@ -615,6 +615,12 @@ export const roundRouter = router({
userAgent: ctx.userAgent,
})
// Delete evaluations first to avoid FK constraint on Evaluation.formId
// (formId FK may not have CASCADE in older DB schemas)
await tx.evaluation.deleteMany({
where: { form: { roundId: input.id } },
})
await tx.round.delete({
where: { id: input.id },
})