mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
add server to monorepo.
This commit is contained in:
339
packages/server/src/services/Roles/AbilitySchema.ts
Normal file
339
packages/server/src/services/Roles/AbilitySchema.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import {
|
||||
ReportsAction,
|
||||
ISubjectAbilitiesSchema,
|
||||
ISubjectAbilitySchema,
|
||||
AbilitySubject,
|
||||
ManualJournalAction,
|
||||
AccountAction,
|
||||
SaleInvoiceAction,
|
||||
ItemAction,
|
||||
VendorAction,
|
||||
CustomerAction,
|
||||
ExpenseAction,
|
||||
PaymentReceiveAction,
|
||||
InventoryAdjustmentAction,
|
||||
SaleEstimateAction,
|
||||
BillAction,
|
||||
SaleReceiptAction,
|
||||
IPaymentMadeAction,
|
||||
CashflowAction,
|
||||
PreferencesAction,
|
||||
CreditNoteAction,
|
||||
VendorCreditAction,
|
||||
} from '@/interfaces';
|
||||
|
||||
export const AbilitySchema: ISubjectAbilitiesSchema[] = [
|
||||
{
|
||||
subject: AbilitySubject.Account,
|
||||
subjectLabel: 'ability.accounts',
|
||||
abilities: [
|
||||
{ key: AccountAction.VIEW, label: 'ability.view' },
|
||||
{ key: AccountAction.CREATE, label: 'ability.create' },
|
||||
{ key: AccountAction.EDIT, label: 'ability.edit' },
|
||||
{ key: AccountAction.DELETE, label: 'ability.delete' },
|
||||
],
|
||||
extraAbilities: [
|
||||
{
|
||||
key: AccountAction.TransactionsLocking,
|
||||
label: 'ability.transactions_locking',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
subjectLabel: 'ability.manual_journal',
|
||||
abilities: [
|
||||
{ key: ManualJournalAction.View, label: 'ability.view' },
|
||||
{ key: ManualJournalAction.Create, label: 'ability.create' },
|
||||
{ key: ManualJournalAction.Edit, label: 'ability.edit' },
|
||||
{ key: ManualJournalAction.Delete, label: 'ability.delete' },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Cashflow,
|
||||
subjectLabel: 'ability.cashflow',
|
||||
abilities: [
|
||||
{ key: CashflowAction.View, label: 'ability.view' },
|
||||
{ key: CashflowAction.Create, label: 'ability.create' },
|
||||
{ key: CashflowAction.Delete, label: 'ability.delete' },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Item,
|
||||
subjectLabel: 'ability.items',
|
||||
abilities: [
|
||||
{ key: ItemAction.VIEW, label: 'ability.view', default: true },
|
||||
{ key: ItemAction.CREATE, label: 'ability.create', default: true },
|
||||
{ key: ItemAction.EDIT, label: 'ability.edit', default: true },
|
||||
{ key: ItemAction.DELETE, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.InventoryAdjustment,
|
||||
subjectLabel: 'ability.inventory_adjustment',
|
||||
abilities: [
|
||||
{
|
||||
key: InventoryAdjustmentAction.VIEW,
|
||||
label: 'ability.view',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
key: InventoryAdjustmentAction.CREATE,
|
||||
label: 'ability.create',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
key: InventoryAdjustmentAction.EDIT,
|
||||
label: 'ability.edit',
|
||||
default: true,
|
||||
},
|
||||
{ key: InventoryAdjustmentAction.DELETE, label: 'ability.delete' },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Customer,
|
||||
subjectLabel: 'ability.customers',
|
||||
// description: 'Description is here',
|
||||
abilities: [
|
||||
{ key: CustomerAction.View, label: 'ability.view', default: true },
|
||||
{ key: CustomerAction.Create, label: 'ability.create', default: true },
|
||||
{ key: CustomerAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: CustomerAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Vendor,
|
||||
subjectLabel: 'ability.vendors',
|
||||
abilities: [
|
||||
{ key: VendorAction.View, label: 'ability.view', default: true },
|
||||
{ key: VendorAction.Create, label: 'ability.create', default: true },
|
||||
{ key: VendorAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: VendorAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.SaleEstimate,
|
||||
subjectLabel: 'ability.sale_estimates',
|
||||
abilities: [
|
||||
{ key: SaleEstimateAction.View, label: 'ability.view', default: true },
|
||||
{
|
||||
key: SaleEstimateAction.Create,
|
||||
label: 'ability.create',
|
||||
default: true,
|
||||
},
|
||||
{ key: SaleEstimateAction.Edit, label: 'ability.edit', default: true },
|
||||
{
|
||||
key: SaleEstimateAction.Delete,
|
||||
label: 'ability.delete',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.SaleInvoice,
|
||||
subjectLabel: 'ability.sale_invoices',
|
||||
abilities: [
|
||||
{ key: SaleInvoiceAction.View, label: 'ability.view', default: true },
|
||||
{ key: SaleInvoiceAction.Create, label: 'ability.create', default: true },
|
||||
{ key: SaleInvoiceAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: SaleInvoiceAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
extraAbilities: [{ key: 'bad-debt', label: 'Due amount to bad debit' }],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.SaleReceipt,
|
||||
subjectLabel: 'ability.sale_receipts',
|
||||
abilities: [
|
||||
{ key: SaleReceiptAction.View, label: 'ability.view', default: true },
|
||||
{ key: SaleReceiptAction.Create, label: 'ability.create', default: true },
|
||||
{ key: SaleReceiptAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: SaleReceiptAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.CreditNote,
|
||||
subjectLabel: 'ability.credit_note',
|
||||
abilities: [
|
||||
{ key: CreditNoteAction.View, label: 'ability.view', default: true },
|
||||
{ key: CreditNoteAction.Create, label: 'ability.create', default: true },
|
||||
{ key: CreditNoteAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: CreditNoteAction.Delete, label: 'ability.delete', default: true },
|
||||
{ key: CreditNoteAction.Refund, label: 'ability.refund', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.PaymentReceive,
|
||||
subjectLabel: 'ability.payments_receive',
|
||||
abilities: [
|
||||
{ key: PaymentReceiveAction.View, label: 'ability.view', default: true },
|
||||
{
|
||||
key: PaymentReceiveAction.Create,
|
||||
label: 'ability.create',
|
||||
default: true,
|
||||
},
|
||||
{ key: PaymentReceiveAction.Edit, label: 'ability.edit', default: true },
|
||||
{
|
||||
key: PaymentReceiveAction.Delete,
|
||||
label: 'ability.delete',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Bill,
|
||||
subjectLabel: 'ability.purchase_invoices',
|
||||
abilities: [
|
||||
{ key: BillAction.View, label: 'ability.view', default: true },
|
||||
{ key: BillAction.Create, label: 'ability.create', default: true },
|
||||
{ key: BillAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: BillAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.VendorCredit,
|
||||
subjectLabel: 'ability.vendor_credit',
|
||||
abilities: [
|
||||
{ key: VendorCreditAction.View, label: 'ability.view', default: true },
|
||||
{
|
||||
key: VendorCreditAction.Create,
|
||||
label: 'ability.create',
|
||||
default: true,
|
||||
},
|
||||
{ key: VendorCreditAction.Edit, label: 'ability.edit', default: true },
|
||||
{
|
||||
key: VendorCreditAction.Delete,
|
||||
label: 'ability.delete',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
key: VendorCreditAction.Refund,
|
||||
label: 'ability.refund',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.PaymentMade,
|
||||
subjectLabel: 'ability.payments_made',
|
||||
abilities: [
|
||||
{ key: IPaymentMadeAction.View, label: 'ability.view', default: true },
|
||||
{
|
||||
key: IPaymentMadeAction.Create,
|
||||
label: 'ability.create',
|
||||
default: true,
|
||||
},
|
||||
{ key: IPaymentMadeAction.Edit, label: 'ability.edit', default: true },
|
||||
{
|
||||
key: IPaymentMadeAction.Delete,
|
||||
label: 'ability.delete',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Expense,
|
||||
subjectLabel: 'ability.expenses',
|
||||
abilities: [
|
||||
{ key: ExpenseAction.View, label: 'ability.view', default: true },
|
||||
{ key: ExpenseAction.Create, label: 'ability.create', default: true },
|
||||
{ key: ExpenseAction.Edit, label: 'ability.edit', default: true },
|
||||
{ key: ExpenseAction.Delete, label: 'ability.delete', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Report,
|
||||
subjectLabel: 'ability.all_reports',
|
||||
extraAbilities: [
|
||||
{
|
||||
key: ReportsAction.READ_BALANCE_SHEET,
|
||||
label: 'ability.balance_sheet_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_PROFIT_LOSS,
|
||||
label: 'ability.profit_loss_sheet',
|
||||
},
|
||||
{ key: ReportsAction.READ_JOURNAL, label: 'ability.journal' },
|
||||
{
|
||||
key: ReportsAction.READ_GENERAL_LEDGET,
|
||||
label: 'ability.general_ledger',
|
||||
},
|
||||
{ key: ReportsAction.READ_CASHFLOW, label: 'ability.cashflow_report' },
|
||||
{
|
||||
key: ReportsAction.READ_AR_AGING_SUMMARY,
|
||||
label: 'ability.AR_aging_summary_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||
label: 'ability.AP_aging_summary_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||
label: 'ability.purchases_by_items',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_SALES_BY_ITEMS,
|
||||
label: 'ability.sales_by_items_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||
label: 'ability.customers_transactions_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||
label: 'ability.vendors_transactions_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||
label: 'ability.customers_summary_balance_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||
label: 'ability.vendors_summary_balance_report',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||
label: 'ability.inventory_valuation_summary',
|
||||
},
|
||||
{
|
||||
key: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||
label: 'ability.inventory_items_details',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
subject: AbilitySubject.Preferences,
|
||||
subjectLabel: 'ability.preferences',
|
||||
extraAbilities: [
|
||||
{
|
||||
key: PreferencesAction.Mutate,
|
||||
label: 'ability.mutate_system_preferences',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieve the permissions subject.
|
||||
* @param {string} key
|
||||
* @returns {ISubjectAbilitiesSchema | null}
|
||||
*/
|
||||
export const getPermissionsSubject = (
|
||||
key: string
|
||||
): ISubjectAbilitiesSchema | null => {
|
||||
return AbilitySchema.find((subject) => subject.subject === key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the permission subject ability.
|
||||
* @param {String} subjectKey
|
||||
* @param {string} abilityKey
|
||||
* @returns
|
||||
*/
|
||||
export const getPermissionAbility = (
|
||||
subjectKey: string,
|
||||
abilityKey: string
|
||||
): ISubjectAbilitySchema | null => {
|
||||
const subject = getPermissionsSubject(subjectKey);
|
||||
|
||||
return subject?.abilities.find((ability) => ability.key === abilityKey);
|
||||
};
|
||||
22
packages/server/src/services/Roles/PurgeAuthorizedUser.ts
Normal file
22
packages/server/src/services/Roles/PurgeAuthorizedUser.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import { ABILITIES_CACHE } from '../../api/middleware/AuthorizationMiddleware';
|
||||
|
||||
@Service()
|
||||
export default class PurgeAuthorizedUserOnceRoleMutate {
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(events.roles.onEdited, this.purgeAuthedUserOnceRoleMutated);
|
||||
bus.subscribe(events.roles.onDeleted, this.purgeAuthedUserOnceRoleMutated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges authorized user once role edited or deleted.
|
||||
*/
|
||||
purgeAuthedUserOnceRoleMutated({}) {
|
||||
ABILITIES_CACHE.reset();
|
||||
}
|
||||
}
|
||||
31
packages/server/src/services/Roles/RolePermissionsSchema.ts
Normal file
31
packages/server/src/services/Roles/RolePermissionsSchema.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as qim from 'qim';
|
||||
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { AbilitySchema } from './AbilitySchema';
|
||||
import I18nService from '@/services/I18n/I18nService';
|
||||
|
||||
@Service()
|
||||
export default class RolePermissionsSchema {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
i18nService: I18nService;
|
||||
|
||||
/**
|
||||
* Retrieve the role permissions schema.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
getRolePermissionsSchema(tenantId: number) {
|
||||
const $abilities = (f) => (f.abilities ? f : undefined);
|
||||
const $extraAbilities = (f) => (f.extraAbilities ? f : undefined);
|
||||
|
||||
const naviagations = [
|
||||
[qim.$each, 'subjectLabel'],
|
||||
[qim.$each, $abilities, 'abilities', qim.$each, 'label'],
|
||||
[qim.$each, $extraAbilities, 'extraAbilities', qim.$each, 'label'],
|
||||
];
|
||||
return this.i18nService.i18nApply(naviagations, AbilitySchema, tenantId);
|
||||
}
|
||||
}
|
||||
31
packages/server/src/services/Roles/RoleTransformer.ts
Normal file
31
packages/server/src/services/Roles/RoleTransformer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
|
||||
export class RoleTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['name', 'description'];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param role
|
||||
* @returns
|
||||
*/
|
||||
public name(role) {
|
||||
return role.predefined ? this.context.i18n.__(role.name) : role.name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param role
|
||||
* @returns
|
||||
*/
|
||||
public description(role) {
|
||||
return role.predefined
|
||||
? this.context.i18n.__(role.description)
|
||||
: role.description;
|
||||
}
|
||||
}
|
||||
291
packages/server/src/services/Roles/RolesService.ts
Normal file
291
packages/server/src/services/Roles/RolesService.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
6
packages/server/src/services/Roles/constants.ts
Normal file
6
packages/server/src/services/Roles/constants.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const ERRORS = {
|
||||
ROLE_NOT_FOUND: 'ROLE_NOT_FOUND',
|
||||
ROLE_PREFINED: 'ROLE_PREFINED',
|
||||
INVALIDATE_PERMISSIONS: 'INVALIDATE_PERMISSIONS',
|
||||
CANNT_DELETE_ROLE_ASSOCIATED_TO_USERS: 'CANNT_DELETE_ROLE_ASSOCIATED_TO_USERS'
|
||||
};
|
||||
42
packages/server/src/services/Roles/utils.ts
Normal file
42
packages/server/src/services/Roles/utils.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { keyBy } from 'lodash';
|
||||
import { ISubjectAbilitiesSchema } from '@/interfaces';
|
||||
|
||||
/**
|
||||
* Transformes ability schema to map.
|
||||
*/
|
||||
export function transformAbilitySchemaToMap(schema: ISubjectAbilitiesSchema[]) {
|
||||
return keyBy(
|
||||
schema.map((item) => ({
|
||||
...item,
|
||||
abilities: keyBy(item.abilities, 'key'),
|
||||
extraAbilities: keyBy(item.extraAbilities, 'key'),
|
||||
})),
|
||||
'subject'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the invalid permissions from the given defined schema.
|
||||
* @param {ISubjectAbilitiesSchema[]} schema
|
||||
* @param permissions
|
||||
* @returns
|
||||
*/
|
||||
export function getInvalidPermissions(
|
||||
schema: ISubjectAbilitiesSchema[],
|
||||
permissions
|
||||
) {
|
||||
const schemaMap = transformAbilitySchemaToMap(schema);
|
||||
|
||||
return permissions.filter((permission) => {
|
||||
const { subject, ability } = permission;
|
||||
|
||||
if (
|
||||
!schemaMap[subject] ||
|
||||
(!schemaMap[subject].abilities[ability] &&
|
||||
!schemaMap[subject].extraAbilities[ability])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user