|
|
|
|
@@ -5,13 +5,46 @@ import {
|
|
|
|
|
} from 'typeorm';
|
|
|
|
|
import {
|
|
|
|
|
ServerChannelEntity,
|
|
|
|
|
ServerTagEntity
|
|
|
|
|
ServerTagEntity,
|
|
|
|
|
ServerRoleEntity,
|
|
|
|
|
ServerUserRoleEntity,
|
|
|
|
|
ServerChannelPermissionEntity
|
|
|
|
|
} from '../entities';
|
|
|
|
|
import { ServerChannelPayload } from './types';
|
|
|
|
|
import {
|
|
|
|
|
AccessRolePayload,
|
|
|
|
|
ChannelPermissionPayload,
|
|
|
|
|
RoleAssignmentPayload,
|
|
|
|
|
ServerChannelPayload,
|
|
|
|
|
ServerPayload,
|
|
|
|
|
ServerPermissionKeyPayload,
|
|
|
|
|
PermissionStatePayload
|
|
|
|
|
} from './types';
|
|
|
|
|
|
|
|
|
|
const SERVER_PERMISSION_KEYS: ServerPermissionKeyPayload[] = [
|
|
|
|
|
'manageServer',
|
|
|
|
|
'manageRoles',
|
|
|
|
|
'manageChannels',
|
|
|
|
|
'manageIcon',
|
|
|
|
|
'kickMembers',
|
|
|
|
|
'banMembers',
|
|
|
|
|
'manageBans',
|
|
|
|
|
'deleteMessages',
|
|
|
|
|
'joinVoice',
|
|
|
|
|
'shareScreen',
|
|
|
|
|
'uploadFiles'
|
|
|
|
|
];
|
|
|
|
|
const SYSTEM_ROLE_IDS = {
|
|
|
|
|
everyone: 'system-everyone',
|
|
|
|
|
moderator: 'system-moderator',
|
|
|
|
|
admin: 'system-admin'
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
interface ServerRelationRecord {
|
|
|
|
|
tags: string[];
|
|
|
|
|
channels: ServerChannelPayload[];
|
|
|
|
|
roles: AccessRolePayload[];
|
|
|
|
|
roleAssignments: RoleAssignmentPayload[];
|
|
|
|
|
channelPermissions: ChannelPermissionPayload[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeChannelName(name: string): string {
|
|
|
|
|
@@ -22,16 +55,125 @@ function channelNameKey(type: ServerChannelPayload['type'], name: string): strin
|
|
|
|
|
return `${type}:${normalizeChannelName(name).toLocaleLowerCase()}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compareText(firstValue: string, secondValue: string): number {
|
|
|
|
|
return firstValue.localeCompare(secondValue, undefined, { sensitivity: 'base' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isFiniteNumber(value: unknown): value is number {
|
|
|
|
|
return typeof value === 'number' && Number.isFinite(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function uniqueStrings(values: readonly string[] | undefined): string[] {
|
|
|
|
|
return Array.from(new Set((values ?? [])
|
|
|
|
|
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
|
|
|
|
.map((value) => value.trim())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizePermissionState(value: unknown): PermissionStatePayload {
|
|
|
|
|
return value === 'allow' || value === 'deny' || value === 'inherit'
|
|
|
|
|
? value
|
|
|
|
|
: 'inherit';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizePermissionMatrix(rawMatrix: unknown): Partial<Record<ServerPermissionKeyPayload, PermissionStatePayload>> {
|
|
|
|
|
const matrix = rawMatrix && typeof rawMatrix === 'object'
|
|
|
|
|
? rawMatrix as Record<string, unknown>
|
|
|
|
|
: {};
|
|
|
|
|
const normalized: Partial<Record<ServerPermissionKeyPayload, PermissionStatePayload>> = {};
|
|
|
|
|
|
|
|
|
|
for (const key of SERVER_PERMISSION_KEYS) {
|
|
|
|
|
const value = normalizePermissionState(matrix[key]);
|
|
|
|
|
|
|
|
|
|
if (value !== 'inherit') {
|
|
|
|
|
normalized[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildDefaultServerRoles(): AccessRolePayload[] {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
id: SYSTEM_ROLE_IDS.everyone,
|
|
|
|
|
name: '@everyone',
|
|
|
|
|
color: '#6b7280',
|
|
|
|
|
position: 0,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
permissions: {
|
|
|
|
|
joinVoice: 'allow',
|
|
|
|
|
shareScreen: 'allow',
|
|
|
|
|
uploadFiles: 'allow'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: SYSTEM_ROLE_IDS.moderator,
|
|
|
|
|
name: 'Moderator',
|
|
|
|
|
color: '#10b981',
|
|
|
|
|
position: 200,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
permissions: {
|
|
|
|
|
kickMembers: 'allow',
|
|
|
|
|
deleteMessages: 'allow'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: SYSTEM_ROLE_IDS.admin,
|
|
|
|
|
name: 'Admin',
|
|
|
|
|
color: '#60a5fa',
|
|
|
|
|
position: 300,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
permissions: {
|
|
|
|
|
kickMembers: 'allow',
|
|
|
|
|
banMembers: 'allow',
|
|
|
|
|
manageBans: 'allow',
|
|
|
|
|
deleteMessages: 'allow',
|
|
|
|
|
manageChannels: 'allow',
|
|
|
|
|
manageIcon: 'allow'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeServerRole(rawRole: Partial<AccessRolePayload>, fallbackRole?: AccessRolePayload): AccessRolePayload | null {
|
|
|
|
|
const id = typeof rawRole.id === 'string' ? rawRole.id.trim() : fallbackRole?.id ?? '';
|
|
|
|
|
const name = typeof rawRole.name === 'string' ? rawRole.name.trim().replace(/\s+/g, ' ') : fallbackRole?.name ?? '';
|
|
|
|
|
|
|
|
|
|
if (!id || !name) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
name,
|
|
|
|
|
color: typeof rawRole.color === 'string' && rawRole.color.trim() ? rawRole.color.trim() : fallbackRole?.color,
|
|
|
|
|
position: isFiniteNumber(rawRole.position) ? rawRole.position : fallbackRole?.position ?? 0,
|
|
|
|
|
isSystem: typeof rawRole.isSystem === 'boolean' ? rawRole.isSystem : fallbackRole?.isSystem,
|
|
|
|
|
permissions: normalizePermissionMatrix(rawRole.permissions ?? fallbackRole?.permissions)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compareRoles(firstRole: AccessRolePayload, secondRole: AccessRolePayload): number {
|
|
|
|
|
if (firstRole.position !== secondRole.position) {
|
|
|
|
|
return firstRole.position - secondRole.position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return compareText(firstRole.name, secondRole.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compareAssignments(firstAssignment: RoleAssignmentPayload, secondAssignment: RoleAssignmentPayload): number {
|
|
|
|
|
return compareText(firstAssignment.oderId || firstAssignment.userId, secondAssignment.oderId || secondAssignment.userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeServerTags(rawTags: unknown): string[] {
|
|
|
|
|
if (!Array.isArray(rawTags)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rawTags.filter((tag): tag is string => typeof tag === 'string');
|
|
|
|
|
return rawTags
|
|
|
|
|
.filter((tag): tag is string => typeof tag === 'string')
|
|
|
|
|
.map((tag) => tag.trim())
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeServerChannels(rawChannels: unknown): ServerChannelPayload[] {
|
|
|
|
|
@@ -72,19 +214,169 @@ export function normalizeServerChannels(rawChannels: unknown): ServerChannelPayl
|
|
|
|
|
return channels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeServerRoles(rawRoles: unknown): AccessRolePayload[] {
|
|
|
|
|
const rolesById = new Map<string, AccessRolePayload>();
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(rawRoles)) {
|
|
|
|
|
for (const rawRole of rawRoles) {
|
|
|
|
|
if (!rawRole || typeof rawRole !== 'object') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizedRole = normalizeServerRole(rawRole as Record<string, unknown>);
|
|
|
|
|
|
|
|
|
|
if (normalizedRole) {
|
|
|
|
|
rolesById.set(normalizedRole.id, normalizedRole);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const defaultRole of buildDefaultServerRoles()) {
|
|
|
|
|
const mergedRole = normalizeServerRole(rolesById.get(defaultRole.id) ?? defaultRole, defaultRole) ?? defaultRole;
|
|
|
|
|
|
|
|
|
|
rolesById.set(defaultRole.id, mergedRole);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(rolesById.values()).sort(compareRoles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeServerRoleAssignments(rawAssignments: unknown, roles: readonly AccessRolePayload[]): RoleAssignmentPayload[] {
|
|
|
|
|
const validRoleIds = new Set(roles.map((role) => role.id).filter((roleId) => roleId !== SYSTEM_ROLE_IDS.everyone));
|
|
|
|
|
const assignmentsByKey = new Map<string, RoleAssignmentPayload>();
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(rawAssignments)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const rawAssignment of rawAssignments) {
|
|
|
|
|
if (!rawAssignment || typeof rawAssignment !== 'object') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const assignment = rawAssignment as Record<string, unknown>;
|
|
|
|
|
const userId = typeof assignment['userId'] === 'string' ? assignment['userId'].trim() : '';
|
|
|
|
|
const oderId = typeof assignment['oderId'] === 'string' ? assignment['oderId'].trim() : undefined;
|
|
|
|
|
const key = oderId || userId;
|
|
|
|
|
|
|
|
|
|
if (!key) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const roleIds = uniqueStrings(Array.isArray(assignment['roleIds']) ? assignment['roleIds'] as string[] : undefined)
|
|
|
|
|
.filter((roleId) => validRoleIds.has(roleId));
|
|
|
|
|
|
|
|
|
|
if (roleIds.length === 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assignmentsByKey.set(key, {
|
|
|
|
|
userId: userId || key,
|
|
|
|
|
oderId,
|
|
|
|
|
roleIds
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(assignmentsByKey.values()).sort(compareAssignments);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeServerChannelPermissions(
|
|
|
|
|
rawChannelPermissions: unknown,
|
|
|
|
|
roles: readonly AccessRolePayload[]
|
|
|
|
|
): ChannelPermissionPayload[] {
|
|
|
|
|
if (!Array.isArray(rawChannelPermissions)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validRoleIds = new Set(roles.map((role) => role.id));
|
|
|
|
|
const overridesByKey = new Map<string, ChannelPermissionPayload>();
|
|
|
|
|
|
|
|
|
|
for (const rawOverride of rawChannelPermissions) {
|
|
|
|
|
if (!rawOverride || typeof rawOverride !== 'object') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const override = rawOverride as Record<string, unknown>;
|
|
|
|
|
const channelId = typeof override['channelId'] === 'string' ? override['channelId'].trim() : '';
|
|
|
|
|
const targetType = override['targetType'] === 'role' || override['targetType'] === 'user' ? override['targetType'] : null;
|
|
|
|
|
const targetId = typeof override['targetId'] === 'string' ? override['targetId'].trim() : '';
|
|
|
|
|
const permission = SERVER_PERMISSION_KEYS.find((key) => key === override['permission']);
|
|
|
|
|
const value = normalizePermissionState(override['value']);
|
|
|
|
|
|
|
|
|
|
if (!channelId || !targetType || !targetId || !permission || value === 'inherit') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetType === 'role' && !validRoleIds.has(targetId)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const key = `${channelId}:${targetType}:${targetId}:${permission}`;
|
|
|
|
|
|
|
|
|
|
overridesByKey.set(key, {
|
|
|
|
|
channelId,
|
|
|
|
|
targetType,
|
|
|
|
|
targetId,
|
|
|
|
|
permission,
|
|
|
|
|
value
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(overridesByKey.values()).sort((firstOverride, secondOverride) => {
|
|
|
|
|
const channelCompare = compareText(firstOverride.channelId, secondOverride.channelId);
|
|
|
|
|
|
|
|
|
|
if (channelCompare !== 0) {
|
|
|
|
|
return channelCompare;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (firstOverride.targetType !== secondOverride.targetType) {
|
|
|
|
|
return compareText(firstOverride.targetType, secondOverride.targetType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targetCompare = compareText(firstOverride.targetId, secondOverride.targetId);
|
|
|
|
|
|
|
|
|
|
if (targetCompare !== 0) {
|
|
|
|
|
return targetCompare;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return compareText(firstOverride.permission, secondOverride.permission);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function replaceServerRelations(
|
|
|
|
|
manager: EntityManager,
|
|
|
|
|
serverId: string,
|
|
|
|
|
options: { tags: unknown; channels: unknown }
|
|
|
|
|
options: {
|
|
|
|
|
tags: unknown;
|
|
|
|
|
channels: unknown;
|
|
|
|
|
roles?: unknown;
|
|
|
|
|
roleAssignments?: unknown;
|
|
|
|
|
channelPermissions?: unknown;
|
|
|
|
|
}
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const tagRepo = manager.getRepository(ServerTagEntity);
|
|
|
|
|
const channelRepo = manager.getRepository(ServerChannelEntity);
|
|
|
|
|
const roleRepo = manager.getRepository(ServerRoleEntity);
|
|
|
|
|
const userRoleRepo = manager.getRepository(ServerUserRoleEntity);
|
|
|
|
|
const channelPermissionRepo = manager.getRepository(ServerChannelPermissionEntity);
|
|
|
|
|
const tags = normalizeServerTags(options.tags);
|
|
|
|
|
const channels = normalizeServerChannels(options.channels);
|
|
|
|
|
const roles = options.roles !== undefined ? normalizeServerRoles(options.roles) : [];
|
|
|
|
|
|
|
|
|
|
await tagRepo.delete({ serverId });
|
|
|
|
|
await channelRepo.delete({ serverId });
|
|
|
|
|
|
|
|
|
|
if (options.roles !== undefined) {
|
|
|
|
|
await roleRepo.delete({ serverId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.roleAssignments !== undefined) {
|
|
|
|
|
await userRoleRepo.delete({ serverId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.channelPermissions !== undefined) {
|
|
|
|
|
await channelPermissionRepo.delete({ serverId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tags.length > 0) {
|
|
|
|
|
await tagRepo.insert(
|
|
|
|
|
tags.map((tag, position) => ({
|
|
|
|
|
@@ -106,6 +398,66 @@ export async function replaceServerRelations(
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.roles !== undefined && roles.length > 0) {
|
|
|
|
|
await roleRepo.insert(
|
|
|
|
|
roles.map((role) => ({
|
|
|
|
|
serverId,
|
|
|
|
|
roleId: role.id,
|
|
|
|
|
name: role.name,
|
|
|
|
|
color: role.color ?? null,
|
|
|
|
|
position: role.position,
|
|
|
|
|
isSystem: role.isSystem ? 1 : 0,
|
|
|
|
|
manageServer: normalizePermissionState(role.permissions?.manageServer),
|
|
|
|
|
manageRoles: normalizePermissionState(role.permissions?.manageRoles),
|
|
|
|
|
manageChannels: normalizePermissionState(role.permissions?.manageChannels),
|
|
|
|
|
manageIcon: normalizePermissionState(role.permissions?.manageIcon),
|
|
|
|
|
kickMembers: normalizePermissionState(role.permissions?.kickMembers),
|
|
|
|
|
banMembers: normalizePermissionState(role.permissions?.banMembers),
|
|
|
|
|
manageBans: normalizePermissionState(role.permissions?.manageBans),
|
|
|
|
|
deleteMessages: normalizePermissionState(role.permissions?.deleteMessages),
|
|
|
|
|
joinVoice: normalizePermissionState(role.permissions?.joinVoice),
|
|
|
|
|
shareScreen: normalizePermissionState(role.permissions?.shareScreen),
|
|
|
|
|
uploadFiles: normalizePermissionState(role.permissions?.uploadFiles)
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.roleAssignments !== undefined) {
|
|
|
|
|
const roleAssignments = normalizeServerRoleAssignments(options.roleAssignments, roles.length > 0 ? roles : normalizeServerRoles([]));
|
|
|
|
|
const rows = roleAssignments.flatMap((assignment) =>
|
|
|
|
|
assignment.roleIds.map((roleId) => ({
|
|
|
|
|
serverId,
|
|
|
|
|
userId: assignment.userId,
|
|
|
|
|
roleId,
|
|
|
|
|
oderId: assignment.oderId ?? null
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (rows.length > 0) {
|
|
|
|
|
await userRoleRepo.insert(rows);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.channelPermissions !== undefined) {
|
|
|
|
|
const channelPermissions = normalizeServerChannelPermissions(
|
|
|
|
|
options.channelPermissions,
|
|
|
|
|
roles.length > 0 ? roles : normalizeServerRoles([])
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (channelPermissions.length > 0) {
|
|
|
|
|
await channelPermissionRepo.insert(
|
|
|
|
|
channelPermissions.map((channelPermission) => ({
|
|
|
|
|
serverId,
|
|
|
|
|
channelId: channelPermission.channelId,
|
|
|
|
|
targetType: channelPermission.targetType,
|
|
|
|
|
targetId: channelPermission.targetId,
|
|
|
|
|
permission: channelPermission.permission,
|
|
|
|
|
value: channelPermission.value
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function loadServerRelationsMap(
|
|
|
|
|
@@ -118,43 +470,134 @@ export async function loadServerRelationsMap(
|
|
|
|
|
return groupedRelations;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [tagRows, channelRows] = await Promise.all([
|
|
|
|
|
const [
|
|
|
|
|
tagRows,
|
|
|
|
|
channelRows,
|
|
|
|
|
roleRows,
|
|
|
|
|
userRoleRows,
|
|
|
|
|
channelPermissionRows
|
|
|
|
|
] = await Promise.all([
|
|
|
|
|
dataSource.getRepository(ServerTagEntity).find({
|
|
|
|
|
where: { serverId: In([...serverIds]) }
|
|
|
|
|
}),
|
|
|
|
|
dataSource.getRepository(ServerChannelEntity).find({
|
|
|
|
|
where: { serverId: In([...serverIds]) }
|
|
|
|
|
}),
|
|
|
|
|
dataSource.getRepository(ServerRoleEntity).find({
|
|
|
|
|
where: { serverId: In([...serverIds]) }
|
|
|
|
|
}),
|
|
|
|
|
dataSource.getRepository(ServerUserRoleEntity).find({
|
|
|
|
|
where: { serverId: In([...serverIds]) }
|
|
|
|
|
}),
|
|
|
|
|
dataSource.getRepository(ServerChannelPermissionEntity).find({
|
|
|
|
|
where: { serverId: In([...serverIds]) }
|
|
|
|
|
})
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
for (const row of tagRows) {
|
|
|
|
|
const relation = groupedRelations.get(row.serverId) ?? { tags: [], channels: [] };
|
|
|
|
|
for (const serverId of serverIds) {
|
|
|
|
|
groupedRelations.set(serverId, {
|
|
|
|
|
tags: [],
|
|
|
|
|
channels: [],
|
|
|
|
|
roles: [],
|
|
|
|
|
roleAssignments: [],
|
|
|
|
|
channelPermissions: []
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
relation.tags.push(row.value);
|
|
|
|
|
groupedRelations.set(row.serverId, relation);
|
|
|
|
|
for (const row of tagRows) {
|
|
|
|
|
groupedRelations.get(row.serverId)?.tags.push(row.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of channelRows) {
|
|
|
|
|
const relation = groupedRelations.get(row.serverId) ?? { tags: [], channels: [] };
|
|
|
|
|
|
|
|
|
|
relation.channels.push({
|
|
|
|
|
groupedRelations.get(row.serverId)?.channels.push({
|
|
|
|
|
id: row.channelId,
|
|
|
|
|
name: row.name,
|
|
|
|
|
type: row.type,
|
|
|
|
|
position: row.position
|
|
|
|
|
});
|
|
|
|
|
groupedRelations.set(row.serverId, relation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of roleRows) {
|
|
|
|
|
groupedRelations.get(row.serverId)?.roles.push({
|
|
|
|
|
id: row.roleId,
|
|
|
|
|
name: row.name,
|
|
|
|
|
color: row.color ?? undefined,
|
|
|
|
|
position: row.position,
|
|
|
|
|
isSystem: !!row.isSystem,
|
|
|
|
|
permissions: normalizePermissionMatrix({
|
|
|
|
|
manageServer: row.manageServer,
|
|
|
|
|
manageRoles: row.manageRoles,
|
|
|
|
|
manageChannels: row.manageChannels,
|
|
|
|
|
manageIcon: row.manageIcon,
|
|
|
|
|
kickMembers: row.kickMembers,
|
|
|
|
|
banMembers: row.banMembers,
|
|
|
|
|
manageBans: row.manageBans,
|
|
|
|
|
deleteMessages: row.deleteMessages,
|
|
|
|
|
joinVoice: row.joinVoice,
|
|
|
|
|
shareScreen: row.shareScreen,
|
|
|
|
|
uploadFiles: row.uploadFiles
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of userRoleRows) {
|
|
|
|
|
const relation = groupedRelations.get(row.serverId);
|
|
|
|
|
|
|
|
|
|
if (!relation) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = relation.roleAssignments.find((assignment) => assignment.userId === row.userId || assignment.oderId === row.oderId);
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
existing.roleIds = uniqueStrings([...existing.roleIds, row.roleId]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
relation.roleAssignments.push({
|
|
|
|
|
userId: row.userId,
|
|
|
|
|
oderId: row.oderId ?? undefined,
|
|
|
|
|
roleIds: [row.roleId]
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of channelPermissionRows) {
|
|
|
|
|
groupedRelations.get(row.serverId)?.channelPermissions.push({
|
|
|
|
|
channelId: row.channelId,
|
|
|
|
|
targetType: row.targetType,
|
|
|
|
|
targetId: row.targetId,
|
|
|
|
|
permission: row.permission as ServerPermissionKeyPayload,
|
|
|
|
|
value: normalizePermissionState(row.value)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [serverId, relation] of groupedRelations) {
|
|
|
|
|
const orderedTags = tagRows
|
|
|
|
|
relation.tags = tagRows
|
|
|
|
|
.filter((row) => row.serverId === serverId)
|
|
|
|
|
.sort((first, second) => first.position - second.position)
|
|
|
|
|
.sort((firstTag, secondTag) => firstTag.position - secondTag.position)
|
|
|
|
|
.map((row) => row.value);
|
|
|
|
|
|
|
|
|
|
relation.tags = orderedTags;
|
|
|
|
|
relation.channels.sort((first, second) => first.position - second.position || first.name.localeCompare(second.name));
|
|
|
|
|
relation.channels.sort(
|
|
|
|
|
(firstChannel, secondChannel) => firstChannel.position - secondChannel.position || compareText(firstChannel.name, secondChannel.name)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
relation.roles.sort(compareRoles);
|
|
|
|
|
relation.roleAssignments.sort(compareAssignments);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return groupedRelations;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function relationRecordToServerPayload(
|
|
|
|
|
row: Pick<ServerPayload, 'slowModeInterval'>,
|
|
|
|
|
relations: ServerRelationRecord
|
|
|
|
|
): Pick<ServerPayload, 'tags' | 'channels' | 'roles' | 'roleAssignments' | 'channelPermissions' | 'slowModeInterval'> {
|
|
|
|
|
return {
|
|
|
|
|
tags: relations.tags,
|
|
|
|
|
channels: relations.channels,
|
|
|
|
|
roles: relations.roles,
|
|
|
|
|
roleAssignments: relations.roleAssignments,
|
|
|
|
|
channelPermissions: relations.channelPermissions,
|
|
|
|
|
slowModeInterval: row.slowModeInterval ?? 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|