fix: tech debt batch 1 — TS errors, vulnerabilities, dead code

- Fixed 12 TypeScript errors across analytics.ts, observer-project-detail.tsx, bulk-upload/page.tsx, settings/profile/page.tsx
- npm audit: 8 vulnerabilities resolved (1 critical, 4 high, 3 moderate)
- Deleted 3 dead files: live-control.ts (618 lines), feature-flags.ts, file-type-categories.ts
- Removed typescript.ignoreBuildErrors: true — TS errors now block builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 23:51:44 +01:00
parent 1ebdf5f9c9
commit 1356809cb1
9 changed files with 149 additions and 842 deletions

View File

@@ -1,6 +1,7 @@
'use client'
import { useState, useCallback, useRef, useEffect, useMemo } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner'

View File

@@ -94,10 +94,10 @@ export default function ProfileSettingsPage() {
setExpertiseTags(user.expertiseTags || [])
setDigestFrequency(user.digestFrequency || 'none')
setPreferredWorkload(user.preferredWorkload ?? null)
const avail = user.availabilityJson as { startDate?: string; endDate?: string } | null
if (avail) {
setAvailabilityStart(avail.startDate || '')
setAvailabilityEnd(avail.endDate || '')
const avail = user.availabilityJson as Array<{ start?: string; end?: string }> | null
if (avail && avail.length > 0) {
setAvailabilityStart(avail[0].start || '')
setAvailabilityEnd(avail[0].end || '')
}
setProfileLoaded(true)
}
@@ -114,10 +114,10 @@ export default function ProfileSettingsPage() {
expertiseTags,
digestFrequency: digestFrequency as 'none' | 'daily' | 'weekly',
preferredWorkload: preferredWorkload ?? undefined,
availabilityJson: (availabilityStart || availabilityEnd) ? {
startDate: availabilityStart || undefined,
endDate: availabilityEnd || undefined,
} : undefined,
availabilityJson: (availabilityStart || availabilityEnd) ? [{
start: availabilityStart || '',
end: availabilityEnd || '',
}] : undefined,
})
toast.success('Profile updated successfully')
refetch()

View File

@@ -933,12 +933,14 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
// Group files by round
type FileItem = (typeof project.files)[number]
const roundMap = new Map<string, { roundId: string | null; roundName: string; sortOrder: number; files: FileItem[] }>()
// Build roundId→round lookup from competitionRounds
const roundLookup = new Map(competitionRounds.map((r, idx) => [r.id, { name: r.name, sortOrder: idx }]))
for (const f of project.files) {
const key = (f as any).roundId ?? '__none__'
const key = f.roundId ?? '__none__'
if (!roundMap.has(key)) {
const round = (f as any).round as { id: string; name: string; sortOrder: number } | null
const round = f.roundId ? roundLookup.get(f.roundId) : null
roundMap.set(key, {
roundId: round?.id ?? null,
roundId: f.roundId ?? null,
roundName: round?.name ?? 'Other Files',
sortOrder: round?.sortOrder ?? 999,
files: [],

View File

@@ -1,49 +0,0 @@
import { prisma } from '@/lib/prisma'
/**
* Feature flag keys — used to control progressive rollout of new architecture.
* Stored as SystemSetting records with category FEATURE_FLAGS.
*/
export const FEATURE_FLAGS = {
/** Use Competition/Round model instead of Pipeline/Track/Stage */
USE_COMPETITION_MODEL: 'feature.useCompetitionModel',
} as const
type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
/**
* Check if a feature flag is enabled (server-side).
* Returns false if the flag doesn't exist in the database.
*/
export async function isFeatureEnabled(flag: FeatureFlagKey): Promise<boolean> {
try {
const setting = await prisma.systemSettings.findUnique({
where: { key: flag },
})
// Default to true for competition model (legacy Pipeline system removed)
if (!setting) return flag === FEATURE_FLAGS.USE_COMPETITION_MODEL ? true : false
return setting.value === 'true'
} catch {
return flag === FEATURE_FLAGS.USE_COMPETITION_MODEL ? true : false
}
}
/**
* Set a feature flag value (server-side, admin only).
*/
export async function setFeatureFlag(
flag: FeatureFlagKey,
enabled: boolean,
): Promise<void> {
await prisma.systemSettings.upsert({
where: { key: flag },
update: { value: String(enabled) },
create: {
key: flag,
value: String(enabled),
type: 'BOOLEAN',
category: 'FEATURE_FLAGS',
description: `Feature flag: ${flag}`,
},
})
}

View File

@@ -1,30 +0,0 @@
export type FileTypeCategory = {
id: string
label: string
mimeTypes: string[]
extensions: string[]
}
export const FILE_TYPE_CATEGORIES: FileTypeCategory[] = [
{ id: 'pdf', label: 'PDF', mimeTypes: ['application/pdf'], extensions: ['.pdf'] },
{ id: 'word', label: 'Word', mimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], extensions: ['.doc', '.docx'] },
{ id: 'powerpoint', label: 'PowerPoint', mimeTypes: ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], extensions: ['.ppt', '.pptx'] },
{ id: 'excel', label: 'Excel', mimeTypes: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], extensions: ['.xls', '.xlsx'] },
{ id: 'images', label: 'Images', mimeTypes: ['image/*'], extensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'] },
{ id: 'videos', label: 'Videos', mimeTypes: ['video/*'], extensions: ['.mp4', '.mov', '.avi', '.webm'] },
]
/** Get active category IDs from a list of mime types */
export function getActiveCategoriesFromMimeTypes(mimeTypes: string[]): string[] {
if (!mimeTypes || !Array.isArray(mimeTypes)) return []
return FILE_TYPE_CATEGORIES.filter((cat) =>
cat.mimeTypes.some((mime) => mimeTypes.includes(mime))
).map((cat) => cat.id)
}
/** Convert category IDs to flat mime type array */
export function categoriesToMimeTypes(categoryIds: string[]): string[] {
return FILE_TYPE_CATEGORIES.filter((cat) => categoryIds.includes(cat.id)).flatMap(
(cat) => cat.mimeTypes
)
}

View File

@@ -1379,11 +1379,10 @@ export const analyticsRouter = router({
bucket: true, objectKey: true, pageCount: true, textPreview: true,
detectedLang: true, langConfidence: true, analyzedAt: true,
roundId: true,
round: { select: { id: true, name: true, roundType: true, sortOrder: true } },
requirementId: true,
requirement: { select: { id: true, name: true, description: true, isRequired: true } },
},
orderBy: [{ round: { sortOrder: 'asc' } }, { createdAt: 'asc' }],
orderBy: [{ createdAt: 'asc' }],
},
teamMembers: {
include: {

View File

@@ -1,618 +0,0 @@
/**
* Live Control Service
*
* Manages real-time control of live final events within a round.
* Handles session management, project cursor navigation, queue reordering,
* pause/resume, and cohort voting windows.
*
* The LiveProgressCursor tracks the current position in a live presentation
* sequence. Cohorts group projects for voting with configurable windows.
*/
import type { PrismaClient } from '@prisma/client'
import { logAudit } from '@/server/utils/audit'
// ─── Types ──────────────────────────────────────────────────────────────────
export interface SessionResult {
success: boolean
sessionId: string | null
cursorId: string | null
errors?: string[]
}
export interface CursorState {
roundId: string
sessionId: string
activeProjectId: string | null
activeOrderIndex: number
isPaused: boolean
totalProjects: number
}
// ─── Helpers ────────────────────────────────────────────────────────────────
function generateSessionId(): string {
const timestamp = Date.now().toString(36)
const random = Math.random().toString(36).substring(2, 8)
return `live-${timestamp}-${random}`
}
// ─── Start Session ──────────────────────────────────────────────────────────
/**
* Create or reset a LiveProgressCursor for a round. If a cursor already exists,
* it is reset to the beginning. A new sessionId is always generated.
*/
export async function startSession(
roundId: string,
actorId: string,
prisma: PrismaClient | any
): Promise<SessionResult> {
try {
// Verify round exists and is a LIVE_FINAL type
const round = await prisma.round.findUnique({
where: { id: roundId },
})
if (!round) {
return {
success: false,
sessionId: null,
cursorId: null,
errors: [`Round ${roundId} not found`],
}
}
if (round.roundType !== 'LIVE_FINAL') {
return {
success: false,
sessionId: null,
cursorId: null,
errors: [
`Round "${round.name}" is type ${round.roundType}, expected LIVE_FINAL`,
],
}
}
// Find the first project in the first cohort
const firstCohortProject = await prisma.cohortProject.findFirst({
where: {
cohort: { roundId },
},
orderBy: { sortOrder: 'asc' as const },
select: { projectId: true },
})
const sessionId = generateSessionId()
// Upsert the cursor (one per round)
const cursor = await prisma.liveProgressCursor.upsert({
where: { roundId },
create: {
roundId,
sessionId,
activeProjectId: firstCohortProject?.projectId ?? null,
activeOrderIndex: 0,
isPaused: false,
},
update: {
sessionId,
activeProjectId: firstCohortProject?.projectId ?? null,
activeOrderIndex: 0,
isPaused: false,
},
})
// Decision audit log
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.session_started',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
actorId,
detailsJson: {
roundId,
sessionId,
firstProjectId: firstCohortProject?.projectId ?? null,
},
},
})
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_SESSION_STARTED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { roundId, sessionId },
})
return {
success: true,
sessionId,
cursorId: cursor.id,
}
} catch (error) {
console.error('[LiveControl] Failed to start session:', error)
return {
success: false,
sessionId: null,
cursorId: null,
errors: [
error instanceof Error ? error.message : 'Failed to start live session',
],
}
}
}
// ─── Set Active Project ─────────────────────────────────────────────────────
/**
* Set the currently active project in the live session.
* Validates that the project belongs to a cohort in this round and performs
* a version check on the cursor's sessionId to prevent stale updates.
*/
export async function setActiveProject(
roundId: string,
projectId: string,
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; errors?: string[] }> {
try {
// Verify cursor exists
const cursor = await prisma.liveProgressCursor.findUnique({
where: { roundId },
})
if (!cursor) {
return {
success: false,
errors: ['No live session found for this round. Start a session first.'],
}
}
// Verify project is in a cohort for this round
const cohortProject = await prisma.cohortProject.findFirst({
where: {
projectId,
cohort: { roundId },
},
select: { id: true, sortOrder: true },
})
if (!cohortProject) {
return {
success: false,
errors: [
`Project ${projectId} is not in any cohort for round ${roundId}`,
],
}
}
// Update cursor
await prisma.liveProgressCursor.update({
where: { roundId },
data: {
activeProjectId: projectId,
activeOrderIndex: cohortProject.sortOrder,
},
})
// Audit
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.cursor_updated',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
actorId,
detailsJson: {
roundId,
projectId,
orderIndex: cohortProject.sortOrder,
action: 'setActiveProject',
},
},
})
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_SET_ACTIVE_PROJECT',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { projectId, orderIndex: cohortProject.sortOrder },
})
return { success: true }
} catch (error) {
console.error('[LiveControl] Failed to set active project:', error)
return {
success: false,
errors: [
error instanceof Error
? error.message
: 'Failed to set active project',
],
}
}
}
// ─── Jump to Project ────────────────────────────────────────────────────────
/**
* Jump to a project by its order index in the cohort queue.
*/
export async function jumpToProject(
roundId: string,
orderIndex: number,
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; projectId?: string; errors?: string[] }> {
try {
const cursor = await prisma.liveProgressCursor.findUnique({
where: { roundId },
})
if (!cursor) {
return {
success: false,
errors: ['No live session found for this round'],
}
}
// Find the CohortProject at the given sort order
const cohortProject = await prisma.cohortProject.findFirst({
where: {
cohort: { roundId },
sortOrder: orderIndex,
},
select: { projectId: true, sortOrder: true },
})
if (!cohortProject) {
return {
success: false,
errors: [`No project found at order index ${orderIndex}`],
}
}
// Update cursor
await prisma.liveProgressCursor.update({
where: { roundId },
data: {
activeProjectId: cohortProject.projectId,
activeOrderIndex: orderIndex,
},
})
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.cursor_updated',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
actorId,
detailsJson: {
roundId,
projectId: cohortProject.projectId,
orderIndex,
action: 'jumpToProject',
},
},
})
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_JUMP_TO_PROJECT',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { orderIndex, projectId: cohortProject.projectId },
})
return { success: true, projectId: cohortProject.projectId }
} catch (error) {
console.error('[LiveControl] Failed to jump to project:', error)
return {
success: false,
errors: [
error instanceof Error ? error.message : 'Failed to jump to project',
],
}
}
}
// ─── Reorder Queue ──────────────────────────────────────────────────────────
/**
* Reorder the presentation queue by updating CohortProject sortOrder values.
* newOrder is an array of cohortProjectIds in the desired order.
*/
export async function reorderQueue(
roundId: string,
newOrder: string[],
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; errors?: string[] }> {
try {
// Verify all provided IDs belong to cohorts in this round
const cohortProjects = await prisma.cohortProject.findMany({
where: {
id: { in: newOrder },
cohort: { roundId },
},
select: { id: true },
})
const validIds = new Set(cohortProjects.map((cp: any) => cp.id))
const invalidIds = newOrder.filter((id) => !validIds.has(id))
if (invalidIds.length > 0) {
return {
success: false,
errors: [
`CohortProject IDs not found in round ${roundId}: ${invalidIds.join(', ')}`,
],
}
}
// Update sortOrder for each item
await prisma.$transaction(
newOrder.map((cohortProjectId, index) =>
prisma.cohortProject.update({
where: { id: cohortProjectId },
data: { sortOrder: index },
})
)
)
const cursor = await prisma.liveProgressCursor.findUnique({
where: { roundId },
})
if (cursor) {
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.queue_reordered',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
actorId,
detailsJson: {
roundId,
newOrderCount: newOrder.length,
},
},
})
}
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_REORDER_QUEUE',
entityType: 'Round',
entityId: roundId,
detailsJson: { reorderedCount: newOrder.length },
})
return { success: true }
} catch (error) {
console.error('[LiveControl] Failed to reorder queue:', error)
return {
success: false,
errors: [
error instanceof Error ? error.message : 'Failed to reorder queue',
],
}
}
}
// ─── Pause / Resume ─────────────────────────────────────────────────────────
/**
* Toggle the pause state of a live session.
*/
export async function pauseResume(
roundId: string,
isPaused: boolean,
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; errors?: string[] }> {
try {
const cursor = await prisma.liveProgressCursor.findUnique({
where: { roundId },
})
if (!cursor) {
return {
success: false,
errors: ['No live session found for this round'],
}
}
await prisma.liveProgressCursor.update({
where: { roundId },
data: { isPaused },
})
await prisma.decisionAuditLog.create({
data: {
eventType: isPaused ? 'live.session_paused' : 'live.session_resumed',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
actorId,
detailsJson: {
roundId,
isPaused,
sessionId: cursor.sessionId,
},
},
})
await logAudit({
prisma,
userId: actorId,
action: isPaused ? 'LIVE_SESSION_PAUSED' : 'LIVE_SESSION_RESUMED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { roundId, isPaused },
})
return { success: true }
} catch (error) {
console.error('[LiveControl] Failed to pause/resume:', error)
return {
success: false,
errors: [
error instanceof Error
? error.message
: 'Failed to toggle pause state',
],
}
}
}
// ─── Cohort Window Management ───────────────────────────────────────────────
/**
* Open a cohort's voting window. Sets isOpen to true and records the
* window open timestamp.
*/
export async function openCohortWindow(
cohortId: string,
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; errors?: string[] }> {
try {
const cohort = await prisma.cohort.findUnique({
where: { id: cohortId },
})
if (!cohort) {
return { success: false, errors: [`Cohort ${cohortId} not found`] }
}
if (cohort.isOpen) {
return {
success: false,
errors: [`Cohort "${cohort.name}" is already open`],
}
}
const now = new Date()
await prisma.cohort.update({
where: { id: cohortId },
data: {
isOpen: true,
windowOpenAt: now,
},
})
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.cohort_opened',
entityType: 'Cohort',
entityId: cohortId,
actorId,
detailsJson: {
cohortName: cohort.name,
roundId: cohort.roundId,
openedAt: now.toISOString(),
},
},
})
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_COHORT_OPENED',
entityType: 'Cohort',
entityId: cohortId,
detailsJson: { cohortName: cohort.name, roundId: cohort.roundId },
})
return { success: true }
} catch (error) {
console.error('[LiveControl] Failed to open cohort window:', error)
return {
success: false,
errors: [
error instanceof Error
? error.message
: 'Failed to open cohort window',
],
}
}
}
/**
* Close a cohort's voting window. Sets isOpen to false and records the
* window close timestamp.
*/
export async function closeCohortWindow(
cohortId: string,
actorId: string,
prisma: PrismaClient | any
): Promise<{ success: boolean; errors?: string[] }> {
try {
const cohort = await prisma.cohort.findUnique({
where: { id: cohortId },
})
if (!cohort) {
return { success: false, errors: [`Cohort ${cohortId} not found`] }
}
if (!cohort.isOpen) {
return {
success: false,
errors: [`Cohort "${cohort.name}" is already closed`],
}
}
const now = new Date()
await prisma.cohort.update({
where: { id: cohortId },
data: {
isOpen: false,
windowCloseAt: now,
},
})
await prisma.decisionAuditLog.create({
data: {
eventType: 'live.cohort_closed',
entityType: 'Cohort',
entityId: cohortId,
actorId,
detailsJson: {
cohortName: cohort.name,
roundId: cohort.roundId,
closedAt: now.toISOString(),
},
},
})
await logAudit({
prisma,
userId: actorId,
action: 'LIVE_COHORT_CLOSED',
entityType: 'Cohort',
entityId: cohortId,
detailsJson: { cohortName: cohort.name, roundId: cohort.roundId },
})
return { success: true }
} catch (error) {
console.error('[LiveControl] Failed to close cohort window:', error)
return {
success: false,
errors: [
error instanceof Error
? error.message
: 'Failed to close cohort window',
],
}
}
}