mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
292 lines
8.3 KiB
TypeScript
292 lines
8.3 KiB
TypeScript
import { Service, Inject } from 'typedi';
|
|
import Knex from 'knex';
|
|
import {
|
|
ICreateRoleDTO,
|
|
ICreateRolePermissionDTO,
|
|
IEditRoleDTO,
|
|
IEditRolePermissionDTO,
|
|
IRole,
|
|
IRoleCreatedPayload,
|
|
IRoleDeletedPayload,
|
|
IRoleEditedPayload,
|
|
} from '@/interfaces';
|
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
import { ServiceError } from '@/exceptions';
|
|
import { AbilitySchema } from './AbilitySchema';
|
|
import { getInvalidPermissions } from './utils';
|
|
import UnitOfWork from '@/services/UnitOfWork';
|
|
import { ERRORS } from './constants';
|
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
import events from '@/subscribers/events';
|
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
import { RoleTransformer } from './RoleTransformer';
|
|
|
|
@Service()
|
|
export default class RolesService {
|
|
@Inject()
|
|
private tenancy: HasTenancyService;
|
|
|
|
@Inject()
|
|
private uow: UnitOfWork;
|
|
|
|
@Inject()
|
|
private eventPublisher: EventPublisher;
|
|
|
|
@Inject()
|
|
private transformer: TransformerInjectable;
|
|
|
|
/**
|
|
* Creates a new role and store it to the storage.
|
|
* @param {number} tenantId
|
|
* @param {ICreateRoleDTO} createRoleDTO
|
|
* @returns
|
|
*/
|
|
public createRole = async (
|
|
tenantId: number,
|
|
createRoleDTO: ICreateRoleDTO
|
|
) => {
|
|
const { Role } = this.tenancy.models(tenantId);
|
|
|
|
// Validates the invalid permissions.
|
|
this.validateInvalidPermissions(createRoleDTO.permissions);
|
|
|
|
// Transformes the permissions DTO.
|
|
const permissions = this.tranaformPermissionsDTO(createRoleDTO.permissions);
|
|
|
|
// Creates a new role with associated entries under unit-of-work.
|
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
// Creates a new role to the storage.
|
|
const role = await Role.query(trx).upsertGraph({
|
|
name: createRoleDTO.roleName,
|
|
description: createRoleDTO.roleDescription,
|
|
permissions,
|
|
});
|
|
// Triggers `onRoleCreated` event.
|
|
await this.eventPublisher.emitAsync(events.roles.onCreated, {
|
|
tenantId,
|
|
createRoleDTO,
|
|
role,
|
|
trx,
|
|
} as IRoleCreatedPayload);
|
|
return role;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Edits details of the given role on the storage.
|
|
* @param {number} tenantId -
|
|
* @param {number} roleId -
|
|
* @param {IEditRoleDTO} editRoleDTO - Edit role DTO.
|
|
*/
|
|
public editRole = async (
|
|
tenantId: number,
|
|
roleId: number,
|
|
editRoleDTO: IEditRoleDTO
|
|
) => {
|
|
const { Role } = this.tenancy.models(tenantId);
|
|
|
|
// Validates the invalid permissions.
|
|
this.validateInvalidPermissions(editRoleDTO.permissions);
|
|
|
|
// Retrieve the given role or throw not found serice error.
|
|
const oldRole = await this.getRoleOrThrowError(tenantId, roleId);
|
|
|
|
const permissions = this.tranaformEditPermissionsDTO(
|
|
editRoleDTO.permissions
|
|
);
|
|
// Updates the role on the storage.
|
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
// Updates the given role to the storage.
|
|
const role = await Role.query(trx).upsertGraph({
|
|
id: roleId,
|
|
name: editRoleDTO.roleName,
|
|
description: editRoleDTO.roleDescription,
|
|
permissions,
|
|
});
|
|
// Triggers `onRoleEdited` event.
|
|
await this.eventPublisher.emitAsync(events.roles.onEdited, {
|
|
editRoleDTO,
|
|
oldRole,
|
|
role,
|
|
trx,
|
|
} as IRoleEditedPayload);
|
|
|
|
return role;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the role or throw not found service error.
|
|
* @param {number} tenantId
|
|
* @param {number} roleId
|
|
* @returns {Promise<IRole>}
|
|
*/
|
|
public getRoleOrThrowError = async (
|
|
tenantId: number,
|
|
roleId: number
|
|
): Promise<IRole> => {
|
|
const { Role } = this.tenancy.models(tenantId);
|
|
|
|
const role = await Role.query().findById(roleId);
|
|
|
|
this.throwRoleNotFound(role);
|
|
|
|
return role;
|
|
};
|
|
|
|
/**
|
|
* Throw role not found service error if the role is not found.
|
|
* @param {IRole|null} role
|
|
*/
|
|
private throwRoleNotFound(role: IRole | null) {
|
|
if (!role) {
|
|
throw new ServiceError(ERRORS.ROLE_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the given role from the storage.
|
|
* @param {number} tenantId -
|
|
* @param {number} roleId - Role id.
|
|
*/
|
|
public deleteRole = async (
|
|
tenantId: number,
|
|
roleId: number
|
|
): Promise<void> => {
|
|
const { Role, RolePermission } = this.tenancy.models(tenantId);
|
|
|
|
// Retrieve the given role or throw not found serice error.
|
|
const oldRole = await this.getRoleOrThrowError(tenantId, roleId);
|
|
|
|
// Validate role is not predefined.
|
|
this.validateRoleNotPredefined(oldRole);
|
|
|
|
// Validates the given role is not associated to any user.
|
|
await this.validateRoleNotAssociatedToUser(tenantId, roleId);
|
|
|
|
// Deletes the given role and associated models under unit-of-work envirement.
|
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
// Deletes the role associated permissions from the storage.
|
|
await RolePermission.query(trx).where('roleId', roleId).delete();
|
|
|
|
// Deletes the role object form the storage.
|
|
await Role.query(trx).findById(roleId).delete();
|
|
|
|
// Triggers `onRoleDeleted` event.
|
|
await this.eventPublisher.emitAsync(events.roles.onDeleted, {
|
|
oldRole,
|
|
roleId,
|
|
tenantId,
|
|
trx,
|
|
} as IRoleDeletedPayload);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the roles list.
|
|
* @param {number} tenantId
|
|
* @param {Promise<IRole[]>}
|
|
*/
|
|
public listRoles = async (tenantId: number): Promise<IRole[]> => {
|
|
const { Role } = this.tenancy.models(tenantId);
|
|
|
|
const roles = await Role.query().withGraphFetched('permissions');
|
|
|
|
return this.transformer.transform(tenantId, roles, new RoleTransformer());
|
|
};
|
|
|
|
/**
|
|
* Retrieve the given role metadata.
|
|
* @param {number} tenantId
|
|
* @param {number} roleId - Role id.
|
|
* @returns {Promise<IRole>}
|
|
*/
|
|
public getRole = async (tenantId: number, roleId: number): Promise<IRole> => {
|
|
const { Role } = this.tenancy.models(tenantId);
|
|
|
|
const role = await Role.query()
|
|
.findById(roleId)
|
|
.withGraphFetched('permissions');
|
|
|
|
this.throwRoleNotFound(role);
|
|
|
|
return this.transformer.transform(tenantId, role, new RoleTransformer());
|
|
};
|
|
|
|
/**
|
|
* Valdiates role is not predefined.
|
|
* @param {IRole} role - Role object.
|
|
*/
|
|
private validateRoleNotPredefined(role: IRole) {
|
|
if (role.predefined) {
|
|
throw new ServiceError(ERRORS.ROLE_PREFINED);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates the invalid given permissions.
|
|
* @param {ICreateRolePermissionDTO[]} permissions -
|
|
*/
|
|
public validateInvalidPermissions = (
|
|
permissions: ICreateRolePermissionDTO[]
|
|
) => {
|
|
const invalidPerms = getInvalidPermissions(AbilitySchema, permissions);
|
|
|
|
if (invalidPerms.length > 0) {
|
|
throw new ServiceError(ERRORS.INVALIDATE_PERMISSIONS, null, {
|
|
invalidPermissions: invalidPerms,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Transformes new permissions DTO.
|
|
* @param {ICreateRolePermissionDTO[]} permissions
|
|
* @returns {ICreateRolePermissionDTO[]}
|
|
*/
|
|
private tranaformPermissionsDTO = (
|
|
permissions: ICreateRolePermissionDTO[]
|
|
) => {
|
|
return permissions.map((permission: ICreateRolePermissionDTO) => ({
|
|
subject: permission.subject,
|
|
ability: permission.ability,
|
|
value: permission.value,
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Transformes edit permissions DTO.
|
|
* @param {ICreateRolePermissionDTO[]} permissions
|
|
* @returns {IEditRolePermissionDTO[]}
|
|
*/
|
|
private tranaformEditPermissionsDTO = (
|
|
permissions: IEditRolePermissionDTO[]
|
|
) => {
|
|
return permissions.map((permission: IEditRolePermissionDTO) => ({
|
|
permissionId: permission.permissionId,
|
|
subject: permission.subject,
|
|
ability: permission.ability,
|
|
value: permission.value,
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Validates the given role is not associated to any tenant users.
|
|
* @param {number} tenantId
|
|
* @param {number} roleId
|
|
*/
|
|
private validateRoleNotAssociatedToUser = async (
|
|
tenantId: number,
|
|
roleId: number
|
|
) => {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
const userAssociatedRole = await User.query().where('roleId', roleId);
|
|
|
|
// Throw service error if the role has associated users.
|
|
if (userAssociatedRole.length > 0) {
|
|
throw new ServiceError(ERRORS.CANNT_DELETE_ROLE_ASSOCIATED_TO_USERS);
|
|
}
|
|
};
|
|
}
|