import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { isNil } from 'lodash'; import { In, ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { BaseRepository } from '@/modules/database/base/repository'; import { BaseTreeRepository } from '@/modules/database/base/tree.repository'; import { SelectTrashMode, TreeChildrenResolve } from '@/modules/database/constants'; import { PaginateOptions, PaginateReturn, QueryHook, ServiceListQueryOption, } from '@/modules/database/types'; import { paginate, treePaginate } from '@/modules/database/utils'; export abstract class BaseService< T extends ObjectLiteral, R extends BaseRepository | BaseTreeRepository, P extends ServiceListQueryOption = ServiceListQueryOption, > { protected repository: R; protected enableTrash = false; protected constructor(repository: R) { this.repository = repository; if ( !( this.repository instanceof BaseRepository || this.repository instanceof BaseTreeRepository ) ) { throw new Error('Repository init error.'); } } protected async buildItemQB(id: string, qb: SelectQueryBuilder, callback?: QueryHook) { qb.where(`${this.repository.qbName}.id = :id`, { id }); if (callback) { return callback(qb); } return qb; } protected async buildListQB(qb: SelectQueryBuilder, options?: P, callback?: QueryHook) { const { trashed } = options ?? {}; const queryName = this.repository.qbName; if (this.enableTrash && trashed in [SelectTrashMode.ALL, SelectTrashMode.ONLY]) { qb.withDeleted(); if (trashed === SelectTrashMode.ONLY) { qb.where(`${queryName}.deletedAt IS NOT NULL`); } } if (callback) { return callback(qb); } return qb; } async list(options?: P, callback?: QueryHook) { const { trashed: isTrashed = false } = options ?? {}; const trashed = isTrashed || SelectTrashMode.NONE; if (this.repository instanceof BaseTreeRepository) { const withTrashed = this.enableTrash && trashed in [SelectTrashMode.ALL, SelectTrashMode.ONLY]; const onlyTrashed = this.enableTrash && trashed === SelectTrashMode.ONLY; const tree = await this.repository.findTrees({ ...options, withTrashed, onlyTrashed }); return this.repository.toFlatTrees(tree); } const qb = await this.buildListQB(this.repository.buildBaseQB(), options, callback); return qb.getMany(); } async paginate(options?: PaginateOptions & P, callback?: QueryHook) { const queryOptions = (options ?? {}) as P; if (this.repository instanceof BaseTreeRepository) { const data = await this.list(queryOptions, callback); return treePaginate(options, data) as PaginateReturn; } const qb = await this.buildListQB(this.repository.buildBaseQB(), queryOptions, callback); return paginate(qb, options); } async detail(id: string, callback?: QueryHook) { const qb = await this.buildItemQB(id, this.repository.buildBaseQB(), callback); const item = await qb.getOne(); if (!item) { throw new NotFoundException(`${this.repository.qbName} ${id} NOT FOUND`); } return item; } async delete(ids: string[], trash?: boolean) { let items: T[]; if (this.repository instanceof BaseTreeRepository) { items = await this.repository.find({ where: { id: In(ids) as any }, withDeleted: this.enableTrash ? true : undefined, relations: ['parent', 'children'], }); if (this.repository.childrenResolve === TreeChildrenResolve.UP) { for (const item of items) { if (isNil(item.children) || item.children.length <= 0) { continue; } const children = [...item.children].map((o) => { o.parent = item.parent; return item; }); await this.repository.save(children); } } } else { items = await this.repository.find({ where: { id: In(ids) as any }, withDeleted: this.enableTrash ? true : undefined, }); } if (this.enableTrash && trash) { const directs = items.filter((item) => !isNil(item.deletedAt)); const softs = items.filter((item) => isNil(item.deletedAt)); return [ ...(await this.repository.remove(directs)), ...(await this.repository.softRemove(softs)), ]; } return this.repository.remove(items); } async restore(ids: string[]) { if (!this.enableTrash) { throw new ForbiddenException( `Can not to retore ${this.repository.qbName},because trash not enabled!`, ); } const items = await this.repository.find({ where: { id: In(ids) as any }, withDeleted: true, }); const trashIds = items.filter((o) => !isNil(o.deletedAt)).map((o) => o.id); if (trashIds.length < 1) { return []; } await this.repository.restore(trashIds); const qb = await this.buildListQB(this.repository.buildBaseQB(), undefined, async (_) => _.andWhereInIds(trashIds), ); return qb.getMany(); } abstract create(data: any, ...others: any[]): Promise; abstract update(data: any, ...others: any[]): Promise; }