feat: real-time SPEAK permission enforcement in voice channels

Permission changes now take effect immediately without requiring
disconnect/reconnect. Modeled as "permission mute" parallel to
server mute — server recomputes SPEAK for all voice participants
on role/override changes and broadcasts state via WebSocket.
Includes amber UI indicators and mic toggle blocking.
This commit is contained in:
Jannis Braun
2026-03-10 14:50:01 +01:00
parent 4d9454382c
commit ce63c5ed36
13 changed files with 173 additions and 28 deletions
+5
View File
@@ -19,6 +19,7 @@ import type {
Role,
} from '@backspace/shared';
import { sanitizeUser } from '../utils/sanitize.js';
import { checkVoicePermissions } from '../ws/events.js';
function rowToSpace(row: typeof schema.spaces.$inferSelect): Space {
return {
@@ -662,6 +663,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
// Force target user's client to re-sync with their new permissions
connectionManager.pushReadyPayload(uid);
checkVoicePermissions(id);
// Build response with populated roles
const updatedMember = db.select()
@@ -829,6 +831,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
for (const m of memberRows) {
connectionManager.pushReadyPayload(m.userId);
}
checkVoicePermissions(id);
return reply.code(201).send(role);
});
@@ -871,6 +874,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
for (const m of memberRows) {
connectionManager.pushReadyPayload(m.userId);
}
checkVoicePermissions(id);
return reply.code(200).send(updated);
});
@@ -903,6 +907,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
for (const m of memberRows) {
connectionManager.pushReadyPayload(m.userId);
}
checkVoicePermissions(id);
return reply.code(200).send({ success: true });
});