mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
|
||||
export default class DynamicListAbstruct {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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,
|
||||
viewSlug: string,
|
||||
model: IModel
|
||||
) => {
|
||||
const { View } = this.tenancy.models(tenantId);
|
||||
|
||||
// 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,
|
||||
tenantId: number
|
||||
) => {
|
||||
const model = dynamicFilter.getModel();
|
||||
|
||||
// Retrieve the custom view or throw not found.
|
||||
const view = await this.getCustomViewOrThrowError(
|
||||
tenantId,
|
||||
customViewSlug,
|
||||
model,
|
||||
);
|
||||
return new DynamicFilterViews(view);
|
||||
};
|
||||
}
|
||||
@@ -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 DynamicFilterAdvancedFilter from '@/lib/DynamicFilter/DynamicFilterAdvancedFilter';
|
||||
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[]
|
||||
): 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,18 @@
|
||||
import { Service } from 'typedi';
|
||||
import { IFilterRole, IModel } from '@/interfaces';
|
||||
import DynamicListAbstruct from './DynamicListAbstruct';
|
||||
import DynamicFilterFilterRoles from '@/lib/DynamicFilter/DynamicFilterFilterRoles';
|
||||
import DynamicFilterSearch from '@/lib/DynamicFilter/DynamicFilterSearch';
|
||||
|
||||
@Service()
|
||||
export default class DynamicListSearch extends DynamicListAbstruct {
|
||||
/**
|
||||
* Dynamic list filter roles.
|
||||
* @param {IModel} model
|
||||
* @param {IFilterRole[]} filterRoles
|
||||
* @returns {DynamicFilterFilterRoles}
|
||||
*/
|
||||
public dynamicSearch = (model: IModel, searchKeyword: string) => {
|
||||
return new DynamicFilterSearch(searchKeyword);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { DynamicFilter } from '@/lib/DynamicFilter';
|
||||
import {
|
||||
IDynamicListFilter,
|
||||
IDynamicListService,
|
||||
IFilterRole,
|
||||
IModel,
|
||||
} from '@/interfaces';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListFilterRoles from './DynamicListFilterRoles';
|
||||
import DynamicListSortBy from './DynamicListSortBy';
|
||||
import DynamicListCustomView from './DynamicListCustomView';
|
||||
import DynamicListSearch from './DynamicListSearch';
|
||||
|
||||
@Service()
|
||||
export default class DynamicListService implements IDynamicListService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
dynamicListFilterRoles: DynamicListFilterRoles;
|
||||
|
||||
@Inject()
|
||||
dynamicListSortBy: DynamicListSortBy;
|
||||
|
||||
@Inject()
|
||||
dynamicListView: DynamicListCustomView;
|
||||
|
||||
@Inject()
|
||||
dynamicListSearch: DynamicListSearch;
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
tenantId: number,
|
||||
model: IModel,
|
||||
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(
|
||||
model,
|
||||
filter.searchKeyword
|
||||
);
|
||||
dynamicFilter.setFilter(dynamicListSearch);
|
||||
}
|
||||
// Custom view filter roles.
|
||||
if (filter.viewSlug) {
|
||||
const dynamicListCustomView =
|
||||
await this.dynamicListView.dynamicListCustomView(
|
||||
dynamicFilter,
|
||||
filter.viewSlug,
|
||||
tenantId
|
||||
);
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware to catch services errors
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public handlerErrorsToResponse(
|
||||
error: 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',
|
||||
message: 'Sort column not found.',
|
||||
code: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'view_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
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',
|
||||
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',
|
||||
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))
|
||||
: [],
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
packages/server/src/services/DynamicListing/constants.ts
Normal file
6
packages/server/src/services/DynamicListing/constants.ts
Normal 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',
|
||||
};
|
||||
Reference in New Issue
Block a user