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:
@@ -472,10 +472,19 @@ export const assignmentRouter = router({
|
||||
})
|
||||
: undefined
|
||||
|
||||
// Send batch notification to each user
|
||||
// Group users by project count so we can send bulk notifications per group
|
||||
const usersByProjectCount = new Map<number, string[]>()
|
||||
for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
|
||||
await createNotification({
|
||||
userId,
|
||||
const existing = usersByProjectCount.get(projectCount) || []
|
||||
existing.push(userId)
|
||||
usersByProjectCount.set(projectCount, existing)
|
||||
}
|
||||
|
||||
// Send bulk notifications for each project count group
|
||||
for (const [projectCount, userIds] of usersByProjectCount) {
|
||||
if (userIds.length === 0) continue
|
||||
await createBulkNotifications({
|
||||
userIds,
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
|
||||
@@ -884,9 +893,19 @@ export const assignmentRouter = router({
|
||||
})
|
||||
: undefined
|
||||
|
||||
// Group users by project count so we can send bulk notifications per group
|
||||
const usersByProjectCount = new Map<number, string[]>()
|
||||
for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
|
||||
await createNotification({
|
||||
userId,
|
||||
const existing = usersByProjectCount.get(projectCount) || []
|
||||
existing.push(userId)
|
||||
usersByProjectCount.set(projectCount, existing)
|
||||
}
|
||||
|
||||
// Send bulk notifications for each project count group
|
||||
for (const [projectCount, userIds] of usersByProjectCount) {
|
||||
if (userIds.length === 0) continue
|
||||
await createBulkNotifications({
|
||||
userIds,
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
|
||||
@@ -972,9 +991,19 @@ export const assignmentRouter = router({
|
||||
})
|
||||
: undefined
|
||||
|
||||
// Group users by project count so we can send bulk notifications per group
|
||||
const usersByProjectCount = new Map<number, string[]>()
|
||||
for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
|
||||
await createNotification({
|
||||
userId,
|
||||
const existing = usersByProjectCount.get(projectCount) || []
|
||||
existing.push(userId)
|
||||
usersByProjectCount.set(projectCount, existing)
|
||||
}
|
||||
|
||||
// Send bulk notifications for each project count group
|
||||
for (const [projectCount, userIds] of usersByProjectCount) {
|
||||
if (userIds.length === 0) continue
|
||||
await createBulkNotifications({
|
||||
userIds,
|
||||
type: NotificationTypes.BATCH_ASSIGNED,
|
||||
title: `${projectCount} Projects Assigned`,
|
||||
message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
|
||||
@@ -1038,7 +1067,7 @@ export const assignmentRouter = router({
|
||||
/**
|
||||
* Get AI assignment job status (for polling)
|
||||
*/
|
||||
getAIAssignmentJobStatus: protectedProcedure
|
||||
getAIAssignmentJobStatus: adminProcedure
|
||||
.input(z.object({ jobId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const job = await ctx.prisma.assignmentJob.findUniqueOrThrow({
|
||||
|
||||
Reference in New Issue
Block a user