push
This commit is contained in:
184
innungsapp/apps/admin/server/routers/messages.ts
Normal file
184
innungsapp/apps/admin/server/routers/messages.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { z } from 'zod'
|
||||
import { router, memberProcedure } from '../trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export const messagesRouter = router({
|
||||
// List all conversations for the current member
|
||||
getConversations: memberProcedure.query(async ({ ctx }) => {
|
||||
const member = await ctx.prisma.member.findFirst({
|
||||
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
|
||||
})
|
||||
if (!member) throw new TRPCError({ code: 'NOT_FOUND' })
|
||||
|
||||
const convMembers = await ctx.prisma.conversationMember.findMany({
|
||||
where: { memberId: member.id },
|
||||
include: {
|
||||
conversation: {
|
||||
include: {
|
||||
members: {
|
||||
include: { member: { select: { id: true, name: true, betrieb: true, avatarUrl: true } } },
|
||||
},
|
||||
messages: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
select: { body: true, createdAt: true, senderId: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { conversation: { updatedAt: 'desc' } },
|
||||
})
|
||||
|
||||
return convMembers.map((cm) => {
|
||||
const other = cm.conversation.members.find((m) => m.memberId !== member.id)?.member
|
||||
const lastMsg = cm.conversation.messages[0] ?? null
|
||||
const unread =
|
||||
lastMsg &&
|
||||
(!cm.lastReadAt || lastMsg.createdAt > cm.lastReadAt) &&
|
||||
lastMsg.senderId !== member.id
|
||||
return {
|
||||
conversationId: cm.conversationId,
|
||||
other,
|
||||
lastMessage: lastMsg,
|
||||
hasUnread: !!unread,
|
||||
updatedAt: cm.conversation.updatedAt,
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
// Get or create a 1-on-1 conversation between current member and another
|
||||
getOrCreate: memberProcedure
|
||||
.input(z.object({ otherMemberId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const member = await ctx.prisma.member.findFirst({
|
||||
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
|
||||
})
|
||||
if (!member) throw new TRPCError({ code: 'NOT_FOUND' })
|
||||
|
||||
const other = await ctx.prisma.member.findFirst({
|
||||
where: { id: input.otherMemberId, orgId: ctx.orgId },
|
||||
})
|
||||
if (!other) throw new TRPCError({ code: 'NOT_FOUND', message: 'Mitglied nicht gefunden' })
|
||||
|
||||
// Find existing conversation between the two members in this org
|
||||
const existing = await ctx.prisma.conversation.findFirst({
|
||||
where: {
|
||||
orgId: ctx.orgId,
|
||||
members: { every: { memberId: { in: [member.id, other.id] } } },
|
||||
AND: [
|
||||
{ members: { some: { memberId: member.id } } },
|
||||
{ members: { some: { memberId: other.id } } },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
if (existing) return { conversationId: existing.id }
|
||||
|
||||
const conv = await ctx.prisma.conversation.create({
|
||||
data: {
|
||||
orgId: ctx.orgId,
|
||||
members: {
|
||||
create: [{ memberId: member.id }, { memberId: other.id }],
|
||||
},
|
||||
},
|
||||
})
|
||||
return { conversationId: conv.id }
|
||||
}),
|
||||
|
||||
// Get messages for a conversation
|
||||
getMessages: memberProcedure
|
||||
.input(z.object({ conversationId: z.string(), cursor: z.string().optional() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const member = await ctx.prisma.member.findFirst({
|
||||
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
|
||||
})
|
||||
if (!member) throw new TRPCError({ code: 'NOT_FOUND' })
|
||||
|
||||
// Verify membership in conversation
|
||||
const cm = await ctx.prisma.conversationMember.findUnique({
|
||||
where: { conversationId_memberId: { conversationId: input.conversationId, memberId: member.id } },
|
||||
})
|
||||
if (!cm) throw new TRPCError({ code: 'FORBIDDEN' })
|
||||
|
||||
const messages = await ctx.prisma.message.findMany({
|
||||
where: { conversationId: input.conversationId },
|
||||
include: { sender: { select: { id: true, name: true, avatarUrl: true } } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 40,
|
||||
...(input.cursor ? { skip: 1, cursor: { id: input.cursor } } : {}),
|
||||
})
|
||||
|
||||
// Mark as read
|
||||
await ctx.prisma.conversationMember.update({
|
||||
where: { conversationId_memberId: { conversationId: input.conversationId, memberId: member.id } },
|
||||
data: { lastReadAt: new Date() },
|
||||
})
|
||||
|
||||
return {
|
||||
messages: messages.reverse(),
|
||||
nextCursor: messages.length === 40 ? messages[0]?.id : undefined,
|
||||
}
|
||||
}),
|
||||
|
||||
// Send a message
|
||||
sendMessage: memberProcedure
|
||||
.input(z.object({ conversationId: z.string(), body: z.string().min(1).max(2000) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const member = await ctx.prisma.member.findFirst({
|
||||
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
|
||||
})
|
||||
if (!member) throw new TRPCError({ code: 'NOT_FOUND' })
|
||||
|
||||
const cm = await ctx.prisma.conversationMember.findUnique({
|
||||
where: { conversationId_memberId: { conversationId: input.conversationId, memberId: member.id } },
|
||||
})
|
||||
if (!cm) throw new TRPCError({ code: 'FORBIDDEN' })
|
||||
|
||||
const message = await ctx.prisma.message.create({
|
||||
data: {
|
||||
conversationId: input.conversationId,
|
||||
senderId: member.id,
|
||||
body: input.body.trim(),
|
||||
},
|
||||
include: { sender: { select: { id: true, name: true, avatarUrl: true } } },
|
||||
})
|
||||
|
||||
// Update conversation updatedAt so it sorts to top
|
||||
await ctx.prisma.conversation.update({
|
||||
where: { id: input.conversationId },
|
||||
data: { updatedAt: new Date() },
|
||||
})
|
||||
|
||||
return message
|
||||
}),
|
||||
|
||||
// Count total unread conversations
|
||||
unreadCount: memberProcedure.query(async ({ ctx }) => {
|
||||
const member = await ctx.prisma.member.findFirst({
|
||||
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
|
||||
})
|
||||
if (!member) return { count: 0 }
|
||||
|
||||
const convMembers = await ctx.prisma.conversationMember.findMany({
|
||||
where: { memberId: member.id },
|
||||
include: {
|
||||
conversation: {
|
||||
include: {
|
||||
messages: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
select: { createdAt: true, senderId: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const count = convMembers.filter((cm) => {
|
||||
const last = cm.conversation.messages[0]
|
||||
return last && last.senderId !== member.id && (!cm.lastReadAt || last.createdAt > cm.lastReadAt)
|
||||
}).length
|
||||
|
||||
return { count }
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user