feat(#380): Workspace-to-Matrix-Room mapping and provisioning
Some checks failed
ci/woodpecker/push/api Pipeline failed
Some checks failed
ci/woodpecker/push/api Pipeline failed
- Add matrix_room_id column to workspace table (migration) - Create MatrixRoomService for room provisioning and mapping - Auto-create Matrix room on workspace provisioning (when configured) - Support manual room linking for existing workspaces - Unit tests for all mapping operations Refs #380
This commit is contained in:
137
apps/api/src/bridge/matrix/matrix-room.service.ts
Normal file
137
apps/api/src/bridge/matrix/matrix-room.service.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { Injectable, Logger, Optional, Inject } from "@nestjs/common";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
import { MatrixService } from "./matrix.service";
|
||||
import type { MatrixClient, RoomCreateOptions } from "matrix-bot-sdk";
|
||||
|
||||
/**
|
||||
* MatrixRoomService - Workspace-to-Matrix-Room mapping and provisioning
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Provision Matrix rooms for Mosaic workspaces
|
||||
* - Map workspaces to Matrix room IDs
|
||||
* - Link/unlink existing rooms to workspaces
|
||||
*
|
||||
* Room provisioning creates a private Matrix room with:
|
||||
* - Name: "Mosaic: {workspace_name}"
|
||||
* - Alias: #mosaic-{workspace_slug}:{server_name}
|
||||
* - Room ID stored in workspace.matrixRoomId
|
||||
*/
|
||||
@Injectable()
|
||||
export class MatrixRoomService {
|
||||
private readonly logger = new Logger(MatrixRoomService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Optional() @Inject(MatrixService) private readonly matrixService: MatrixService | null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Provision a Matrix room for a workspace and store the mapping.
|
||||
*
|
||||
* @param workspaceId - The workspace UUID
|
||||
* @param workspaceName - Human-readable workspace name
|
||||
* @param workspaceSlug - URL-safe workspace identifier for the room alias
|
||||
* @returns The Matrix room ID, or null if Matrix is not configured
|
||||
*/
|
||||
async provisionRoom(
|
||||
workspaceId: string,
|
||||
workspaceName: string,
|
||||
workspaceSlug: string
|
||||
): Promise<string | null> {
|
||||
if (!this.matrixService?.isConnected()) {
|
||||
this.logger.warn("Matrix is not configured or not connected; skipping room provisioning");
|
||||
return null;
|
||||
}
|
||||
|
||||
const client = this.getMatrixClient();
|
||||
if (!client) {
|
||||
this.logger.warn("Matrix client is not available; skipping room provisioning");
|
||||
return null;
|
||||
}
|
||||
|
||||
const roomOptions: RoomCreateOptions = {
|
||||
name: `Mosaic: ${workspaceName}`,
|
||||
room_alias_name: `mosaic-${workspaceSlug}`,
|
||||
topic: `Mosaic workspace: ${workspaceName}`,
|
||||
preset: "private_chat",
|
||||
visibility: "private",
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
`Provisioning Matrix room for workspace "${workspaceName}" (${workspaceId})...`
|
||||
);
|
||||
|
||||
const roomId = await client.createRoom(roomOptions);
|
||||
|
||||
// Store the room mapping
|
||||
await this.prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { matrixRoomId: roomId },
|
||||
});
|
||||
|
||||
this.logger.log(`Matrix room ${roomId} provisioned and linked to workspace ${workspaceId}`);
|
||||
|
||||
return roomId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the Matrix room ID mapped to a workspace.
|
||||
*
|
||||
* @param workspaceId - The workspace UUID
|
||||
* @returns The Matrix room ID, or null if no room is mapped
|
||||
*/
|
||||
async getRoomForWorkspace(workspaceId: string): Promise<string | null> {
|
||||
const workspace = await this.prisma.workspace.findUnique({
|
||||
where: { id: workspaceId },
|
||||
select: { matrixRoomId: true },
|
||||
});
|
||||
|
||||
return workspace?.matrixRoomId ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually link an existing Matrix room to a workspace.
|
||||
*
|
||||
* @param workspaceId - The workspace UUID
|
||||
* @param roomId - The Matrix room ID to link
|
||||
*/
|
||||
async linkWorkspaceToRoom(workspaceId: string, roomId: string): Promise<void> {
|
||||
await this.prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { matrixRoomId: roomId },
|
||||
});
|
||||
|
||||
this.logger.log(`Linked workspace ${workspaceId} to Matrix room ${roomId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the Matrix room mapping from a workspace.
|
||||
*
|
||||
* @param workspaceId - The workspace UUID
|
||||
*/
|
||||
async unlinkWorkspace(workspaceId: string): Promise<void> {
|
||||
await this.prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { matrixRoomId: null },
|
||||
});
|
||||
|
||||
this.logger.log(`Unlinked Matrix room from workspace ${workspaceId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the underlying MatrixClient from the MatrixService.
|
||||
*
|
||||
* The MatrixService stores the client as a private field, so we
|
||||
* access it via a known private property name. This is intentional
|
||||
* to avoid exposing the client publicly on the service interface.
|
||||
*/
|
||||
private getMatrixClient(): MatrixClient | null {
|
||||
if (!this.matrixService) return null;
|
||||
|
||||
// Access the private client field from MatrixService.
|
||||
// MatrixService stores `client` as a private property; we use a type assertion
|
||||
// to access it since exposing it publicly is not appropriate for the service API.
|
||||
const service = this.matrixService as unknown as { client: MatrixClient | null };
|
||||
return service.client;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user