fix: resource advanced view filter.

This commit is contained in:
Ahmed Bouhuolia
2020-09-16 21:41:09 +02:00
parent a22c8395f3
commit ca92c925a9
72 changed files with 997 additions and 2324 deletions

View File

@@ -0,0 +1,167 @@
import { Service, Inject } from "typedi";
import validator from 'is-my-json-valid';
import { Router, Request, Response, NextFunction } from 'express';
import { ServiceError } from 'exceptions';
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from 'lib/DynamicFilter';
import {
validateFieldKeyExistance,
validateFilterRolesFieldsExistance,
} from 'lib/ViewRolesBuilder';
import TenancyService from 'services/Tenancy/TenancyService';
import { IDynamicListFilterDTO, IFilterRole } from 'interfaces';
const ERRORS = {
VIEW_NOT_FOUND: 'view_not_found',
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
};
@Service()
export default class DynamicListService {
@Inject()
tenancy: TenancyService;
/**
* Middleware to catch services errors
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
handlerErrorsToResponse(error, req: Request, res: Response, next: NextFunction) {
if (error instanceof ServiceError) {
if (error.errorType === 'sort_column_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'SORT.COLUMN.NOT.FOUND', code: 200 }],
});
}
if (error.errorType === 'view_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'CUSTOM.VIEW.NOT.FOUND', code: 100 }]
})
}
if (error.errorType === 'filter_roles_fields_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'FILTER.ROLES.FIELDS.NOT.FOUND', code: 300 }],
});
}
if (error.errorType === 'stringified_filter_roles_invalid') {
return res.boom.badRequest(null, {
errors: [{ type: 'STRINGIFIED_FILTER_ROLES_INVALID', code: 400 }],
});
}
}
next(error);
}
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @return {Promise<IView>}
*/
async getCustomViewOrThrowError(tenantId: number, viewId: number) {
const { viewRepository } = this.tenancy.repositories(tenantId);
const view = await viewRepository.getById(viewId);
if (!view || view.resourceModel !== 'Account') {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return view;
}
/**
* Validates the sort column whether exists.
* @param {IModel} model
* @param {string} columnSortBy - Sort column
* @throws {ServiceError}
*/
validateSortColumnExistance(model: any, columnSortBy: string) {
const notExistsField = validateFieldKeyExistance(model.tableName, columnSortBy);
if (notExistsField) {
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
}
}
/**
* Validates existance the fields of filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
validateRolesFieldsExistance(model: any, filterRoles: IFilterRole[]) {
const invalidFieldsKeys = validateFilterRolesFieldsExistance(model.tableName, filterRoles);
if (invalidFieldsKeys.length > 0) {
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
}
}
/**
* Validates filter roles schema.
* @param {IFilterRole[]} filterRoles
*/
validateFilterRolesSchema(filterRoles: IFilterRole[]) {
const validate = validator({
required: true,
type: 'object',
properties: {
fieldKey: { required: true, type: 'string' },
value: { required: true, type: 'string' },
},
});
const invalidFields = filterRoles.filter((filterRole) => {
const isValid = validate(filterRole);
return isValid ? false : true;
});
if (invalidFields.length > 0) {
throw new ServiceError('stringified_filter_roles_invalid');
}
}
/**
* Dynamic listing.
* @param {number} tenantId
* @param {IModel} model
* @param {IAccountsFilter} filter
*/
async dynamicList(tenantId: number, model: any, filter: IDynamicListFilterDTO) {
const { viewRoleRepository } = this.tenancy.repositories(tenantId);
const dynamicFilter = new DynamicFilter(model.tableName);
// Custom view filter roles.
if (filter.customViewId) {
const view = await this.getCustomViewOrThrowError(tenantId, filter.customViewId);
const viewRoles = await viewRoleRepository.allByView(view.id);
const viewFilter = new DynamicFilterViews(viewRoles, view.rolesLogicExpression);
dynamicFilter.setFilter(viewFilter);
}
// Sort by the given column.
if (filter.columnSortBy) {
this.validateSortColumnExistance(model, filter.columnSortBy);;
const sortByFilter = new DynamicFilterSortBy(
filter.columnSortBy, filter.sortOrder
);
dynamicFilter.setFilter(sortByFilter);
}
// Filter roles.
if (filter.filterRoles.length > 0) {
this.validateFilterRolesSchema(filter.filterRoles);
this.validateRolesFieldsExistance(model, filter.filterRoles);
// Validate the accounts resource fields.
const filterRoles = new DynamicFilterFilterRoles(filter.filterRoles);
dynamicFilter.setFilter(filterRoles);
}
return dynamicFilter;
}
}

View File

@@ -1,75 +0,0 @@
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from 'lib/DynamicFilter';
import {
mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter,
} from 'lib/ViewRolesBuilder';
export const DYNAMIC_LISTING_ERRORS = {
LOGIC_INVALID: 'VIEW.LOGIC.EXPRESSION.INVALID',
RESOURCE_HAS_NO_FIELDS: 'RESOURCE.HAS.NO.GIVEN.FIELDS',
};
export default class DynamicListing {
/**
* Constructor method.
* @param {DynamicListingBuilder} dynamicListingBuilder
* @return {DynamicListing|Error}
*/
constructor(dynamicListingBuilder) {
this.listingBuilder = dynamicListingBuilder;
this.dynamicFilter = new DynamicFilter(this.listingBuilder.modelClass.tableName);
return this.init();
}
/**
* Initialize the dynamic listing.
*/
init() {
// Initialize the column sort by.
if (this.listingBuilder.columnSortBy) {
const sortByFilter = new DynamicFilterSortBy(
filter.column_sort_by,
filter.sort_order
);
this.dynamicFilter.setFilter(sortByFilter);
}
// Initialize the view filter roles.
if (this.listingBuilder.view && this.listingBuilder.view.roles.length > 0) {
const viewFilter = new DynamicFilterViews(
mapViewRolesToConditionals(this.listingBuilder.view.roles),
this.listingBuilder.view.rolesLogicExpression
);
if (!viewFilter.validateFilterRoles()) {
return new Error(DYNAMIC_LISTING_ERRORS.LOGIC_INVALID);
}
this.dynamicFilter.setFilter(viewFilter);
}
// Initialize the dynamic filter roles.
if (this.listingBuilder.filterRoles.length > 0) {
const filterRoles = new DynamicFilterFilterRoles(
mapFilterRolesToDynamicFilter(filter.filter_roles),
accountsResource.fields
);
this.dynamicFilter.setFilter(filterRoles);
if (filterRoles.validateFilterRoles().length > 0) {
return new Error(DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS);
}
}
return this;
}
/**
* Build query.
*/
buildQuery(){
return this.dynamicFilter.buildQuery();
}
}

View File

@@ -1,25 +0,0 @@
export default class DynamicListingBuilder {
addModelClass(modelClass) {
this.modelClass = modelClass;
}
addCustomViewId(customViewId) {
this.customViewId = customViewId;
}
addFilterRoles (filterRoles) {
this.filterRoles = filterRoles;
}
addSortBy(sortBy, sortOrder) {
this.sortBy = sortBy;
this.sortOrder = sortOrder;
}
addView(view) {
this.view = view;
}
}

View File

@@ -1,22 +0,0 @@
import { DYNAMIC_LISTING_ERRORS } from 'services/DynamicListing/DynamicListing';
export const dynamicListingErrorsToResponse = (error) => {
let _errors;
if (error.message === DYNAMIC_LISTING_ERRORS.LOGIC_INVALID) {
_errors.push({
type: DYNAMIC_LISTING_ERRORS.LOGIC_INVALID,
code: 200,
});
}
if (
error.message ===
DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS
) {
_errors.push({
type: DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS,
code: 300,
});
}
return _errors;
};