mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
334
packages/server/src/modules/Roles/AbilitySchema.ts
Normal file
334
packages/server/src/modules/Roles/AbilitySchema.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { ItemAction } from "@/interfaces/Item";
|
||||
import { ReportsAction } from "../FinancialStatements/types/Report.types";
|
||||
import { InventoryAdjustmentAction } from "../InventoryAdjutments/types/InventoryAdjustments.types";
|
||||
import { CashflowAction } from "../BankingTransactions/types/BankingTransactions.types";
|
||||
import { ManualJournalAction } from "../ManualJournals/types/ManualJournals.types";
|
||||
import { AccountAction } from "@/interfaces/Account";
|
||||
import { VendorCreditAction } from "../VendorCredit/types/VendorCredit.types";
|
||||
import { IPaymentMadeAction } from "../BillPayments/types/BillPayments.types";
|
||||
import { ExpenseAction } from "../Expenses/Expenses.types";
|
||||
import { CustomerAction, VendorAction } from "../Customers/types/Customers.types";
|
||||
import { SaleEstimateAction } from "../SaleEstimates/types/SaleEstimates.types";
|
||||
import { SaleInvoiceAction } from "../SaleInvoices/SaleInvoice.types";
|
||||
import { CreditNoteAction } from "../CreditNotes/types/CreditNotes.types";
|
||||
import { SaleReceiptAction } from "../SaleReceipts/types/SaleReceipts.types";
|
||||
import { BillAction } from "../Bills/Bills.types";
|
||||
import { AbilitySubject, ISubjectAbilitiesSchema, ISubjectAbilitySchema } from "./Roles.types";
|
||||
import { PaymentReceiveAction } from "../PaymentReceived/types/PaymentReceived.types";
|
||||
import { PreferencesAction } from "../Settings/Settings.types";
|
||||
|
||||
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);
|
||||
};
|
||||
56
packages/server/src/modules/Roles/Authorization.guard.ts
Normal file
56
packages/server/src/modules/Roles/Authorization.guard.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { ABILITIES_CACHE, getAbilityForRole } from './TenantAbilities';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { TenantUser } from '../Tenancy/TenancyModels/models/TenantUser.model';
|
||||
|
||||
/**
|
||||
* Authorization guard for checking user abilities
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuthorizationGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private readonly clsService: ClsService,
|
||||
|
||||
@Inject(TenantUser.name)
|
||||
private readonly tenantUserModel: TenantModelProxy<typeof TenantUser>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Checks if the user has the required abilities to access the route
|
||||
* @param context - The execution context
|
||||
* @returns A boolean indicating if the user can access the route
|
||||
*/
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const { tenantId, user } = request as any;
|
||||
|
||||
if (ABILITIES_CACHE.has(user.id)) {
|
||||
(request as any).ability = ABILITIES_CACHE.get(user.id);
|
||||
} else {
|
||||
const ability = await this.getAbilityForUser();
|
||||
(request as any).ability = ability;
|
||||
ABILITIES_CACHE.set(user.id, ability);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getAbilityForUser() {
|
||||
const userId = this.clsService.get('userId');
|
||||
const tenantUser = await this.tenantUserModel()
|
||||
.query()
|
||||
.findOne('systemUserId', userId)
|
||||
.withGraphFetched('role.permissions');
|
||||
|
||||
return getAbilityForRole(tenantUser.role);
|
||||
}
|
||||
}
|
||||
62
packages/server/src/modules/Roles/Roles.application.ts
Normal file
62
packages/server/src/modules/Roles/Roles.application.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateRoleService } from './commands/CreateRole.service';
|
||||
import { DeleteRoleService } from './commands/DeleteRole.service';
|
||||
import { EditRoleService } from './commands/EditRole.service';
|
||||
import { GetRoleService } from './queries/GetRole.service';
|
||||
import { GetRolesService } from './queries/GetRoles.service';
|
||||
|
||||
@Injectable()
|
||||
export class RolesApplication {
|
||||
constructor(
|
||||
private readonly createRoleService: CreateRoleService,
|
||||
private readonly editRoleService: EditRoleService,
|
||||
private readonly deleteRoleService: DeleteRoleService,
|
||||
private readonly getRoleService: GetRoleService,
|
||||
private readonly getRolesService: GetRolesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new role.
|
||||
* @param createRoleDto The data for creating a new role.
|
||||
* @returns The created role.
|
||||
*/
|
||||
async createRole(createRoleDto: any) {
|
||||
return this.createRoleService.createRole(createRoleDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing role.
|
||||
* @param roleId The ID of the role to edit.
|
||||
* @param editRoleDto The data for editing the role.
|
||||
* @returns The edited role.
|
||||
*/
|
||||
async editRole(roleId: number, editRoleDto: any) {
|
||||
return this.editRoleService.editRole(roleId, editRoleDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a role.
|
||||
* @param roleId The ID of the role to delete.
|
||||
* @returns The result of the deletion operation.
|
||||
*/
|
||||
async deleteRole(roleId: number) {
|
||||
return this.deleteRoleService.deleteRole(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific role by ID.
|
||||
* @param roleId The ID of the role to retrieve.
|
||||
* @returns The requested role.
|
||||
*/
|
||||
async getRole(roleId: number) {
|
||||
return this.getRoleService.getRole(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all roles.
|
||||
* @returns A list of all roles.
|
||||
*/
|
||||
async getRoles() {
|
||||
return this.getRolesService.getRoles();
|
||||
}
|
||||
}
|
||||
113
packages/server/src/modules/Roles/Roles.controller.ts
Normal file
113
packages/server/src/modules/Roles/Roles.controller.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
Req,
|
||||
Res,
|
||||
Next,
|
||||
HttpStatus,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { CreateRoleDto, EditRoleDto } from './dtos/Role.dto';
|
||||
import { RolesApplication } from './Roles.application';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiParam,
|
||||
ApiBody,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Roles')
|
||||
@Controller('roles')
|
||||
export class RolesController {
|
||||
constructor(private readonly rolesApp: RolesApplication) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new role' })
|
||||
@ApiBody({ type: CreateRoleDto })
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
description: 'Role created successfully',
|
||||
})
|
||||
async createRole(
|
||||
@Res() res: Response,
|
||||
@Next() next: NextFunction,
|
||||
@Body() createRoleDto: CreateRoleDto,
|
||||
) {
|
||||
const role = await this.rolesApp.createRole(createRoleDto);
|
||||
|
||||
return res.status(HttpStatus.OK).send({
|
||||
data: { roleId: role.id },
|
||||
message: 'The role has been created successfully.',
|
||||
});
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
@ApiOperation({ summary: 'Edit an existing role' })
|
||||
@ApiParam({ name: 'id', description: 'Role ID' })
|
||||
@ApiBody({ type: EditRoleDto })
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
description: 'Role updated successfully',
|
||||
})
|
||||
async editRole(
|
||||
@Res() res: Response,
|
||||
@Next() next: NextFunction,
|
||||
@Param('id', ParseIntPipe) roleId: number,
|
||||
@Body() editRoleDto: EditRoleDto,
|
||||
) {
|
||||
const role = await this.rolesApp.editRole(roleId, editRoleDto);
|
||||
|
||||
return res.status(HttpStatus.OK).send({
|
||||
data: { roleId },
|
||||
message: 'The given role has been updated successfully.',
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete a role' })
|
||||
@ApiParam({ name: 'id', description: 'Role ID' })
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
description: 'Role deleted successfully',
|
||||
})
|
||||
async deleteRole(
|
||||
@Res() res: Response,
|
||||
@Next() next: NextFunction,
|
||||
@Param('id', ParseIntPipe) roleId: number,
|
||||
) {
|
||||
await this.rolesApp.deleteRole(roleId);
|
||||
|
||||
return res.status(HttpStatus.OK).send({
|
||||
data: { roleId },
|
||||
message: 'The given role has been deleted successfully.',
|
||||
});
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get all roles' })
|
||||
@ApiResponse({ status: HttpStatus.OK, description: 'List of all roles' })
|
||||
async getRoles(@Res() res: Response) {
|
||||
const roles = await this.rolesApp.getRoles();
|
||||
|
||||
return res.status(HttpStatus.OK).send({ roles });
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get a specific role by ID' })
|
||||
@ApiParam({ name: 'id', description: 'Role ID' })
|
||||
@ApiResponse({ status: HttpStatus.OK, description: 'Role details' })
|
||||
async getRole(
|
||||
@Res() res: Response,
|
||||
@Param('id', ParseIntPipe) roleId: number,
|
||||
) {
|
||||
const role = await this.rolesApp.getRole(roleId);
|
||||
|
||||
return res.status(HttpStatus.OK).send({ role });
|
||||
}
|
||||
}
|
||||
31
packages/server/src/modules/Roles/Roles.module.ts
Normal file
31
packages/server/src/modules/Roles/Roles.module.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CreateRoleService } from './commands/CreateRole.service';
|
||||
import { EditRoleService } from './commands/EditRole.service';
|
||||
import { DeleteRoleService } from './commands/DeleteRole.service';
|
||||
import { GetRoleService } from './queries/GetRole.service';
|
||||
import { GetRolesService } from './queries/GetRoles.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { Role } from './models/Role.model';
|
||||
import { RolePermission } from './models/RolePermission.model';
|
||||
import { RolesController } from './Roles.controller';
|
||||
import { RolesApplication } from './Roles.application';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(Role),
|
||||
RegisterTenancyModel(RolePermission),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [...models],
|
||||
providers: [
|
||||
CreateRoleService,
|
||||
EditRoleService,
|
||||
DeleteRoleService,
|
||||
GetRoleService,
|
||||
GetRolesService,
|
||||
RolesApplication,
|
||||
],
|
||||
controllers: [RolesController],
|
||||
exports: [...models],
|
||||
})
|
||||
export class RolesModule {}
|
||||
83
packages/server/src/modules/Roles/Roles.types.ts
Normal file
83
packages/server/src/modules/Roles/Roles.types.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Ability, RawRuleOf, ForcedSubject } from '@casl/ability';
|
||||
import { CreateRoleDto, EditRoleDto } from './dtos/Role.dto';
|
||||
import { Role } from './models/Role.model';
|
||||
|
||||
export const actions = [
|
||||
'manage',
|
||||
'create',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
] as const;
|
||||
export const subjects = ['Article', 'all'] as const;
|
||||
|
||||
export type Abilities = [
|
||||
typeof actions[number],
|
||||
(
|
||||
| typeof subjects[number]
|
||||
| ForcedSubject<Exclude<typeof subjects[number], 'all'>>
|
||||
)
|
||||
];
|
||||
|
||||
export type AppAbility = Ability<Abilities>;
|
||||
|
||||
export const createAbility = (rules: RawRuleOf<AppAbility>[]) =>
|
||||
new Ability<Abilities>(rules);
|
||||
|
||||
|
||||
export interface ISubjectAbilitySchema {
|
||||
key: string;
|
||||
label: string;
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface ISubjectAbilitiesSchema {
|
||||
subject: string;
|
||||
subjectLabel: string;
|
||||
description?: string;
|
||||
abilities?: ISubjectAbilitySchema[];
|
||||
extraAbilities?: ISubjectAbilitySchema[];
|
||||
}
|
||||
|
||||
export enum AbilitySubject {
|
||||
Item = 'Item',
|
||||
InventoryAdjustment = 'InventoryAdjustment',
|
||||
Report = 'Report',
|
||||
Account = 'Account',
|
||||
SaleInvoice = 'SaleInvoice',
|
||||
SaleEstimate = 'SaleEstimate',
|
||||
SaleReceipt = 'SaleReceipt',
|
||||
PaymentReceive = 'PaymentReceive',
|
||||
Bill = 'Bill',
|
||||
PaymentMade = 'PaymentMade',
|
||||
Expense = 'Expense',
|
||||
Customer = 'Customer',
|
||||
Vendor = 'Vendor',
|
||||
Cashflow = 'Cashflow',
|
||||
ManualJournal = 'ManualJournal',
|
||||
Preferences = 'Preferences',
|
||||
CreditNote = 'CreditNode',
|
||||
VendorCredit = 'VendorCredit',
|
||||
Project = 'Project',
|
||||
TaxRate = 'TaxRate'
|
||||
}
|
||||
|
||||
export interface IRoleCreatedPayload {
|
||||
createRoleDTO: CreateRoleDto;
|
||||
role: Role;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IRoleEditedPayload {
|
||||
editRoleDTO: EditRoleDto;
|
||||
oldRole: Role;
|
||||
role: Role;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IRoleDeletedPayload {
|
||||
oldRole: Role;
|
||||
roleId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
13
packages/server/src/modules/Roles/Roles.utils.ts
Normal file
13
packages/server/src/modules/Roles/Roles.utils.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { ERRORS } from './constants';
|
||||
import { Role } from './models/Role.model';
|
||||
|
||||
/**
|
||||
* Valdiates role is not predefined.
|
||||
* @param {IRole} role - Role object.
|
||||
*/
|
||||
export const validateRoleNotPredefined = (role: Role) => {
|
||||
if (role.predefined) {
|
||||
throw new ServiceError(ERRORS.ROLE_PREFINED);
|
||||
}
|
||||
};
|
||||
55
packages/server/src/modules/Roles/TenantAbilities.ts
Normal file
55
packages/server/src/modules/Roles/TenantAbilities.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Ability } from '@casl/ability';
|
||||
import LruCache from 'lru-cache';
|
||||
import { Role } from './models/Role.model';
|
||||
import { RolePermission } from './models/RolePermission.model';
|
||||
|
||||
// store abilities of 1000 most active users
|
||||
export const ABILITIES_CACHE = new LruCache(1000);
|
||||
|
||||
/**
|
||||
* Retrieve ability for the given role.
|
||||
* @param {} role
|
||||
* @returns
|
||||
*/
|
||||
export function getAbilityForRole(role) {
|
||||
const rules = getAbilitiesRolesConds(role);
|
||||
return new Ability(rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve abilities of the given role.
|
||||
* @param {IRole} role
|
||||
* @returns {}
|
||||
*/
|
||||
function getAbilitiesRolesConds(role: Role) {
|
||||
switch (role.slug) {
|
||||
case 'admin': // predefined role.
|
||||
return getSuperAdminRules();
|
||||
default:
|
||||
return getRulesFromRolePermissions(role.permissions || []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the super admin rules.
|
||||
* @returns {}
|
||||
*/
|
||||
function getSuperAdminRules() {
|
||||
return [{ action: 'manage', subject: 'all' }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve CASL rules from role permissions.
|
||||
* @param {RolePermission[]} permissions -
|
||||
* @returns {}
|
||||
*/
|
||||
function getRulesFromRolePermissions(permissions: RolePermission[]) {
|
||||
return permissions
|
||||
.filter((permission: RolePermission) => permission.value)
|
||||
.map((permission: RolePermission) => {
|
||||
return {
|
||||
action: permission.ability,
|
||||
subject: permission.subject,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Knex } from 'knex';
|
||||
import { IRoleCreatedPayload } from '../Roles.types';
|
||||
import { Role } from './../models/Role.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { CreateRoleDto } from '../dtos/Role.dto';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { validateInvalidPermissions } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class CreateRoleService {
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(Role.name)
|
||||
private readonly roleModel: TenantModelProxy<typeof Role>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new role and store it to the storage.
|
||||
* @param {CreateRoleDto} createRoleDTO -
|
||||
* @returns
|
||||
*/
|
||||
public async createRole(createRoleDTO: CreateRoleDto) {
|
||||
// Validates the invalid permissions.
|
||||
validateInvalidPermissions(createRoleDTO.permissions);
|
||||
|
||||
// Transformes the permissions DTO.
|
||||
const permissions = createRoleDTO.permissions;
|
||||
|
||||
// Creates a new role with associated entries under unit-of-work.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Creates a new role to the storage.
|
||||
const role = await this.roleModel().query(trx).upsertGraph({
|
||||
name: createRoleDTO.roleName,
|
||||
description: createRoleDTO.roleDescription,
|
||||
permissions,
|
||||
});
|
||||
// Triggers `onRoleCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.roles.onCreated, {
|
||||
createRoleDTO,
|
||||
role,
|
||||
trx,
|
||||
} as IRoleCreatedPayload);
|
||||
|
||||
return role;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Knex } from 'knex';
|
||||
import { IRoleDeletedPayload } from '../Roles.types';
|
||||
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
|
||||
import { Role } from '../models/Role.model';
|
||||
import { RolePermission } from '../models/RolePermission.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { validateRoleNotPredefined } from '../Roles.utils';
|
||||
import { TenantUser } from '@/modules/Tenancy/TenancyModels/models/TenantUser.model';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { ERRORS } from '../constants';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteRoleService {
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(Role.name)
|
||||
private readonly roleModel: TenantModelProxy<typeof Role>,
|
||||
|
||||
@Inject(RolePermission.name)
|
||||
private readonly rolePermissionModel: TenantModelProxy<
|
||||
typeof RolePermission
|
||||
>,
|
||||
|
||||
@Inject(TenantUser.name)
|
||||
private readonly tenantUserModel: TenantModelProxy<typeof TenantUser>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes the given role from the storage.
|
||||
* @param {number} roleId - Role id.
|
||||
*/
|
||||
public async deleteRole(roleId: number): Promise<void> {
|
||||
// Retrieve the given role or throw not found service error.
|
||||
const oldRole = await this.roleModel()
|
||||
.query()
|
||||
.findById(roleId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate role is not predefined.
|
||||
validateRoleNotPredefined(oldRole);
|
||||
|
||||
// Validates the given role is not associated to any user.
|
||||
await this.validateRoleNotAssociatedToUser(roleId);
|
||||
|
||||
// Deletes the given role and associated models under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Deletes the role associated permissions from the storage.
|
||||
await this.rolePermissionModel()
|
||||
.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,
|
||||
trx,
|
||||
} as IRoleDeletedPayload);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Validates the given role is not associated to any tenant users.
|
||||
* @param {number} roleId
|
||||
*/
|
||||
private validateRoleNotAssociatedToUser = async (roleId: number) => {
|
||||
const userAssociatedRole = await this.tenantUserModel()
|
||||
.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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Knex } from 'knex';
|
||||
import { IRoleEditedPayload } from '../Roles.types';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { EditRoleDto } from '../dtos/Role.dto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Role } from '../models/Role.model';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { validateInvalidPermissions } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class EditRoleService {
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(Role.name)
|
||||
private readonly roleModel: TenantModelProxy<typeof Role>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Edits details of the given role on the storage.
|
||||
* @param {number} roleId - Role id.
|
||||
* @param {IEditRoleDTO} editRoleDTO - Edit role DTO.
|
||||
*/
|
||||
public async editRole(roleId: number, editRoleDTO: EditRoleDto) {
|
||||
// Validates the invalid permissions.
|
||||
validateInvalidPermissions(editRoleDTO.permissions);
|
||||
|
||||
// Retrieve the given role or throw not found serice error.
|
||||
const oldRole = await this.roleModel().query().findById(roleId);
|
||||
const permissions = editRoleDTO.permissions;
|
||||
|
||||
// Updates the role on the storage.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Updates the given role to the storage.
|
||||
const role = await this.roleModel().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;
|
||||
});
|
||||
}
|
||||
}
|
||||
6
packages/server/src/modules/Roles/constants.ts
Normal file
6
packages/server/src/modules/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'
|
||||
};
|
||||
92
packages/server/src/modules/Roles/dtos/Role.dto.ts
Normal file
92
packages/server/src/modules/Roles/dtos/Role.dto.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsString,
|
||||
Length,
|
||||
MinLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CommandRolePermissionDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'subject',
|
||||
description: 'The subject of the permission',
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'read',
|
||||
description: 'The action of the permission',
|
||||
})
|
||||
ability: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'The value of the permission',
|
||||
})
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export class CreateRolePermissionDto extends CommandRolePermissionDto {}
|
||||
export class EditRolePermissionDto extends CommandRolePermissionDto {
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'The permission ID',
|
||||
})
|
||||
permissionId: number;
|
||||
}
|
||||
|
||||
class CommandRoleDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'admin',
|
||||
description: 'The name of the role',
|
||||
})
|
||||
roleName: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'Administrator',
|
||||
description: 'The description of the role',
|
||||
})
|
||||
roleDescription: string;
|
||||
}
|
||||
|
||||
export class CreateRoleDto extends CommandRoleDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandRolePermissionDto)
|
||||
@ApiProperty({
|
||||
type: [CommandRolePermissionDto],
|
||||
description: 'The permissions of the role',
|
||||
})
|
||||
permissions: Array<CreateRolePermissionDto>;
|
||||
}
|
||||
|
||||
export class EditRoleDto extends CommandRoleDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandRolePermissionDto)
|
||||
@ApiProperty({
|
||||
type: [CommandRolePermissionDto],
|
||||
description: 'The permissions of the role',
|
||||
})
|
||||
permissions: Array<EditRolePermissionDto>;
|
||||
}
|
||||
39
packages/server/src/modules/Roles/models/Role.model.ts
Normal file
39
packages/server/src/modules/Roles/models/Role.model.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
import { RolePermission } from './RolePermission.model';
|
||||
|
||||
export class Role extends TenantBaseModel {
|
||||
name: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
predefined: boolean;
|
||||
permissions: Array<RolePermission>;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'roles';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { RolePermission } = require('./RolePermission.model');
|
||||
|
||||
return {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
permissions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: RolePermission,
|
||||
join: {
|
||||
from: 'roles.id',
|
||||
to: 'role_permissions.roleId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class RolePermission extends TenantBaseModel {
|
||||
value: any;
|
||||
ability: any;
|
||||
subject: any;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'role_permissions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { Role } = require('./Role.model');
|
||||
|
||||
return {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
role: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Role,
|
||||
join: {
|
||||
from: 'role_permissions.roleId',
|
||||
to: 'roles.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
51
packages/server/src/modules/Roles/queries/GetRole.service.ts
Normal file
51
packages/server/src/modules/Roles/queries/GetRole.service.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { AbilitySchema } from '../AbilitySchema';
|
||||
import { RoleTransformer } from './RoleTransformer';
|
||||
import { Role } from '../models/Role.model';
|
||||
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
|
||||
import { ServiceError } from '../../Items/ServiceError';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { CommandRolePermissionDto } from '../dtos/Role.dto';
|
||||
import { ERRORS } from '../constants';
|
||||
import { getInvalidPermissions } from '../utils';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetRoleService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(Role.name)
|
||||
private readonly roleModel: TenantModelProxy<typeof Role>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the given role metadata.
|
||||
* @param {number} roleId - Role id.
|
||||
* @returns {Promise<IRole>}
|
||||
*/
|
||||
public async getRole(roleId: number): Promise<Role> {
|
||||
const role = await this.roleModel()
|
||||
.query()
|
||||
.findById(roleId)
|
||||
.withGraphFetched('permissions')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(role, new RoleTransformer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the invalid given permissions.
|
||||
* @param {ICreateRolePermissionDTO[]} permissions -
|
||||
*/
|
||||
public validateInvalidPermissions = (
|
||||
permissions: CommandRolePermissionDto[],
|
||||
) => {
|
||||
const invalidPerms = getInvalidPermissions(AbilitySchema, permissions);
|
||||
|
||||
if (invalidPerms.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVALIDATE_PERMISSIONS, null, {
|
||||
invalidPermissions: invalidPerms,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Role } from '../models/Role.model';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { RoleTransformer } from './RoleTransformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetRolesService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(Role.name)
|
||||
private readonly roleModel: TenantModelProxy<typeof Role>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the roles list.
|
||||
* @param {Promise<Role[]>}
|
||||
*/
|
||||
public getRoles = async (): Promise<Role[]> => {
|
||||
const roles = await this.roleModel()
|
||||
.query()
|
||||
.withGraphFetched('permissions');
|
||||
|
||||
return this.transformer.transform(roles, new RoleTransformer());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AbilitySchema } from '../AbilitySchema';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class RolePermissionsSchema {
|
||||
/**
|
||||
* Retrieve the role permissions schema.
|
||||
*/
|
||||
getRolePermissionsSchema() {
|
||||
return AbilitySchema;
|
||||
}
|
||||
}
|
||||
31
packages/server/src/modules/Roles/queries/RoleTransformer.ts
Normal file
31
packages/server/src/modules/Roles/queries/RoleTransformer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Transformer } from '../../Transformer/Transformer';
|
||||
|
||||
export class RoleTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['name', 'description'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the localized role name if is predefined or stored name.
|
||||
* @param role
|
||||
* @returns {string}
|
||||
*/
|
||||
public name(role) {
|
||||
return role.predefined ? this.context.i18n.t(role.name) : role.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the localized role description if is predefined or stored description.
|
||||
* @param role
|
||||
* @returns {string}
|
||||
*/
|
||||
public description(role) {
|
||||
return role.predefined
|
||||
? this.context.i18n.t(role.description)
|
||||
: role.description;
|
||||
}
|
||||
}
|
||||
62
packages/server/src/modules/Roles/utils.ts
Normal file
62
packages/server/src/modules/Roles/utils.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { keyBy } from 'lodash';
|
||||
import { ISubjectAbilitiesSchema } from './Roles.types';
|
||||
import { CommandRolePermissionDto } from './dtos/Role.dto';
|
||||
import { AbilitySchema } from './AbilitySchema';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the invalid given permissions.
|
||||
* @param {ICreateRolePermissionDTO[]} permissions -
|
||||
*/
|
||||
export const validateInvalidPermissions = (
|
||||
permissions: CommandRolePermissionDto[],
|
||||
) => {
|
||||
const invalidPerms = getInvalidPermissions(AbilitySchema, permissions);
|
||||
|
||||
if (invalidPerms.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVALIDATE_PERMISSIONS, null, {
|
||||
invalidPermissions: invalidPerms,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user