feat: mentor workspace files end-to-end with secure presign

Adds generateMentorObjectKey helper producing
<projectName>/mentorship/<timestamp>-<file>. Replaces the
client-supplied bucket/objectKey on workspaceUploadFile with an
HMAC-signed upload token that binds bucket, objectKey, uploader,
and a 1h expiry — paths can no longer be forged from the client.

Adds workspaceGetUploadUrl, workspaceGetFiles,
workspaceGetFileDownloadUrl, workspaceDeleteFile procedures with
mentor-or-team-member auth. Builds <WorkspaceFilesPanel> and
wires it into the mentor workspace Files tab and the applicant
/applicant/mentor page. Replaces the file-promotion-panel mock
array with a real workspaceGetFiles query.

Tests cover token sign/verify (5), key construction (5), and
end-to-end procedure flow including auth + tampered tokens (7).

Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §F.1
Plan: docs/superpowers/plans/2026-04-28-pr2-mentor-workspace-files.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-28 13:33:18 +02:00
parent dd48db5eea
commit 2e7b545a1b
11 changed files with 699 additions and 30 deletions

View File

@@ -32,8 +32,11 @@ export function FilePromotionPanel({ mentorAssignmentId }: FilePromotionPanelPro
const [selectedSlot, setSelectedSlot] = useState<string>('')
const utils = trpc.useUtils()
// Mock workspace files - in real implementation, would fetch from workspaceGetFiles
const workspaceFiles: any[] = [] // Placeholder
const { data: workspaceFiles = [], isLoading: filesLoading } =
trpc.mentor.workspaceGetFiles.useQuery(
{ mentorAssignmentId },
{ enabled: !!mentorAssignmentId },
)
const promoteMutation = trpc.mentor.workspacePromoteFile.useMutation({
onSuccess: () => {
@@ -56,7 +59,7 @@ export function FilePromotionPanel({ mentorAssignmentId }: FilePromotionPanelPro
})
}
const isLoading = false // Placeholder
const isLoading = filesLoading
if (isLoading) {
return (