2026-02-14 15:26:42 +01:00
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router , adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
import { sendAnnouncementEmail } from '@/lib/email'
import type { PrismaClient } from '@prisma/client'
/ * *
* Send round - entry notification emails to project team members .
* Fire - and - forget : errors are logged but never block the assignment .
* /
async function sendRoundEntryEmails (
prisma : PrismaClient ,
projectIds : string [ ] ,
roundName : string ,
) {
try {
// Fetch projects with team members' user emails + fallback submittedByEmail
const projects = await prisma . project . findMany ( {
where : { id : { in : projectIds } } ,
select : {
id : true ,
title : true ,
submittedByEmail : true ,
teamMembers : {
select : {
user : { select : { email : true , name : true } } ,
} ,
} ,
} ,
} )
const emailPromises : Promise < void > [ ] = [ ]
for ( const project of projects ) {
// Collect unique emails for this project
const recipients = new Map < string , string | null > ( )
for ( const tm of project . teamMembers ) {
if ( tm . user . email ) {
recipients . set ( tm . user . email , tm . user . name )
}
}
// Fallback: if no team members have emails, use submittedByEmail
if ( recipients . size === 0 && project . submittedByEmail ) {
recipients . set ( project . submittedByEmail , null )
}
for ( const [ email , name ] of recipients ) {
emailPromises . push (
sendAnnouncementEmail (
email ,
name ,
` Your project has entered: ${ roundName } ` ,
` Your project " ${ project . title } " has been added to the round " ${ roundName } " in the Monaco Ocean Protection Challenge. You will receive further instructions as the round progresses. ` ,
'View Your Dashboard' ,
` ${ process . env . NEXTAUTH_URL || 'https://monaco-opc.com' } /dashboard ` ,
) . catch ( ( err ) = > {
console . error ( ` [round-entry-email] Failed to send to ${ email } : ` , err )
} ) ,
)
}
}
await Promise . allSettled ( emailPromises )
} catch ( err ) {
console . error ( '[round-entry-email] Failed to send round entry emails:' , err )
}
}
2026-02-14 15:26:42 +01:00
/ * *
* Project Pool Router
*
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
* Manages the project pool for assigning projects to competition rounds .
* Shows all projects by default , with optional filtering for unassigned - only
* or projects not yet in a specific round .
2026-02-14 15:26:42 +01:00
* /
export const projectPoolRouter = router ( {
/ * *
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
* List projects in the pool with filtering and pagination .
* By default shows ALL projects . Use filters to narrow :
* - unassignedOnly : true → only projects not in any round
* - excludeRoundId : "..." → only projects not already in that round
2026-02-14 15:26:42 +01:00
* /
listUnassigned : adminProcedure
. input (
z . object ( {
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
programId : z.string ( ) ,
2026-02-14 15:26:42 +01:00
competitionCategory : z
. enum ( [ 'STARTUP' , 'BUSINESS_CONCEPT' ] )
. optional ( ) ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
search : z.string ( ) . optional ( ) ,
unassignedOnly : z.boolean ( ) . optional ( ) . default ( false ) ,
excludeRoundId : z.string ( ) . optional ( ) ,
2026-02-14 15:26:42 +01:00
page : z.number ( ) . int ( ) . min ( 1 ) . default ( 1 ) ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
perPage : z.number ( ) . int ( ) . min ( 1 ) . max ( 200 ) . default ( 50 ) ,
2026-02-14 15:26:42 +01:00
} )
)
. query ( async ( { ctx , input } ) = > {
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
const { programId , competitionCategory , search , unassignedOnly , excludeRoundId , page , perPage } = input
2026-02-14 15:26:42 +01:00
const skip = ( page - 1 ) * perPage
// Build where clause
const where : Record < string , unknown > = {
programId ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
}
// Optional: only show projects not in any round
if ( unassignedOnly ) {
where . projectRoundStates = { none : { } }
}
// Optional: exclude projects already in a specific round
if ( excludeRoundId && ! unassignedOnly ) {
where . projectRoundStates = {
none : { roundId : excludeRoundId } ,
}
2026-02-14 15:26:42 +01:00
}
// Filter by competition category
if ( competitionCategory ) {
where . competitionCategory = competitionCategory
}
// Search in title, teamName, description
if ( search ) {
where . OR = [
{ title : { contains : search , mode : 'insensitive' } } ,
{ teamName : { contains : search , mode : 'insensitive' } } ,
{ description : { contains : search , mode : 'insensitive' } } ,
]
}
// Execute queries in parallel
const [ projects , total ] = await Promise . all ( [
ctx . prisma . project . findMany ( {
where ,
skip ,
take : perPage ,
orderBy : { createdAt : 'desc' } ,
select : {
id : true ,
title : true ,
teamName : true ,
description : true ,
competitionCategory : true ,
oceanIssue : true ,
country : true ,
status : true ,
submittedAt : true ,
createdAt : true ,
tags : true ,
wantsMentorship : true ,
programId : true ,
_count : {
select : {
files : true ,
teamMembers : true ,
} ,
} ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
projectRoundStates : {
select : {
roundId : true ,
state : true ,
round : {
select : {
name : true ,
roundType : true ,
sortOrder : true ,
} ,
} ,
} ,
orderBy : {
round : { sortOrder : 'asc' } ,
} ,
} ,
2026-02-14 15:26:42 +01:00
} ,
} ) ,
ctx . prisma . project . count ( { where } ) ,
] )
return {
projects ,
total ,
page ,
perPage ,
totalPages : Math.ceil ( total / perPage ) ,
}
} ) ,
/ * *
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
* Bulk assign projects to a round
2026-02-14 15:26:42 +01:00
* /
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
assignToRound : adminProcedure
2026-02-14 15:26:42 +01:00
. input (
z . object ( {
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
projectIds : z.array ( z . string ( ) ) . min ( 1 ) . max ( 200 ) ,
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
roundId : z.string ( ) ,
2026-02-14 15:26:42 +01:00
} )
)
. mutation ( async ( { ctx , input } ) = > {
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
const { projectIds , roundId } = input
2026-02-14 15:26:42 +01:00
// Step 1: Fetch all projects to validate
const projects = await ctx . prisma . project . findMany ( {
where : {
id : { in : projectIds } ,
} ,
select : {
id : true ,
title : true ,
programId : true ,
} ,
} )
// Validate all projects were found
if ( projects . length !== projectIds . length ) {
const foundIds = new Set ( projects . map ( ( p ) = > p . id ) )
const missingIds = projectIds . filter ( ( id ) = > ! foundIds . has ( id ) )
throw new TRPCError ( {
code : 'BAD_REQUEST' ,
message : ` Some projects were not found: ${ missingIds . join ( ', ' ) } ` ,
} )
}
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
// Verify round exists and get config
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
const round = await ctx . prisma . round . findUniqueOrThrow ( {
where : { id : roundId } ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
select : { id : true , name : true , configJson : true } ,
2026-02-14 15:26:42 +01:00
} )
// Step 2: Perform bulk assignment in a transaction
const result = await ctx . prisma . $transaction ( async ( tx ) = > {
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// Create ProjectRoundState entries for each project (skip existing)
const assignmentData = projectIds . map ( ( projectId ) = > ( {
2026-02-14 15:26:42 +01:00
projectId ,
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
roundId ,
2026-02-14 15:26:42 +01:00
} ) )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
await tx . projectRoundState . createMany ( {
data : assignmentData ,
2026-02-14 15:26:42 +01:00
skipDuplicates : true ,
} )
// Update project statuses
const updatedProjects = await tx . project . updateMany ( {
where : {
id : { in : projectIds } ,
} ,
data : {
status : 'ASSIGNED' ,
} ,
} )
// Create status history records for each project
await tx . projectStatusHistory . createMany ( {
data : projectIds.map ( ( projectId ) = > ( {
projectId ,
status : 'ASSIGNED' ,
changedBy : ctx.user?.id ,
} ) ) ,
} )
return updatedProjects
} )
Round detail overhaul, file requirements, project management, audit log fix
- Redesign round detail page with 6 tabs (overview, projects, filtering, assignments, config, documents)
- Add jury group assignment selector in round stats bar
- Add FileRequirementsEditor component replacing SubmissionWindowManager
- Add FilteringDashboard component for AI-powered project screening
- Add project removal from rounds (single + bulk) with cascading to subsequent rounds
- Add project add/remove UI in ProjectStatesTable with confirmation dialogs
- Fix logAudit inside $transaction pattern across all 12 router files
(PostgreSQL aborted-transaction state caused silent operation failures)
- Fix special awards creation, deletion, status update, and winner assignment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:49:39 +01:00
// Audit outside transaction so failures don't roll back the assignment
await logAudit ( {
prisma : ctx.prisma ,
userId : ctx.user?.id ,
action : 'BULK_ASSIGN_TO_ROUND' ,
entityType : 'Project' ,
detailsJson : {
roundId ,
projectCount : projectIds.length ,
projectIds ,
} ,
ipAddress : ctx.ip ,
userAgent : ctx.userAgent ,
} )
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
// Send round-entry notification emails if enabled (fire-and-forget)
const config = ( round . configJson as Record < string , unknown > ) || { }
if ( config . notifyOnEntry ) {
void sendRoundEntryEmails ( ctx . prisma as unknown as PrismaClient , projectIds , round . name )
}
2026-02-14 15:26:42 +01:00
return {
success : true ,
assignedCount : result.count ,
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
roundId ,
2026-02-14 15:26:42 +01:00
}
} ) ,
2026-02-16 07:06:59 +01:00
/ * *
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
* Assign ALL matching projects in a program to a round ( server - side , no ID limit ) .
* Skips projects already in the target round .
2026-02-16 07:06:59 +01:00
* /
assignAllToRound : adminProcedure
. input (
z . object ( {
programId : z.string ( ) ,
roundId : z.string ( ) ,
competitionCategory : z.enum ( [ 'STARTUP' , 'BUSINESS_CONCEPT' ] ) . optional ( ) ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
unassignedOnly : z.boolean ( ) . optional ( ) . default ( false ) ,
2026-02-16 07:06:59 +01:00
} )
)
. mutation ( async ( { ctx , input } ) = > {
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
const { programId , roundId , competitionCategory , unassignedOnly } = input
2026-02-16 07:06:59 +01:00
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
// Verify round exists and get config
const round = await ctx . prisma . round . findUniqueOrThrow ( {
2026-02-16 07:06:59 +01:00
where : { id : roundId } ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
select : { id : true , name : true , configJson : true } ,
2026-02-16 07:06:59 +01:00
} )
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
// Find projects to assign
2026-02-16 07:06:59 +01:00
const where : Record < string , unknown > = {
programId ,
}
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
if ( unassignedOnly ) {
// Only projects not in any round
where . projectRoundStates = { none : { } }
} else {
// All projects not already in the target round
where . projectRoundStates = {
none : { roundId } ,
}
}
2026-02-16 07:06:59 +01:00
if ( competitionCategory ) {
where . competitionCategory = competitionCategory
}
const projects = await ctx . prisma . project . findMany ( {
where ,
select : { id : true } ,
} )
if ( projects . length === 0 ) {
return { success : true , assignedCount : 0 , roundId }
}
const projectIds = projects . map ( ( p ) = > p . id )
const result = await ctx . prisma . $transaction ( async ( tx ) = > {
await tx . projectRoundState . createMany ( {
data : projectIds.map ( ( projectId ) = > ( { projectId , roundId } ) ) ,
skipDuplicates : true ,
} )
const updated = await tx . project . updateMany ( {
where : { id : { in : projectIds } } ,
data : { status : 'ASSIGNED' } ,
} )
await tx . projectStatusHistory . createMany ( {
data : projectIds.map ( ( projectId ) = > ( {
projectId ,
status : 'ASSIGNED' ,
changedBy : ctx.user?.id ,
} ) ) ,
} )
return updated
} )
Round detail overhaul, file requirements, project management, audit log fix
- Redesign round detail page with 6 tabs (overview, projects, filtering, assignments, config, documents)
- Add jury group assignment selector in round stats bar
- Add FileRequirementsEditor component replacing SubmissionWindowManager
- Add FilteringDashboard component for AI-powered project screening
- Add project removal from rounds (single + bulk) with cascading to subsequent rounds
- Add project add/remove UI in ProjectStatesTable with confirmation dialogs
- Fix logAudit inside $transaction pattern across all 12 router files
(PostgreSQL aborted-transaction state caused silent operation failures)
- Fix special awards creation, deletion, status update, and winner assignment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:49:39 +01:00
// Audit outside transaction so failures don't roll back the assignment
await logAudit ( {
prisma : ctx.prisma ,
userId : ctx.user?.id ,
action : 'BULK_ASSIGN_ALL_TO_ROUND' ,
entityType : 'Project' ,
detailsJson : {
roundId ,
programId ,
competitionCategory : competitionCategory || 'ALL' ,
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
unassignedOnly ,
Round detail overhaul, file requirements, project management, audit log fix
- Redesign round detail page with 6 tabs (overview, projects, filtering, assignments, config, documents)
- Add jury group assignment selector in round stats bar
- Add FileRequirementsEditor component replacing SubmissionWindowManager
- Add FilteringDashboard component for AI-powered project screening
- Add project removal from rounds (single + bulk) with cascading to subsequent rounds
- Add project add/remove UI in ProjectStatesTable with confirmation dialogs
- Fix logAudit inside $transaction pattern across all 12 router files
(PostgreSQL aborted-transaction state caused silent operation failures)
- Fix special awards creation, deletion, status update, and winner assignment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:49:39 +01:00
projectCount : projectIds.length ,
} ,
ipAddress : ctx.ip ,
userAgent : ctx.userAgent ,
} )
Pool, competition & round pages overhaul: deep-link context, inline project management, AI filtering UX, email toggle
- Pool page: auto-select program from edition context, URL params for roundId/competitionId deep-linking, unassigned toggle, round badges column
- Competition detail: rich round cards with project counts, dates, jury info, status badges replacing flat list
- Round detail: readiness checklist, embedded assignment dashboard, file requirements in config tab, notifyOnEntry toggle
- ProjectStatesTable: search input, project links, quick-add dialog, pool links with context params
- FilteringDashboard: expandable rows with AI reasoning inline, quick override buttons, search, clickable stats
- Backend: notifyOnEntry in round configJson triggers announcement emails on project assignment via existing email infra
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:23:40 +01:00
// Send round-entry notification emails if enabled (fire-and-forget)
const config = ( round . configJson as Record < string , unknown > ) || { }
if ( config . notifyOnEntry ) {
void sendRoundEntryEmails ( ctx . prisma as unknown as PrismaClient , projectIds , round . name )
}
2026-02-16 07:06:59 +01:00
return { success : true , assignedCount : result.count , roundId }
} ) ,
2026-02-14 15:26:42 +01:00
} )