KI-AGENT: Zentrale Benachrichtigungsengine mit Desktop Push umsetzen
This commit is contained in:
@@ -1,8 +1,22 @@
|
||||
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
|
||||
@@ -33,6 +47,45 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
@@ -138,10 +191,13 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
|
||||
server.post("/communication/matrix/rooms/general/call-session", async (req, reply) => {
|
||||
try {
|
||||
return await matrix.createLiveKitRoomSession(req.user.user_id, req.user.tenant_id, {
|
||||
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")
|
||||
}
|
||||
@@ -226,11 +282,14 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
|
||||
server.post("/communication/matrix/rooms/:roomKey/call-session", async (req, reply) => {
|
||||
try {
|
||||
return await matrix.createLiveKitRoomSession(
|
||||
const room = roomOptionsFromRequest(req)
|
||||
const session = await matrix.createLiveKitRoomSession(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req)
|
||||
room
|
||||
)
|
||||
await notifyTenantUsersAboutCall(req, room, callModeFromRequest(req))
|
||||
return session
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix call session failed")
|
||||
}
|
||||
|
||||
@@ -1,31 +1,92 @@
|
||||
// routes/notifications.routes.ts
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import { NotificationService, UserDirectory } from '../modules/notification.service';
|
||||
import { eq } from "drizzle-orm";
|
||||
import { authUsers } from "../../db/schema";
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { authUsers } from "../../db/schema"
|
||||
import { NotificationService, UserDirectory } from "../modules/notification.service"
|
||||
|
||||
// Beispiel: E-Mail aus eigener User-Tabelle laden
|
||||
const getUserDirectory: UserDirectory = async (server:FastifyInstance, userId, tenantId) => {
|
||||
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)
|
||||
const data = rows[0]
|
||||
if (!data) return null;
|
||||
return { email: data.email };
|
||||
};
|
||||
if (!data) return null
|
||||
return { email: data.email }
|
||||
}
|
||||
|
||||
const requireTenant = (tenantId: number | null) => {
|
||||
if (!tenantId) throw new Error("Kein aktiver Mandant")
|
||||
return tenantId
|
||||
}
|
||||
|
||||
export default async function notificationsRoutes(server: FastifyInstance) {
|
||||
const svc = new NotificationService(server, getUserDirectory);
|
||||
const svc = new NotificationService(server, getUserDirectory)
|
||||
|
||||
server.post('/notifications/trigger', async (req, reply) => {
|
||||
try {
|
||||
const res = await svc.trigger(req.body as any);
|
||||
reply.send(res);
|
||||
} catch (err: any) {
|
||||
server.log.error(err);
|
||||
reply.code(500).send({ error: err.message });
|
||||
server.get("/notifications", async (req) => {
|
||||
const limit = Number((req.query as { limit?: string })?.limit || 50)
|
||||
return await svc.listForUser(requireTenant(req.user.tenant_id), req.user.user_id, limit)
|
||||
})
|
||||
|
||||
server.post("/notifications/:id/read", async (req, reply) => {
|
||||
const params = req.params as { id: string }
|
||||
const item = await svc.markRead(requireTenant(req.user.tenant_id), req.user.user_id, params.id)
|
||||
if (!item) return reply.code(404).send({ error: "Benachrichtigung nicht gefunden" })
|
||||
return item
|
||||
})
|
||||
|
||||
server.get("/notifications/push/config", async () => {
|
||||
return svc.getPublicPushConfig()
|
||||
})
|
||||
|
||||
server.post("/notifications/push/subscribe", async (req) => {
|
||||
const tenantId = requireTenant(req.user.tenant_id)
|
||||
const userAgent = req.headers["user-agent"]
|
||||
const subscription = await svc.registerPushSubscription(
|
||||
tenantId,
|
||||
req.user.user_id,
|
||||
req.body as any,
|
||||
Array.isArray(userAgent) ? userAgent.join(" ") : userAgent
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: subscription?.id,
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
server.delete("/notifications/push/subscribe", async (req) => {
|
||||
const body = (req.body || {}) as { endpoint?: string }
|
||||
if (!body.endpoint) throw new Error("endpoint fehlt")
|
||||
return await svc.disablePushSubscription(requireTenant(req.user.tenant_id), req.user.user_id, body.endpoint)
|
||||
})
|
||||
|
||||
server.post("/notifications/test-push", async (req) => {
|
||||
return await svc.trigger({
|
||||
tenantId: requireTenant(req.user.tenant_id),
|
||||
userId: req.user.user_id,
|
||||
eventType: "system.test_push",
|
||||
title: "FEDEO Desktop Push ist aktiv",
|
||||
message: "Diese Testbenachrichtigung wurde von FEDEO selbst zugestellt.",
|
||||
payload: {
|
||||
link: "/",
|
||||
icon: "/favicon.ico",
|
||||
},
|
||||
channels: ["inapp", "push"],
|
||||
})
|
||||
})
|
||||
|
||||
server.post("/notifications/trigger", async (req, reply) => {
|
||||
try {
|
||||
const body = req.body as any
|
||||
const tenantId = body.tenantId || req.user.tenant_id
|
||||
const res = await svc.trigger({
|
||||
...body,
|
||||
tenantId: requireTenant(tenantId),
|
||||
})
|
||||
reply.send(res)
|
||||
} catch (err: any) {
|
||||
server.log.error(err)
|
||||
reply.code(500).send({ error: err.message })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user