KI-AGENT: Matrix-Teilnehmer synchronisieren
This commit is contained in:
@@ -2,7 +2,7 @@ import { createHash, createHmac, randomBytes } from "node:crypto"
|
||||
import { existsSync, readFileSync } from "node:fs"
|
||||
import { resolve } from "node:path"
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { authProfiles, authUsers, communicationRooms, tenants } from "../../db/schema"
|
||||
import { authProfiles, authTenantUsers, authUsers, communicationRooms, tenants } from "../../db/schema"
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import { secrets } from "../utils/secrets"
|
||||
|
||||
@@ -944,21 +944,15 @@ export function matrixService(server: FastifyInstance) {
|
||||
return session
|
||||
}
|
||||
|
||||
try {
|
||||
await requestMatrixJson(
|
||||
`/_matrix/client/v3/join/${encodeURIComponent(room.roomId || room.alias)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
)
|
||||
} catch (err: any) {
|
||||
if (err.errcode !== "M_FORBIDDEN" && err.errcode !== "M_UNKNOWN") {
|
||||
throw err
|
||||
await requestMatrixJson(
|
||||
`/_matrix/client/v3/join/${encodeURIComponent(room.roomId || room.alias)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
matrixJoinedRoomCache.set(joinCacheKey, Date.now() + 30 * 60 * 1000)
|
||||
return session
|
||||
@@ -1112,6 +1106,125 @@ export function matrixService(server: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const listTenantCommunicationUsers = async (tenantId: number | null) => {
|
||||
const tenant = await getCurrentTenant(tenantId)
|
||||
const rows = await server.db
|
||||
.select({
|
||||
userId: authTenantUsers.user_id,
|
||||
email: authUsers.email,
|
||||
profileActive: authProfiles.active,
|
||||
firstName: authProfiles.first_name,
|
||||
lastName: authProfiles.last_name,
|
||||
fullName: authProfiles.full_name,
|
||||
})
|
||||
.from(authTenantUsers)
|
||||
.innerJoin(authUsers, eq(authUsers.id, authTenantUsers.user_id))
|
||||
.leftJoin(authProfiles, and(
|
||||
eq(authProfiles.user_id, authTenantUsers.user_id),
|
||||
eq(authProfiles.tenant_id, tenant.id)
|
||||
))
|
||||
.where(eq(authTenantUsers.tenant_id, tenant.id))
|
||||
|
||||
return rows
|
||||
.filter((row) => row.profileActive !== false)
|
||||
.map((row) => ({
|
||||
userId: row.userId,
|
||||
email: row.email,
|
||||
displayName: row.fullName || `${row.firstName || ""} ${row.lastName || ""}`.trim() || row.email,
|
||||
}))
|
||||
}
|
||||
|
||||
const inviteMatrixUserToRoom = async (
|
||||
room: { roomId: string },
|
||||
matrixUserId: string,
|
||||
reason?: string
|
||||
) => {
|
||||
const serviceLogin = await ensureServiceAccessToken()
|
||||
|
||||
try {
|
||||
await requestMatrixJson(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(room.roomId)}/invite`,
|
||||
serviceLogin.accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
user_id: matrixUserId,
|
||||
reason,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return "invited"
|
||||
} catch (err: any) {
|
||||
if (
|
||||
err.errcode === "M_FORBIDDEN" ||
|
||||
err.errcode === "M_BAD_STATE" ||
|
||||
err.errcode === "M_UNKNOWN"
|
||||
) {
|
||||
return "already_available"
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const syncTenantRoomMembers = async (
|
||||
requestingUserId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {}
|
||||
) => {
|
||||
const room = await provisionTenantRoom(requestingUserId, tenantId, options)
|
||||
const users = await listTenantCommunicationUsers(tenantId)
|
||||
const results = []
|
||||
|
||||
for (const user of users) {
|
||||
try {
|
||||
const account = await provisionCurrentUser(user.userId, tenantId)
|
||||
await inviteMatrixUserToRoom(
|
||||
{ roomId: room.roomId },
|
||||
account.matrixUserId,
|
||||
`FEDEO-Raumsynchronisation: ${room.name}`
|
||||
)
|
||||
await ensureCurrentUserJoinedRoom(user.userId, tenantId, {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
})
|
||||
|
||||
results.push({
|
||||
userId: user.userId,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
matrixUserId: account.matrixUserId,
|
||||
status: "joined",
|
||||
ok: true,
|
||||
})
|
||||
} catch (err: any) {
|
||||
results.push({
|
||||
userId: user.userId,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
status: "failed",
|
||||
ok: false,
|
||||
error: err.message || "Synchronisation fehlgeschlagen",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
name: room.name,
|
||||
total: results.length,
|
||||
joined: results.filter((item) => item.status === "joined").length,
|
||||
invited: results.filter((item) => item.status === "invited").length,
|
||||
alreadyAvailable: results.filter((item) => item.status === "already_available").length,
|
||||
failed: results.filter((item) => !item.ok).length,
|
||||
results,
|
||||
}
|
||||
}
|
||||
|
||||
const getGeneralRoomMessages = (userId: string, tenantId: number | null) =>
|
||||
getTenantRoomMessages(userId, tenantId, {
|
||||
key: "allgemein",
|
||||
@@ -1149,6 +1262,7 @@ export function matrixService(server: FastifyInstance) {
|
||||
getTenantRoomMessages,
|
||||
getTenantRoomMembers,
|
||||
sendTenantRoomMessage,
|
||||
syncTenantRoomMembers,
|
||||
getGeneralRoomMessages,
|
||||
getGeneralRoomMembers,
|
||||
sendGeneralRoomMessage,
|
||||
|
||||
@@ -125,6 +125,17 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
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 }
|
||||
@@ -179,6 +190,18 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
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 }
|
||||
|
||||
@@ -19,6 +19,7 @@ const roomCreateForm = ref({
|
||||
const loading = ref(false)
|
||||
const roomProvisioning = ref(false)
|
||||
const roomCreating = ref(false)
|
||||
const roomMembersSyncing = ref(false)
|
||||
const matrixMessagesLoading = ref(false)
|
||||
const matrixMembersLoading = ref(false)
|
||||
const matrixMessageSending = ref(false)
|
||||
@@ -288,6 +289,32 @@ const loadRoomMembers = async ({ silent = false } = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
const syncRoomMembers = async () => {
|
||||
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||
|
||||
roomMembersSyncing.value = true
|
||||
try {
|
||||
const res = await $api(`${activeRoomEndpoint.value}/members/sync`, {
|
||||
method: "POST"
|
||||
})
|
||||
|
||||
toast.add({
|
||||
title: "Teilnehmer synchronisiert",
|
||||
description: `${res.joined || 0} synchronisiert, ${res.failed || 0} fehlgeschlagen`,
|
||||
color: res.failed ? "warning" : "success"
|
||||
})
|
||||
|
||||
await loadRoomMembers()
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Teilnehmer konnten nicht synchronisiert werden",
|
||||
color: "error"
|
||||
})
|
||||
} finally {
|
||||
roomMembersSyncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadRoomChat = async ({ silent = false, includeMembers = false } = {}) => {
|
||||
await loadRoomMessages({ silent })
|
||||
|
||||
@@ -752,15 +779,26 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
<h4 class="text-xs font-medium uppercase text-muted">
|
||||
Teilnehmer
|
||||
</h4>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:loading="matrixMembersLoading"
|
||||
:disabled="!canUseMatrixChat || !activeRoom?.exists"
|
||||
@click="loadRoomMembers"
|
||||
/>
|
||||
<div class="flex items-center gap-1">
|
||||
<UButton
|
||||
icon="i-heroicons-user-plus"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:loading="roomMembersSyncing"
|
||||
:disabled="!canUseMatrixChat || !activeRoom?.exists"
|
||||
@click="syncRoomMembers"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:loading="matrixMembersLoading"
|
||||
:disabled="!canUseMatrixChat || !activeRoom?.exists"
|
||||
@click="loadRoomMembers"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user