KI-AGENT: Matrix-Raum-API verallgemeinern
This commit is contained in:
@@ -35,6 +35,12 @@ type MatrixUserSession = {
|
|||||||
validUntilMs: number
|
validUntilMs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatrixTenantRoomOptions = {
|
||||||
|
key?: string
|
||||||
|
name?: string
|
||||||
|
topic?: string
|
||||||
|
}
|
||||||
|
|
||||||
type MatrixCachedValue<T = any> = {
|
type MatrixCachedValue<T = any> = {
|
||||||
exists: true
|
exists: true
|
||||||
cachedUntil: number
|
cachedUntil: number
|
||||||
@@ -48,6 +54,13 @@ const matrixTenantSpaceCache = new Map<string, MatrixCachedValue>()
|
|||||||
const matrixTenantRoomCache = new Map<string, MatrixCachedValue>()
|
const matrixTenantRoomCache = new Map<string, MatrixCachedValue>()
|
||||||
let matrixServiceSessionCache: MatrixUserSession | null = null
|
let matrixServiceSessionCache: MatrixUserSession | null = null
|
||||||
|
|
||||||
|
const defaultTenantRooms: Required<Pick<MatrixTenantRoomOptions, "key" | "name">>[] = [
|
||||||
|
{
|
||||||
|
key: "allgemein",
|
||||||
|
name: "Allgemeiner Chat",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "")
|
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "")
|
||||||
const readLocalDevRegistrationSharedSecret = () => {
|
const readLocalDevRegistrationSharedSecret = () => {
|
||||||
if (process.env.NODE_ENV === "production") return ""
|
if (process.env.NODE_ENV === "production") return ""
|
||||||
@@ -173,6 +186,19 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
roomKey: string
|
roomKey: string
|
||||||
) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${serverName()}`
|
) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${serverName()}`
|
||||||
|
|
||||||
|
const normalizeTenantRoomOptions = (options: MatrixTenantRoomOptions = {}) => {
|
||||||
|
const fallbackName = options.key || options.name || "Allgemeiner Chat"
|
||||||
|
const key = normalizeMatrixAliasSeed(options.key || fallbackName)
|
||||||
|
const name = (options.name || fallbackName).trim() || "Allgemeiner Chat"
|
||||||
|
const topic = options.topic?.trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
name,
|
||||||
|
topic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buildSharedSecretMac = (
|
const buildSharedSecretMac = (
|
||||||
nonce: string,
|
nonce: string,
|
||||||
username: string,
|
username: string,
|
||||||
@@ -637,16 +663,13 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
const provisionTenantRoom = async (
|
const provisionTenantRoom = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
tenantId: number | null,
|
tenantId: number | null,
|
||||||
options: {
|
options: MatrixTenantRoomOptions = {}
|
||||||
key?: string
|
|
||||||
name?: string
|
|
||||||
topic?: string
|
|
||||||
} = {}
|
|
||||||
) => {
|
) => {
|
||||||
const tenant = await getCurrentTenant(tenantId)
|
const tenant = await getCurrentTenant(tenantId)
|
||||||
const key = normalizeMatrixAliasSeed(options.key || options.name || "allgemein")
|
const normalizedOptions = normalizeTenantRoomOptions(options)
|
||||||
const name = (options.name || "Allgemeiner Chat").trim() || "Allgemeiner Chat"
|
const key = normalizedOptions.key
|
||||||
const topic = (options.topic || `Allgemeiner Kommunikationsraum für ${tenant.name}`).trim()
|
const name = normalizedOptions.name
|
||||||
|
const topic = (normalizedOptions.topic || `Allgemeiner Kommunikationsraum für ${tenant.name}`).trim()
|
||||||
const cacheKey = `${tenant.id}:${key}`
|
const cacheKey = `${tenant.id}:${key}`
|
||||||
const cachedRoom = matrixTenantRoomCache.get(cacheKey)
|
const cachedRoom = matrixTenantRoomCache.get(cacheKey)
|
||||||
|
|
||||||
@@ -780,11 +803,39 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGeneralRoomMessages = async (userId: string, tenantId: number | null) => {
|
const listTenantRooms = async (tenantId: number | null) => {
|
||||||
const room = await provisionTenantRoom(userId, tenantId, {
|
const tenant = await getCurrentTenant(tenantId)
|
||||||
key: "allgemein",
|
const rooms = new Map<string, any>()
|
||||||
name: "Allgemeiner Chat",
|
|
||||||
})
|
for (const room of defaultTenantRooms) {
|
||||||
|
rooms.set(room.key, await getTenantRoomStatus(tenant.id, room.key, room.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [cacheKey, cachedRoom] of matrixTenantRoomCache.entries()) {
|
||||||
|
const [cachedTenantId, roomKey] = cacheKey.split(":")
|
||||||
|
if (cachedTenantId !== String(tenant.id) || !cachedRoom.value) continue
|
||||||
|
|
||||||
|
rooms.set(roomKey, {
|
||||||
|
...cachedRoom.value,
|
||||||
|
exists: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tenantId: tenant.id,
|
||||||
|
tenantName: tenant.name,
|
||||||
|
rooms: Array.from(rooms.values()).sort((a, b) =>
|
||||||
|
String(a.name || a.key).localeCompare(String(b.name || b.key), "de")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTenantRoomMessages = async (
|
||||||
|
userId: string,
|
||||||
|
tenantId: number | null,
|
||||||
|
options: MatrixTenantRoomOptions = {}
|
||||||
|
) => {
|
||||||
|
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||||
|
|
||||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
@@ -807,6 +858,8 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
return {
|
return {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
alias: room.alias,
|
alias: room.alias,
|
||||||
|
key: room.key,
|
||||||
|
name: room.name,
|
||||||
matrixUserId: session.matrixUserId,
|
matrixUserId: session.matrixUserId,
|
||||||
messages: response.chunk
|
messages: response.chunk
|
||||||
.filter((event) => event.type === "m.room.message" && event.content?.msgtype === "m.text")
|
.filter((event) => event.type === "m.room.message" && event.content?.msgtype === "m.text")
|
||||||
@@ -822,11 +875,12 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGeneralRoomMembers = async (userId: string, tenantId: number | null) => {
|
const getTenantRoomMembers = async (
|
||||||
const room = await provisionTenantRoom(userId, tenantId, {
|
userId: string,
|
||||||
key: "allgemein",
|
tenantId: number | null,
|
||||||
name: "Allgemeiner Chat",
|
options: MatrixTenantRoomOptions = {}
|
||||||
})
|
) => {
|
||||||
|
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||||
|
|
||||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
@@ -840,6 +894,8 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
return {
|
return {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
alias: room.alias,
|
alias: room.alias,
|
||||||
|
key: room.key,
|
||||||
|
name: room.name,
|
||||||
members: Object.entries(members.joined).map(([matrixUserId, member]) => ({
|
members: Object.entries(members.joined).map(([matrixUserId, member]) => ({
|
||||||
matrixUserId,
|
matrixUserId,
|
||||||
displayName: member.display_name || matrixUserId,
|
displayName: member.display_name || matrixUserId,
|
||||||
@@ -849,9 +905,10 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendGeneralRoomMessage = async (
|
const sendTenantRoomMessage = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
tenantId: number | null,
|
tenantId: number | null,
|
||||||
|
options: MatrixTenantRoomOptions = {},
|
||||||
text: string
|
text: string
|
||||||
) => {
|
) => {
|
||||||
const message = text.trim()
|
const message = text.trim()
|
||||||
@@ -863,10 +920,7 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = await provisionTenantRoom(userId, tenantId, {
|
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||||
key: "allgemein",
|
|
||||||
name: "Allgemeiner Chat",
|
|
||||||
})
|
|
||||||
|
|
||||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
@@ -896,9 +950,33 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
own: true,
|
own: true,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
alias: room.alias,
|
alias: room.alias,
|
||||||
|
key: room.key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getGeneralRoomMessages = (userId: string, tenantId: number | null) =>
|
||||||
|
getTenantRoomMessages(userId, tenantId, {
|
||||||
|
key: "allgemein",
|
||||||
|
name: "Allgemeiner Chat",
|
||||||
|
})
|
||||||
|
|
||||||
|
const getGeneralRoomMembers = (userId: string, tenantId: number | null) =>
|
||||||
|
getTenantRoomMembers(userId, tenantId, {
|
||||||
|
key: "allgemein",
|
||||||
|
name: "Allgemeiner Chat",
|
||||||
|
})
|
||||||
|
|
||||||
|
const sendGeneralRoomMessage = (userId: string, tenantId: number | null, text: string) =>
|
||||||
|
sendTenantRoomMessage(
|
||||||
|
userId,
|
||||||
|
tenantId,
|
||||||
|
{
|
||||||
|
key: "allgemein",
|
||||||
|
name: "Allgemeiner Chat",
|
||||||
|
},
|
||||||
|
text
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getStatus,
|
getStatus,
|
||||||
matrixUserIdForUser,
|
matrixUserIdForUser,
|
||||||
@@ -906,9 +984,13 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
provisionCurrentUser,
|
provisionCurrentUser,
|
||||||
getTenantSpaceStatus,
|
getTenantSpaceStatus,
|
||||||
provisionCurrentTenantSpace,
|
provisionCurrentTenantSpace,
|
||||||
|
listTenantRooms,
|
||||||
getTenantRoomStatus,
|
getTenantRoomStatus,
|
||||||
provisionTenantRoom,
|
provisionTenantRoom,
|
||||||
createAccessTokenForUser,
|
createAccessTokenForUser,
|
||||||
|
getTenantRoomMessages,
|
||||||
|
getTenantRoomMembers,
|
||||||
|
sendTenantRoomMessage,
|
||||||
getGeneralRoomMessages,
|
getGeneralRoomMessages,
|
||||||
getGeneralRoomMembers,
|
getGeneralRoomMembers,
|
||||||
sendGeneralRoomMessage,
|
sendGeneralRoomMessage,
|
||||||
|
|||||||
@@ -3,6 +3,23 @@ import { matrixService } from "../modules/matrix.service"
|
|||||||
|
|
||||||
export default async function communicationRoutes(server: FastifyInstance) {
|
export default async function communicationRoutes(server: FastifyInstance) {
|
||||||
const matrix = matrixService(server)
|
const matrix = matrixService(server)
|
||||||
|
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 }
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: params.roomKey || body.key,
|
||||||
|
name: body.name,
|
||||||
|
topic: body.topic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
server.get("/communication/matrix/status", async () => {
|
server.get("/communication/matrix/status", async () => {
|
||||||
return matrix.getStatus()
|
return matrix.getStatus()
|
||||||
@@ -21,10 +38,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.provisionCurrentUser(req.user.user_id, req.user.tenant_id)
|
return await matrix.provisionCurrentUser(req.user.user_id, req.user.tenant_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix provisioning failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix provisioning failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,10 +46,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.getTenantSpaceStatus(req.user.tenant_id)
|
return await matrix.getTenantSpaceStatus(req.user.tenant_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix tenant space status failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix tenant space status failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -43,10 +54,27 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.provisionCurrentTenantSpace(req.user.user_id, req.user.tenant_id)
|
return await matrix.provisionCurrentTenantSpace(req.user.user_id, req.user.tenant_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix tenant space provisioning failed")
|
||||||
return reply
|
}
|
||||||
.code(err.statusCode || 500)
|
})
|
||||||
.send({ error: err.message || "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")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,10 +82,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
|
return await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix room status failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix room status failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -68,10 +93,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
name: "Allgemeiner Chat",
|
name: "Allgemeiner Chat",
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix room provisioning failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix room provisioning failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -79,10 +101,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.getGeneralRoomMessages(req.user.user_id, req.user.tenant_id)
|
return await matrix.getGeneralRoomMessages(req.user.user_id, req.user.tenant_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix messages failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix messages failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -90,10 +109,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
return await matrix.getGeneralRoomMembers(req.user.user_id, req.user.tenant_id)
|
return await matrix.getGeneralRoomMembers(req.user.user_id, req.user.tenant_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix members failed")
|
||||||
return reply
|
|
||||||
.code(err.statusCode || 500)
|
|
||||||
.send({ error: err.message || "Matrix members failed" })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,10 +118,66 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
const body = req.body as { text?: string }
|
const body = req.body as { text?: string }
|
||||||
return await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "")
|
return await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "")
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
req.log.error(err)
|
return handleMatrixError(req, reply, err, "Matrix message send failed")
|
||||||
return reply
|
}
|
||||||
.code(err.statusCode || 500)
|
})
|
||||||
.send({ error: err.message || "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, 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/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")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ const { $api } = useNuxtApp()
|
|||||||
|
|
||||||
const status = ref(null)
|
const status = ref(null)
|
||||||
const identity = ref(null)
|
const identity = ref(null)
|
||||||
const generalRoom = ref(null)
|
const matrixRooms = ref([])
|
||||||
|
const activeRoomKey = ref("allgemein")
|
||||||
const matrixMessages = ref([])
|
const matrixMessages = ref([])
|
||||||
const matrixMembers = ref([])
|
const matrixMembers = ref([])
|
||||||
const matrixMessageDraft = ref("")
|
const matrixMessageDraft = ref("")
|
||||||
@@ -24,17 +25,26 @@ const canUseMatrixChat = computed(() =>
|
|||||||
Boolean(status.value?.reachable && status.value?.provisioningConfigured)
|
Boolean(status.value?.reachable && status.value?.provisioningConfigured)
|
||||||
)
|
)
|
||||||
|
|
||||||
const activeRoom = computed(() => ({
|
const activeRoom = computed(() =>
|
||||||
key: "general",
|
matrixRooms.value.find((room) => room.key === activeRoomKey.value) || {
|
||||||
name: generalRoom.value?.name || "Allgemeiner Chat",
|
key: activeRoomKey.value,
|
||||||
description: generalRoom.value?.alias || "Mandantenweiter Austausch",
|
name: activeRoomKey.value,
|
||||||
exists: Boolean(generalRoom.value?.exists),
|
description: "Mandantenweiter Austausch",
|
||||||
roomId: generalRoom.value?.roomId || "",
|
exists: false,
|
||||||
unread: 0
|
roomId: "",
|
||||||
}))
|
unread: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeRoomEndpoint = computed(() =>
|
||||||
|
`/api/communication/matrix/rooms/${encodeURIComponent(activeRoomKey.value)}`
|
||||||
|
)
|
||||||
|
|
||||||
const rooms = computed(() => [
|
const rooms = computed(() => [
|
||||||
activeRoom.value,
|
...matrixRooms.value.map((room) => ({
|
||||||
|
...room,
|
||||||
|
description: room.alias || room.roomId || "Mandantenweiter Austausch"
|
||||||
|
})),
|
||||||
{
|
{
|
||||||
key: "projects",
|
key: "projects",
|
||||||
name: "Projekt-Chats",
|
name: "Projekt-Chats",
|
||||||
@@ -51,6 +61,15 @@ const rooms = computed(() => [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const setActiveRoom = async (room) => {
|
||||||
|
if (room.disabled || room.key === activeRoomKey.value) return
|
||||||
|
|
||||||
|
activeRoomKey.value = room.key
|
||||||
|
matrixMessages.value = []
|
||||||
|
matrixMembers.value = []
|
||||||
|
await loadRoomChat({ includeMembers: true })
|
||||||
|
}
|
||||||
|
|
||||||
const mergeMatrixMessages = (incomingMessages) => {
|
const mergeMatrixMessages = (incomingMessages) => {
|
||||||
const byId = new Map()
|
const byId = new Map()
|
||||||
|
|
||||||
@@ -81,19 +100,19 @@ const scrollMessagesToBottom = async () => {
|
|||||||
const loadChatInfo = async () => {
|
const loadChatInfo = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const [statusRes, identityRes, roomRes] = await Promise.all([
|
const [statusRes, identityRes, roomsRes] = await Promise.all([
|
||||||
$api("/api/communication/matrix/status"),
|
$api("/api/communication/matrix/status"),
|
||||||
$api("/api/communication/matrix/me"),
|
$api("/api/communication/matrix/me"),
|
||||||
$api("/api/communication/matrix/rooms/general")
|
$api("/api/communication/matrix/rooms")
|
||||||
])
|
])
|
||||||
|
|
||||||
status.value = statusRes
|
status.value = statusRes
|
||||||
identity.value = identityRes
|
identity.value = identityRes
|
||||||
generalRoom.value = roomRes
|
matrixRooms.value = roomsRes.rooms || []
|
||||||
lastUpdated.value = new Date()
|
lastUpdated.value = new Date()
|
||||||
|
|
||||||
if (roomRes?.exists && canUseMatrixChat.value) {
|
if (activeRoom.value?.exists && canUseMatrixChat.value) {
|
||||||
await loadGeneralChat({ silent: true, includeMembers: true })
|
await loadRoomChat({ silent: true, includeMembers: true })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({
|
toast.add({
|
||||||
@@ -105,33 +124,27 @@ const loadChatInfo = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const provisionGeneralRoom = async () => {
|
const provisionActiveRoom = async () => {
|
||||||
roomProvisioning.value = true
|
roomProvisioning.value = true
|
||||||
try {
|
try {
|
||||||
const res = await $api("/api/communication/matrix/rooms/general/provision", {
|
const res = await $api(`${activeRoomEndpoint.value}/provision`, {
|
||||||
method: "POST"
|
method: "POST"
|
||||||
})
|
})
|
||||||
|
|
||||||
generalRoom.value = {
|
const roomWasKnown = matrixRooms.value.some((room) => room.key === res.key)
|
||||||
tenantId: res.tenantId,
|
matrixRooms.value = roomWasKnown
|
||||||
tenantName: res.tenantName,
|
? matrixRooms.value.map((room) => room.key === res.key ? { ...room, ...res, exists: true } : room)
|
||||||
key: res.key,
|
: [...matrixRooms.value, { ...res, exists: true }]
|
||||||
name: res.name,
|
|
||||||
alias: res.alias,
|
|
||||||
exists: true,
|
|
||||||
roomId: res.roomId,
|
|
||||||
servers: res.servers || []
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: res.alreadyExisted ? "Allgemeiner Chat ist bereit" : "Allgemeiner Chat erstellt",
|
title: res.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
|
||||||
color: "success"
|
color: "success"
|
||||||
})
|
})
|
||||||
|
|
||||||
await loadGeneralChat({ includeMembers: true })
|
await loadRoomChat({ includeMembers: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({
|
toast.add({
|
||||||
title: "Allgemeiner Chat konnte nicht erstellt werden",
|
title: "Chatraum konnte nicht erstellt werden",
|
||||||
color: "error"
|
color: "error"
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -139,22 +152,22 @@ const provisionGeneralRoom = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadGeneralMessages = async ({ silent = false } = {}) => {
|
const loadRoomMessages = async ({ silent = false } = {}) => {
|
||||||
if (!canUseMatrixChat.value || !generalRoom.value?.exists) return
|
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||||
if (matrixMessagesRequestActive) return
|
if (matrixMessagesRequestActive) return
|
||||||
|
|
||||||
matrixMessagesRequestActive = true
|
matrixMessagesRequestActive = true
|
||||||
if (!silent) matrixMessagesLoading.value = true
|
if (!silent) matrixMessagesLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await $api("/api/communication/matrix/rooms/general/messages")
|
const res = await $api(`${activeRoomEndpoint.value}/messages`)
|
||||||
mergeMatrixMessages(res.messages || [])
|
mergeMatrixMessages(res.messages || [])
|
||||||
generalRoom.value = {
|
matrixRooms.value = matrixRooms.value.map((room) => room.key === activeRoomKey.value ? {
|
||||||
...generalRoom.value,
|
...room,
|
||||||
alias: res.alias || generalRoom.value?.alias,
|
alias: res.alias || room.alias,
|
||||||
exists: true,
|
exists: true,
|
||||||
roomId: res.roomId || generalRoom.value?.roomId
|
roomId: res.roomId || room.roomId
|
||||||
}
|
} : room)
|
||||||
await scrollMessagesToBottom()
|
await scrollMessagesToBottom()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
@@ -169,15 +182,15 @@ const loadGeneralMessages = async ({ silent = false } = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadGeneralMembers = async ({ silent = false } = {}) => {
|
const loadRoomMembers = async ({ silent = false } = {}) => {
|
||||||
if (!canUseMatrixChat.value || !generalRoom.value?.exists) return
|
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||||
if (matrixMembersRequestActive) return
|
if (matrixMembersRequestActive) return
|
||||||
|
|
||||||
matrixMembersRequestActive = true
|
matrixMembersRequestActive = true
|
||||||
if (!silent) matrixMembersLoading.value = true
|
if (!silent) matrixMembersLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await $api("/api/communication/matrix/rooms/general/members")
|
const res = await $api(`${activeRoomEndpoint.value}/members`)
|
||||||
matrixMembers.value = res.members || []
|
matrixMembers.value = res.members || []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
@@ -192,17 +205,17 @@ const loadGeneralMembers = async ({ silent = false } = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadGeneralChat = async ({ silent = false, includeMembers = false } = {}) => {
|
const loadRoomChat = async ({ silent = false, includeMembers = false } = {}) => {
|
||||||
await loadGeneralMessages({ silent })
|
await loadRoomMessages({ silent })
|
||||||
|
|
||||||
if (includeMembers) {
|
if (includeMembers) {
|
||||||
await loadGeneralMembers({ silent })
|
await loadRoomMembers({ silent })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMatrixMessage = async () => {
|
const sendMatrixMessage = async () => {
|
||||||
const text = matrixMessageDraft.value.trim()
|
const text = matrixMessageDraft.value.trim()
|
||||||
if (!text || !canUseMatrixChat.value || !generalRoom.value?.exists) return
|
if (!text || !canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||||
|
|
||||||
const optimisticId = `pending-${Date.now()}`
|
const optimisticId = `pending-${Date.now()}`
|
||||||
const optimisticMessage = {
|
const optimisticMessage = {
|
||||||
@@ -225,7 +238,7 @@ const sendMatrixMessage = async () => {
|
|||||||
|
|
||||||
matrixMessageSending.value = true
|
matrixMessageSending.value = true
|
||||||
try {
|
try {
|
||||||
const message = await $api("/api/communication/matrix/rooms/general/messages", {
|
const message = await $api(`${activeRoomEndpoint.value}/messages`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { text }
|
body: { text }
|
||||||
})
|
})
|
||||||
@@ -251,8 +264,8 @@ const startMatrixAutoRefresh = () => {
|
|||||||
|
|
||||||
matrixAutoRefreshActive.value = true
|
matrixAutoRefreshActive.value = true
|
||||||
matrixRefreshInterval = window.setInterval(() => {
|
matrixRefreshInterval = window.setInterval(() => {
|
||||||
if (!document.hidden && canUseMatrixChat.value && generalRoom.value?.exists) {
|
if (!document.hidden && canUseMatrixChat.value && activeRoom.value?.exists) {
|
||||||
loadGeneralChat({ silent: true })
|
loadRoomChat({ silent: true })
|
||||||
}
|
}
|
||||||
}, 15000)
|
}, 15000)
|
||||||
}
|
}
|
||||||
@@ -315,7 +328,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
:loading="loading || matrixMessagesLoading"
|
:loading="loading || matrixMessagesLoading"
|
||||||
:disabled="!canUseMatrixChat"
|
:disabled="!canUseMatrixChat"
|
||||||
@click="loadGeneralChat({ includeMembers: true })"
|
@click="loadRoomChat({ includeMembers: true })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,6 +341,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
class="mb-1 flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition"
|
class="mb-1 flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition"
|
||||||
:class="room.disabled ? 'cursor-not-allowed opacity-50' : 'bg-muted text-highlighted'"
|
:class="room.disabled ? 'cursor-not-allowed opacity-50' : 'bg-muted text-highlighted'"
|
||||||
:disabled="room.disabled"
|
:disabled="room.disabled"
|
||||||
|
@click="setActiveRoom(room)"
|
||||||
>
|
>
|
||||||
<span class="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
<span class="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||||
<UIcon name="i-heroicons-chat-bubble-left-right" class="size-5" />
|
<UIcon name="i-heroicons-chat-bubble-left-right" class="size-5" />
|
||||||
@@ -368,7 +382,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
{{ activeRoom.name }}
|
{{ activeRoom.name }}
|
||||||
</h2>
|
</h2>
|
||||||
<UBadge
|
<UBadge
|
||||||
v-if="matrixAutoRefreshActive && generalRoom?.exists"
|
v-if="matrixAutoRefreshActive && activeRoom?.exists"
|
||||||
color="success"
|
color="success"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -395,8 +409,8 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:loading="matrixMessagesLoading"
|
:loading="matrixMessagesLoading"
|
||||||
:disabled="!canUseMatrixChat || !generalRoom?.exists"
|
:disabled="!canUseMatrixChat || !activeRoom?.exists"
|
||||||
@click="loadGeneralChat({ includeMembers: true })"
|
@click="loadRoomChat({ includeMembers: true })"
|
||||||
>
|
>
|
||||||
Laden
|
Laden
|
||||||
</UButton>
|
</UButton>
|
||||||
@@ -417,7 +431,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="status && !generalRoom?.exists"
|
v-else-if="status && !activeRoom?.exists"
|
||||||
class="border-b border-default bg-default px-4 py-3"
|
class="border-b border-default bg-default px-4 py-3"
|
||||||
>
|
>
|
||||||
<UAlert
|
<UAlert
|
||||||
@@ -431,7 +445,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
<UButton
|
<UButton
|
||||||
icon="i-heroicons-plus"
|
icon="i-heroicons-plus"
|
||||||
:loading="roomProvisioning"
|
:loading="roomProvisioning"
|
||||||
@click="provisionGeneralRoom"
|
@click="provisionActiveRoom"
|
||||||
>
|
>
|
||||||
Chat erstellen
|
Chat erstellen
|
||||||
</UButton>
|
</UButton>
|
||||||
@@ -487,14 +501,14 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
v-model="matrixMessageDraft"
|
v-model="matrixMessageDraft"
|
||||||
class="min-w-0 flex-1"
|
class="min-w-0 flex-1"
|
||||||
placeholder="Nachricht schreiben"
|
placeholder="Nachricht schreiben"
|
||||||
:disabled="matrixMessageSending || !canUseMatrixChat || !generalRoom?.exists"
|
:disabled="matrixMessageSending || !canUseMatrixChat || !activeRoom?.exists"
|
||||||
@keydown.enter.exact.prevent="sendMatrixMessage"
|
@keydown.enter.exact.prevent="sendMatrixMessage"
|
||||||
/>
|
/>
|
||||||
<UButton
|
<UButton
|
||||||
type="submit"
|
type="submit"
|
||||||
icon="i-heroicons-paper-airplane"
|
icon="i-heroicons-paper-airplane"
|
||||||
:loading="matrixMessageSending"
|
:loading="matrixMessageSending"
|
||||||
:disabled="!matrixMessageDraft.trim() || !canUseMatrixChat || !generalRoom?.exists"
|
:disabled="!matrixMessageDraft.trim() || !canUseMatrixChat || !activeRoom?.exists"
|
||||||
>
|
>
|
||||||
Senden
|
Senden
|
||||||
</UButton>
|
</UButton>
|
||||||
@@ -507,7 +521,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
Raumdetails
|
Raumdetails
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 break-all font-mono text-xs text-muted">
|
<p class="mt-1 break-all font-mono text-xs text-muted">
|
||||||
{{ generalRoom?.roomId || generalRoom?.alias || "Noch kein Matrix-Raum" }}
|
{{ activeRoom?.roomId || activeRoom?.alias || "Noch kein Matrix-Raum" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -523,26 +537,26 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="xs"
|
size="xs"
|
||||||
:loading="matrixMembersLoading"
|
:loading="matrixMembersLoading"
|
||||||
:disabled="!canUseMatrixChat || !generalRoom?.exists"
|
:disabled="!canUseMatrixChat || !activeRoom?.exists"
|
||||||
@click="loadGeneralMembers"
|
@click="loadRoomMembers"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div
|
<div
|
||||||
v-for="member in matrixMembers"
|
v-for="member in matrixMembers"
|
||||||
:key="member.userId"
|
:key="member.matrixUserId"
|
||||||
class="flex items-center gap-2"
|
class="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span class="flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-xs font-medium text-highlighted">
|
<span class="flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-xs font-medium text-highlighted">
|
||||||
{{ (member.displayName || member.userId || "?").slice(0, 1).toUpperCase() }}
|
{{ (member.displayName || member.matrixUserId || "?").slice(0, 1).toUpperCase() }}
|
||||||
</span>
|
</span>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="truncate text-sm font-medium text-highlighted">
|
<p class="truncate text-sm font-medium text-highlighted">
|
||||||
{{ member.displayName || member.userId }}
|
{{ member.displayName || member.matrixUserId }}
|
||||||
</p>
|
</p>
|
||||||
<p class="truncate text-xs text-muted">
|
<p class="truncate text-xs text-muted">
|
||||||
{{ member.userId }}
|
{{ member.matrixUserId }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user