172 lines
5.7 KiB
TypeScript
172 lines
5.7 KiB
TypeScript
import {
|
|
RoomPermissionMatrix,
|
|
RoomPermissions,
|
|
RoomRole
|
|
} from '../../../../shared-kernel';
|
|
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
|
import {
|
|
buildRoleLookup,
|
|
buildSystemRole,
|
|
nextRolePosition,
|
|
normalizeName,
|
|
normalizePermissionMatrix,
|
|
roleSortAscending,
|
|
roleSortDescending
|
|
} from '../util/access-control.util';
|
|
|
|
const ROLE_COLORS = {
|
|
everyone: '#6b7280',
|
|
moderator: '#10b981',
|
|
admin: '#60a5fa'
|
|
} as const;
|
|
|
|
function resolveNormalizedRolePosition(
|
|
position: unknown,
|
|
fallbackPosition: number | undefined,
|
|
existingRoles: readonly RoomRole[],
|
|
defaultRoles: readonly RoomRole[]
|
|
): number {
|
|
if (typeof position === 'number' && Number.isFinite(position)) {
|
|
return position;
|
|
}
|
|
|
|
if (typeof fallbackPosition === 'number') {
|
|
return fallbackPosition;
|
|
}
|
|
|
|
return nextRolePosition(existingRoles.length > 0 ? existingRoles : defaultRoles);
|
|
}
|
|
|
|
function normalizeRoomRoleEntry(
|
|
role: RoomRole | null | undefined,
|
|
defaultsById: Map<string, RoomRole>,
|
|
existingRoles: readonly RoomRole[],
|
|
defaultRoles: readonly RoomRole[]
|
|
): RoomRole | null {
|
|
if (!role) {
|
|
return null;
|
|
}
|
|
|
|
const id = typeof role.id === 'string' ? role.id.trim() : '';
|
|
const fallbackRole = defaultsById.get(id);
|
|
const name = normalizeName(typeof role.name === 'string' ? role.name : (fallbackRole?.name ?? 'Role'));
|
|
|
|
if (!id || !name) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id,
|
|
name,
|
|
position: resolveNormalizedRolePosition(role.position, fallbackRole?.position, existingRoles, defaultRoles),
|
|
color: typeof role.color === 'string' && role.color.trim() ? role.color.trim() : fallbackRole?.color,
|
|
isSystem: typeof role.isSystem === 'boolean' ? role.isSystem : fallbackRole?.isSystem,
|
|
permissions: normalizePermissionMatrix(role.permissions ?? fallbackRole?.permissions)
|
|
};
|
|
}
|
|
|
|
export function buildDefaultRoomRoles(legacyPermissions?: RoomPermissions): RoomRole[] {
|
|
const everyonePermissions: RoomPermissionMatrix = {
|
|
joinVoice: legacyPermissions?.allowVoice === false ? 'deny' : 'allow',
|
|
shareScreen: legacyPermissions?.allowScreenShare === false ? 'deny' : 'allow',
|
|
uploadFiles: legacyPermissions?.allowFileUploads === false ? 'deny' : 'allow'
|
|
};
|
|
const moderatorPermissions: RoomPermissionMatrix = {
|
|
kickMembers: 'allow',
|
|
deleteMessages: 'allow',
|
|
manageChannels: legacyPermissions?.moderatorsManageRooms ? 'allow' : 'inherit',
|
|
manageIcon: legacyPermissions?.moderatorsManageIcon ? 'allow' : 'inherit'
|
|
};
|
|
const adminPermissions: RoomPermissionMatrix = {
|
|
kickMembers: 'allow',
|
|
banMembers: 'allow',
|
|
manageBans: 'allow',
|
|
deleteMessages: 'allow',
|
|
manageChannels: legacyPermissions?.adminsManageRooms ? 'allow' : 'inherit',
|
|
manageIcon: legacyPermissions?.adminsManageIcon ? 'allow' : 'inherit'
|
|
};
|
|
|
|
return [
|
|
buildSystemRole(SYSTEM_ROLE_IDS.everyone, '@everyone', 0, everyonePermissions, ROLE_COLORS.everyone),
|
|
buildSystemRole(SYSTEM_ROLE_IDS.moderator, 'Moderator', 200, moderatorPermissions, ROLE_COLORS.moderator),
|
|
buildSystemRole(SYSTEM_ROLE_IDS.admin, 'Admin', 300, adminPermissions, ROLE_COLORS.admin)
|
|
];
|
|
}
|
|
|
|
export function sortRolesForDisplay(roles: readonly RoomRole[]): RoomRole[] {
|
|
return [...roles].sort(roleSortDescending);
|
|
}
|
|
|
|
export function normalizeRoomRoles(roles: readonly RoomRole[] | undefined, legacyPermissions?: RoomPermissions): RoomRole[] {
|
|
const defaultRoles = buildDefaultRoomRoles(legacyPermissions);
|
|
const defaultsById = buildRoleLookup(defaultRoles);
|
|
const normalizedById = new Map<string, RoomRole>();
|
|
|
|
for (const role of roles ?? []) {
|
|
const normalizedRole = normalizeRoomRoleEntry(role, defaultsById, Array.from(normalizedById.values()), defaultRoles);
|
|
|
|
if (normalizedRole) {
|
|
normalizedById.set(normalizedRole.id, normalizedRole);
|
|
}
|
|
}
|
|
|
|
for (const [roleId, role] of defaultsById) {
|
|
if (!normalizedById.has(roleId)) {
|
|
normalizedById.set(roleId, role);
|
|
}
|
|
}
|
|
|
|
return Array.from(normalizedById.values()).sort(roleSortAscending);
|
|
}
|
|
|
|
export function getRoomRoleById(roles: readonly RoomRole[] | undefined, roleId: string): RoomRole | undefined {
|
|
return (roles ?? []).find((role) => role.id === roleId);
|
|
}
|
|
|
|
export function createCustomRoomRole(name: string, roles: readonly RoomRole[]): RoomRole {
|
|
const normalizedName = normalizeName(name) || 'New Role';
|
|
|
|
return {
|
|
id: `role-${crypto.randomUUID()}`,
|
|
name: normalizedName,
|
|
position: nextRolePosition(roles),
|
|
permissions: {}
|
|
};
|
|
}
|
|
|
|
export function reorderRoles(roles: readonly RoomRole[], orderedRoleIds: readonly string[]): RoomRole[] {
|
|
const roleLookup = buildRoleLookup(roles);
|
|
const systemRoles = roles.filter((role) => role.isSystem);
|
|
const customRoles = orderedRoleIds.map((roleId) => roleLookup.get(roleId)).filter((role): role is RoomRole => !!role && !role.isSystem);
|
|
const remainingCustomRoles = roles.filter((role) => !role.isSystem && !orderedRoleIds.includes(role.id));
|
|
const orderedRoles = sortRolesForDisplay(systemRoles).concat(customRoles)
|
|
.concat(sortRolesForDisplay(remainingCustomRoles));
|
|
|
|
return orderedRoles
|
|
.map((role, index) => ({
|
|
...role,
|
|
position: (orderedRoles.length - index - 1) * 100
|
|
}))
|
|
.sort(roleSortAscending);
|
|
}
|
|
|
|
export function withUpdatedRole(roles: readonly RoomRole[], roleId: string, updates: Partial<RoomRole>): RoomRole[] {
|
|
return normalizeRoomRoles(
|
|
roles.map((role) => {
|
|
if (role.id !== roleId) {
|
|
return role;
|
|
}
|
|
|
|
return {
|
|
...role,
|
|
...updates,
|
|
permissions: normalizePermissionMatrix(updates.permissions ?? role.permissions)
|
|
};
|
|
})
|
|
);
|
|
}
|
|
|
|
export function findAssignableRoles(roles: readonly RoomRole[]): RoomRole[] {
|
|
return sortRolesForDisplay(roles).filter((role) => role.id !== SYSTEM_ROLE_IDS.everyone);
|
|
}
|