mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
refactor: wip migrate ot nestjs
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { forEach } from 'lodash';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
import { IDynamicFilter, IFilterRole, IModel } from '@/interfaces';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export class DynamicFilter extends DynamicFilterAbstractor {
|
||||
private model: BaseModel;
|
||||
private dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {String} tableName -
|
||||
*/
|
||||
constructor(model: BaseModel) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
this.dynamicFilters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given dynamic filter.
|
||||
* @param {IDynamicFilter} filterRole - Filter role.
|
||||
*/
|
||||
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||
dynamicFilter.setModel(this.model);
|
||||
dynamicFilter.onInitialize();
|
||||
|
||||
this.dynamicFilters.push(dynamicFilter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter build queries.
|
||||
* @returns
|
||||
*/
|
||||
private dynamicFiltersBuildQuery = () => {
|
||||
return this.dynamicFilters.map((filter) => {
|
||||
return filter.buildQuery();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter roles.
|
||||
* @returns {IFilterRole[]}
|
||||
*/
|
||||
private dynamicFilterTableColumns = (): IFilterRole[] => {
|
||||
const localFilterRoles = [];
|
||||
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const { filterRoles } = dynamicFilter;
|
||||
|
||||
localFilterRoles.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles]),
|
||||
);
|
||||
});
|
||||
return localFilterRoles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds queries of filter roles.
|
||||
*/
|
||||
public buildQuery = () => {
|
||||
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||
const tableColumns = this.dynamicFilterTableColumns();
|
||||
|
||||
return (builder) => {
|
||||
buildersCallbacks.forEach((builderCallback) => {
|
||||
builderCallback(builder);
|
||||
});
|
||||
this.buildFilterRolesJoins(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve response metadata from all filters adapters.
|
||||
*/
|
||||
public getResponseMeta = () => {
|
||||
const responseMeta = {};
|
||||
|
||||
this.dynamicFilters.forEach((filter) => {
|
||||
const { responseMeta: filterMeta } = filter;
|
||||
|
||||
forEach(filterMeta, (value, key) => {
|
||||
responseMeta[key] = value;
|
||||
});
|
||||
});
|
||||
return responseMeta;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
// import { IModel, ISortOrder } from "./Model";
|
||||
|
||||
export type ISortOrder = 'DESC' | 'ASC';
|
||||
|
||||
export interface IDynamicFilter {
|
||||
setModel(model: BaseModel): void;
|
||||
buildQuery(): void;
|
||||
getResponseMeta();
|
||||
}
|
||||
export interface IFilterRole {
|
||||
fieldKey: string;
|
||||
value: string;
|
||||
condition?: string;
|
||||
index?: number;
|
||||
comparator?: string;
|
||||
}
|
||||
export interface IDynamicListFilter {
|
||||
customViewId?: number;
|
||||
filterRoles?: IFilterRole[];
|
||||
columnSortBy: ISortOrder;
|
||||
sortOrder: string;
|
||||
stringifiedFilterRoles: string;
|
||||
searchKeyword?: string;
|
||||
viewSlug?: string;
|
||||
}
|
||||
|
||||
export interface IDynamicListService {
|
||||
dynamicList(
|
||||
model: any,
|
||||
filter: IDynamicListFilter,
|
||||
): Promise<any>;
|
||||
handlerErrorsToResponse(error, req, res, next): void;
|
||||
}
|
||||
|
||||
// Search role.
|
||||
export interface ISearchRole {
|
||||
fieldKey: string;
|
||||
comparator: string;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { IDynamicFilter } from './DynamicFilter.types';
|
||||
|
||||
export class DynamicFilterAbstractor {
|
||||
model: BaseModel;
|
||||
dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
* @param {String} column -
|
||||
* @return {String} - join relation table.
|
||||
*/
|
||||
protected getTableFromRelationColumn = (column: string) => {
|
||||
const splitedColumn = column.split('.');
|
||||
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds view roles join queries.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Roles.
|
||||
*/
|
||||
protected buildFilterRolesJoins = (builder) => {
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const relationsFields = dynamicFilter.relationFields;
|
||||
|
||||
this.buildFieldsJoinQueries(builder, relationsFields);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds join queries of fields.
|
||||
* @param builder -
|
||||
* @param {string[]} fieldsRelations -
|
||||
*/
|
||||
private buildFieldsJoinQueries = (builder, fieldsRelations: string[]) => {
|
||||
fieldsRelations.forEach((fieldRelation) => {
|
||||
const relation = this.model.relationMappings[fieldRelation];
|
||||
|
||||
if (relation) {
|
||||
const splitToRelation = relation.join.to.split('.');
|
||||
const relationTable = splitToRelation[0] || '';
|
||||
|
||||
builder.join(relationTable, relation.join.from, '=', relation.join.to);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the dynamic filter mode.
|
||||
*/
|
||||
protected getModel() {
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { IFilterRole } from './DynamicFilter.types';
|
||||
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
|
||||
|
||||
export class DynamicFilterAdvancedFilter extends DynamicFilterFilterRoles {
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Array} filterRoles -
|
||||
* @param {Array} resourceFields -
|
||||
*/
|
||||
constructor(filterRoles: IFilterRole[]) {
|
||||
super();
|
||||
|
||||
this.filterRoles = filterRoles;
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
private setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
filterRoles: this.filterRoles,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
import { IFilterRole } from '@/interfaces';
|
||||
|
||||
export class DynamicFilterFilterRoles extends DynamicFilterAbstractor {
|
||||
private filterRoles: IFilterRole[];
|
||||
/**
|
||||
* On initialize filter roles.
|
||||
*/
|
||||
public onInitialize() {
|
||||
super.onInitialize();
|
||||
this.setFilterRolesRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter roles logic expression.
|
||||
* @return {string}
|
||||
*/
|
||||
private buildLogicExpression(): string {
|
||||
let expression = '';
|
||||
|
||||
this.filterRoles.forEach((role, index) => {
|
||||
expression +=
|
||||
index === 0 ? `${role.index} ` : `${role.condition} ${role.index} `;
|
||||
});
|
||||
return expression.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
protected buildQuery() {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
|
||||
return (builder) => {
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
logicExpression
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filter roles relations if field was relation type.
|
||||
*/
|
||||
private setFilterRolesRelations() {
|
||||
this.filterRoles.forEach((relationRole) => {
|
||||
this.setRelationIfRelationField(relationRole.fieldKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { OPERATION } from '@/libs/logic-evaluation/Parser';
|
||||
|
||||
export default class QueryParser {
|
||||
constructor(tree, queries) {
|
||||
this.tree = tree;
|
||||
this.queries = queries;
|
||||
this.query = null;
|
||||
}
|
||||
|
||||
setQuery(query) {
|
||||
this.query = query.clone();
|
||||
}
|
||||
|
||||
parse() {
|
||||
return this.parseNode(this.tree);
|
||||
}
|
||||
|
||||
parseNode(node) {
|
||||
if (typeof node === 'string') {
|
||||
const nodeQuery = this.getQuery(node);
|
||||
return (query) => {
|
||||
nodeQuery(query);
|
||||
};
|
||||
}
|
||||
if (OPERATION[node.operation] === undefined) {
|
||||
throw new Error(`unknow expression ${node.operation}`);
|
||||
}
|
||||
const leftQuery = this.getQuery(node.left);
|
||||
const rightQuery = this.getQuery(node.right);
|
||||
|
||||
switch (node.operation) {
|
||||
case '&&':
|
||||
case 'AND':
|
||||
default:
|
||||
return (nodeQuery) =>
|
||||
nodeQuery.where((query) => {
|
||||
query.where((q) => {
|
||||
leftQuery(q);
|
||||
});
|
||||
query.andWhere((q) => {
|
||||
rightQuery(q);
|
||||
});
|
||||
});
|
||||
case '||':
|
||||
case 'OR':
|
||||
return (nodeQuery) =>
|
||||
nodeQuery.where((query) => {
|
||||
query.where((q) => {
|
||||
leftQuery(q);
|
||||
});
|
||||
query.orWhere((q) => {
|
||||
rightQuery(q);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getQuery(node) {
|
||||
if (typeof node !== 'string' && node !== null) {
|
||||
return this.parseNode(node);
|
||||
}
|
||||
const value = parseFloat(node);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
if (typeof this.queries[node] === 'undefined') {
|
||||
throw new Error(`unknow query under index ${node}`);
|
||||
}
|
||||
return this.queries[node];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { IFilterRole, IDynamicFilter, } from './DynamicFilter.types';
|
||||
import Parser from '@/libs/logic-evaluation/Parser';
|
||||
import { Lexer } from '@/libs/logic-evaluation/Lexer';
|
||||
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
|
||||
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export abstract class DynamicFilterAbstractor
|
||||
implements IDynamicFilter
|
||||
{
|
||||
protected filterRoles: IFilterRole[] = [];
|
||||
protected tableName: string;
|
||||
protected model: BaseModel;
|
||||
protected responseMeta: { [key: string]: any } = {};
|
||||
public relationFields = [];
|
||||
|
||||
/**
|
||||
* Sets model the dynamic filter service.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
public setModel(model: BaseModel) {
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes filter roles to map by index.
|
||||
* @param {IModel} model
|
||||
* @param {IFilterRole[]} roles
|
||||
* @returns
|
||||
*/
|
||||
protected convertRolesMapByIndex = (model, roles) => {
|
||||
const rolesIndexSet = {};
|
||||
|
||||
roles.forEach((role) => {
|
||||
rolesIndexSet[role.index] = this.buildRoleQuery(model, role);
|
||||
});
|
||||
return rolesIndexSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query from stored view roles.
|
||||
* @param {Array} roles -
|
||||
* @return {Function}
|
||||
*/
|
||||
protected buildFilterRolesQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
) => {
|
||||
const rolesIndexSet = this.convertRolesMapByIndex(model, roles);
|
||||
|
||||
// Lexer for logic expression.
|
||||
const lexer = new Lexer(logicExpression);
|
||||
const tokens = lexer.getTokens();
|
||||
|
||||
// Parse the logic expression.
|
||||
const parser = new Parser(tokens);
|
||||
const parsedTree = parser.parse();
|
||||
|
||||
const queryParser = new DynamicFilterQueryParser(parsedTree, rolesIndexSet);
|
||||
|
||||
return queryParser.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the logic expression to base expression.
|
||||
* @param {string} logicExpression -
|
||||
* @return {string}
|
||||
*/
|
||||
private parseLogicExpression(logicExpression: string): string {
|
||||
return R.compose(
|
||||
R.replace(/or|OR/g, '||'),
|
||||
R.replace(/and|AND/g, '&&'),
|
||||
)(logicExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter query for query builder.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Filter roles.
|
||||
* @param {String} logicExpression - Logic expression.
|
||||
*/
|
||||
protected buildFilterQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
) => {
|
||||
const basicExpression = this.parseLogicExpression(logicExpression);
|
||||
|
||||
return (builder) => {
|
||||
this.buildFilterRolesQuery(model, roles, basicExpression)(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve relation column of comparator fieldز
|
||||
*/
|
||||
private getFieldComparatorRelationColumn(field) {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationColumn =
|
||||
field.relationEntityKey === 'id'
|
||||
? 'id'
|
||||
: relationModel.getField(field.relationEntityKey, 'column');
|
||||
|
||||
return `${relationModel.tableName}.${relationColumn}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} model -
|
||||
* @param {} -
|
||||
*/
|
||||
private getFieldComparatorColumn = (field) => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
protected buildRoleQuery = (model: BaseModel, role: IFilterRole) => {
|
||||
const field = model.getField(role.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
// Field relation custom query.
|
||||
if (typeof field.filterCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
field.filterCustomQuery(builder, role);
|
||||
};
|
||||
}
|
||||
switch (field.fieldType) {
|
||||
case FIELD_TYPE.BOOLEAN:
|
||||
case FIELD_TYPE.ENUMERATION:
|
||||
return this.booleanRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.NUMBER:
|
||||
return this.numberRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.DATE:
|
||||
return this.dateQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.TEXT:
|
||||
default:
|
||||
return this.textRoleQueryBuilder(role, comparatorColumn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Boolean column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected booleanRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<>', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Numeric column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_THAN:
|
||||
case COMPARATOR_TYPE.BIGGER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_THAN:
|
||||
case COMPARATOR_TYPE.SMALLER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected textRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.CONTAIN:
|
||||
case COMPARATOR_TYPE.CONTAINS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_CONTAIN:
|
||||
case COMPARATOR_TYPE.NOT_CONTAINS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case COMPARATOR_TYPE.STARTS_WITH:
|
||||
case COMPARATOR_TYPE.START_WITH:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `${role.value}%`);
|
||||
};
|
||||
case COMPARATOR_TYPE.ENDS_WITH:
|
||||
case COMPARATOR_TYPE.END_WITH:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}`);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected dateQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.AFTER:
|
||||
case COMPARATOR_TYPE.BEFORE:
|
||||
return (builder) => {
|
||||
this.dateQueryAfterBeforeComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
case COMPARATOR_TYPE.IN:
|
||||
return (builder) => {
|
||||
this.dateQueryInComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date query 'IN' comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryInComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (hasTimeFormat) {
|
||||
const targetDateTime = moment(role.value).format(dateFormat);
|
||||
builder.where(comparatorColumn, '=', targetDateTime);
|
||||
} else {
|
||||
const startDate = moment(role.value).startOf('day');
|
||||
const endDate = moment(role.value).endOf('day');
|
||||
|
||||
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date query after/before comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn - Column.
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryAfterBeforeComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const comparator = role.comparator === COMPARATOR_TYPE.BEFORE ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (!hasTimeFormat) {
|
||||
if (role.comparator === COMPARATOR_TYPE.BEFORE) {
|
||||
targetDate.startOf('day');
|
||||
} else {
|
||||
targetDate.endOf('day');
|
||||
}
|
||||
}
|
||||
const comparatorValue = targetDate.format(dateFormat);
|
||||
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers relation field if the given field was relation type
|
||||
* and not registered.
|
||||
* @param {string} fieldKey - Field key.
|
||||
*/
|
||||
protected setRelationIfRelationField = (fieldKey: string): void => {
|
||||
const field = this.model.getField(fieldKey);
|
||||
const isAlreadyRegistered = this.relationFields.some(
|
||||
(field) => field === fieldKey
|
||||
);
|
||||
|
||||
if (
|
||||
!isAlreadyRegistered &&
|
||||
field &&
|
||||
field.fieldType === FIELD_TYPE.RELATION
|
||||
) {
|
||||
this.relationFields.push(field.relationKey);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the model.
|
||||
*/
|
||||
getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
/**
|
||||
* On initialize the registered dynamic filter.
|
||||
*/
|
||||
onInitialize() {}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { IFilterRole } from './DynamicFilter.types';
|
||||
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
|
||||
|
||||
export class DynamicFilterSearch extends DynamicFilterFilterRoles {
|
||||
private searchKeyword: string;
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {string} searchKeyword - Search keyword.
|
||||
*/
|
||||
constructor(searchKeyword: string) {
|
||||
super();
|
||||
this.searchKeyword = searchKeyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* On initialize the dynamic filter.
|
||||
*/
|
||||
public onInitialize() {
|
||||
super.onInitialize();
|
||||
this.filterRoles = this.getModelSearchFilterRoles(this.searchKeyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the filter roles from model search roles.
|
||||
* @param {string} searchKeyword
|
||||
* @returns {IFilterRole[]}
|
||||
*/
|
||||
private getModelSearchFilterRoles(searchKeyword: string): IFilterRole[] {
|
||||
const model = this.getModel();
|
||||
|
||||
return model.searchRoles.map((searchRole, index) => ({
|
||||
...searchRole,
|
||||
value: searchKeyword,
|
||||
index: index + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
searchKeyword: this.searchKeyword,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { FIELD_TYPE } from './constants';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
|
||||
interface ISortRole {
|
||||
fieldKey: string;
|
||||
order: string;
|
||||
}
|
||||
|
||||
export class DynamicFilterSortBy extends DynamicFilterAbstractor {
|
||||
private sortRole: ISortRole = {};
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {string} sortByFieldKey
|
||||
* @param {string} sortDirection
|
||||
*/
|
||||
constructor(sortByFieldKey: string, sortDirection: string) {
|
||||
super();
|
||||
|
||||
this.sortRole = {
|
||||
fieldKey: sortByFieldKey,
|
||||
order: sortDirection,
|
||||
};
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* On initialize the dyanmic sort by.
|
||||
*/
|
||||
public onInitialize() {
|
||||
this.setRelationIfRelationField(this.sortRole.fieldKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve field comparator relatin column.
|
||||
* @param field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorRelationColumn = (field): string => {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationField = relationModel.getField(field.relationEntityLabel);
|
||||
|
||||
return `${relationModel.tableName}.${relationField.column}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorColumn = (field): string => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query of sort by column on the given direction.
|
||||
*/
|
||||
public buildQuery = () => {
|
||||
const field = this.model.getField(this.sortRole.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
// Sort custom query.
|
||||
if (typeof field.sortCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
field.sortCustomQuery(builder, this.sortRole);
|
||||
};
|
||||
}
|
||||
|
||||
return (builder) => {
|
||||
if (this.sortRole.fieldKey) {
|
||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
public setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
sortOrder: this.sortRole.fieldKey,
|
||||
sortBy: this.sortRole.order,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { omit } from 'lodash';
|
||||
import { IView, IViewRole } from '@/interfaces';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
|
||||
export class DynamicFilterViews extends DynamicFilterAbstractor {
|
||||
private viewSlug: string;
|
||||
private logicExpression: string;
|
||||
private filterRoles: IViewRole[];
|
||||
private viewColumns = [];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IView} view -
|
||||
*/
|
||||
constructor(view: IView) {
|
||||
super();
|
||||
|
||||
this.viewSlug = view.slug;
|
||||
this.filterRoles = view.roles;
|
||||
this.viewColumns = view.columns;
|
||||
this.logicExpression = view.rolesLogicExpression
|
||||
.replace('AND', '&&')
|
||||
.replace('OR', '||');
|
||||
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
public buildQuery() {
|
||||
return (builder) => {
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
this.logicExpression
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
public setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
view: {
|
||||
logicExpression: this.logicExpression,
|
||||
filterRoles: this.filterRoles.map((filterRole) => ({
|
||||
...omit(filterRole, ['id', 'viewId']),
|
||||
})),
|
||||
viewSlug: this.viewSlug,
|
||||
viewColumns: this.viewColumns,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
export const COMPARATOR_TYPE = {
|
||||
EQUAL: 'equal',
|
||||
EQUALS: 'equals',
|
||||
|
||||
NOT_EQUAL: 'not_equal',
|
||||
NOT_EQUALS: 'not_equals',
|
||||
|
||||
BIGGER_THAN: 'bigger_than',
|
||||
BIGGER: 'bigger',
|
||||
BIGGER_OR_EQUALS: 'bigger_or_equals',
|
||||
|
||||
SMALLER_THAN: 'smaller_than',
|
||||
SMALLER: 'smaller',
|
||||
SMALLER_OR_EQUALS: 'smaller_or_equals',
|
||||
|
||||
IS: 'is',
|
||||
IS_NOT: 'is_not',
|
||||
|
||||
CONTAINS: 'contains',
|
||||
CONTAIN: 'contain',
|
||||
NOT_CONTAINS: 'contains',
|
||||
NOT_CONTAIN: 'contain',
|
||||
|
||||
AFTER: 'after',
|
||||
BEFORE: 'before',
|
||||
IN: 'in',
|
||||
|
||||
STARTS_WITH: 'starts_with',
|
||||
START_WITH: 'start_with',
|
||||
|
||||
ENDS_WITH: 'ends_with',
|
||||
END_WITH: 'end_with'
|
||||
};
|
||||
|
||||
export const FIELD_TYPE = {
|
||||
TEXT: 'text',
|
||||
NUMBER: 'number',
|
||||
ENUMERATION: 'enumeration',
|
||||
BOOLEAN: 'boolean',
|
||||
RELATION: 'relation',
|
||||
DATE: 'date',
|
||||
COMPUTED: 'computed'
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { DynamicFilter } from './DynamicFilter';
|
||||
import { DynamicFilterSortBy } from './DynamicFilterSortBy';
|
||||
import { DynamicFilterViews } from './DynamicFilterViews';
|
||||
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
|
||||
|
||||
export {
|
||||
DynamicFilter,
|
||||
DynamicFilterSortBy,
|
||||
DynamicFilterViews,
|
||||
DynamicFilterFilterRoles,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export class DynamicListAbstract {}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { ERRORS } from './constants';
|
||||
import { DynamicFilterViews } from './DynamicFilter';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListCustomView extends DynamicListAbstract {
|
||||
/**
|
||||
* Retreive custom view or throws error not found.
|
||||
* @param {number} tenantId
|
||||
* @param {number} viewId
|
||||
* @return {Promise<IView>}
|
||||
*/
|
||||
private getCustomViewOrThrowError = async (
|
||||
viewSlug: string,
|
||||
model: BaseModel,
|
||||
) => {
|
||||
// Finds the default view by the given view slug.
|
||||
const defaultView = model.getDefaultViewBySlug(viewSlug);
|
||||
|
||||
if (!defaultView) {
|
||||
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
|
||||
}
|
||||
return defaultView;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic list custom view.
|
||||
* @param {IModel} model
|
||||
* @param {number} customViewId
|
||||
* @returns
|
||||
*/
|
||||
public dynamicListCustomView = async (
|
||||
dynamicFilter: any,
|
||||
customViewSlug: string,
|
||||
) => {
|
||||
const model = dynamicFilter.getModel();
|
||||
|
||||
// Retrieve the custom view or throw not found.
|
||||
const view = await this.getCustomViewOrThrowError(customViewSlug, model);
|
||||
return new DynamicFilterViews(view);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as R from 'ramda';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import validator from 'is-my-json-valid';
|
||||
import { IFilterRole } from './DynamicFilter/DynamicFilter.types';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter';
|
||||
import { ERRORS } from './constants';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListFilterRoles extends DynamicListAbstract {
|
||||
/**
|
||||
* Validates filter roles schema.
|
||||
* @param {IFilterRole[]} filterRoles - Filter roles.
|
||||
*/
|
||||
private validateFilterRolesSchema = (filterRoles: IFilterRole[]) => {
|
||||
const validate = validator({
|
||||
required: ['fieldKey', 'value'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
condition: { type: 'string' },
|
||||
fieldKey: { type: 'string' },
|
||||
value: { type: 'string' },
|
||||
},
|
||||
});
|
||||
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 {BaseModel} model
|
||||
* @param {IFilterRole} filterRoles
|
||||
* @returns {string[]}
|
||||
*/
|
||||
private getFilterRolesFieldsNotExist = (
|
||||
model: BaseModel,
|
||||
filterRoles: IFilterRole[],
|
||||
): string[] => {
|
||||
return filterRoles
|
||||
.filter((filterRole) => !model.getField(filterRole.fieldKey))
|
||||
.map((filterRole) => filterRole.fieldKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates existance the fields of filter roles.
|
||||
* @param {BaseModel} model
|
||||
* @param {IFilterRole[]} filterRoles
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
private validateFilterRolesFieldsExistance = (
|
||||
model: BaseModel,
|
||||
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 {BaseModel} model
|
||||
* @param {IFilterRole[]} filterRoles
|
||||
* @returns {DynamicFilterFilterRoles}
|
||||
*/
|
||||
public dynamicList = (
|
||||
model: BaseModel,
|
||||
filterRoles: IFilterRole[],
|
||||
): DynamicFilterAdvancedFilter => {
|
||||
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 DynamicFilterAdvancedFilter(filterRolesParsed);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { DynamicFilterSearch } from './DynamicFilter/DynamicFilterSearch';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListSearch extends DynamicListAbstract {
|
||||
/**
|
||||
* Dynamic list filter roles.
|
||||
* @param {string} searchKeyword - Search keyword.
|
||||
* @returns {DynamicFilterFilterRoles}
|
||||
*/
|
||||
public dynamicSearch = (searchKeyword: string) => {
|
||||
return new DynamicFilterSearch(searchKeyword);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import {
|
||||
IDynamicListFilter,
|
||||
IDynamicListService,
|
||||
} from './DynamicFilter/DynamicFilter.types';
|
||||
import { DynamicListSortBy } from './DynamicListSortBy';
|
||||
import { DynamicListSearch } from './DynamicListSearch';
|
||||
import { DynamicListCustomView } from './DynamicListCustomView';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListFilterRoles } from './DynamicListFilterRoles';
|
||||
import { DynamicFilter } from './DynamicFilter';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListService implements IDynamicListService {
|
||||
constructor(
|
||||
private dynamicListFilterRoles: DynamicListFilterRoles,
|
||||
private dynamicListSearch: DynamicListSearch,
|
||||
private dynamicListSortBy: DynamicListSortBy,
|
||||
private dynamicListView: DynamicListCustomView,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parses filter DTO.
|
||||
* @param {IMode} model -
|
||||
* @param {} filterDTO -
|
||||
*/
|
||||
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 {IDynamicListFilter} filter - Dynamic filter DTO.
|
||||
*/
|
||||
public dynamicList = async (model: BaseModel, filter: IDynamicListFilter) => {
|
||||
const dynamicFilter = new DynamicFilter(model);
|
||||
|
||||
// Parses the filter object.
|
||||
const parsedFilter = this.parseFilterObject(model, filter);
|
||||
|
||||
// Search by keyword.
|
||||
if (filter.searchKeyword) {
|
||||
const dynamicListSearch = this.dynamicListSearch.dynamicSearch(
|
||||
filter.searchKeyword,
|
||||
);
|
||||
dynamicFilter.setFilter(dynamicListSearch);
|
||||
}
|
||||
// Custom view filter roles.
|
||||
if (filter.viewSlug) {
|
||||
const dynamicListCustomView =
|
||||
await this.dynamicListView.dynamicListCustomView(
|
||||
dynamicFilter,
|
||||
filter.viewSlug,
|
||||
);
|
||||
dynamicFilter.setFilter(dynamicListCustomView);
|
||||
}
|
||||
// Sort by the given column.
|
||||
if (parsedFilter.columnSortBy) {
|
||||
const dynmaicListSortBy = this.dynamicListSortBy.dynamicSortBy(
|
||||
model,
|
||||
parsedFilter.columnSortBy,
|
||||
parsedFilter.sortOrder,
|
||||
);
|
||||
dynamicFilter.setFilter(dynmaicListSortBy);
|
||||
}
|
||||
// Filter roles.
|
||||
if (!isEmpty(parsedFilter.filterRoles)) {
|
||||
const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
|
||||
model,
|
||||
parsedFilter.filterRoles,
|
||||
);
|
||||
dynamicFilter.setFilter(dynamicFilterRoles);
|
||||
}
|
||||
return dynamicFilter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses stringified filter roles.
|
||||
* @param {string} stringifiedFilterRoles - Stringified filter roles.
|
||||
*/
|
||||
public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
|
||||
return {
|
||||
...filterRoles,
|
||||
filterRoles: filterRoles.stringifiedFilterRoles
|
||||
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
|
||||
: [],
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { ISortOrder } from './DynamicFilter/DynamicFilter.types';
|
||||
import { ERRORS } from './constants';
|
||||
import { DynamicFilterSortBy } from './DynamicFilter';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListSortBy extends DynamicListAbstract {
|
||||
/**
|
||||
* Dynamic list sort by.
|
||||
* @param {BaseModel} model
|
||||
* @param {string} columnSortBy
|
||||
* @param {ISortOrder} sortOrder
|
||||
* @returns {DynamicFilterSortBy}
|
||||
*/
|
||||
public dynamicSortBy(
|
||||
model: BaseModel,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
Reference in New Issue
Block a user