Consolidated round management, AI filtering enhancements, MinIO storage restructure
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s

- Fix STAGE_ACTIVE bug in assignment router (now ROUND_ACTIVE)
- Add evaluation form CRUD (getForm + upsertForm endpoints)
- Add advanceProjects mutation for manual project advancement
- Rewrite round detail page: 7-tab consolidated interface
- Add filtering rules UI with full CRUD (field-based, document check, AI screening)
- Add pageCount field to ProjectFile for document page limit filtering
- Enhance AI filtering: per-file page limits, category/region-aware guidelines
- Restructure MinIO paths: {ProjectName}/{RoundName}/{timestamp}-{file}
- Update dashboard and pool page links from /admin/competitions to /admin/rounds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 09:20:02 +01:00
parent 845554fdb8
commit 8e5fc18da6
14 changed files with 2606 additions and 303 deletions

View File

@@ -119,14 +119,39 @@ export async function deleteObject(
}
/**
* Generate a unique object key for a project file
* Sanitize a name for use as a MinIO path segment.
* Removes special characters, replaces spaces with underscores, limits length.
*/
export function generateObjectKey(
projectId: string,
fileName: string
): string {
const timestamp = Date.now()
const sanitizedName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_')
return `projects/${projectId}/${timestamp}-${sanitizedName}`
function sanitizePath(name: string): string {
return (
name
.trim()
.replace(/[^a-zA-Z0-9\-_ ]/g, '')
.replace(/\s+/g, '_')
.substring(0, 100) || 'unnamed'
)
}
/**
* Generate a unique object key for a project file.
*
* Structure: {ProjectName}/{RoundName}/{timestamp}-{fileName}
* - projectName: human-readable project title (sanitized)
* - roundName: round name for submission context (sanitized), defaults to "general"
* - fileName: original file name (sanitized)
*
* Existing files with old-style keys (projects/{id}/...) are unaffected
* because retrieval uses the objectKey stored in the database.
*/
export function generateObjectKey(
projectName: string,
fileName: string,
roundName?: string
): string {
const timestamp = Date.now()
const sanitizedProject = sanitizePath(projectName)
const sanitizedRound = roundName ? sanitizePath(roundName) : 'general'
const sanitizedFile = fileName.replace(/[^a-zA-Z0-9.-]/g, '_')
return `${sanitizedProject}/${sanitizedRound}/${timestamp}-${sanitizedFile}`
}