refactor: dynamic list to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-14 22:57:54 +02:00
parent 081fdebee0
commit e7e7a95aa1
81 changed files with 596 additions and 742 deletions

View File

@@ -1,15 +1,18 @@
import { forEach } from 'lodash';
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
import { IDynamicFilter, IFilterRole } from './DynamicFilter.types';
import { BaseModel } from '@/models/Model';
import { IFilterRole } from './DynamicFilter.types';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
import { MetableModel } from '../types/DynamicList.types';
export class DynamicFilter<R extends {}> extends DynamicFilterAbstractor {
public model: MetableModel;
public dynamicFilters: DynamicFilterRoleAbstractor[];
export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Constructor.
* @param {String} tableName -
* @param {MetableModel} model - Metable model.
*/
constructor(model: typeof BaseModel) {
constructor(model: MetableModel) {
super();
this.model = model;
@@ -29,7 +32,7 @@ export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Retrieve dynamic filter build queries.
* @returns
* @returns {Function[]}
*/
private dynamicFiltersBuildQuery = () => {
return this.dynamicFilters.map((filter) => {
@@ -72,16 +75,16 @@ export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Retrieve response metadata from all filters adapters.
*/
public getResponseMeta = () => {
public getResponseMeta = (): R => {
const responseMeta = {};
this.dynamicFilters.forEach((filter) => {
const { responseMeta: filterMeta } = filter;
const filterMeta = filter.getResponseMeta();
forEach(filterMeta, (value, key) => {
responseMeta[key] = value;
});
});
return responseMeta;
return responseMeta as R;
};
}

View File

@@ -19,7 +19,7 @@ export interface IDynamicListFilter {
customViewId?: number;
filterRoles?: IFilterRole[];
columnSortBy: ISortOrder;
sortOrder: string;
sortOrder: ISortOrder;
stringifiedFilterRoles?: string;
searchKeyword?: string;
viewSlug?: string;

View File

@@ -1,13 +1,13 @@
import { BaseModel } from '@/models/Model';
import { IDynamicFilter } from './DynamicFilter.types';
import { MetableModel } from '../types/DynamicList.types';
export class DynamicFilterAbstractor {
public model: typeof BaseModel;
public model: MetableModel;
public dynamicFilters: IDynamicFilter[];
/**
* Extract relation table name from relation.
* @param {String} column -
* @param {String} column - Column name
* @return {String} - join relation table.
*/
protected getTableFromRelationColumn = (column: string) => {

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { OPERATION } from '@/libs/logic-evaluation/Parser';
export default class QueryParser {
export class DynamicFilterQueryParser {
constructor(tree, queries) {
this.tree = tree;
this.queries = queries;

View File

@@ -1,27 +1,26 @@
import moment from 'moment';
import * as R from 'ramda';
import { IFilterRole, IDynamicFilter } from './DynamicFilter.types';
import Parser from '@/libs/logic-evaluation/Parser';
import { Parser } from '@/libs/logic-evaluation/Parser';
import { Lexer } from '@/libs/logic-evaluation/Lexer';
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
import { DynamicFilterQueryParser } from './DynamicFilterQueryParser';
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
import { BaseModel } from '@/models/Model';
import { IMetadataModel } from '../models/MetadataModel';
type MetadataModel = typeof BaseModel & IMetadataModel;
import { MetableModel } from '../types/DynamicList.types';
import { Knex } from 'knex';
export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
protected filterRoles: IFilterRole[] = [];
protected tableName: string;
protected model: MetadataModel;
protected responseMeta: { [key: string]: any } = {};
public filterRoles: IFilterRole[] = [];
public tableName: string;
public model: MetableModel;
public responseMeta: { [key: string]: any } = {};
public relationFields = [];
/**
* Sets model the dynamic filter service.
* @param {IModel} model
*/
public setModel(model: MetadataModel) {
public setModel(model: MetableModel) {
this.model = model;
this.tableName = model.tableName;
}
@@ -118,7 +117,7 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
* @param {IModel} model -
* @param {} -
*/
private getFieldComparatorColumn = (field) => {
protected getFieldComparatorColumn = (field) => {
return field.fieldType === FIELD_TYPE.RELATION
? this.getFieldComparatorRelationColumn(field)
: `${this.tableName}.${field.column}`;
@@ -129,7 +128,7 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
* @param {IModel} model -
* @param {Object} role -
*/
protected buildRoleQuery = (model: MetadataModel, role: IFilterRole) => {
protected buildRoleQuery = (model: MetableModel, role: IFilterRole) => {
const field = model.getField(role.fieldKey);
const comparatorColumn = this.getFieldComparatorColumn(field);
@@ -384,10 +383,17 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
*/
onInitialize() {}
buildQuery(): void {
/**
* Builds the query.
*/
buildQuery(): (builder: Knex.QueryBuilder) => void {
throw new Error('Method not implemented.');
}
/**
* Retrieves the response meta.
*/
getResponseMeta() {
throw new Error('Method not implemented.');
}
}
}

View File

@@ -1,6 +1,10 @@
import { IFilterRole } from './DynamicFilter.types';
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
export interface IDynamicFilterSearchResponseMeta {
searchKeyword: string;
}
export class DynamicFilterSearch extends DynamicFilterFilterRoles {
private searchKeyword: string;
@@ -23,7 +27,7 @@ export class DynamicFilterSearch extends DynamicFilterFilterRoles {
/**
* Retrieve the filter roles from model search roles.
* @param {string} searchKeyword
* @param {string} searchKeyword
* @returns {IFilterRole[]}
*/
private getModelSearchFilterRoles(searchKeyword: string): IFilterRole[] {
@@ -37,11 +41,21 @@ export class DynamicFilterSearch extends DynamicFilterFilterRoles {
}
/**
*
* Sets the response meta.
*/
setResponseMeta() {
this.responseMeta = {
searchKeyword: this.searchKeyword,
};
}
/**
* Retrieves the response meta.
* @returns {IDynamicFilterSearchResponseMeta}
*/
public getResponseMeta(): IDynamicFilterSearchResponseMeta {
return {
searchKeyword: this.searchKeyword,
};
}
}

View File

@@ -1,5 +1,4 @@
import { FIELD_TYPE } from './constants';
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
interface ISortRole {
@@ -8,7 +7,10 @@ interface ISortRole {
}
export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
private sortRole: ISortRole = {};
private sortRole: ISortRole = {
fieldKey: '',
order: '',
};
/**
* Constructor method.
@@ -22,7 +24,6 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
fieldKey: sortByFieldKey,
order: sortDirection,
};
this.setResponseMeta();
}
/**
@@ -54,7 +55,7 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
* @param {IModel} field
* @returns {string}
*/
private getFieldComparatorColumn = (field): string => {
getFieldComparatorColumn = (field) => {
return field.fieldType === FIELD_TYPE.RELATION
? this.getFieldComparatorRelationColumn(field)
: `${this.tableName}.${field.column}`;
@@ -84,10 +85,10 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
/**
* Sets response meta.
*/
public setResponseMeta() {
this.responseMeta = {
sortOrder: this.sortRole.fieldKey,
sortBy: this.sortRole.order,
public getResponseMeta(): ISortRole {
return {
fieldKey: this.sortRole.fieldKey,
order: this.sortRole.order,
};
}
}

View File

@@ -1,5 +1,6 @@
import { omit } from 'lodash';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
import { IView } from '@/modules/Views/Views.types';
export class DynamicFilterViews extends DynamicFilterRoleAbstractor {
private viewSlug: string;

View File

@@ -6,7 +6,8 @@ import { DynamicListCustomView } from './DynamicListCustomView.service';
import { Injectable } from '@nestjs/common';
import { DynamicListFilterRoles } from './DynamicListFilterRoles.service';
import { DynamicFilter } from './DynamicFilter';
import { BaseModel } from '@/models/Model';
import { MetableModel } from './types/DynamicList.types';
import { IFilterMeta } from '@/interfaces/Model';
@Injectable()
export class DynamicListService {
@@ -19,10 +20,13 @@ export class DynamicListService {
/**
* Parses filter DTO.
* @param {IMode} model -
* @param {} filterDTO -
* @param {MetableModel} model - Metable model.
* @param {IDynamicListFilter} filterDTO - Dynamic list filter DTO.
*/
private parseFilterObject = (model, filterDTO) => {
private parseFilterObject = (
model: MetableModel,
filterDTO: IDynamicListFilter,
) => {
return {
// Merges the default properties with filter object.
...(model.defaultSort
@@ -37,15 +41,14 @@ export class DynamicListService {
/**
* Dynamic listing.
* @param {number} tenantId - Tenant id.
* @param {IModel} model - Model.
* @param {IModel} model - Metable model.
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
*/
public dynamicList = async (
model: typeof BaseModel,
model: MetableModel,
filter: IDynamicListFilter,
) => {
const dynamicFilter = new DynamicFilter(model);
const dynamicFilter = new DynamicFilter<IFilterMeta>(model);
// Parses the filter object.
const parsedFilter = this.parseFilterObject(model, filter);
@@ -99,5 +102,5 @@ export class DynamicListService {
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
: [],
};
};
}
}

View File

@@ -2,21 +2,22 @@ import { Injectable } from '@nestjs/common';
import { ERRORS } from './constants';
import { DynamicFilterViews } from './DynamicFilter';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicListServiceAbstract } from './DynamicListServiceAbstract';
import { IView } from '../Views/Views.types';
import { MetableModel } from './types/DynamicList.types';
@Injectable()
export class DynamicListCustomView extends DynamicListServiceAbstract {
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @param {string} viewSlug - View slug.
* @param {MetableModel} model - Metable model.
* @return {Promise<IView>}
*/
private getCustomViewOrThrowError = async (
private async getCustomViewOrThrowError(
viewSlug: string,
model: BaseModel,
) => {
model: MetableModel,
): Promise<IView> {
// Finds the default view by the given view slug.
const defaultView = model.getDefaultViewBySlug(viewSlug);
@@ -24,12 +25,12 @@ export class DynamicListCustomView extends DynamicListServiceAbstract {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return defaultView;
};
}
/**
* Dynamic list custom view.
* @param {IModel} model
* @param {number} customViewId
* @param {DynamicFilter} dynamicFilter - Dynamic filter.
* @param {string} customViewSlug - Custom view slug.
* @returns {DynamicFilterRoleAbstractor}
*/
public dynamicListCustomView = async (

View File

@@ -3,10 +3,10 @@ import { Injectable } from '@nestjs/common';
import validator from 'is-my-json-valid';
import { IFilterRole } from './DynamicFilter/DynamicFilter.types';
import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter';
import { ERRORS } from './constants';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
import { MetableModel } from './types/DynamicList.types';
import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './constants';
@Injectable()
export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
@@ -34,12 +34,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Retrieve filter roles fields key that not exists on the given model.
* @param {BaseModel} model
* @param {MetableModel} model
* @param {IFilterRole} filterRoles
* @returns {string[]}
*/
private getFilterRolesFieldsNotExist = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
): string[] => {
return filterRoles
@@ -49,12 +49,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Validates existance the fields of filter roles.
* @param {BaseModel} model
* @param {MetableModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
private validateFilterRolesFieldsExistance = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
) => {
const invalidFieldsKeys = this.getFilterRolesFieldsNotExist(
@@ -82,12 +82,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Dynamic list filter roles.
* @param {BaseModel} model
* @param {IFilterRole[]} filterRoles
* @param {MetableModel} model - Metable model.
* @param {IFilterRole[]} filterRoles - Filter roles.
* @returns {DynamicFilterFilterRoles}
*/
public dynamicList = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
): DynamicFilterAdvancedFilter => {
const filterRolesParsed = R.compose(this.incrementFilterRolesIndex)(

View File

@@ -4,10 +4,11 @@ import { ERRORS } from './constants';
import { DynamicFilterSortBy } from './DynamicFilter';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
import { DynamicFilterAbstractor } from './DynamicFilter/DynamicFilterAbstractor';
import { MetableModel } from './types/DynamicList.types';
@Injectable()
export class DynamicListSortBy extends DynamicFilterRoleAbstractor {
export class DynamicListSortBy extends DynamicFilterAbstractor {
/**
* Dynamic list sort by.
* @param {BaseModel} model
@@ -16,7 +17,7 @@ export class DynamicListSortBy extends DynamicFilterRoleAbstractor {
* @returns {DynamicFilterSortBy}
*/
public dynamicSortBy(
model: BaseModel,
model: MetableModel,
columnSortBy: string,
sortOrder: ISortOrder,
) {

View File

@@ -11,7 +11,7 @@ const defaultModelMeta = {
fields2: {},
};
export interface IMetadataModel extends BaseModel {
export interface IMetadataModel {
meta: IModelMeta;
parsedMeta: IModelMeta;
fields: { [key: string]: IModelMetaField };

View File

@@ -1,9 +1,13 @@
import { BaseModel } from '@/models/Model';
import { IModelMeta } from '@/interfaces/Model';
import { ISearchRole } from '../DynamicFilter.types';
import { ISearchRole } from '../DynamicFilter/DynamicFilter.types';
type GConstructor<T = {}> = new (...args: any[]) => T;
export interface ISearchableBaseModel {
searchRoles: ISearchRole[];
}
export const SearchableBaseModelMixin = <T extends GConstructor<BaseModel>>(
Model: T,
) =>
@@ -11,7 +15,7 @@ export const SearchableBaseModelMixin = <T extends GConstructor<BaseModel>>(
/**
* Searchable model.
*/
static get searchable(): IModelMeta {
static get searchable(): boolean {
throw true;
}

View File

@@ -1,5 +1,9 @@
import { ISortOrder } from '@/interfaces/Model';
import { BaseModel } from '@/models/Model';
import { ICustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
import { IFilterRole } from '../DynamicFilter/DynamicFilter.types';
import { IMetadataModel } from '../models/MetadataModel';
import { ISearchableBaseModel } from '../models/SearchableBaseModel';
export interface IDynamicListFilter {
customViewId?: number;
@@ -8,4 +12,9 @@ export interface IDynamicListFilter {
sortOrder: string;
stringifiedFilterRoles: string;
searchKeyword?: string;
}
}
export type MetableModel = typeof BaseModel &
IMetadataModel &
ISearchableBaseModel &
ICustomViewBaseModel;