feat(communication): add chat reactions emojis and images

This commit is contained in:
2026-10-03 17:56:07 +02:00
parent 59538f46b7
commit 29a9ec2242
25 changed files with 737 additions and 57 deletions

View File

@@ -15,6 +15,7 @@ import {
} from "../../db/schema"
import { NotificationService, UserDirectory } from "../modules/notification.service"
import { assertCustomRoomKey, chatError, projectChatAccess } from "../modules/communication-access"
import { chatContent, chatImage, chatReactions, imageMetadata, setChatReaction } from "../modules/chat-content"
type ChatUser = {
userId: string
@@ -160,7 +161,10 @@ export default async function communicationRoutes(server: FastifyInstance) {
const messagesForRoom = async (roomId: string, currentUserId: string, tenantId: number, afterId = 0, limit = 50) => {
const conditions = [eq(communicationMessages.roomId, roomId)]
if (afterId > 0) conditions.push(gt(communicationMessages.id, afterId))
const rows = await server.db.select().from(communicationMessages)
const rows = await server.db.select({ id: communicationMessages.id, body: communicationMessages.body,
authorUserId: communicationMessages.authorUserId, createdAt: communicationMessages.createdAt,
image: sql`${communicationMessages.image} - 'dataUrl'`,
}).from(communicationMessages)
.where(and(...conditions))
.orderBy(afterId > 0 ? asc(communicationMessages.id) : desc(communicationMessages.id))
.limit(Math.min(Math.max(limit, 1), 100))
@@ -181,6 +185,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
return orderedRows.map((message) => ({
id: message.id,
body: message.body,
image: imageMetadata(message.image),
sender: message.authorUserId,
senderDisplayName: displayName(authorById.get(message.authorUserId) || {}),
timestamp: message.createdAt.getTime(),
@@ -466,17 +471,33 @@ export default async function communicationRoutes(server: FastifyInstance) {
return { ...roomToApi(room), messages, nextId: messages[messages.length - 1]?.id || afterId }
})
server.post("/communication/chat/rooms/:roomKey/messages", async (req: any, reply) => {
server.post("/communication/chat/rooms/:roomKey/messages", async (req: any) => {
const tenantId = await requireTenant(req)
const room = await requireRoom(req, tenantId, req.user.user_id, req.params.roomKey)
const body = String(req.body?.text || "").trim()
if (!body) return reply.code(400).send({ error: "Nachricht darf nicht leer sein" })
if (body.length > 10_000) return reply.code(400).send({ error: "Nachricht ist zu lang" })
const { body, image } = await chatContent(req.body)
const [created] = await server.db.insert(communicationMessages).values({
tenantId, roomId: room.id, authorUserId: req.user.user_id, body,
tenantId, roomId: room.id, authorUserId: req.user.user_id, body, image,
}).returning()
await notifyMessageRecipients(tenantId, room, req.user.user_id, { id: created.id, body })
return { id: created.id, body: created.body, sender: created.authorUserId, senderDisplayName: "Du", timestamp: created.createdAt.getTime(), own: true }
await notifyMessageRecipients(tenantId, room, req.user.user_id, { id: created.id, body: body || "📷 Bild" })
return { id: created.id, body: created.body, image: imageMetadata(created.image), sender: created.authorUserId, senderDisplayName: "Du", timestamp: created.createdAt.getTime(), own: true }
})
server.get("/communication/chat/rooms/:roomKey/image", async (req: any, reply) => {
reply.header("Cache-Control", "no-store")
const tenantId = await requireTenant(req)
const room = await requireRoom(req, tenantId, req.user.user_id, req.params.roomKey)
return chatImage(server.db, "communication", room.id, req.query.messageId)
})
server.get("/communication/chat/rooms/:roomKey/reactions", async (req: any, reply) => {
reply.header("Cache-Control", "no-store")
const tenantId = await requireTenant(req)
const room = await requireRoom(req, tenantId, req.user.user_id, req.params.roomKey)
return chatReactions(server.db, "communication", room.id, req.user.user_id, req.query.ids)
})
server.put("/communication/chat/rooms/:roomKey/reactions", async (req: any) => {
const tenantId = await requireTenant(req)
const room = await requireRoom(req, tenantId, req.user.user_id, req.params.roomKey)
return setChatReaction(server.db, "communication", room.id, req.user.user_id, req.body)
})
server.post("/communication/chat/rooms/:roomKey/read", async (req: any) => {