mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
fix: accounts TS interfaces.
This commit is contained in:
@@ -83,8 +83,9 @@ export default class AccountsController extends BaseController{
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
router.delete(
|
||||
'/',
|
||||
this.bulkDeleteSchema,
|
||||
'/', [
|
||||
...this.bulkDeleteSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkAccounts.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
@@ -279,8 +280,9 @@ export default class AccountsController extends BaseController{
|
||||
const { ids: accountsIds } = req.query;
|
||||
|
||||
try {
|
||||
const isActive = (type === 'activate' ? 1 : 0);
|
||||
await this.accountsService.activateAccounts(tenantId, accountsIds, isActive)
|
||||
const isActive = (type === 'activate' ? true : false);
|
||||
await this.accountsService.activateAccounts(tenantId, accountsIds, isActive);
|
||||
|
||||
return res.status(200).send({ ids: accountsIds });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
217
server/src/api/controllers/Views.ts
Normal file
217
server/src/api/controllers/Views.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, NextFunction, Response } from 'express';
|
||||
import {
|
||||
check,
|
||||
query,
|
||||
param,
|
||||
oneOf,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import {
|
||||
validateViewRoles,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import ViewsService from 'services/Views/ViewsService';
|
||||
import BaseController from './BaseController';
|
||||
import { IViewDTO } from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@Service()
|
||||
export default class ViewsController extends BaseController{
|
||||
@Inject()
|
||||
viewsService: ViewsService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.listViews)
|
||||
);
|
||||
router.post('/', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.createView)
|
||||
);
|
||||
|
||||
router.post('/:view_id', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.editView)
|
||||
);
|
||||
|
||||
router.delete('/:view_id', [
|
||||
...this.viewParamSchemaValidation
|
||||
],
|
||||
asyncMiddleware(this.deleteView));
|
||||
|
||||
router.get('/:view_id', [
|
||||
...this.viewParamSchemaValidation
|
||||
]
|
||||
asyncMiddleware(this.getView)
|
||||
);
|
||||
|
||||
router.get('/:view_id/resource', [
|
||||
...this.viewParamSchemaValidation
|
||||
],
|
||||
asyncMiddleware(this.getViewResource)
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
get viewDTOSchemaValidation() {
|
||||
return [
|
||||
check('resource_name').exists().escape().trim(),
|
||||
check('name').exists().escape().trim(),
|
||||
check('logic_expression').exists().trim().escape(),
|
||||
check('roles').isArray({ min: 1 }),
|
||||
check('roles.*.field_key').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
check('columns').exists().isArray({ min: 1 }),
|
||||
check('columns.*.key').exists().escape().trim(),
|
||||
check('columns.*.index').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get viewParamSchemaValidation() {
|
||||
return [
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* List all views that associated with the given resource.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
listViews(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = req.query;
|
||||
|
||||
try {
|
||||
const views = this.viewsService.listViews(tenantId, filter);
|
||||
return res.status(200).send({ views });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
getView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const view = this.viewsService.getView(tenantId, viewId);
|
||||
return res.status(200).send({ view });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new view.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
createView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const viewDTO: IViewDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.viewsService.newView(tenantId, viewDTO);
|
||||
return res.status(200).send({ id: 1 });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits views metadata.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
editView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: viewId } = req.params;
|
||||
const { body: viewEditDTO } = req;
|
||||
|
||||
try {
|
||||
await this.viewsService.editView(tenantId, viewId, viewEditDTO);
|
||||
return res.status(200).send({ id: viewId });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given view.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
deleteView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: viewId } = req.params;
|
||||
|
||||
try {
|
||||
await this.viewsService.deleteView(tenantId, viewId);
|
||||
return res.status(200).send();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'INVALID_LOGIC_EXPRESSION') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === '') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === '') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'COLUMNS_NOT_EXIST', code: 200 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'VIEW_NOT_FOUND') {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'VIEW_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'VIEW_PREDEFINED') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PREDEFINED_VIEW', code: 200 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,14 +4,22 @@ export interface IAccountDTO {
|
||||
name: string,
|
||||
code: string,
|
||||
description: string,
|
||||
accountTypeNumber: number,
|
||||
accountTypeId: number,
|
||||
parentAccountId: number,
|
||||
active: boolean,
|
||||
};
|
||||
|
||||
export interface IAccount {
|
||||
name: string,
|
||||
slug: string,
|
||||
code: string,
|
||||
description: string,
|
||||
accountTypeNumber: number,
|
||||
accountTypeId: number,
|
||||
parentAccountId: number,
|
||||
active: boolean,
|
||||
predefined: boolean,
|
||||
amount: number,
|
||||
currencyCode: string,
|
||||
};
|
||||
|
||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -263,7 +263,7 @@ export default class AccountsService {
|
||||
* @param {IAccount} account
|
||||
*/
|
||||
private throwErrorIfAccountPredefined(account: IAccount) {
|
||||
if (account.prefined) {
|
||||
if (account.predefined) {
|
||||
throw new ServiceError('account_predefined');
|
||||
}
|
||||
}
|
||||
@@ -344,6 +344,11 @@ export default class AccountsService {
|
||||
return storedAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether one of the given accounts is predefined.
|
||||
* @param {IAccount[]} accounts -
|
||||
* @return {IAccount[]} - Predefined accounts
|
||||
*/
|
||||
private validatePrefinedAccounts(accounts: IAccount[]) {
|
||||
const predefined = accounts.filter((account: IAccount) => account.predefined);
|
||||
|
||||
@@ -405,7 +410,7 @@ export default class AccountsService {
|
||||
*/
|
||||
public async activateAccounts(tenantId: number, accountsIds: number[], activate: boolean = true) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const accounts = await this.getAccountsOrThrowError(tenantId, accountsIds);
|
||||
await this.getAccountsOrThrowError(tenantId, accountsIds);
|
||||
|
||||
this.logger.info('[account] trying activate/inactive the given accounts ids.', { accountsIds });
|
||||
await Account.query().whereIn('id', accountsIds)
|
||||
@@ -438,7 +443,7 @@ export default class AccountsService {
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountsFilter} accountsFilter
|
||||
*/
|
||||
async getAccountsList(tenantId: number, filter: IAccountsFilter) {
|
||||
public async getAccountsList(tenantId: number, filter: IAccountsFilter) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
const dynamicList = await this.dynamicListService.dynamicList(tenantId, Account, filter);
|
||||
|
||||
288
server/src/services/Views/ViewsService.ts
Normal file
288
server/src/services/Views/ViewsService.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { Service, Inject } from "typedi";
|
||||
import { pick, difference } from 'lodash';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import {
|
||||
IViewsService,
|
||||
IViewDTO,
|
||||
IView,
|
||||
IViewRole,
|
||||
IViewHasColumn,
|
||||
} from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { validateRolesLogicExpression } from 'lib/ViewRolesBuilder';
|
||||
|
||||
const ERRORS = {
|
||||
VIEW_NOT_FOUND: 'VIEW_NOT_FOUND',
|
||||
VIEW_PREDEFINED: 'VIEW_PREDEFINED',
|
||||
INVALID_LOGIC_EXPRESSION: 'INVALID_LOGIC_EXPRESSION',
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class ViewsService implements IViewsService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Listing resource views.
|
||||
* @param {number} tenantId
|
||||
* @param {string} resourceModel
|
||||
*/
|
||||
public async listViews(tenantId: number, resourceModel: string) {
|
||||
const { View } = this.tenancy.models(tenantId);
|
||||
return View.query().where('resource_model', resourceModel);
|
||||
}
|
||||
|
||||
validateResourceFieldsExistance() {
|
||||
|
||||
}
|
||||
|
||||
validateResourceColumnsExistance() {
|
||||
|
||||
}
|
||||
|
||||
getView(tenantId: number, viewId: number) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Precedures.
|
||||
* ----––––––
|
||||
* - Validate resource fields existance.
|
||||
* - Validate resource columns existance.
|
||||
* - Validate view logic expression.
|
||||
* - Store view to the storage.
|
||||
* - Store view columns to the storage.
|
||||
* - Store view roles/conditions to the storage.
|
||||
* ---------
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IViewDTO} viewDTO - View DTO.
|
||||
*/
|
||||
async newView(tenantId: number, viewDTO: IViewDTO): Promise<void> {
|
||||
const { View, ViewColumn, ViewRole } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[views] trying to create a new view.', { tenantId, viewDTO });
|
||||
// Validates the view conditional logic expression.
|
||||
if (!validateRolesLogicExpression(viewDTO.logicExpression, viewDTO.roles)) {
|
||||
throw new ServiceError(ERRORS.INVALID_LOGIC_EXPRESSION);
|
||||
}
|
||||
// Save view details.
|
||||
const view = await View.query().insert({
|
||||
name: viewDTO.name,
|
||||
predefined: false,
|
||||
rolesLogicExpression: viewDTO.logicExpression,
|
||||
});
|
||||
this.logger.info('[views] inserted to the storage.', { tenantId, viewDTO });
|
||||
|
||||
// Save view roles async operations.
|
||||
const saveViewRolesOpers = [];
|
||||
|
||||
viewDTO.roles.forEach((role) => {
|
||||
const saveViewRoleOper = ViewRole.query().insert({
|
||||
...pick(role, ['fieldKey', 'comparator', 'value', 'index']),
|
||||
viewId: view.id,
|
||||
});
|
||||
saveViewRolesOpers.push(saveViewRoleOper);
|
||||
});
|
||||
|
||||
viewDTO.columns.forEach((column) => {
|
||||
const saveViewColumnOper = ViewColumn.query().insert({
|
||||
viewId: view.id,
|
||||
index: column.index,
|
||||
});
|
||||
saveViewRolesOpers.push(saveViewColumnOper);
|
||||
});
|
||||
this.logger.info('[views] roles and columns inserted to the storage.', { tenantId, viewDTO });
|
||||
|
||||
await Promise.all(saveViewRolesOpers);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} viewId
|
||||
* @param {IViewEditDTO}
|
||||
*/
|
||||
async editView(tenantId: number, viewId: number, viewEditDTO: IViewEditDTO) {
|
||||
const { View, ViewRole, ViewColumn } = req.models;
|
||||
const view = await View.query().where('id', viewId)
|
||||
.withGraphFetched('roles.field')
|
||||
.withGraphFetched('columns')
|
||||
.first();
|
||||
|
||||
const errorReasons = [];
|
||||
const fieldsSlugs = viewEditDTO.roles.map((role) => role.field_key);
|
||||
const resourceFieldsKeys = resource.fields.map((f) => f.key);
|
||||
const resourceFieldsKeysMap = new Map(resource.fields.map((field) => [field.key, field]));
|
||||
const columnsKeys = viewEditDTO.columns.map((c) => c.key);
|
||||
|
||||
// The difference between the stored resource fields and submit fields keys.
|
||||
const notFoundFields = difference(fieldsSlugs, resourceFieldsKeys);
|
||||
|
||||
// Validate not found resource fields keys.
|
||||
if (notFoundFields.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100, fields: notFoundFields,
|
||||
});
|
||||
}
|
||||
// The difference between the stored resource fields and the submit columns keys.
|
||||
const notFoundColumns = difference(columnsKeys, resourceFieldsKeys);
|
||||
|
||||
// Validate not found view columns.
|
||||
if (notFoundColumns.length > 0) {
|
||||
errorReasons.push({ type: 'RESOURCE_COLUMNS_NOT_EXIST', code: 200, columns: notFoundColumns });
|
||||
}
|
||||
// Validates the view conditional logic expression.
|
||||
if (!validateViewRoles(viewEditDTO.roles, viewEditDTO.logicExpression)) {
|
||||
errorReasons.push({ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 });
|
||||
}
|
||||
|
||||
const viewRolesIds = view.roles.map((r) => r.id);
|
||||
const viewColumnsIds = view.columns.map((c) => c.id);
|
||||
|
||||
const formUpdatedRoles = viewEditDTO.roles.filter((r) => r.id);
|
||||
const formInsertRoles = viewEditDTO.roles.filter((r) => !r.id);
|
||||
|
||||
const formRolesIds = formUpdatedRoles.map((r) => r.id);
|
||||
|
||||
const formUpdatedColumns = viewEditDTO.columns.filter((r) => r.id);
|
||||
const formInsertedColumns = viewEditDTO.columns.filter((r) => !r.id);
|
||||
const formColumnsIds = formUpdatedColumns.map((r) => r.id);
|
||||
|
||||
const rolesIdsShouldDeleted = difference(viewRolesIds, formRolesIds);
|
||||
const columnsIdsShouldDelete = difference(viewColumnsIds, formColumnsIds);
|
||||
|
||||
const notFoundViewRolesIds = difference(formRolesIds, viewRolesIds);
|
||||
const notFoundViewColumnsIds = difference(viewColumnsIds, viewColumnsIds);
|
||||
|
||||
// Validate the not found view roles ids.
|
||||
if (notFoundViewRolesIds.length) {
|
||||
errorReasons.push({ type: 'VIEW.ROLES.IDS.NOT.FOUND', code: 500, ids: notFoundViewRolesIds });
|
||||
}
|
||||
// Validate the not found view columns ids.
|
||||
if (notFoundViewColumnsIds.length) {
|
||||
errorReasons.push({ type: 'VIEW.COLUMNS.IDS.NOT.FOUND', code: 600, ids: notFoundViewColumnsIds });
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
const asyncOpers = [];
|
||||
|
||||
// Save view details.
|
||||
await View.query()
|
||||
.where('id', view.id)
|
||||
.patch({
|
||||
name: viewEditDTO.name,
|
||||
roles_logic_expression: viewEditDTO.logicExpression,
|
||||
});
|
||||
|
||||
// Update view roles.
|
||||
if (formUpdatedRoles.length > 0) {
|
||||
formUpdatedRoles.forEach((role) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
|
||||
const updateOper = ViewRole.query()
|
||||
.where('id', role.id)
|
||||
.update({
|
||||
...pick(role, ['comparator', 'value', 'index']),
|
||||
field_id: fieldModel.id,
|
||||
});
|
||||
asyncOpers.push(updateOper);
|
||||
});
|
||||
}
|
||||
// Insert a new view roles.
|
||||
if (formInsertRoles.length > 0) {
|
||||
formInsertRoles.forEach((role) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
|
||||
const insertOper = ViewRole.query()
|
||||
.insert({
|
||||
...pick(role, ['comparator', 'value', 'index']),
|
||||
field_id: fieldModel.id,
|
||||
view_id: view.id,
|
||||
});
|
||||
asyncOpers.push(insertOper);
|
||||
});
|
||||
}
|
||||
// Delete view roles.
|
||||
if (rolesIdsShouldDeleted.length > 0) {
|
||||
const deleteOper = ViewRole.query()
|
||||
.whereIn('id', rolesIdsShouldDeleted)
|
||||
.delete();
|
||||
asyncOpers.push(deleteOper);
|
||||
}
|
||||
// Insert a new view columns to the storage.
|
||||
if (formInsertedColumns.length > 0) {
|
||||
formInsertedColumns.forEach((column) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(column.key);
|
||||
const insertOper = ViewColumn.query()
|
||||
.insert({
|
||||
field_id: fieldModel.id,
|
||||
index: column.index,
|
||||
view_id: view.id,
|
||||
});
|
||||
asyncOpers.push(insertOper);
|
||||
});
|
||||
}
|
||||
// Update the view columns on the storage.
|
||||
if (formUpdatedColumns.length > 0) {
|
||||
formUpdatedColumns.forEach((column) => {
|
||||
const updateOper = ViewColumn.query()
|
||||
.where('id', column.id)
|
||||
.update({
|
||||
index: column.index,
|
||||
});
|
||||
asyncOpers.push(updateOper);
|
||||
});
|
||||
}
|
||||
// Delete the view columns from the storage.
|
||||
if (columnsIdsShouldDelete.length > 0) {
|
||||
const deleteOper = ViewColumn.query()
|
||||
.whereIn('id', columnsIdsShouldDelete)
|
||||
.delete();
|
||||
asyncOpers.push(deleteOper);
|
||||
}
|
||||
await Promise.all(asyncOpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve views details of the given id or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {number} viewId
|
||||
*/
|
||||
private async getViewByIdOrThrowError(tenantId: number, viewId: number): Promise<IView> {
|
||||
const { View } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[views] get stored view.', { tenantId, viewId });
|
||||
const view = await View.query().findById(viewId);
|
||||
|
||||
if (!view) {
|
||||
this.logger.info('[views] the given id not found.', { tenantId, viewId });
|
||||
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given view with associated roles and columns.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} viewId - View id.
|
||||
*/
|
||||
public async deleteView(tenantId: number, viewId: number): Promise<void> {
|
||||
const { View } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[views] trying to delete the given view.', { tenantId, viewId });
|
||||
const view = await this.getViewByIdOrThrowError(tenantId, viewId);
|
||||
|
||||
if (view.predefined) {
|
||||
this.logger.info('[views] cannot delete predefined.', { tenantId, viewId });
|
||||
throw new ServiceError(ERRORS.VIEW_PREDEFINED);
|
||||
}
|
||||
await Promise.all([
|
||||
view.$relatedQuery('roles').delete(),
|
||||
view.$relatedQuery('columns').delete(),
|
||||
]);
|
||||
await View.query().where('id', view.id).delete();
|
||||
this.logger.info('[views] deleted successfully.', { tenantId, viewId });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user