Files
fluxer/fluxer_api/src/api/guild/services/member/GuildMemberValidationService.ts
T

84 lines
3.5 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {type GuildID, guildIdToRoleId, type RoleID, type UserID} from '@app/api/BrandedTypes';
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
import type {GuildMember} from '@app/api/models/GuildMember';
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {MissingPermissionsError} from '@fluxer/errors/src/domains/core/MissingPermissionsError';
import {UnknownGuildRoleError} from '@fluxer/errors/src/domains/guild/UnknownGuildRoleError';
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
function ensureNotEveryoneRole(roleId: RoleID, guildId: GuildID, path: string): void {
if (roleId === guildIdToRoleId(guildId)) {
throw InputValidationError.fromCode(path, ValidationErrorCodes.INVALID_ROLE_ID);
}
}
export class GuildMemberValidationService {
constructor(private readonly guildRepository: GuildRepository) {}
async validateAndGetRoleIds(params: {
userId: UserID;
guildId: GuildID;
guildData: GuildResponse;
targetId: UserID;
targetMember: GuildMember;
newRoles: Array<RoleID>;
hasPermission: (permission: bigint) => Promise<boolean>;
canManageRoles: (targetUserId: UserID, targetRoleId: RoleID) => Promise<boolean>;
}): Promise<Array<RoleID>> {
const {userId, guildId, guildData, targetId, targetMember, newRoles, hasPermission, canManageRoles} = params;
for (const roleId of newRoles) {
ensureNotEveryoneRole(roleId, guildId, 'roles');
}
const existingRoles = await this.guildRepository.listRolesByIds(newRoles, guildId);
const existingRoleIds = existingRoles.map((role) => role.id);
if (guildData && guildData.owner_id === userId.toString()) {
return existingRoleIds;
}
if (!(await hasPermission(Permissions.MANAGE_ROLES))) {
throw new MissingPermissionsError();
}
const currentRoles = targetMember.roleIds;
const rolesToRemove = [...currentRoles].filter((roleId) => !existingRoleIds.includes(roleId));
const rolesToAdd = existingRoleIds.filter((roleId) => !currentRoles.has(roleId));
const rolesToCheck = [...rolesToAdd, ...rolesToRemove].filter((roleId) => roleId !== guildIdToRoleId(guildId));
const checks = await Promise.all(
rolesToCheck.map((roleId) => canManageRoles(targetId, roleId).then((ok) => ({roleId, ok}))),
);
const denied = checks.find((check) => !check.ok);
if (denied) {
throw new MissingPermissionsError();
}
return existingRoleIds;
}
async validateRoleAssignment(params: {
guildData: GuildResponse;
guildId: GuildID;
userId: UserID;
targetId: UserID;
roleId: RoleID;
hasPermission: (permission: bigint) => Promise<boolean>;
canManageRoles: (targetUserId: UserID, targetRoleId: RoleID) => Promise<boolean>;
}): Promise<void> {
const {guildData, guildId, userId, targetId, roleId, hasPermission, canManageRoles} = params;
ensureNotEveryoneRole(roleId, guildId, 'role_id');
if (guildData && guildData.owner_id === userId.toString()) {
const role = await this.guildRepository.getRole(roleId, guildId);
if (!role || role.id === guildIdToRoleId(guildId)) {
throw new UnknownGuildRoleError();
}
} else {
if (!(await hasPermission(Permissions.MANAGE_ROLES))) {
throw new MissingPermissionsError();
}
if (!(await canManageRoles(targetId, roleId))) {
throw new MissingPermissionsError();
}
}
}
}