@mee4dy/crud
Version:
Create a backend and frontend in 5 minutes! With our powerful full stack crud system, customize it to suit you.
293 lines (243 loc) • 7.7 kB
text/typescript
import { Op, Model, ModelStatic } from 'sequelize';
import { FilterType } from '../common/enums/filter-type.enum';
import { OrderDirection } from '../common/enums/order-direction.enum';
import { Order } from '../common/interfaces/order.interface';
import { Filter } from '../common/interfaces/filter.interface';
import { merge } from '../common/helpers/merge.helper';
import { FindParams } from './interfaces/find-params.interface';
import { PK } from '../common/constatns/constatns';
export abstract class CrudService<T extends Model> {
constructor(params?) {
if (params) {
Object.assign(this, { ...params });
}
}
protected pk: string = 'id';
protected repository: ModelStatic<T>;
protected limit: number;
protected allowFilters: Filter[] = [{ key: PK }];
protected allowGroups: string[] = [];
protected allowOrders: string[] = [PK];
protected defaultGroups: string[] = [PK];
protected defaultOrders: Order[] = [[PK, OrderDirection.desc]];
protected fields;
protected fieldsExclude: string[];
getPK(): string {
return this.pk;
}
getRepository(): ModelStatic<T> {
return this.repository;
}
getFields(groups: string[] = []): { include: any; exclude: any } {
const fields = this.fields || [];
const fieldsExclude = this.fieldsExclude || [];
const fieldsInclude: any = [];
for (const [fieldQuery, fieldName] of fields) {
if (this.allowGroups.includes(fieldName)) {
if (groups.length && groups.includes(fieldName)) {
fieldsInclude.push([fieldQuery, fieldName]);
} else {
fieldsExclude.push(fieldName);
}
} else {
fieldsInclude.push([fieldQuery, fieldName]);
}
}
return {
include: fieldsInclude,
exclude: fieldsExclude,
};
}
getQueryFilters(queryFilters: object) {
const filters = structuredClone(queryFilters);
if (filters?.[PK]) {
filters[this.pk] = filters[PK];
delete filters[PK];
}
return filters;
}
getAllowFilters(): Filter[] {
const allowFilters = structuredClone(this.allowFilters);
return allowFilters.map((filter) => {
return {
...filter,
key: filter.key === PK ? this.pk : filter.key,
};
});
}
getAllowOrders(): string[] {
const allowOrders = this.allowOrders;
return allowOrders.map((key) => {
return key === PK ? this.pk : key;
});
}
getFilters(query) {
const filters = [];
const queryFilters = this.getQueryFilters(query.filters);
const allowFilters = this.getAllowFilters();
if (queryFilters) {
for (const { key, field, type } of allowFilters) {
const filterKey = key;
const filterFiled = field || filterKey;
const filterValue = queryFilters?.[filterKey];
const filterValueFrom = queryFilters?.[`${filterFiled}_from`];
const filterValueTo = queryFilters?.[`${filterFiled}_to`];
const filterField = (this.fields || []).find(([select, fieldKey]) => fieldKey === filterFiled); // Custom field
let whereField = filterField
? filterField[0]
: this.repository.sequelize.col(`${this.repository.name}.${filterFiled}`);
const whereValue: any = {};
switch (type) {
case FilterType.text:
if (filterValue) {
whereValue[Op.like] = `%${filterValue}%`;
}
break;
case FilterType.period:
case FilterType.range:
if (filterValueFrom) {
whereValue[Op.gte] = filterValueFrom;
}
if (filterValueTo) {
whereValue[Op.lte] = filterValueTo;
}
break;
case FilterType.number:
default:
if (filterValue) {
whereValue[Op.eq] = filterValue;
}
break;
}
if (whereField && Reflect.ownKeys(whereValue).length) {
filters.push(this.repository.sequelize.where(whereField, whereValue));
}
}
}
return filters;
}
getGroups(query) {
const groups: any = [];
const queryGroups = (query.groups || this.defaultGroups).map((key) => (key === PK ? this.pk : key));
if (queryGroups) {
for (let key of this.allowGroups) {
const fieldKey = key === PK ? this.pk : key;
if (queryGroups.includes(fieldKey)) {
groups.push(fieldKey);
}
}
}
return groups;
}
getOrders(query) {
const orders = [];
const allowOrders = this.getAllowOrders();
const queryOrders = query.orders
? Object.entries(query.orders).map(([key, direction]) => [key, direction])
: this.defaultOrders;
const groups = this.getGroups(query);
const fields = this.getFields(groups);
const fieldNames = fields.include.map((field) => field[1]);
if (queryOrders) {
for (const key of allowOrders) {
const fieldKey = key;
const fieldInSelect = !fieldNames.length || fieldNames.includes(fieldKey);
const order = queryOrders
.map(([field, direction]) => {
if (field === PK) {
field = this.pk;
}
return [field, direction];
})
.find(([field, direction]) => {
return field === fieldKey;
});
if (order && fieldInSelect) {
orders.push(order);
}
}
}
return orders;
}
getLimit(query): number {
if (query?.limit) {
return +query.limit;
}
return this.limit;
}
getOffset(query): number {
return +query?.offset || 0;
}
getIncludes(query) {
const includes = query.includes || [];
return includes;
}
findAll({ scope, ...params }: FindParams) {
return this.repository.scope(scope).findAll(params as object);
}
findOne({ scope, ...params }: FindParams) {
return this.repository.scope(scope).findOne(params as object);
}
getFindParams({ params, query }): FindParams {
const filters = this.getFilters(query);
const includes = this.getIncludes(query);
const orders = this.getOrders(query);
const groups = this.getGroups(query);
const fields = this.getFields(groups);
const limit = this.getLimit(query);
const offset = this.getOffset(query);
const findParams = params || {};
const findParamsBase = {
attributes: {
include: fields?.include?.length ? fields?.include : [],
exclude: fields?.exclude?.length ? fields?.exclude : [],
},
include: includes,
scope: ['defaultScope'],
limit: limit,
offset: offset,
where: filters.length ? filters : undefined,
order: orders,
group: groups,
};
return merge(findParamsBase, findParams);
}
getItems({ params, query }: { params?; query? }) {
const findParams = this.getFindParams({ params, query });
return this.findAll({
...findParams,
});
}
getItem({ params, query }: { params?; query? }) {
const findParams = this.getFindParams({ params, query });
return this.findOne({
...findParams,
});
}
create(data: object) {
return this.repository.create(data as any);
}
update(pk: number, data: object, returning: boolean = true) {
return this.repository
.update(data, {
where: {
[this.pk]: pk,
} as any,
})
.then((result: any) => {
if (returning) {
return this.findOne({
where: {
[this.pk]: pk,
},
});
}
return result;
});
}
delete(where?) {
return this.repository.destroy({
where,
});
}
}