Files
calendar/packages/features/pbac/services/role.service.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

137 lines
4.5 KiB
TypeScript

import db from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import { RoleType as DomainRoleType } from "../domain/models/Role";
import type { CreateRoleData, UpdateRolePermissionsData } from "../domain/models/Role";
import type { IRoleRepository } from "../domain/repositories/IRoleRepository";
import { RoleRepository } from "../infrastructure/repositories/RoleRepository";
import { DEFAULT_ROLE_IDS, DefaultPBACRole } from "../lib/constants";
import { PermissionDiffService } from "./permission-diff.service";
import { PermissionService } from "./permission.service";
export class RoleService {
constructor(
private readonly repository: IRoleRepository = new RoleRepository(),
private readonly permissionService: PermissionService = new PermissionService(),
private readonly permissionDiffService: PermissionDiffService = new PermissionDiffService()
) {}
async createRole(data: CreateRoleData) {
// Check if role name conflicts with default roles
const existingRole = await this.repository.findByName(data.name, data.teamId);
if (existingRole) {
throw new Error(`Role with name "${data.name}" already exists`);
}
// Validate permissions
const validationResult = this.permissionService.validatePermissions(data.permissions);
if (!validationResult.isValid) {
throw new Error(validationResult.error || "Invalid permissions provided");
}
return this.repository.create(data);
}
async getDefaultRoleId(role: MembershipRole): Promise<string> {
return DEFAULT_ROLE_IDS[role];
}
private getMembershipRoleFromRoleId(roleId: string): MembershipRole | null {
const entry = Object.entries(DEFAULT_ROLE_IDS).find(([, id]) => id === roleId);
return entry ? (entry[0] as MembershipRole) : null;
}
async assignRoleToMember(roleId: string, membershipId: number) {
const role = await this.repository.findById(roleId);
if (!role) throw new Error("Role not found");
const membershipRole = this.getMembershipRoleFromRoleId(roleId);
await db.membership.update({
where: { id: membershipId },
data: {
customRoleId: roleId,
...(membershipRole ? { role: membershipRole } : {}),
},
});
return role;
}
async getRolePermissions(roleId: string) {
const role = await this.repository.findById(roleId);
return role?.permissions ?? [];
}
async removeRoleFromMember(membershipId: number) {
await db.membership.update({
where: { id: membershipId },
data: { customRoleId: null },
});
}
async getRole(roleId: string) {
return this.repository.findById(roleId);
}
async getTeamRoles(teamId: number) {
return this.repository.findByTeamId(teamId);
}
async deleteRole(roleId: string) {
const role = await this.repository.findById(roleId);
if (!role) {
throw new Error("Role not found");
}
// Don't allow deleting default roles
if (role.type === DomainRoleType.SYSTEM) {
throw new Error("Cannot delete default roles");
}
// Reassign all users with this role to the members_role
await this.repository.reassignUsersToRole(roleId, DefaultPBACRole.MEMBER_ROLE);
await this.repository.delete(roleId);
}
async update(data: UpdateRolePermissionsData) {
const role = await this.repository.findById(data.roleId);
if (!role) {
throw new Error("Role not found");
}
// Don't allow updating default roles
if (role.type === DomainRoleType.SYSTEM) {
throw new Error("Cannot update default roles");
}
const permissionChanges = await this.getUpdatePermissionChanges(data);
return this.repository.update(data.roleId, permissionChanges, {
color: data.updates?.color,
name: data.updates?.name,
});
}
private async getUpdatePermissionChanges(data: UpdateRolePermissionsData) {
if (!data.permissions) {
return {
toAdd: [],
toRemove: [],
};
}
const validationResult = this.permissionService.validatePermissions(data.permissions);
if (!validationResult.isValid) {
throw new Error(validationResult.error || "Invalid permissions provided");
}
const existingPermissions = await this.repository.getPermissions(data.roleId);
const permissionChanges = this.permissionDiffService.calculateDiff(data.permissions, existingPermissions);
return permissionChanges;
}
async roleBelongsToTeam(roleId: string, teamId: number) {
return this.repository.roleBelongsToTeam(roleId, teamId);
}
}