Fix round deletion FK constraint with migration and defensive code
Build and Push Docker Image / build (push) Failing after 7m3s
Details
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:
parent
5c4200158f
commit
573785e440
|
|
@ -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;
|
||||||
|
|
@ -38,6 +38,7 @@ import {
|
||||||
import { getInitials } from '@/lib/utils'
|
import { getInitials } from '@/lib/utils'
|
||||||
import { Logo } from '@/components/shared/logo'
|
import { Logo } from '@/components/shared/logo'
|
||||||
import { EditionSelector } from '@/components/shared/edition-selector'
|
import { EditionSelector } from '@/components/shared/edition-selector'
|
||||||
|
import { useEdition } from '@/contexts/edition-context'
|
||||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||||
import { NotificationBell } from '@/components/shared/notification-bell'
|
import { NotificationBell } from '@/components/shared/notification-bell'
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
|
|
@ -145,10 +146,19 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||||
const { data: avatarUrl } = trpc.avatar.getUrl.useQuery()
|
const { data: avatarUrl } = trpc.avatar.getUrl.useQuery()
|
||||||
|
const { currentEdition } = useEdition()
|
||||||
|
|
||||||
const isSuperAdmin = user.role === 'SUPER_ADMIN'
|
const isSuperAdmin = user.role === 'SUPER_ADMIN'
|
||||||
const roleLabel = roleLabels[user.role || ''] || 'User'
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Mobile menu button */}
|
{/* 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">
|
<p className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
||||||
Administration
|
Administration
|
||||||
</p>
|
</p>
|
||||||
{adminNavigation.map((item) => {
|
{dynamicAdminNav.map((item) => {
|
||||||
let isActive = pathname.startsWith(item.href)
|
let isActive = pathname.startsWith(item.href)
|
||||||
if (item.activeMatch) {
|
if (item.activeMatch) {
|
||||||
isActive = pathname.includes(item.activeMatch)
|
isActive = pathname.includes(item.activeMatch)
|
||||||
|
|
|
||||||
|
|
@ -615,6 +615,12 @@ export const roundRouter = router({
|
||||||
userAgent: ctx.userAgent,
|
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({
|
await tx.round.delete({
|
||||||
where: { id: input.id },
|
where: { id: input.id },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue