feat(mentor-workspace): re-scope files from assignment to project for team-wide visibility
- MentorFile.projectId is the new access boundary; mentorAssignmentId stays as informational audit FK (nullable). - uploadFile derives projectId from the assignment; getFiles takes projectId directly; deleteFile/addFileComment auth checks any mentor on the project OR a project team member. - HMAC upload token now binds to projectId (in addition to assignmentId). - promoteFile reads file.projectId directly (no more mentorAssignment null navigation). - Removes 3 placeholder NOT_FOUND guards added in Task 4.
This commit is contained in:
@@ -17,7 +17,7 @@ import { FileText, Upload, CheckCircle2, ArrowUp } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface FilePromotionPanelProps {
|
||||
mentorAssignmentId: string
|
||||
projectId: string
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
@@ -28,14 +28,14 @@ function formatFileSize(bytes: number): string {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
export function FilePromotionPanel({ mentorAssignmentId }: FilePromotionPanelProps) {
|
||||
export function FilePromotionPanel({ projectId }: FilePromotionPanelProps) {
|
||||
const [selectedSlot, setSelectedSlot] = useState<string>('')
|
||||
const utils = trpc.useUtils()
|
||||
|
||||
const { data: workspaceFiles = [], isLoading: filesLoading } =
|
||||
trpc.mentor.workspaceGetFiles.useQuery(
|
||||
{ mentorAssignmentId },
|
||||
{ enabled: !!mentorAssignmentId },
|
||||
{ projectId },
|
||||
{ enabled: !!projectId },
|
||||
)
|
||||
|
||||
const promoteMutation = trpc.mentor.workspacePromoteFile.useMutation({
|
||||
|
||||
@@ -16,6 +16,13 @@ import { FileText, Upload, Download, Trash2, MessageSquare } from 'lucide-react'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
|
||||
interface Props {
|
||||
/** Project the workspace belongs to — drives file list (project-scoped). */
|
||||
projectId: string
|
||||
/**
|
||||
* One MentorAssignment id on this project — needed only to mint upload tokens
|
||||
* (the token is signed against the assignment + project pair, but the file
|
||||
* itself is project-scoped so co-mentors see it).
|
||||
*/
|
||||
mentorAssignmentId: string
|
||||
/** Set true on the applicant side to label uploads as "Team upload" — purely cosmetic. */
|
||||
asApplicant?: boolean
|
||||
@@ -29,21 +36,21 @@ function formatSize(bytes: number): string {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
export function WorkspaceFilesPanel({ mentorAssignmentId, asApplicant }: Props) {
|
||||
export function WorkspaceFilesPanel({ projectId, mentorAssignmentId, asApplicant }: Props) {
|
||||
const utils = trpc.useUtils()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [description, setDescription] = useState('')
|
||||
|
||||
const { data: files, isLoading } = trpc.mentor.workspaceGetFiles.useQuery(
|
||||
{ mentorAssignmentId },
|
||||
{ enabled: !!mentorAssignmentId }
|
||||
{ projectId },
|
||||
{ enabled: !!projectId }
|
||||
)
|
||||
|
||||
const presign = trpc.mentor.workspaceGetUploadUrl.useMutation()
|
||||
const recordUpload = trpc.mentor.workspaceUploadFile.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.mentor.workspaceGetFiles.invalidate({ mentorAssignmentId })
|
||||
utils.mentor.workspaceGetFiles.invalidate({ projectId })
|
||||
setDescription('')
|
||||
toast.success('File uploaded')
|
||||
},
|
||||
@@ -51,7 +58,7 @@ export function WorkspaceFilesPanel({ mentorAssignmentId, asApplicant }: Props)
|
||||
const downloadMutation = trpc.mentor.workspaceGetFileDownloadUrl.useMutation()
|
||||
const deleteMutation = trpc.mentor.workspaceDeleteFile.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.mentor.workspaceGetFiles.invalidate({ mentorAssignmentId })
|
||||
utils.mentor.workspaceGetFiles.invalidate({ projectId })
|
||||
toast.success('File deleted')
|
||||
},
|
||||
onError: (e) => toast.error(e.message),
|
||||
|
||||
Reference in New Issue
Block a user