import { FastifyInstance } from "fastify" import { and, eq, ne } from "drizzle-orm" import { authTenantUsers, authUsers } from "../../db/schema" import { matrixService } from "../modules/matrix.service" import { NotificationService, UserDirectory } from "../modules/notification.service" const getUserDirectory: UserDirectory = async (server: FastifyInstance, userId) => { const rows = await server.db .select({ email: authUsers.email }) .from(authUsers) .where(eq(authUsers.id, userId)) .limit(1) return rows[0] || null } export default async function communicationRoutes(server: FastifyInstance) { const matrix = matrixService(server) const notifications = new NotificationService(server, getUserDirectory) const handleMatrixError = (req: any, reply: any, err: any, fallbackMessage: string) => { req.log.error(err) return reply .code(err.statusCode || 500) .send({ error: err.message || fallbackMessage }) } const roomOptionsFromRequest = (req: any) => { const params = req.params as { roomKey?: string } const body = (req.body || {}) as { key?: string name?: string topic?: string type?: string entityType?: string | null entityId?: number | null entityUuid?: string | null } return { key: params.roomKey || body.key, name: body.name, topic: body.topic, type: body.type, entityType: body.entityType, entityId: body.entityId, entityUuid: body.entityUuid, } } const callModeFromRequest = (req: any): "audio" | "video" => { const body = (req.body || {}) as { mode?: string } return body.mode === "audio" ? "audio" : "video" } const notifyTenantUsersAboutCall = async (req: any, room: { key?: string; name?: string }, mode: "audio" | "video") => { if (!req.user.tenant_id) return try { const recipientRows = await server.db .select({ userId: authTenantUsers.user_id }) .from(authTenantUsers) .where(and( eq(authTenantUsers.tenant_id, req.user.tenant_id), ne(authTenantUsers.user_id, req.user.user_id) )) const userIds = recipientRows.map((row) => row.userId) if (!userIds.length) return await notifications.trigger({ tenantId: req.user.tenant_id, userIds, eventType: "communication.call.started", title: mode === "audio" ? "Audioanruf gestartet" : "Videokonferenz gestartet", message: `${room.name || room.key || "Ein Chatraum"} hat eine laufende Besprechung.`, payload: { link: "/communication/chat", roomKey: room.key, roomName: room.name, mode, }, channels: ["inapp", "push"], }) } catch (err) { req.log.error({ err }, "Call-Benachrichtigung konnte nicht ausgelöst werden") } } server.get("/communication/matrix/status", async () => { return matrix.getStatus() }) server.get("/communication/matrix/me", async (req) => { const userId = req.user.user_id return { matrixUserId: await matrix.matrixUserIdForUser(userId, req.user.tenant_id), displayName: await matrix.getCurrentUserDisplayName(userId, req.user.tenant_id), } }) server.post("/communication/matrix/me/provision", async (req, reply) => { try { return await matrix.provisionCurrentUser(req.user.user_id, req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix provisioning failed") } }) server.get("/communication/matrix/tenant-space", async (req, reply) => { try { return await matrix.getTenantSpaceStatus(req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix tenant space status failed") } }) server.post("/communication/matrix/tenant-space/provision", async (req, reply) => { try { return await matrix.provisionCurrentTenantSpace(req.user.user_id, req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix tenant space provisioning failed") } }) server.get("/communication/matrix/rooms", async (req, reply) => { try { return await matrix.listTenantRooms(req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix rooms failed") } }) server.post("/communication/matrix/rooms", async (req, reply) => { try { return await matrix.provisionTenantRoom( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix room provisioning failed") } }) server.get("/communication/matrix/rooms/general", async (req, reply) => { try { return await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat") } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix room status failed") } }) server.post("/communication/matrix/rooms/general/provision", async (req, reply) => { try { return await matrix.provisionTenantRoom(req.user.user_id, req.user.tenant_id, { key: "allgemein", name: "Allgemeiner Chat", }) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix room provisioning failed") } }) server.get("/communication/matrix/rooms/general/messages", async (req, reply) => { try { return await matrix.getGeneralRoomMessages(req.user.user_id, req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix messages failed") } }) server.get("/communication/matrix/rooms/general/members", async (req, reply) => { try { return await matrix.getGeneralRoomMembers(req.user.user_id, req.user.tenant_id) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix members failed") } }) server.post("/communication/matrix/rooms/general/session", async (req, reply) => { try { return await matrix.createElementRoomSession(req.user.user_id, req.user.tenant_id, { key: "allgemein", name: "Allgemeiner Chat", }) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix session failed") } }) server.post("/communication/matrix/rooms/general/call-session", async (req, reply) => { try { const room = { key: "allgemein", name: "Allgemeiner Chat", } const session = await matrix.createLiveKitRoomSession(req.user.user_id, req.user.tenant_id, room) await notifyTenantUsersAboutCall(req, room, callModeFromRequest(req)) return session } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix call session failed") } }) server.post("/communication/matrix/rooms/general/members/sync", async (req, reply) => { try { return await matrix.syncTenantRoomMembers(req.user.user_id, req.user.tenant_id, { key: "allgemein", name: "Allgemeiner Chat", }) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix member sync failed") } }) server.post("/communication/matrix/rooms/general/messages", async (req, reply) => { try { const body = req.body as { text?: string } return await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "") } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix message send failed") } }) server.get("/communication/matrix/rooms/:roomKey", async (req, reply) => { try { const params = req.params as { roomKey: string } return await matrix.getTenantRoomStatus(req.user.tenant_id, params.roomKey) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix room status failed") } }) server.post("/communication/matrix/rooms/:roomKey/provision", async (req, reply) => { try { return await matrix.provisionTenantRoom( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix room provisioning failed") } }) server.get("/communication/matrix/rooms/:roomKey/messages", async (req, reply) => { try { return await matrix.getTenantRoomMessages( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix messages failed") } }) server.get("/communication/matrix/rooms/:roomKey/members", async (req, reply) => { try { return await matrix.getTenantRoomMembers( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix members failed") } }) server.post("/communication/matrix/rooms/:roomKey/session", async (req, reply) => { try { return await matrix.createElementRoomSession( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix session failed") } }) server.post("/communication/matrix/rooms/:roomKey/call-session", async (req, reply) => { try { const room = roomOptionsFromRequest(req) const session = await matrix.createLiveKitRoomSession( req.user.user_id, req.user.tenant_id, room ) await notifyTenantUsersAboutCall(req, room, callModeFromRequest(req)) return session } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix call session failed") } }) server.post("/communication/matrix/rooms/:roomKey/members/sync", async (req, reply) => { try { return await matrix.syncTenantRoomMembers( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req) ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix member sync failed") } }) server.post("/communication/matrix/rooms/:roomKey/messages", async (req, reply) => { try { const body = req.body as { text?: string } return await matrix.sendTenantRoomMessage( req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req), body.text || "" ) } catch (err: any) { return handleMatrixError(req, reply, err, "Matrix message send failed") } }) }