/** * Mentor Workspace Service * * Manages mentor-applicant workspace: activation, messaging, file management, * and file promotion to official submissions. Operates on MentorAssignment, * MentorMessage, MentorFile, MentorFileComment, SubmissionPromotionEvent. */ import type { PrismaClient, Prisma } from '@prisma/client' import { logAudit } from '@/server/utils/audit' // ─── Types ────────────────────────────────────────────────────────────────── type WorkspaceResult = { success: boolean; errors?: string[] } // ─── Workspace Activation ─────────────────────────────────────────────────── /** * Activate a mentor workspace for a given assignment. */ export async function activateWorkspace( workspaceId: string, actorId: string, prisma: PrismaClient, ): Promise { try { const assignment = await prisma.mentorAssignment.findUnique({ where: { id: workspaceId }, }) if (!assignment) { return { success: false, errors: ['Mentor assignment not found'] } } if (assignment.workspaceEnabled) { return { success: false, errors: ['Workspace is already enabled'] } } await prisma.$transaction(async (tx: Prisma.TransactionClient) => { await tx.mentorAssignment.update({ where: { id: workspaceId }, data: { workspaceEnabled: true, workspaceOpenAt: new Date(), }, }) await tx.decisionAuditLog.create({ data: { eventType: 'mentor_workspace.activated', entityType: 'MentorAssignment', entityId: workspaceId, actorId, detailsJson: { projectId: assignment.projectId, mentorId: assignment.mentorId, }, snapshotJson: { timestamp: new Date().toISOString(), emittedBy: 'mentor-workspace' }, }, }) await logAudit({ prisma: tx, userId: actorId, action: 'WORKSPACE_ACTIVATE', entityType: 'MentorAssignment', entityId: workspaceId, detailsJson: { projectId: assignment.projectId }, }) }) return { success: true } } catch (error) { console.error('[MentorWorkspace] activateWorkspace failed:', error) return { success: false, errors: [error instanceof Error ? error.message : 'Unknown error'], } } } // ─── Messaging ────────────────────────────────────────────────────────────── /** * Send a message in a mentor workspace. */ export async function sendMessage( params: { workspaceId: string senderId: string message: string role: 'MENTOR_ROLE' | 'APPLICANT_ROLE' | 'ADMIN_ROLE' }, prisma: PrismaClient, ) { const assignment = await prisma.mentorAssignment.findUnique({ where: { id: params.workspaceId }, }) if (!assignment) { throw new Error('Mentor assignment not found') } if (!assignment.workspaceEnabled) { throw new Error('Workspace is not enabled for this assignment') } return prisma.mentorMessage.create({ data: { workspaceId: params.workspaceId, projectId: assignment.projectId, senderId: params.senderId, message: params.message, senderRole: params.role, }, include: { sender: { select: { id: true, name: true, email: true } }, }, }) } /** * Get messages for a workspace. */ export async function getMessages( workspaceId: string, prisma: PrismaClient, ) { return prisma.mentorMessage.findMany({ where: { workspaceId }, include: { sender: { select: { id: true, name: true, email: true, role: true } }, }, orderBy: { createdAt: 'asc' }, }) } /** * Mark a message as read. */ export async function markRead( messageId: string, prisma: PrismaClient, ): Promise { await prisma.mentorMessage.update({ where: { id: messageId }, data: { isRead: true }, }) } // ─── File Management ──────────────────────────────────────────────────────── /** * Record a file upload in a workspace. */ export async function uploadFile( params: { workspaceId: string uploadedByUserId: string fileName: string mimeType: string size: number bucket: string objectKey: string description?: string }, prisma: PrismaClient, ) { const assignment = await prisma.mentorAssignment.findUnique({ where: { id: params.workspaceId }, }) if (!assignment) { throw new Error('Mentor assignment not found') } if (!assignment.workspaceEnabled) { throw new Error('Workspace is not enabled for this assignment') } return prisma.mentorFile.create({ data: { mentorAssignmentId: params.workspaceId, uploadedByUserId: params.uploadedByUserId, fileName: params.fileName, mimeType: params.mimeType, size: params.size, bucket: params.bucket, objectKey: params.objectKey, description: params.description, }, include: { uploadedBy: { select: { id: true, name: true, email: true } }, }, }) } /** * Add a comment to a file. */ export async function addFileComment( params: { mentorFileId: string authorId: string content: string parentCommentId?: string }, prisma: PrismaClient, ) { return prisma.mentorFileComment.create({ data: { mentorFileId: params.mentorFileId, authorId: params.authorId, content: params.content, parentCommentId: params.parentCommentId, }, include: { author: { select: { id: true, name: true, email: true } }, }, }) } // ─── File Promotion ───────────────────────────────────────────────────────── /** * Promote a mentor file to an official submission. * Creates SubmissionPromotionEvent and marks MentorFile.isPromoted = true. */ export async function promoteFile( params: { mentorFileId: string roundId: string slotKey: string promotedById: string }, prisma: PrismaClient, ): Promise<{ success: boolean; errors?: string[] }> { try { const file = await prisma.mentorFile.findUnique({ where: { id: params.mentorFileId }, include: { mentorAssignment: { select: { projectId: true } }, }, }) if (!file) { return { success: false, errors: ['Mentor file not found'] } } if (file.isPromoted) { return { success: false, errors: ['File is already promoted'] } } await prisma.$transaction(async (tx: Prisma.TransactionClient) => { // Mark file as promoted await tx.mentorFile.update({ where: { id: params.mentorFileId }, data: { isPromoted: true, promotedAt: new Date(), promotedByUserId: params.promotedById, }, }) // Create promotion event await tx.submissionPromotionEvent.create({ data: { projectId: file.mentorAssignment.projectId, roundId: params.roundId, slotKey: params.slotKey, sourceType: 'MENTOR_FILE', sourceFileId: params.mentorFileId, promotedById: params.promotedById, }, }) await tx.decisionAuditLog.create({ data: { eventType: 'mentor_file.promoted', entityType: 'MentorFile', entityId: params.mentorFileId, actorId: params.promotedById, detailsJson: { projectId: file.mentorAssignment.projectId, roundId: params.roundId, slotKey: params.slotKey, fileName: file.fileName, }, snapshotJson: { timestamp: new Date().toISOString(), emittedBy: 'mentor-workspace' }, }, }) await logAudit({ prisma: tx, userId: params.promotedById, action: 'MENTOR_FILE_PROMOTE', entityType: 'MentorFile', entityId: params.mentorFileId, detailsJson: { projectId: file.mentorAssignment.projectId, slotKey: params.slotKey, }, }) }) return { success: true } } catch (error) { console.error('[MentorWorkspace] promoteFile failed:', error) return { success: false, errors: [error instanceof Error ? error.message : 'Unknown error'], } } }