feat: optimize dynamic list service.

feat: inactive mode for accounts, items, customers and vendors services.
This commit is contained in:
a.bouhuolia
2021-07-29 08:46:41 +02:00
parent 720dc5b7d7
commit 9186076676
80 changed files with 2748 additions and 1806 deletions

View File

@@ -0,0 +1,6 @@
export default class DynamicListAbstruct {
}

View File

@@ -0,0 +1,52 @@
import { Inject, Service } from 'typedi';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterViews from 'lib/DynamicFilter/DynamicFilterViews';
import { ServiceError } from 'exceptions';
import HasTenancyService from 'services/Tenancy/TenancyService';
import {ERRORS } from './constants';
import { IModel }from 'interfaces';
@Service()
export default class DynamicListCustomView extends DynamicListAbstruct {
@Inject()
tenancy: HasTenancyService;
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @return {Promise<IView>}
*/
private getCustomViewOrThrowError = async (
tenantId: number,
viewId: number,
model: IModel
) => {
const { viewRepository } = this.tenancy.repositories(tenantId);
const view = await viewRepository.findOneById(viewId, 'roles');
if (!view || view.resourceModel !== model.name) {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return view;
};
/**
* Dynamic list custom view.
* @param {IModel} model
* @param {number} customViewId
* @returns
*/
public dynamicListCustomView = async (
tenantId: number,
model,
customViewId: number
) => {
const view = await this.getCustomViewOrThrowError(
tenantId,
customViewId,
model
);
return new DynamicFilterViews(view);
};
}

View File

@@ -0,0 +1,103 @@
import { Service } from 'typedi';
import * as R from 'ramda';
import validator from 'is-my-json-valid';
import { IFilterRole, IModel } from 'interfaces';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterFilterRoles from 'lib/DynamicFilter/DynamicFilterFilterRoles';
import { ERRORS } from './constants';
import { ServiceError } from 'exceptions';
@Service()
export default class DynamicListFilterRoles extends DynamicListAbstruct {
/**
* Validates filter roles schema.
* @param {IFilterRole[]} filterRoles - Filter roles.
*/
private validateFilterRolesSchema = (filterRoles: IFilterRole[]) => {
const validate = validator({
required: true,
type: 'object',
properties: {
condition: { type: 'string' },
fieldKey: { required: true, type: 'string' },
value: { required: true },
},
});
const invalidFields = filterRoles.filter((filterRole) => {
return !validate(filterRole);
});
if (invalidFields.length > 0) {
throw new ServiceError(ERRORS.STRINGIFIED_FILTER_ROLES_INVALID);
}
};
/**
* Retrieve filter roles fields key that not exists on the given model.
* @param {IModel} model
* @param {IFilterRole} filterRoles
* @returns {string[]}
*/
private getFilterRolesFieldsNotExist = (
model,
filterRoles: IFilterRole[]
): string[] => {
return filterRoles
.filter((filterRole) => !model.getField(filterRole.fieldKey))
.map((filterRole) => filterRole.fieldKey);
};
/**
* Validates existance the fields of filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
private validateFilterRolesFieldsExistance = (
model: IModel,
filterRoles: IFilterRole[]
) => {
const invalidFieldsKeys = this.getFilterRolesFieldsNotExist(
model,
filterRoles
);
if (invalidFieldsKeys.length > 0) {
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
}
};
/**
* Associate index to filter roles.
* @param {IFilterRole[]} filterRoles
* @returns {IFilterRole[]}
*/
private incrementFilterRolesIndex = (
filterRoles: IFilterRole[]
): IFilterRole[] => {
return filterRoles.map((filterRole, index) => ({
...filterRole,
index: index + 1,
}));
};
/**
* Dynamic list filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @returns {DynamicFilterFilterRoles}
*/
public dynamicList = (
model: IModel,
filterRoles: IFilterRole[]
): DynamicFilterFilterRoles => {
const filterRolesParsed = R.compose(this.incrementFilterRolesIndex)(
filterRoles
);
// Validate filter roles json schema.
this.validateFilterRolesSchema(filterRolesParsed);
// Validate the model resource fields.
this.validateFilterRolesFieldsExistance(model, filterRoles);
return new DynamicFilterFilterRoles(filterRolesParsed);
};
}

View File

@@ -1,161 +1,88 @@
import { Service, Inject } from 'typedi';
import validator from 'is-my-json-valid';
import { Request, Response, NextFunction } from 'express';
import { castArray, isEmpty } from 'lodash';
import { ServiceError } from 'exceptions';
import { DynamicFilter } from 'lib/DynamicFilter';
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from 'lib/DynamicFilter';
import {
validateFieldKeyExistance,
validateFilterRolesFieldsExistance,
} from 'lib/ViewRolesBuilder';
import {
IDynamicListFilterDTO,
IFilterRole,
IDynamicListFilter,
IDynamicListService,
IFilterRole,
IModel,
} from 'interfaces';
import TenancyService from 'services/Tenancy/TenancyService';
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',
};
import DynamicListFilterRoles from './DynamicListFilterRoles';
import DynamicListSortBy from './DynamicListSortBy';
import DynamicListCustomView from './DynamicListCustomView';
@Service()
export default class DynamicListService implements IDynamicListService {
@Inject()
tenancy: TenancyService;
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @return {Promise<IView>}
*/
private async getCustomViewOrThrowError(
tenantId: number,
viewId: number,
model: IModel
) {
const { viewRepository } = this.tenancy.repositories(tenantId);
const view = await viewRepository.findOneById(viewId, 'roles');
@Inject()
dynamicListFilterRoles: DynamicListFilterRoles;
if (!view || view.resourceModel !== model.name) {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return view;
}
@Inject()
dynamicListSortBy: DynamicListSortBy;
@Inject()
dynamicListView: DynamicListCustomView;
/**
* Validates the sort column whether exists.
* @param {IModel} model
* @param {string} columnSortBy - Sort column
* @throws {ServiceError}
* Parses filter DTO.
*/
private validateSortColumnExistance(model: any, columnSortBy: string) {
const notExistsField = validateFieldKeyExistance(model, 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}
*/
private validateRolesFieldsExistance(
model: IModel,
filterRoles: IFilterRole[]
) {
const invalidFieldsKeys = validateFilterRolesFieldsExistance(
model,
filterRoles
);
if (invalidFieldsKeys.length > 0) {
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
}
}
/**
* Validates filter roles schema.
* @param {IFilterRole[]} filterRoles
*/
private validateFilterRolesSchema(filterRoles: IFilterRole[]) {
const validate = validator({
required: true,
type: 'object',
properties: {
condition: { type: 'string' },
fieldKey: { required: true, type: 'string' },
value: { required: true },
},
});
const invalidFields = filterRoles.filter((filterRole) => {
const isValid = validate(filterRole);
return isValid ? false : true;
});
if (invalidFields.length > 0) {
throw new ServiceError('stringified_filter_roles_invalid');
}
}
private parseFilterObject = (model, filterDTO) => {
return {
// Merges the default properties with filter object.
...model.defaultSort ? {
sortOrder: model.defaultSort.sortOrder,
columnSortBy: model.defaultSort.sortOrder,
} : {},
...filterDTO,
};
};
/**
* Dynamic listing.
* @param {number} tenantId - Tenant id.
* @param {IModel} model - Model.
* @param {IDynamicListFilterDTO} filter - Dynamic filter DTO.
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
*/
public async dynamicList(
public dynamicList = async (
tenantId: number,
model: IModel,
filter: IDynamicListFilterDTO
) {
filter: IDynamicListFilter
) => {
const dynamicFilter = new DynamicFilter(model);
// Custom view filter roles.
if (filter.customViewId) {
const view = await this.getCustomViewOrThrowError(
tenantId,
filter.customViewId,
model
);
const viewFilter = new DynamicFilterViews(view);
dynamicFilter.setFilter(viewFilter);
}
// Sort by the given column.
if (filter.columnSortBy) {
this.validateSortColumnExistance(model, filter.columnSortBy);
// Parses the filter object.
const parsedFilter = this.parseFilterObject(model, filter);
const sortByFilter = new DynamicFilterSortBy(
filter.columnSortBy,
filter.sortOrder
// Custom view filter roles.
// if (filter.customViewId) {
// const dynamicListCustomView = this.dynamicListView.dynamicListCustomView();
// dynamicFilter.setFilter(dynamicListCustomView);
// }
// Sort by the given column.
if (parsedFilter.columnSortBy) {
const dynmaicListSortBy = this.dynamicListSortBy.dynamicSortBy(
model,
parsedFilter.columnSortBy,
parsedFilter.sortOrder,
);
dynamicFilter.setFilter(sortByFilter);
dynamicFilter.setFilter(dynmaicListSortBy);
}
// Filter roles.
if (filter.filterRoles.length > 0) {
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
...filterRole,
index: index + 1,
}));
this.validateFilterRolesSchema(filterRoles);
this.validateRolesFieldsExistance(model, filterRoles);
// Validate the model resource fields.
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
if (!isEmpty(parsedFilter.filterRoles)) {
const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
model,
parsedFilter.filterRoles
);
dynamicFilter.setFilter(dynamicFilterRoles);
}
return dynamicFilter;
}
};
/**
* Middleware to catch services errors
@@ -173,25 +100,62 @@ export default class DynamicListService implements IDynamicListService {
if (error instanceof ServiceError) {
if (error.errorType === 'sort_column_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'SORT.COLUMN.NOT.FOUND', code: 200 }],
errors: [
{
type: 'SORT.COLUMN.NOT.FOUND',
message: '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 }],
errors: [
{
type: 'CUSTOM.VIEW.NOT.FOUND',
message: '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 }],
errors: [
{
type: 'FILTER.ROLES.FIELDS.NOT.FOUND',
message: '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 }],
errors: [
{
type: 'STRINGIFIED_FILTER_ROLES_INVALID',
message: 'Stringified filter roles json invalid.',
code: 400,
},
],
});
}
}
next(error);
}
/**
* Parses stringified filter roles.
* @param {string} stringifiedFilterRoles - Stringified filter roles.
*/
public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
return {
...filterRoles,
filterRoles: filterRoles.stringifiedFilterRoles
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
: [],
};
};
}

View File

@@ -0,0 +1,40 @@
import { Service } from 'typedi';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterSortBy from 'lib/DynamicFilter/DynamicFilterSortBy';
import { IModel, ISortOrder } from 'interfaces';
import { ServiceError } from 'exceptions';
import { ERRORS } from './constants';
@Service()
export default class DynamicListSortBy extends DynamicListAbstruct {
/**
* Dynamic list sort by.
* @param {IModel} model
* @param {string} columnSortBy
* @param {ISortOrder} sortOrder
* @returns {DynamicFilterSortBy}
*/
public dynamicSortBy(
model: IModel,
columnSortBy: string,
sortOrder: ISortOrder
) {
this.validateSortColumnExistance(model, columnSortBy);
return new DynamicFilterSortBy(columnSortBy, sortOrder);
}
/**
* Validates the sort column whether exists.
* @param {IModel} model - Model.
* @param {string} columnSortBy - Sort column
* @throws {ServiceError}
*/
private validateSortColumnExistance(model: any, columnSortBy: string) {
const field = model.getField(columnSortBy);
if (!field) {
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,6 @@
export const ERRORS = {
STRINGIFIED_FILTER_ROLES_INVALID: 'stringified_filter_roles_invalid',
VIEW_NOT_FOUND: 'view_not_found',
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
};