feat(workspace): mentor + applicant message previews (§F.2)
mentor.getRecentMessages: last N unread messages from teams across all of a mentor's assignments. Drives a Recent Messages card on /mentor. applicant.getMentorConversationPreview: last 3 messages + unread count for a given project. Drives a 'Conversation with [Mentor]' card on /applicant — auto-hides when no mentor is assigned. Both procedures use the existing MentorMessage(projectId, createdAt) composite index — no new index needed. Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
This commit is contained in:
@@ -2659,4 +2659,50 @@ export const applicantRouter = router({
|
||||
|
||||
return { success: true, roundId: activePrs.roundId, roundName: activePrs.round.name }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Last N messages + unread count for the applicant's project mentor workspace.
|
||||
* Drives the 'Conversation with [Mentor]' card on /applicant.
|
||||
*/
|
||||
getMentorConversationPreview: protectedProcedure
|
||||
.input(z.object({ projectId: z.string(), limit: z.number().min(1).max(10).default(3) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const teamMembership = await ctx.prisma.teamMember.findFirst({
|
||||
where: { projectId: input.projectId, userId: ctx.user.id },
|
||||
select: { id: true },
|
||||
})
|
||||
if (!teamMembership) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'Not a team member of this project',
|
||||
})
|
||||
}
|
||||
|
||||
const assignment = await ctx.prisma.mentorAssignment.findUnique({
|
||||
where: { projectId: input.projectId },
|
||||
include: { mentor: { select: { id: true, name: true, email: true } } },
|
||||
})
|
||||
|
||||
const [messages, unreadCount] = await Promise.all([
|
||||
ctx.prisma.mentorMessage.findMany({
|
||||
where: { projectId: input.projectId },
|
||||
include: { sender: { select: { id: true, name: true, email: true } } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: input.limit,
|
||||
}),
|
||||
ctx.prisma.mentorMessage.count({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
senderId: { not: ctx.user.id },
|
||||
isRead: false,
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
return {
|
||||
mentor: assignment?.mentor ?? null,
|
||||
messages: messages.reverse(),
|
||||
unreadCount,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1259,6 +1259,30 @@ export const mentorRouter = router({
|
||||
return messages
|
||||
}),
|
||||
|
||||
/**
|
||||
* Recent unread messages from team members across all of the mentor's
|
||||
* assignments. Drives the 'Recent Messages' card on /mentor.
|
||||
*/
|
||||
getRecentMessages: mentorProcedure
|
||||
.input(z.object({ limit: z.number().min(1).max(20).default(5) }).optional())
|
||||
.query(async ({ ctx, input }) => {
|
||||
const limit = input?.limit ?? 5
|
||||
const unread = await ctx.prisma.mentorMessage.findMany({
|
||||
where: {
|
||||
senderId: { not: ctx.user.id },
|
||||
isRead: false,
|
||||
workspace: { mentorId: ctx.user.id },
|
||||
},
|
||||
include: {
|
||||
sender: { select: { id: true, name: true, email: true } },
|
||||
project: { select: { id: true, title: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit,
|
||||
})
|
||||
return { unread }
|
||||
}),
|
||||
|
||||
/**
|
||||
* List all mentor assignments (admin)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user