Comprehensive platform review: security fixes, query optimization, UI improvements, and code cleanup

Security (Critical/High):
- Fix path traversal bypass in local storage provider (path.resolve + prefix check)
- Fix timing-unsafe HMAC comparison (crypto.timingSafeEqual)
- Add auth + ownership checks to email API routes (verify-credentials, change-password)
- Remove hardcoded secret key fallback in local storage provider
- Add production credential check for MinIO (fail loudly if not set)
- Remove DB error details from health check response
- Add stricter rate limiting on application submissions (5/hour)
- Add rate limiting on email availability check (anti-enumeration)
- Change getAIAssignmentJobStatus to adminProcedure
- Block dangerous file extensions on upload
- Reduce project list max perPage from 5000 to 200

Query Optimization:
- Optimize analytics getProjectRankings with select instead of full includes
- Fix N+1 in mentor.getSuggestions (batch findMany instead of loop)
- Use _count for files instead of fetching full file records in project list
- Switch to bulk notifications in assignment and user bulk operations
- Batch filtering upserts (25 per transaction instead of all at once)

UI/UX:
- Replace Inter font with Montserrat in public layout (brand consistency)
- Use Logo component in public layout instead of placeholder
- Create branded 404 and error pages
- Make admin rounds table responsive with mobile card layout
- Fix notification bell paths to be role-aware
- Replace hardcoded slate colors with semantic tokens in admin sidebar
- Force light mode (dark mode untested)
- Adjust CardTitle default size
- Improve muted-foreground contrast for accessibility (A11Y)
- Move profile form state initialization to useEffect

Code Quality:
- Extract shared toProjectWithRelations to anonymization.ts (removed 3 duplicates)
- Remove dead code: getObjectInfo, isValidImageSize, unused batch tag functions, debug logs
- Remove unused twilio dependency
- Remove redundant email index from schema
- Add actual storage object deletion when file records are deleted
- Wrap evaluation submit + assignment update in
- Add comprehensive platform review document

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 20:31:08 +01:00
parent a1f32597a0
commit 8d0979e649
35 changed files with 2463 additions and 730 deletions

View File

@@ -1,18 +1,53 @@
import Link from 'next/link'
import Image from 'next/image'
import { Button } from '@/components/ui/button'
import { FileQuestion } from 'lucide-react'
export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 text-center">
<FileQuestion className="h-16 w-16 text-muted-foreground/50" />
<h1 className="text-2xl font-semibold">Page Not Found</h1>
<p className="text-muted-foreground">
The page you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<Button asChild>
<Link href="/">Go Home</Link>
</Button>
<div className="flex min-h-screen flex-col bg-background">
{/* Header */}
<header className="border-b border-border bg-white">
<div className="container-app flex h-16 items-center">
<Link href="/">
<Image
src="/images/MOPC-blue-long.png"
alt="MOPC - Monaco Ocean Protection Challenge"
width={140}
height={45}
className="h-10 w-auto"
priority
/>
</Link>
</div>
</header>
{/* Content */}
<main className="flex flex-1 flex-col items-center justify-center px-4 py-16 text-center">
<p className="text-[8rem] font-bold leading-none tracking-tight text-brand-blue/10 sm:text-[12rem]">
404
</p>
<h1 className="-mt-4 text-display font-bold text-brand-blue sm:-mt-8">
Page Not Found
</h1>
<p className="mt-4 max-w-md text-body text-muted-foreground">
The page you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<div className="mt-8">
<Button asChild size="lg">
<Link href="/">Return to Dashboard</Link>
</Button>
</div>
</main>
{/* Footer */}
<footer className="border-t border-border bg-brand-blue py-6 text-white">
<div className="container-app text-center">
<p className="text-small">
&copy; {new Date().getFullYear()} Monaco Ocean Protection Challenge.
All rights reserved.
</p>
</div>
</footer>
</div>
)
}