feat: Allow admin to create new text channels
This commit is contained in:
@@ -8,7 +8,10 @@ import {
|
||||
createEffect,
|
||||
ofType
|
||||
} from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
Action,
|
||||
Store
|
||||
} from '@ngrx/store';
|
||||
import {
|
||||
of,
|
||||
from,
|
||||
@@ -29,7 +32,11 @@ import { RoomsActions } from './rooms.actions';
|
||||
import { UsersActions } from '../users/users.actions';
|
||||
import { MessagesActions } from '../messages/messages.actions';
|
||||
import { selectCurrentUser, selectAllUsers } from '../users/users.selectors';
|
||||
import { selectCurrentRoom, selectSavedRooms } from './rooms.selectors';
|
||||
import {
|
||||
selectActiveChannelId,
|
||||
selectCurrentRoom,
|
||||
selectSavedRooms
|
||||
} from './rooms.selectors';
|
||||
import { RealtimeSessionFacade } from '../../core/realtime';
|
||||
import { DatabaseService } from '../../infrastructure/persistence';
|
||||
import {
|
||||
@@ -54,6 +61,7 @@ import {
|
||||
removeRoomMember,
|
||||
transferRoomOwnership
|
||||
} from './room-members.helpers';
|
||||
import { defaultChannels } from './room-channels.defaults';
|
||||
|
||||
/** Build a minimal User object from signaling payload. */
|
||||
function buildSignalingUser(
|
||||
@@ -224,6 +232,7 @@ export class RoomsEffects {
|
||||
createdAt: Date.now(),
|
||||
userCount: 1,
|
||||
maxUsers: 50,
|
||||
channels: defaultChannels(),
|
||||
sourceId: endpoint?.id,
|
||||
sourceName: endpoint?.name,
|
||||
sourceUrl: endpoint?.url
|
||||
@@ -246,7 +255,8 @@ export class RoomsEffects {
|
||||
isPrivate: room.isPrivate,
|
||||
userCount: 1,
|
||||
maxUsers: room.maxUsers || 50,
|
||||
tags: []
|
||||
tags: [],
|
||||
channels: room.channels ?? defaultChannels()
|
||||
}, endpoint ? {
|
||||
sourceId: endpoint.id,
|
||||
sourceUrl: endpoint.url
|
||||
@@ -290,6 +300,7 @@ export class RoomsEffects {
|
||||
const resolvedRoom: Room = {
|
||||
...room,
|
||||
isPrivate: typeof serverInfo?.isPrivate === 'boolean' ? serverInfo.isPrivate : room.isPrivate,
|
||||
channels: Array.isArray(serverInfo?.channels) ? serverInfo.channels : room.channels,
|
||||
sourceId: serverInfo?.sourceId ?? room.sourceId,
|
||||
sourceName: serverInfo?.sourceName ?? room.sourceName,
|
||||
sourceUrl: serverInfo?.sourceUrl ?? room.sourceUrl,
|
||||
@@ -303,6 +314,7 @@ export class RoomsEffects {
|
||||
sourceId: resolvedRoom.sourceId,
|
||||
sourceName: resolvedRoom.sourceName,
|
||||
sourceUrl: resolvedRoom.sourceUrl,
|
||||
channels: resolvedRoom.channels,
|
||||
hasPassword: resolvedRoom.hasPassword,
|
||||
isPrivate: resolvedRoom.isPrivate
|
||||
});
|
||||
@@ -322,6 +334,7 @@ export class RoomsEffects {
|
||||
createdAt: Date.now(),
|
||||
userCount: 1,
|
||||
maxUsers: 50,
|
||||
channels: Array.isArray(serverInfo.channels) ? serverInfo.channels : undefined,
|
||||
sourceId: serverInfo.sourceId,
|
||||
sourceName: serverInfo.sourceName,
|
||||
sourceUrl: serverInfo.sourceUrl
|
||||
@@ -346,6 +359,7 @@ export class RoomsEffects {
|
||||
createdAt: serverData.createdAt || Date.now(),
|
||||
userCount: serverData.userCount,
|
||||
maxUsers: serverData.maxUsers,
|
||||
channels: Array.isArray(serverData.channels) ? serverData.channels : undefined,
|
||||
sourceId: serverData.sourceId,
|
||||
sourceName: serverData.sourceName,
|
||||
sourceUrl: serverData.sourceUrl
|
||||
@@ -679,6 +693,50 @@ export class RoomsEffects {
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
syncChannelChanges$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(RoomsActions.addChannel, RoomsActions.removeChannel, RoomsActions.renameChannel),
|
||||
withLatestFrom(
|
||||
this.store.select(selectCurrentUser),
|
||||
this.store.select(selectCurrentRoom)
|
||||
),
|
||||
tap(([, currentUser, currentRoom]) => {
|
||||
if (!currentUser || !currentRoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const role = this.getUserRoleForRoom(currentRoom, currentUser, currentRoom);
|
||||
|
||||
if (!this.canManageChannelsInRoom(currentRoom, currentUser, currentRoom, role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channels = currentRoom.channels ?? defaultChannels();
|
||||
|
||||
this.db.updateRoom(currentRoom.id, { channels });
|
||||
|
||||
this.webrtc.broadcastMessage({
|
||||
type: 'channels-update',
|
||||
roomId: currentRoom.id,
|
||||
channels
|
||||
});
|
||||
|
||||
this.serverDirectory.updateServer(currentRoom.id, {
|
||||
currentOwnerId: currentUser.id,
|
||||
actingRole: role ?? undefined,
|
||||
channels
|
||||
}, {
|
||||
sourceId: currentRoom.sourceId,
|
||||
sourceUrl: currentRoom.sourceUrl
|
||||
}).subscribe({
|
||||
error: () => {}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
/** Updates room permission grants (host-only) and broadcasts to peers. */
|
||||
updateRoomPermissions$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
@@ -953,14 +1011,16 @@ export class RoomsEffects {
|
||||
this.store.select(selectCurrentRoom),
|
||||
this.store.select(selectSavedRooms),
|
||||
this.store.select(selectAllUsers),
|
||||
this.store.select(selectCurrentUser)
|
||||
this.store.select(selectCurrentUser),
|
||||
this.store.select(selectActiveChannelId)
|
||||
),
|
||||
mergeMap(([
|
||||
event,
|
||||
currentRoom,
|
||||
savedRooms,
|
||||
allUsers,
|
||||
currentUser
|
||||
currentUser,
|
||||
activeChannelId
|
||||
]) => {
|
||||
switch (event.type) {
|
||||
case 'voice-state':
|
||||
@@ -975,6 +1035,8 @@ export class RoomsEffects {
|
||||
return this.handleRoomSettingsUpdate(event, currentRoom, savedRooms);
|
||||
case 'room-permissions-update':
|
||||
return this.handleRoomPermissionsUpdate(event, currentRoom, savedRooms);
|
||||
case 'channels-update':
|
||||
return this.handleChannelsUpdate(event, currentRoom, savedRooms, activeChannelId);
|
||||
case 'server-icon-summary':
|
||||
return this.handleIconSummary(event, currentRoom, savedRooms);
|
||||
case 'server-icon-request':
|
||||
@@ -1261,6 +1323,37 @@ export class RoomsEffects {
|
||||
);
|
||||
}
|
||||
|
||||
private handleChannelsUpdate(
|
||||
event: ChatEvent,
|
||||
currentRoom: Room | null,
|
||||
savedRooms: Room[],
|
||||
activeChannelId: string
|
||||
): Action[] {
|
||||
const roomId = typeof event.roomId === 'string' ? event.roomId : currentRoom?.id;
|
||||
const room = this.resolveRoom(roomId, currentRoom, savedRooms);
|
||||
const channels = Array.isArray(event.channels) ? event.channels : null;
|
||||
|
||||
if (!room || !channels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions: Action[] = [
|
||||
RoomsActions.updateRoom({
|
||||
roomId: room.id,
|
||||
changes: { channels }
|
||||
})
|
||||
];
|
||||
|
||||
if (!channels.some((channel) => channel.id === activeChannelId)) {
|
||||
const fallbackChannelId = channels.find((channel) => channel.type === 'text')?.id
|
||||
?? 'general';
|
||||
|
||||
actions.push(RoomsActions.selectChannel({ channelId: fallbackChannelId }));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private handleIconSummary(event: ChatEvent, currentRoom: Room | null, savedRooms: Room[]) {
|
||||
const roomId = typeof event.roomId === 'string' ? event.roomId : currentRoom?.id;
|
||||
const room = this.resolveRoom(roomId, currentRoom, savedRooms);
|
||||
@@ -1540,6 +1633,29 @@ export class RoomsEffects {
|
||||
|| null;
|
||||
}
|
||||
|
||||
private canManageChannelsInRoom(
|
||||
room: Room,
|
||||
currentUser: User,
|
||||
currentRoom: Room | null,
|
||||
currentUserRole = this.getUserRoleForRoom(room, currentUser, currentRoom)
|
||||
): boolean {
|
||||
if (currentUserRole === 'host') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permissions = room.permissions || {};
|
||||
|
||||
if (currentUserRole === 'admin' && permissions.adminsManageRooms) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentUserRole === 'moderator' && permissions.moderatorsManageRooms) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private getPersistedCurrentUserId(): string | null {
|
||||
return localStorage.getItem('metoyou_currentUserId');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user