From 9ce4c58dc1512f0f579865600950c92cdbcad25b Mon Sep 17 00:00:00 2001 From: ashar Date: Fri, 31 May 2024 11:06:50 +0700 Subject: [PATCH] feat(SPG-123) Abstraction Transaction Data --- package.json | 2 +- .../modules/data/model/base-core.model.ts | 4 +- .../modules/data/model/base-status.model.ts | 15 +- src/core/modules/data/model/base.model.ts | 7 +- .../modules/data/service/base-data.service.ts | 96 ++++++------ .../modules/data/service/base-read.service.ts | 70 ++++----- .../data/service/base-tree-read.service.ts | 81 +++++----- .../domain/entities/base-core.entity.ts | 4 +- .../domain/entities/base-filter.entity.ts | 32 ++-- .../domain/entities/base-status.entity.ts | 8 +- .../modules/domain/entities/base.entity.ts | 16 +- .../domain/usecase/base-read.manager.ts | 77 ++++----- .../modules/domain/usecase/base.manager.ts | 148 +++++++++--------- .../managers/base-batch-delete.manager.ts | 67 ++++++++ .../base-batch-update-status.manager.ts | 75 +++++++++ .../usecase/managers/base-create.manager.ts | 65 ++++---- .../usecase/managers/base-delete.manager.ts | 68 ++++---- .../managers/base-update-status.manager.ts | 67 ++++---- .../usecase/managers/base-update.manager.ts | 68 ++++---- .../base-data-transaction.orchestrator.ts | 18 ++- .../orchestrators/base-data.orchestrator.ts | 13 +- .../orchestrators/base-read.orchestrator.ts | 8 +- .../infrastructure/dto/base-batch.dto.ts | 6 + .../infrastructure/dto/base-core.dto.ts | 6 +- .../infrastructure/dto/base-filter.dto.ts | 18 ++- .../infrastructure/dto/base-status.dto.ts | 10 +- .../modules/infrastructure/dto/base.dto.ts | 18 +-- .../domain/exceptions/handled-exception.ts | 2 +- .../response/domain/ok-response.interface.ts | 7 + src/core/strings/constants/base.constants.ts | 30 ++-- .../strings/constants/interface.constants.ts | 10 +- .../strings/constants/module.constants.ts | 4 +- src/core/strings/constants/table.constants.ts | 4 +- yarn.lock | 10 +- 34 files changed, 651 insertions(+), 483 deletions(-) create mode 100644 src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts create mode 100644 src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts create mode 100644 src/core/modules/infrastructure/dto/base-batch.dto.ts diff --git a/package.json b/package.json index 3dc6eea..0c9e0e2 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.13", "@types/jest": "29.5.12", - "@types/node": "18.11.18", + "@types/node": "^20.12.13", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/src/core/modules/data/model/base-core.model.ts b/src/core/modules/data/model/base-core.model.ts index 50a08f4..1af5b5d 100644 --- a/src/core/modules/data/model/base-core.model.ts +++ b/src/core/modules/data/model/base-core.model.ts @@ -1,5 +1,5 @@ -import { Entity, PrimaryGeneratedColumn } from "typeorm"; -import { BaseCoreEntity } from "../../domain/entities/base-core.entity"; +import { Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { BaseCoreEntity } from '../../domain/entities/base-core.entity'; @Entity() export abstract class BaseCoreModel implements BaseCoreEntity { diff --git a/src/core/modules/data/model/base-status.model.ts b/src/core/modules/data/model/base-status.model.ts index a8cece6..4fd0259 100644 --- a/src/core/modules/data/model/base-status.model.ts +++ b/src/core/modules/data/model/base-status.model.ts @@ -1,10 +1,13 @@ -import { Column, Entity } from "typeorm"; -import { BaseModel } from "./base.model"; -import { STATUS } from "src/core/strings/constants/base.constants"; -import { BaseStatusEntity } from "../../domain/entities/base-status.entity"; +import { Column, Entity } from 'typeorm'; +import { BaseModel } from './base.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BaseStatusEntity } from '../../domain/entities/base-status.entity'; @Entity() -export abstract class BaseStatusModel extends BaseModel implements BaseStatusEntity { +export abstract class BaseStatusModel + extends BaseModel + implements BaseStatusEntity +{ @Column('enum', { name: 'status', enum: STATUS, default: STATUS.DRAFT }) status: STATUS; -} \ No newline at end of file +} diff --git a/src/core/modules/data/model/base.model.ts b/src/core/modules/data/model/base.model.ts index 6be4c60..f0bd313 100644 --- a/src/core/modules/data/model/base.model.ts +++ b/src/core/modules/data/model/base.model.ts @@ -3,7 +3,10 @@ import { BaseCoreModel } from './base-core.model'; import { BaseEntity } from '../../domain/entities/base.entity'; @Entity() -export abstract class BaseModel extends BaseCoreModel implements BaseEntity { +export abstract class BaseModel + extends BaseCoreModel + implements BaseEntity +{ @Column('varchar', { name: 'creator_id', length: 36, nullable: true }) creator_id: string; @@ -19,6 +22,6 @@ export abstract class BaseModel extends BaseCoreModel implements @Column({ type: 'bigint', nullable: false }) created_at: number; - @Column({ type: 'bigint', nullable: false }) + @Column({ type: 'bigint', nullable: false }) updated_at: number; } diff --git a/src/core/modules/data/service/base-data.service.ts b/src/core/modules/data/service/base-data.service.ts index 6d0f585..b957808 100644 --- a/src/core/modules/data/service/base-data.service.ts +++ b/src/core/modules/data/service/base-data.service.ts @@ -1,54 +1,58 @@ -import { EntityTarget, FindManyOptions, QueryRunner, Repository } from "typeorm"; +import { + EntityTarget, + FindManyOptions, + QueryRunner, + Repository, +} from 'typeorm'; export abstract class BaseDataService { - - constructor(private repository: Repository) {} + constructor(private repository: Repository) {} - getRepository(): Repository { - return this.repository; - } + getRepository(): Repository { + return this.repository; + } - async create( - queryRunner: QueryRunner, - entityTarget: EntityTarget, - entity: Entity, - ): Promise { - const newEntity = queryRunner.manager.create(entityTarget, entity); - return await queryRunner.manager.save(newEntity); - } - - async update( - queryRunner: QueryRunner, - entityTarget: EntityTarget, - filterUpdate: any, - entity: Entity, - ): Promise { - const newEntity = await queryRunner.manager.findOne(entityTarget, { - where: filterUpdate, - }); + async create( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + entity: Entity, + ): Promise { + const newEntity = queryRunner.manager.create(entityTarget, entity); + return await queryRunner.manager.save(newEntity); + } - if (!newEntity) throw new Error('Data not found!'); - Object.assign(newEntity, entity); - return await queryRunner.manager.save(newEntity); - } + async update( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + filterUpdate: any, + entity: Entity, + ): Promise { + const newEntity = await queryRunner.manager.findOne(entityTarget, { + where: filterUpdate, + }); - async deleteById( - queryRunner: QueryRunner, - entityTarget: EntityTarget, - id: string, - ): Promise { - await queryRunner.manager.delete(entityTarget, { id }); - } + if (!newEntity) throw new Error('Data not found!'); + Object.assign(newEntity, entity); + return await queryRunner.manager.save(newEntity); + } - async deleteByOptions( - queryRunner: QueryRunner, - entityTarget: EntityTarget, - findManyOptions: FindManyOptions, - ): Promise { - await queryRunner.manager.delete(entityTarget, findManyOptions); - } + async deleteById( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + id: string, + ): Promise { + await queryRunner.manager.delete(entityTarget, { id }); + } - async getOneByOptions(findOneOptions): Promise { - return await this.repository.findOne(findOneOptions); - } -} \ No newline at end of file + async deleteByOptions( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + findManyOptions: FindManyOptions, + ): Promise { + await queryRunner.manager.delete(entityTarget, findManyOptions); + } + + async getOneByOptions(findOneOptions): Promise { + return await this.repository.findOne(findOneOptions); + } +} diff --git a/src/core/modules/data/service/base-read.service.ts b/src/core/modules/data/service/base-read.service.ts index a840884..6136c01 100644 --- a/src/core/modules/data/service/base-read.service.ts +++ b/src/core/modules/data/service/base-read.service.ts @@ -1,42 +1,40 @@ -import { FindOneOptions, Repository, SelectQueryBuilder } from "typeorm"; -import { BaseFilterEntity } from "../../domain/entities/base-filter.entity"; -import { PaginationResponse } from "src/core/response/domain/ok-response.interface"; +import { FindOneOptions, Repository, SelectQueryBuilder } from 'typeorm'; +import { BaseFilterEntity } from '../../domain/entities/base-filter.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; export abstract class BaseReadService { - - constructor(private repository: Repository) {} + constructor(private repository: Repository) {} - getRepository(): Repository { - return this.repository; - } + getRepository(): Repository { + return this.repository; + } - async getIndex( - queryBuilder: SelectQueryBuilder, - params: BaseFilterEntity, - ): Promise> { - const [data, total] = await queryBuilder - .take(+params.limit) - .skip(+params.limit * +params.page - +params.limit) - .getManyAndCount(); + async getIndex( + queryBuilder: SelectQueryBuilder, + params: BaseFilterEntity, + ): Promise> { + const [data, total] = await queryBuilder + .take(+params.limit) + .skip(+params.limit * +params.page - +params.limit) + .getManyAndCount(); - return { - data, - total, - }; - } - - async getOneByOptions(findOneOptions): Promise { - return await this.repository.findOne(findOneOptions); - } - - async getOneOrFailByOptions( - findOneOptions: FindOneOptions, - ): Promise { - return await this.repository.findOneOrFail(findOneOptions); - } - - async getManyByOptions(findManyOptions): Promise { - return await this.repository.find(findManyOptions); - } + return { + data, + total, + }; + } -} \ No newline at end of file + async getOneByOptions(findOneOptions): Promise { + return await this.repository.findOne(findOneOptions); + } + + async getOneOrFailByOptions( + findOneOptions: FindOneOptions, + ): Promise { + return await this.repository.findOneOrFail(findOneOptions); + } + + async getManyByOptions(findManyOptions): Promise { + return await this.repository.find(findManyOptions); + } +} diff --git a/src/core/modules/data/service/base-tree-read.service.ts b/src/core/modules/data/service/base-tree-read.service.ts index 2e18469..0516077 100644 --- a/src/core/modules/data/service/base-tree-read.service.ts +++ b/src/core/modules/data/service/base-tree-read.service.ts @@ -1,52 +1,41 @@ -import { Repository, TreeRepository } from "typeorm"; -import { BaseReadService } from "./base-read.service"; +import { Repository, TreeRepository } from 'typeorm'; +import { BaseReadService } from './base-read.service'; -export abstract class BaseTreeReadService extends BaseReadService { - - constructor( - private dataRepository: Repository, - private treeRepository: TreeRepository - ) { - super(dataRepository); - } +export abstract class BaseTreeReadService< + Entity, +> extends BaseReadService { + constructor( + private dataRepository: Repository, + private treeRepository: TreeRepository, + ) { + super(dataRepository); + } - async findRoots() { - return this.treeRepository.findRoots() - } + async findRoots() { + return this.treeRepository.findRoots(); + } - async findDescendants( - parent, - relations = [], - ): Promise { - return this.treeRepository.findDescendants(parent, { - relations: relations, - }); - } - - async findDescendantsTree( - parent, - relations = [], - ): Promise { - return this.treeRepository.findDescendantsTree(parent, { - relations: relations, - }); - } - - async findAncestors( - parent, - relations = [], - ): Promise { - return await this.treeRepository.findAncestors(parent, { - relations: relations, + async findDescendants(parent, relations = []): Promise { + return this.treeRepository.findDescendants(parent, { + relations: relations, }); - } + } - async findAncestorsTree( - parent, - relations = [], - ): Promise { - return await this.treeRepository.findAncestorsTree(parent, { - relations: relations, + async findDescendantsTree(parent, relations = []): Promise { + return this.treeRepository.findDescendantsTree(parent, { + relations: relations, }); - } -} \ No newline at end of file + } + + async findAncestors(parent, relations = []): Promise { + return await this.treeRepository.findAncestors(parent, { + relations: relations, + }); + } + + async findAncestorsTree(parent, relations = []): Promise { + return await this.treeRepository.findAncestorsTree(parent, { + relations: relations, + }); + } +} diff --git a/src/core/modules/domain/entities/base-core.entity.ts b/src/core/modules/domain/entities/base-core.entity.ts index 4035652..caf7b15 100644 --- a/src/core/modules/domain/entities/base-core.entity.ts +++ b/src/core/modules/domain/entities/base-core.entity.ts @@ -1,3 +1,3 @@ export interface BaseCoreEntity { - id: string; -} \ No newline at end of file + id: string; +} diff --git a/src/core/modules/domain/entities/base-filter.entity.ts b/src/core/modules/domain/entities/base-filter.entity.ts index 6ee27c7..b6581b2 100644 --- a/src/core/modules/domain/entities/base-filter.entity.ts +++ b/src/core/modules/domain/entities/base-filter.entity.ts @@ -1,18 +1,18 @@ -import { ORDER_TYPE, STATUS } from "src/core/strings/constants/base.constants"; +import { ORDER_TYPE, STATUS } from 'src/core/strings/constants/base.constants'; export interface BaseFilterEntity { - page: number; - limit: number; - q?: string; - names: string[]; - entity_ids: string[]; - order_by: string; - order_type: ORDER_TYPE; - statuses: STATUS[]; - created_ids: string[]; - created_from: number; - created_to: number; - updated_ids: string[]; - updated_from: number; - updated_to: number; -} \ No newline at end of file + page: number; + limit: number; + q?: string; + names: string[]; + entity_ids: string[]; + order_by: string; + order_type: ORDER_TYPE; + statuses: STATUS[]; + created_ids: string[]; + created_from: number; + created_to: number; + updated_ids: string[]; + updated_from: number; + updated_to: number; +} diff --git a/src/core/modules/domain/entities/base-status.entity.ts b/src/core/modules/domain/entities/base-status.entity.ts index eeed952..31af323 100644 --- a/src/core/modules/domain/entities/base-status.entity.ts +++ b/src/core/modules/domain/entities/base-status.entity.ts @@ -1,6 +1,6 @@ -import { STATUS } from "src/core/strings/constants/base.constants"; -import { BaseEntity } from "./base.entity"; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BaseEntity } from './base.entity'; export interface BaseStatusEntity extends BaseEntity { - status: STATUS; -} \ No newline at end of file + status: STATUS; +} diff --git a/src/core/modules/domain/entities/base.entity.ts b/src/core/modules/domain/entities/base.entity.ts index 2dc5b9a..af8c04e 100644 --- a/src/core/modules/domain/entities/base.entity.ts +++ b/src/core/modules/domain/entities/base.entity.ts @@ -1,10 +1,10 @@ -import { BaseCoreEntity } from "./base-core.entity"; +import { BaseCoreEntity } from './base-core.entity'; export interface BaseEntity extends BaseCoreEntity { - creator_id: string; - creator_name: string; - editor_id: string; - editor_name: string; - created_at: number; - updated_at: number; -} \ No newline at end of file + creator_id: string; + creator_name: string; + editor_id: string; + editor_name: string; + created_at: number; + updated_at: number; +} diff --git a/src/core/modules/domain/usecase/base-read.manager.ts b/src/core/modules/domain/usecase/base-read.manager.ts index 316c384..f987e61 100644 --- a/src/core/modules/domain/usecase/base-read.manager.ts +++ b/src/core/modules/domain/usecase/base-read.manager.ts @@ -1,52 +1,53 @@ -import { Inject, Injectable, Logger } from "@nestjs/common"; -import { UserProvider, UsersSession } from "src/core/sessions"; -import { BLANK_USER } from "src/core/strings/constants/base.constants"; -import { TABLE_NAME } from "src/core/strings/constants/table.constants"; +import { Inject, Injectable, Logger } from '@nestjs/common'; +import { UserProvider, UsersSession } from 'src/core/sessions'; +import { BLANK_USER } from 'src/core/strings/constants/base.constants'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; @Injectable() export abstract class BaseReadManager { - - public user: UsersSession; - public dataService: any; - public queryBuilder: any; - protected tableName: TABLE_NAME; - @Inject() - protected userProvider: UserProvider; + public user: UsersSession; + public dataService: any; + public queryBuilder: any; + protected tableName: TABLE_NAME; + @Inject() + protected userProvider: UserProvider; - private readonly baseLog = new Logger(BaseReadManager.name); + private readonly baseLog = new Logger(BaseReadManager.name); - setUser() { - try { - this.user = this.userProvider?.user; - } catch (error) { - this.user = BLANK_USER; - } + setUser() { + try { + this.user = this.userProvider?.user; + } catch (error) { + this.user = BLANK_USER; } + } - setService(dataService) { - this.dataService = dataService; - this.queryBuilder = this.dataService.getRepository().createQueryBuilder(this.tableName); - } + setService(dataService) { + this.dataService = dataService; + this.queryBuilder = this.dataService + .getRepository() + .createQueryBuilder(this.tableName); + } - async execute(): Promise { - this.baseLog.log(`prepareData`, BaseReadManager.name); - await this.prepareData(); + async execute(): Promise { + this.baseLog.log(`prepareData`, BaseReadManager.name); + await this.prepareData(); - this.baseLog.log(`beforeProcess`, BaseReadManager.name); - await this.beforeProcess(); - - this.baseLog.log('process', BaseReadManager.name); - await this.process(); + this.baseLog.log(`beforeProcess`, BaseReadManager.name); + await this.beforeProcess(); - this.baseLog.log('afterProcess', BaseReadManager.name); - await this.afterProcess(); - } + this.baseLog.log('process', BaseReadManager.name); + await this.process(); - abstract prepareData(): Promise; + this.baseLog.log('afterProcess', BaseReadManager.name); + await this.afterProcess(); + } - abstract beforeProcess(): Promise; + abstract prepareData(): Promise; - abstract process(): Promise; + abstract beforeProcess(): Promise; - abstract afterProcess(): Promise; -} \ No newline at end of file + abstract process(): Promise; + + abstract afterProcess(): Promise; +} diff --git a/src/core/modules/domain/usecase/base.manager.ts b/src/core/modules/domain/usecase/base.manager.ts index a4bbeb4..de0d828 100644 --- a/src/core/modules/domain/usecase/base.manager.ts +++ b/src/core/modules/domain/usecase/base.manager.ts @@ -1,85 +1,91 @@ -import { BadRequestException, Inject, Injectable, Logger } from "@nestjs/common"; -import { EventBus } from "@nestjs/cqrs"; -import { UserProvider, UsersSession } from "src/core/sessions"; -import { BLANK_USER } from "src/core/strings/constants/base.constants"; -import { EventTopics } from "src/core/strings/constants/interface.constants"; -import { QueryRunner } from "typeorm"; +import { + Inject, + Injectable, + Logger, +} from '@nestjs/common'; +import { EventBus } from '@nestjs/cqrs'; +import { UserProvider, UsersSession } from 'src/core/sessions'; +import { BLANK_USER } from 'src/core/strings/constants/base.constants'; +import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { QueryRunner } from 'typeorm'; @Injectable() export abstract class BaseManager { - public user: UsersSession; - public queryRunner: QueryRunner; - public dataService: any; - protected data: any; - @Inject() - protected userProvider: UserProvider; - @Inject() - protected eventBus: EventBus; + public user: UsersSession; + public queryRunner: QueryRunner; + public dataService: any; + protected data: any; + @Inject() + protected userProvider: UserProvider; + @Inject() + protected eventBus: EventBus; - private readonly baseLog = new Logger(BaseManager.name); + private readonly baseLog = new Logger(BaseManager.name); - setUser() { - try { - this.user = this.userProvider?.user; - } catch (error) { - this.user = BLANK_USER; - } + setUser() { + try { + this.user = this.userProvider?.user; + } catch (error) { + this.user = BLANK_USER; } + } - setService(dataService) { - this.dataService = dataService; - this.queryRunner = this.dataService.getRepository().manager.connection.createQueryRunner(); + setService(dataService) { + this.dataService = dataService; + this.queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + } + + abstract get eventTopics(): EventTopics[]; + + async execute(): Promise { + try { + this.setUser(); + + this.queryRunner.startTransaction(); + this.baseLog.verbose('prepareData'); + await this.prepareData(); + + if (!this.dataService) { + throw new Error('data or service not implemented.'); + } + + this.baseLog.verbose('validateProcess'); + await this.validateProcess(); + + this.baseLog.verbose('beforeProcess'); + await this.beforeProcess(); + + this.baseLog.verbose('process'); + await this.process(); + + this.baseLog.verbose('afterProcess'); + await this.afterProcess(); + + this.baseLog.verbose('commitTransaction'); + await this.queryRunner.commitTransaction(); + + this.publishEvents(); + + await this.queryRunner.release(); + } catch (e) { + if (e.response) throw new Error(JSON.stringify(e.response)); + else throw new Error(e.message); } + } - abstract get eventTopics(): EventTopics[]; + abstract prepareData(): Promise; - async execute(): Promise { - try { - this.setUser(); + abstract validateProcess(): Promise; - this.queryRunner.startTransaction(); - this.baseLog.verbose('prepareData'); - await this.prepareData(); + abstract beforeProcess(): Promise; - if (!this.data || !this.dataService) { - throw new Error("data or service not implemented."); - } + abstract process(): Promise; - this.baseLog.verbose('validateProcess'); - await this.validateProcess(); + abstract afterProcess(): Promise; - this.baseLog.verbose('beforeProcess'); - await this.beforeProcess(); - - this.baseLog.verbose('process'); - await this.process(); - - this.baseLog.verbose('afterProcess'); - await this.afterProcess(); - - this.baseLog.verbose('commitTransaction'); - await this.queryRunner.commitTransaction(); - - this.publishEvents(); - - await this.queryRunner.release(); - } catch (e) { - if (e.response) throw new Error(JSON.stringify(e.response)); - else throw new Error(e.message); - } - } - - abstract prepareData(): Promise; - - abstract validateProcess(): Promise; - - abstract beforeProcess(): Promise; - - abstract process(): Promise; - - abstract afterProcess(): Promise; - - async publishEvents() { - if (!this.eventTopics.length) return - }; -} \ No newline at end of file + async publishEvents() { + if (!this.eventTopics.length) return; + } +} diff --git a/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts new file mode 100644 index 0000000..5a28f2d --- /dev/null +++ b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts @@ -0,0 +1,67 @@ +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BaseManager } from '../base.manager'; +import { HttpStatus, NotFoundException } from '@nestjs/common'; + +export abstract class BaseBatchDeleteManager extends BaseManager { + protected dataIds: string[]; + protected result: BatchResult; + abstract get entityTarget(): any; + + setData(ids: string[]): void { + this.dataIds = ids; + } + + validateProcess(): Promise { + return; + } + + prepareData(): Promise { + return; + } + + async process(): Promise { + let totalFailed = 0; + let totalSuccess = 0; + let messages = []; + + for (const id of this.dataIds) { + try { + const entity = await this.dataService.getOneByOptions({ + where: { + id: id, + }, + }); + + if (!entity) { + throw new NotFoundException({ + statusCode: HttpStatus.NOT_FOUND, + message: `Failed! Entity with id ${id} not found`, + error: 'Entity Not Found', + }); + } + + await this.validateData(entity); + await this.dataService.deleteById( + this.queryRunner, + this.entityTarget, + id, + ); + + totalSuccess = totalSuccess + 1; + } catch (error) { + totalFailed = totalFailed + 1; + messages.push(error.response?.message ?? error.message); + } + } + + this.result = { + total_items: this.dataIds.length, + total_failed: totalFailed, + total_success: totalSuccess, + messages: messages, + }; + } + + abstract validateData(data: Entity): Promise; + abstract getResult(): BatchResult; +} diff --git a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts new file mode 100644 index 0000000..4e4cfb1 --- /dev/null +++ b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts @@ -0,0 +1,75 @@ +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BaseManager } from '../base.manager'; +import { HttpStatus, NotFoundException } from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export abstract class BaseBatchUpdateStatusManager extends BaseManager { + protected dataIds: string[]; + protected result: BatchResult; + protected dataStatus: STATUS; + abstract get entityTarget(): any; + + setData(ids: string[], status: STATUS): void { + this.dataIds = ids; + this.dataStatus = status; + } + + validateProcess(): Promise { + return; + } + + prepareData(): Promise { + return; + } + + async process(): Promise { + let totalFailed = 0; + let totalSuccess = 0; + let messages = []; + + for (const id of this.dataIds) { + try { + const entity = await this.dataService.getOneByOptions({ + where: { + id: id, + }, + }); + + if (!entity) { + throw new NotFoundException({ + statusCode: HttpStatus.NOT_FOUND, + message: `Failed! Entity with id ${id} not found`, + error: 'Entity Not Found', + }); + } + + await this.validateData(entity); + await this.dataService.update( + this.queryRunner, + this.entityTarget, + { id: id }, + { + status: this.dataStatus, + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + }, + ); + totalSuccess = totalSuccess + 1; + } catch (error) { + totalFailed = totalFailed + 1; + messages.push(error.response?.message ?? error.message); + } + } + + this.result = { + total_items: this.dataIds.length, + total_failed: totalFailed, + total_success: totalSuccess, + messages: messages, + }; + } + + abstract validateData(data: Entity): Promise; + abstract getResult(): BatchResult; +} diff --git a/src/core/modules/domain/usecase/managers/base-create.manager.ts b/src/core/modules/domain/usecase/managers/base-create.manager.ts index 66947c1..13ff9a9 100644 --- a/src/core/modules/domain/usecase/managers/base-create.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-create.manager.ts @@ -1,40 +1,37 @@ -import { BaseManager } from "../base.manager"; -import { Injectable } from "@nestjs/common"; +import { BaseManager } from '../base.manager'; +import { Injectable } from '@nestjs/common'; -@Injectable() export abstract class BaseCreateManager extends BaseManager { - - protected result: Entity; - protected duplicateColumn: string[]; - abstract get entityTarget(): any; + protected result: Entity; + protected duplicateColumn: string[]; + abstract get entityTarget(): any; - setData(entity: Entity): void { - this.data = entity; - } + setData(entity: Entity): void { + this.data = entity; + } - async prepareData(): Promise { - Object.assign(this.data, { - creator_id: this.user.id, - creator_name: this.user.name, - created_at: new Date().getTime(), - updated_at: new Date().getTime(), - }); - } + async prepareData(): Promise { + Object.assign(this.data, { + creator_id: this.user.id, + creator_name: this.user.name, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + } - async process(): Promise { - this.result = await this.dataService.create( - this.queryRunner, - this.entityTarget, - this.data, - ); - } + async process(): Promise { + this.result = await this.dataService.create( + this.queryRunner, + this.entityTarget, + this.data, + ); + } - async getResult(): Promise { - return await this.dataService.getOneByOptions({ - where: { - id: this.result['id'] - } - }) - } - -} \ No newline at end of file + async getResult(): Promise { + return await this.dataService.getOneByOptions({ + where: { + id: this.result['id'], + }, + }); + } +} diff --git a/src/core/modules/domain/usecase/managers/base-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-delete.manager.ts index 60ecb96..f5c187a 100644 --- a/src/core/modules/domain/usecase/managers/base-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-delete.manager.ts @@ -1,41 +1,43 @@ -import { HttpStatus, Injectable, UnauthorizedException, UnprocessableEntityException } from "@nestjs/common"; -import { BaseManager } from "../base.manager"; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseManager } from '../base.manager'; -@Injectable() export abstract class BaseDeleteManager extends BaseManager { - - protected dataId: string; - protected result: Entity; - abstract get entityTarget(): any; + protected dataId: string; + protected result: Entity; + abstract get entityTarget(): any; - setData(id: string): void { - this.dataId = id; - } + setData(id: string): void { + this.dataId = id; + } - async prepareData(): Promise { - this.data = await this.dataService.getOneByOptions({ - where: { - id: this.dataId - } - }) + async prepareData(): Promise { + this.data = await this.dataService.getOneByOptions({ + where: { + id: this.dataId, + }, + }); - if (!this.data) - throw new UnprocessableEntityException({ - statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Data with id ${this.dataId} not found`, - error: 'Unprocessable Entity', - }); + if (!this.data) + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Data with id ${this.dataId} not found`, + error: 'Unprocessable Entity', + }); - return; - } + return; + } - async process(): Promise { - await this.dataService.deleteById( - this.queryRunner, - this.entityTarget, - this.dataId, - ); - } + async process(): Promise { + await this.dataService.deleteById( + this.queryRunner, + this.entityTarget, + this.dataId, + ); + } - abstract getResult(): string; -} \ No newline at end of file + abstract getResult(): string; +} diff --git a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts index 3bb7320..fa6290c 100644 --- a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts @@ -1,42 +1,39 @@ -import { Injectable } from "@nestjs/common"; -import { BaseManager } from "../base.manager"; -import { STATUS } from "src/core/strings/constants/base.constants"; -import { UserPrivilegeModel } from "src/modules/user-related/user-privilege/data/model/user-privilege.model"; +import { BaseManager } from '../base.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { UserPrivilegeModel } from 'src/modules/user-related/user-privilege/data/model/user-privilege.model'; -@Injectable() export abstract class BaseUpdateStatusManager extends BaseManager { + protected dataId: string; + protected result: Entity; + protected dataStatus: STATUS; + protected duplicateColumn: string[]; + abstract get entityTarget(): any; - protected dataId: string; - protected result: Entity; - protected dataStatus: STATUS; - protected duplicateColumn: string[]; - abstract get entityTarget(): any; + setData(id: string, status: STATUS): void { + this.dataId = id; + this.dataStatus = status; + } - setData(id: string, status: STATUS): void { - this.dataId = id; - this.dataStatus = status; - } + async prepareData(): Promise { + this.data = new UserPrivilegeModel(); - async prepareData(): Promise { - this.data = new UserPrivilegeModel(); + Object.assign(this.data, { + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + id: this.dataId, + status: this.dataStatus, + }); + } - Object.assign(this.data, { - editor_id: this.user.id, - editor_name: this.user.name, - updated_at: new Date().getTime(), - id: this.dataId, - status: this.dataStatus, - }); - } + async process(): Promise { + this.result = await this.dataService.update( + this.queryRunner, + this.entityTarget, + { id: this.dataId }, + this.data, + ); + } - async process(): Promise { - this.result = await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.dataId }, - this.data, - ); - } - - abstract getResult(): string; -} \ No newline at end of file + abstract getResult(): string; +} diff --git a/src/core/modules/domain/usecase/managers/base-update.manager.ts b/src/core/modules/domain/usecase/managers/base-update.manager.ts index 7dd2017..1eb356b 100644 --- a/src/core/modules/domain/usecase/managers/base-update.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update.manager.ts @@ -1,41 +1,39 @@ -import { Injectable } from "@nestjs/common"; -import { BaseManager } from "../base.manager"; +import { Injectable } from '@nestjs/common'; +import { BaseManager } from '../base.manager'; -@Injectable() export abstract class BaseUpdateManager extends BaseManager { + protected dataId: string; + protected result: Entity; + protected duplicateColumn: string[]; + abstract get entityTarget(): any; - protected dataId: string; - protected result: Entity; - protected duplicateColumn: string[]; - abstract get entityTarget(): any; + setData(id: string, entity: Entity): void { + this.dataId = id; + this.data = entity; + } - setData(id: string, entity: Entity): void { - this.dataId = id; - this.data = entity; - } + async prepareData(): Promise { + Object.assign(this.data, { + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + }); + } - async prepareData(): Promise { - Object.assign(this.data, { - editor_id: this.user.id, - editor_name: this.user.name, - updated_at: new Date().getTime(), - }); - } + async process(): Promise { + this.result = await this.dataService.update( + this.queryRunner, + this.entityTarget, + { id: this.dataId }, + this.data, + ); + } - async process(): Promise { - this.result = await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.dataId }, - this.data, - ); - } - - async getResult(): Promise { - return await this.dataService.getOneByOptions({ - where: { - id: this.dataId - } - }) - } -} \ No newline at end of file + async getResult(): Promise { + return await this.dataService.getOneByOptions({ + where: { + id: this.dataId, + }, + }); + } +} diff --git a/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts b/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts index 5f5f20e..a3e844f 100644 --- a/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts +++ b/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts @@ -1,7 +1,13 @@ -import { BaseDataOrchestrator } from "./base-data.orchestrator"; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BaseDataOrchestrator } from './base-data.orchestrator'; -export abstract class BaseDataTransactionOrchestrator extends BaseDataOrchestrator { - abstract active(dataId: string): Promise; - abstract confirm(dataId: string): Promise; - abstract inactive(dataId: string): Promise; -} \ No newline at end of file +export abstract class BaseDataTransactionOrchestrator< + Entity, +> extends BaseDataOrchestrator { + abstract active(dataId: string): Promise; + abstract confirm(dataId: string): Promise; + abstract inactive(dataId: string): Promise; + abstract batchConfirm(dataIds: string[]): Promise; + abstract batchActive(dataIds: string[]): Promise; + abstract batchInactive(dataIds: string[]): Promise; +} diff --git a/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts b/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts index 30f52b6..0d61ece 100644 --- a/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts +++ b/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts @@ -1,7 +1,8 @@ +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; + export abstract class BaseDataOrchestrator { - - abstract create(data: Entity): Promise; - abstract update(dataId: string, data: Entity): Promise; - abstract delete(dataId: string): Promise; - -} \ No newline at end of file + abstract create(data: Entity): Promise; + abstract update(dataId: string, data: Entity): Promise; + abstract delete(dataId: string): Promise; + abstract batchDelete(dataIds: string[]): Promise; +} diff --git a/src/core/modules/domain/usecase/orchestrators/base-read.orchestrator.ts b/src/core/modules/domain/usecase/orchestrators/base-read.orchestrator.ts index 5441d23..aaca2a2 100644 --- a/src/core/modules/domain/usecase/orchestrators/base-read.orchestrator.ts +++ b/src/core/modules/domain/usecase/orchestrators/base-read.orchestrator.ts @@ -1,6 +1,6 @@ -import { PaginationResponse } from "src/core/response/domain/ok-response.interface"; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; export abstract class BaseReadOrchestrator { - abstract index(params): Promise>; - abstract detail(dataId: string): Promise; -} \ No newline at end of file + abstract index(params): Promise>; + abstract detail(dataId: string): Promise; +} diff --git a/src/core/modules/infrastructure/dto/base-batch.dto.ts b/src/core/modules/infrastructure/dto/base-batch.dto.ts new file mode 100644 index 0000000..1f34544 --- /dev/null +++ b/src/core/modules/infrastructure/dto/base-batch.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class BatchIdsDto { + @ApiProperty({ type: [String] }) + ids: string[]; +} diff --git a/src/core/modules/infrastructure/dto/base-core.dto.ts b/src/core/modules/infrastructure/dto/base-core.dto.ts index 9792a19..1c4409e 100644 --- a/src/core/modules/infrastructure/dto/base-core.dto.ts +++ b/src/core/modules/infrastructure/dto/base-core.dto.ts @@ -1,5 +1,5 @@ -import { BaseCoreEntity } from "../../domain/entities/base-core.entity"; +import { BaseCoreEntity } from '../../domain/entities/base-core.entity'; export class BaseCoreDto implements BaseCoreEntity { - id: string; -} \ No newline at end of file + id: string; +} diff --git a/src/core/modules/infrastructure/dto/base-filter.dto.ts b/src/core/modules/infrastructure/dto/base-filter.dto.ts index f0c3820..9e4bf24 100644 --- a/src/core/modules/infrastructure/dto/base-filter.dto.ts +++ b/src/core/modules/infrastructure/dto/base-filter.dto.ts @@ -1,8 +1,14 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { BaseFilterEntity } from "../../domain/entities/base-filter.entity"; -import { Transform } from "class-transformer"; -import { IsArray, IsEnum, IsNumber, IsString, ValidateIf } from "class-validator"; -import { ORDER_TYPE, STATUS } from "src/core/strings/constants/base.constants"; +import { ApiProperty } from '@nestjs/swagger'; +import { BaseFilterEntity } from '../../domain/entities/base-filter.entity'; +import { Transform } from 'class-transformer'; +import { + IsArray, + IsEnum, + IsNumber, + IsString, + ValidateIf, +} from 'class-validator'; +import { ORDER_TYPE, STATUS } from 'src/core/strings/constants/base.constants'; export class BaseFilterDto implements BaseFilterEntity { @ApiProperty({ type: Number, required: false, default: 1 }) @@ -16,7 +22,7 @@ export class BaseFilterDto implements BaseFilterEntity { @ValidateIf((body) => body.limit) @IsNumber() limit = 10; - + @ApiProperty({ type: String, required: false }) q: string; diff --git a/src/core/modules/infrastructure/dto/base-status.dto.ts b/src/core/modules/infrastructure/dto/base-status.dto.ts index a641891..836b6d5 100644 --- a/src/core/modules/infrastructure/dto/base-status.dto.ts +++ b/src/core/modules/infrastructure/dto/base-status.dto.ts @@ -1,7 +1,7 @@ -import { STATUS } from "src/core/strings/constants/base.constants"; -import { BaseStatusEntity } from "../../domain/entities/base-status.entity"; -import { BaseDto } from "./base.dto"; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BaseStatusEntity } from '../../domain/entities/base-status.entity'; +import { BaseDto } from './base.dto'; export class BaseStatusDto extends BaseDto implements BaseStatusEntity { - status: STATUS; -} \ No newline at end of file + status: STATUS; +} diff --git a/src/core/modules/infrastructure/dto/base.dto.ts b/src/core/modules/infrastructure/dto/base.dto.ts index 1f600cd..584c3a6 100644 --- a/src/core/modules/infrastructure/dto/base.dto.ts +++ b/src/core/modules/infrastructure/dto/base.dto.ts @@ -1,11 +1,11 @@ -import { BaseEntity } from "../../domain/entities/base.entity"; -import { BaseCoreDto } from "./base-core.dto"; +import { BaseEntity } from '../../domain/entities/base.entity'; +import { BaseCoreDto } from './base-core.dto'; export class BaseDto extends BaseCoreDto implements BaseEntity { - creator_id: string; - creator_name: string; - editor_id: string; - editor_name: string; - created_at: number; - updated_at: number; -} \ No newline at end of file + creator_id: string; + creator_name: string; + editor_id: string; + editor_name: string; + created_at: number; + updated_at: number; +} diff --git a/src/core/response/domain/exceptions/handled-exception.ts b/src/core/response/domain/exceptions/handled-exception.ts index 57bdff1..2b76ef2 100644 --- a/src/core/response/domain/exceptions/handled-exception.ts +++ b/src/core/response/domain/exceptions/handled-exception.ts @@ -16,7 +16,7 @@ export class HttpExceptionFilter implements ExceptionFilter { let body: any; let exceptionResponse; - try { + try { exceptionResponse = JSON.parse(exception.message); } catch (error) {} diff --git a/src/core/response/domain/ok-response.interface.ts b/src/core/response/domain/ok-response.interface.ts index ea0a949..133ad58 100644 --- a/src/core/response/domain/ok-response.interface.ts +++ b/src/core/response/domain/ok-response.interface.ts @@ -15,3 +15,10 @@ export interface PaginationResponse { data: T[]; total: number; } + +export interface BatchResult { + messages: string[]; + total_items: number; + total_success: number; + total_failed: number; +} diff --git a/src/core/strings/constants/base.constants.ts b/src/core/strings/constants/base.constants.ts index 8d968b7..2fc60a6 100644 --- a/src/core/strings/constants/base.constants.ts +++ b/src/core/strings/constants/base.constants.ts @@ -1,22 +1,22 @@ export enum STATUS { - ACTIVE = 'active', - CANCEL = 'cancel', - CONFIRMED = 'confirmed', - DRAFT = 'draft', - EXPIRED = 'expired', - INACTIVE = 'inactive', - PENDING = 'pending', - REFUNDED = 'refunded', - SETTLED = 'settled', + ACTIVE = 'active', + CANCEL = 'cancel', + CONFIRMED = 'confirmed', + DRAFT = 'draft', + EXPIRED = 'expired', + INACTIVE = 'inactive', + PENDING = 'pending', + REFUNDED = 'refunded', + SETTLED = 'settled', } export enum ORDER_TYPE { - ASC = 'ASC', - DESC = 'DESC', + ASC = 'ASC', + DESC = 'DESC', } export enum CONNECTION_NAME { - DEFAULT = 'default', + DEFAULT = 'default', } export enum METHOD { @@ -38,6 +38,6 @@ export enum OPERATION { } export const BLANK_USER = { - id: null, - name: null, -} \ No newline at end of file + id: null, + name: null, +}; diff --git a/src/core/strings/constants/interface.constants.ts b/src/core/strings/constants/interface.constants.ts index e5f2600..827be18 100644 --- a/src/core/strings/constants/interface.constants.ts +++ b/src/core/strings/constants/interface.constants.ts @@ -1,9 +1,9 @@ -import { UsersSession } from "src/core/sessions"; -import { OPERATION } from "./base.constants"; +import { UsersSession } from 'src/core/sessions'; +import { OPERATION } from './base.constants'; export interface EventTopics { - topic: any, - data: IEvent, + topic: any; + data?: IEvent; } export interface IEvent { @@ -14,4 +14,4 @@ export interface IEvent { description: null | string; module: string; op: OPERATION; -} \ No newline at end of file +} diff --git a/src/core/strings/constants/module.constants.ts b/src/core/strings/constants/module.constants.ts index 1bb3e26..f1738a7 100644 --- a/src/core/strings/constants/module.constants.ts +++ b/src/core/strings/constants/module.constants.ts @@ -1,3 +1,3 @@ export enum MODULE_NAME { - USER_PRIVILEGE = 'user-privileges' -} \ No newline at end of file + USER_PRIVILEGE = 'user-privileges', +} diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 7d91910..41116db 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -1,3 +1,3 @@ export enum TABLE_NAME { - USER_PRIVILEGE = 'user_privileges' -} \ No newline at end of file + USER_PRIVILEGE = 'user_privileges', +} diff --git a/yarn.lock b/yarn.lock index dbbdb96..6133ac6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1120,10 +1120,12 @@ dependencies: undici-types "~5.26.4" -"@types/node@18.11.18": - version "18.11.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" - integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== +"@types/node@^20.12.13": + version "20.12.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" + integrity sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA== + dependencies: + undici-types "~5.26.4" "@types/qs@*": version "6.9.15"