mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
84 lines
3.5 KiB
TypeScript
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();
|
|
}
|
|
}
|
|
}
|
|
}
|