diff --git a/packages/nestjs-report/src/dto/report-create.dto.ts b/packages/nestjs-report/src/dto/report-create.dto.ts index 7b0386f0..2c2e5c56 100644 --- a/packages/nestjs-report/src/dto/report-create.dto.ts +++ b/packages/nestjs-report/src/dto/report-create.dto.ts @@ -1,6 +1,6 @@ -import { Exclude } from 'class-transformer'; -import { PickType } from '@nestjs/swagger'; import { ReportCreatableInterface } from '@concepta/ts-common'; +import { PickType } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; import { ReportDto } from './report.dto'; /** @@ -8,5 +8,5 @@ import { ReportDto } from './report.dto'; */ @Exclude() export class ReportCreateDto - extends PickType(ReportDto, ['serviceKey', 'name'] as const) + extends PickType(ReportDto, ['serviceKey', 'name', 'status'] as const) implements ReportCreatableInterface {} diff --git a/packages/nestjs-report/src/dto/report-update.dto.ts b/packages/nestjs-report/src/dto/report-update.dto.ts new file mode 100644 index 00000000..3d048da0 --- /dev/null +++ b/packages/nestjs-report/src/dto/report-update.dto.ts @@ -0,0 +1,16 @@ +import { Exclude } from 'class-transformer'; +import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; +import { ReportCreatableInterface } from '@concepta/ts-common'; +import { ReportDto } from './report.dto'; +import { ReportUpdatableInterface } from '@concepta/ts-common/src/report/interfaces/report-updatable.interface'; + +/** + * Report Update DTO + */ +@Exclude() +export class ReportUpdateDto + extends IntersectionType( + PickType(ReportDto, ['status', 'file'] as const), + PartialType(PickType(ReportDto, ['errorMessage'] as const)) + ) + implements ReportUpdatableInterface {} diff --git a/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts new file mode 100644 index 00000000..aa2496ae --- /dev/null +++ b/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts @@ -0,0 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-exception'; + +export class ReportDownloadUrlMissingException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Error trying to generate signed download url', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'REPORT_DOWNLOAD_URL_ERROR'; + } +} diff --git a/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts new file mode 100644 index 00000000..8b4339ca --- /dev/null +++ b/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts @@ -0,0 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-exception'; + +export class ReportnameMissingException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Reportname is missing.', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'REPORTNAME_MISSING_ERROR'; + } +} diff --git a/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts new file mode 100644 index 00000000..2ccc6c3a --- /dev/null +++ b/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts @@ -0,0 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-exception'; + +export class ReportServiceKeyMissingException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Service key is missing.', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'SERVICE_KEY_MISSING_ERROR'; + } +} diff --git a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts index 89c1ed26..b009901b 100644 --- a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts @@ -1,5 +1,4 @@ -import { ReportInterface } from '@concepta/ts-common'; +import { ReportUpdatableInterface } from '@concepta/ts-common/src/report/interfaces/report-updatable.interface'; +import { ReferenceIdInterface } from '@concepta/ts-core'; -export interface ReportGeneratorResultInterface - extends Pick, - Partial> {} +export interface ReportGeneratorResultInterface extends ReportUpdatableInterface, ReferenceIdInterface {} \ No newline at end of file diff --git a/packages/nestjs-report/src/interfaces/report-lookup-service.interface.ts b/packages/nestjs-report/src/interfaces/report-lookup-service.interface.ts new file mode 100644 index 00000000..4b9f3a76 --- /dev/null +++ b/packages/nestjs-report/src/interfaces/report-lookup-service.interface.ts @@ -0,0 +1,12 @@ +import { ReportCreatableInterface, ReportInterface } from '@concepta/ts-common'; +import { LookupIdInterface, ReferenceId, ReferenceIdInterface } from '@concepta/ts-core'; +import { QueryOptionsInterface } from '@concepta/typeorm-common'; + +export interface ReportLookupServiceInterface + extends LookupIdInterface { + getUniqueReport( + org: Pick, + queryOptions?: QueryOptionsInterface, + ): Promise; + getWithFile(report: ReferenceIdInterface, queryOptions?: QueryOptionsInterface): Promise; +} diff --git a/packages/nestjs-report/src/interfaces/report-mutate-service.interface.ts b/packages/nestjs-report/src/interfaces/report-mutate-service.interface.ts new file mode 100644 index 00000000..659fe257 --- /dev/null +++ b/packages/nestjs-report/src/interfaces/report-mutate-service.interface.ts @@ -0,0 +1,11 @@ +import { ReportCreatableInterface } from '@concepta/ts-common'; +import { CreateOneInterface, ReferenceIdInterface, UpdateOneInterface } from '@concepta/ts-core'; +import { ReportEntityInterface } from './report-entity.interface'; +import { ReportUpdatableInterface } from '@concepta/ts-common/src/report/interfaces/report-updatable.interface'; + +export interface ReportMutateServiceInterface + extends CreateOneInterface, + UpdateOneInterface< + ReportUpdatableInterface & ReferenceIdInterface, + ReportEntityInterface + > {} diff --git a/packages/nestjs-report/src/interfaces/report-status.interface.ts b/packages/nestjs-report/src/interfaces/report-status.interface.ts new file mode 100644 index 00000000..cc689c5e --- /dev/null +++ b/packages/nestjs-report/src/interfaces/report-status.interface.ts @@ -0,0 +1,3 @@ +import { ReportInterface } from '@concepta/ts-common'; + +export interface ReportStatusInterface extends Pick {} diff --git a/packages/nestjs-report/src/report.module-definition.ts b/packages/nestjs-report/src/report.module-definition.ts index 2204e07d..5b31d24a 100644 --- a/packages/nestjs-report/src/report.module-definition.ts +++ b/packages/nestjs-report/src/report.module-definition.ts @@ -21,6 +21,8 @@ import { ReportService } from './services/report.service'; import { ReportStrategyService } from './services/report-strategy.service'; import { reportDefaultConfig } from './config/report-default.config'; +import { ReportMutateService } from './services/report-mutate.service'; +import { ReportLookupService } from './services/report-lookup.service'; const RAW_OPTIONS_TOKEN = Symbol('__REPORT_MODULE_RAW_OPTIONS_TOKEN__'); @@ -88,6 +90,8 @@ export function createReportProviders(options: { ...(options.providers ?? []), createReportSettingsProvider(options.overrides), createStrategyServiceProvider(options.overrides), + ReportMutateService, + ReportLookupService, ReportStrategyService, ReportService, ]; diff --git a/packages/nestjs-report/src/report.module.spec.ts b/packages/nestjs-report/src/report.module.spec.ts index aa68b5cc..23f4245d 100644 --- a/packages/nestjs-report/src/report.module.spec.ts +++ b/packages/nestjs-report/src/report.module.spec.ts @@ -82,6 +82,8 @@ describe(ReportModule, () => { const result = await reportService.generate({ name: REPORT_NAME_FIXTURE, serviceKey: REPORT_KEY_FIXTURE, + // TODO: check if proceesing should be defined here or automatically + status: ReportStatusEnum.Processing, }); return result; }; @@ -93,6 +95,7 @@ describe(ReportModule, () => { const result = await reportService.generate({ name: REPORT_NAME_FIXTURE, serviceKey: REPORT_SHORT_DELAY_KEY_FIXTURE, + status: ReportStatusEnum.Processing, }); return result; }; @@ -185,6 +188,7 @@ describe(ReportModule, () => { const result = await reportService.generate({ name: REPORT_NAME_FIXTURE, serviceKey: REPORT_KEY_FIXTURE, + status: ReportStatusEnum.Processing, }); expect(result.serviceKey).toBe(REPORT_KEY_FIXTURE); expect(result.name).toBe(REPORT_NAME_FIXTURE); diff --git a/packages/nestjs-report/src/services/report-lookup.service.ts b/packages/nestjs-report/src/services/report-lookup.service.ts new file mode 100644 index 00000000..54dfa67f --- /dev/null +++ b/packages/nestjs-report/src/services/report-lookup.service.ts @@ -0,0 +1,67 @@ +import { InjectDynamicRepository } from '@concepta/nestjs-typeorm-ext'; +import { LookupService, QueryOptionsInterface } from '@concepta/typeorm-common'; +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; + +import { REPORT_MODULE_REPORT_ENTITY_KEY } from '../report.constants'; +import { ReportEntityInterface } from '../interfaces/report-entity.interface'; +import { ReportLookupServiceInterface } from '../interfaces/report-lookup-service.interface'; +import { ReportInterface } from '@concepta/ts-common'; +import { ReportServiceKeyMissingException } from '../exceptions/report-service-key-missing.exception'; +import { ReportnameMissingException } from '../exceptions/report-name-missing.exception'; +import { ReferenceIdInterface } from '@concepta/ts-core'; +import { ReportQueryException } from '../exceptions/report-query.exception'; + +/** + * Report lookup service + */ +@Injectable() +export class ReportLookupService + extends LookupService + implements ReportLookupServiceInterface +{ + constructor( + @InjectDynamicRepository(REPORT_MODULE_REPORT_ENTITY_KEY) + repo: Repository, + ) { + super(repo); + } + async getUniqueReport( + report: Pick, + queryOptions?: QueryOptionsInterface, + ) { + if (!report.serviceKey) { + throw new ReportServiceKeyMissingException(); + } + if (!report.name) { + throw new ReportnameMissingException(); + } + return this.findOne( + { + where: { + serviceKey: report.serviceKey, + name: report.name, + }, + }, + queryOptions, + ); + } + + async getWithFile( + report: ReferenceIdInterface, + queryOptions?: QueryOptionsInterface, + ) { + try { + return this.findOne({ + where: { + id: report.id, + }, + relations: ['file'], + }, + queryOptions, + ); + } catch (originalError) { + throw new ReportQueryException({ originalError }); + } + } +} diff --git a/packages/nestjs-report/src/services/report-mutate.service.ts b/packages/nestjs-report/src/services/report-mutate.service.ts new file mode 100644 index 00000000..bf22ae65 --- /dev/null +++ b/packages/nestjs-report/src/services/report-mutate.service.ts @@ -0,0 +1,40 @@ +import { InjectDynamicRepository } from '@concepta/nestjs-typeorm-ext'; +import { ReportCreatableInterface } from '@concepta/ts-common'; +import { MutateService } from '@concepta/typeorm-common'; +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { ReportEntityInterface } from '../interfaces/report-entity.interface'; + +import { ReportUpdatableInterface } from '@concepta/ts-common/src/report/interfaces/report-updatable.interface'; +import { ReportCreateDto } from '../dto/report-create.dto'; +import { ReportUpdateDto } from '../dto/report-update.dto'; +import { ReportMutateServiceInterface } from '../interfaces/report-mutate-service.interface'; +import { REPORT_MODULE_REPORT_ENTITY_KEY } from '../report.constants'; + +/** + * Report mutate service + */ +@Injectable() +export class ReportMutateService + extends MutateService< + ReportEntityInterface, + ReportCreatableInterface, + ReportUpdatableInterface + > + implements ReportMutateServiceInterface +{ + protected createDto = ReportCreateDto; + protected updateDto = ReportUpdateDto; + + /** + * Constructor + * + * @param repo - instance of the report repo + */ + constructor( + @InjectDynamicRepository(REPORT_MODULE_REPORT_ENTITY_KEY) + repo: Repository, + ) { + super(repo); + } +} diff --git a/packages/nestjs-report/src/services/report.service.spec.ts b/packages/nestjs-report/src/services/report.service.spec.ts index 9750549a..504e51d9 100644 --- a/packages/nestjs-report/src/services/report.service.spec.ts +++ b/packages/nestjs-report/src/services/report.service.spec.ts @@ -11,6 +11,8 @@ import { ReportQueryException } from '../exceptions/report-query.exception'; import { ReportEntityInterface } from '../interfaces/report-entity.interface'; import { ReportStrategyService } from './report-strategy.service'; import { ReportService } from './report.service'; +import { ReportMutateService } from './report-mutate.service'; +import { ReportLookupService } from './report-lookup.service'; const mockReport: ReportEntityInterface = { id: randomUUID(), @@ -28,17 +30,23 @@ const mockReport: ReportEntityInterface = { const mockReportCreateDto: ReportCreateDto = { serviceKey: mockReport.serviceKey, name: mockReport.name, + status: ReportStatusEnum.Processing, }; describe(ReportService.name, () => { let reportService: ReportService; let reportRepo: jest.Mocked>; let reportStrategyService: jest.Mocked; + let reportMutateService: ReportMutateService; + let reportLookupService: ReportLookupService; beforeEach(() => { reportRepo = createMockRepository(); reportStrategyService = createMockReportStrategyService(); - reportService = new ReportService(reportRepo, reportStrategyService); + reportMutateService = new ReportMutateService(reportRepo); + reportLookupService = new ReportLookupService(reportRepo); + + reportService = new ReportService(reportStrategyService, reportMutateService, reportLookupService); reportRepo.create.mockReturnValue(mockReport); const mockTransactionalEntityManager = { findOne: jest.fn().mockResolvedValue(null), diff --git a/packages/nestjs-report/src/services/report.service.ts b/packages/nestjs-report/src/services/report.service.ts index bf19e3e6..aff220e1 100644 --- a/packages/nestjs-report/src/services/report.service.ts +++ b/packages/nestjs-report/src/services/report.service.ts @@ -18,23 +18,25 @@ import { ReportCreateException } from '../exceptions/report-create.exception'; import { ReportIdMissingException } from '../exceptions/report-id-missing.exception'; import { ReportDuplicateEntryException } from '../exceptions/report-duplicated.exception'; import { ReportGeneratorResultInterface } from '../interfaces/report-generator-result.interface'; +import { ReportMutateService } from './report-mutate.service'; +import { ReportMutateServiceInterface } from '../interfaces/report-mutate-service.interface'; +import { ReportLookupService } from './report-lookup.service'; +import { ReportLookupServiceInterface } from '../interfaces/report-lookup-service.interface'; /** * Service responsible for managing report operations. */ @Injectable() -export class ReportService - extends BaseService - implements ReportServiceInterface +export class ReportService implements ReportServiceInterface { constructor( - @InjectDynamicRepository(REPORT_MODULE_REPORT_ENTITY_KEY) - private reportRepo: Repository, @Inject(REPORT_STRATEGY_SERVICE_KEY) private reportStrategyService: ReportStrategyService, - ) { - super(reportRepo); - } + @Inject(ReportMutateService) + private reportMutateService: ReportMutateServiceInterface, + @Inject(ReportLookupService) + private reportLookupService: ReportLookupServiceInterface, + ) {} async generate(report: ReportCreateDto): Promise { await this.checkExistingReport(report); @@ -55,19 +57,8 @@ export class ReportService throw new ReportIdMissingException(); } - let dbReport: ReportEntityInterface | null = null; - - try { - dbReport = await this.reportRepo.findOne({ - where: { - id: report.id, - }, - relations: ['file'], - }); - } catch (originalError) { - throw new ReportQueryException({ originalError }); - } - + const dbReport = await this.reportLookupService.getWithFile(report); + if (!dbReport) { throw new ReportQueryException({ message: 'Report with id %s not found', @@ -84,29 +75,7 @@ export class ReportService throw new ReportIdMissingException(); } - let reportToUpdate: ReportEntityInterface | null = null; - - try { - reportToUpdate = await this.reportRepo.findOne({ - where: { id: report.id }, - }); - } catch (originalError) { - throw new ReportQueryException({ originalError }); - } - - if (!reportToUpdate) { - throw new ReportQueryException({ - message: 'Report with id %s not found', - messageParams: [report.id], - httpStatus: HttpStatus.NOT_FOUND, - }); - } - - reportToUpdate.status = report.status; - reportToUpdate.errorMessage = report.errorMessage ?? null; - reportToUpdate.file = report.file; - - await this.reportRepo.save(reportToUpdate); + this.reportMutateService.update(report); } protected async generateAndProcessReport( @@ -128,21 +97,11 @@ export class ReportService protected async createAndSaveReport( report: ReportCreateDto, ): Promise { - const newReport = this.reportRepo.create({ - ...report, - status: ReportStatusEnum.Processing, - }); - newReport.status = ReportStatusEnum.Processing; - return await this.reportRepo.save(newReport); + return await this.reportMutateService.create(report); } protected async checkExistingReport(report: ReportCreateDto): Promise { - const existingReport = await this.reportRepo.findOne({ - where: { - serviceKey: report.serviceKey, - name: report.name, - }, - }); + const existingReport = await this.reportLookupService.getUniqueReport(report); if (existingReport) { throw new ReportDuplicateEntryException(report.serviceKey, report.name); @@ -153,9 +112,13 @@ export class ReportService report: ReportEntityInterface, ): Promise { if (report.file?.id) { - report.downloadUrl = await this.reportStrategyService.getDownloadUrl( - report, - ); + try { + report.downloadUrl = await this.reportStrategyService.getDownloadUrl( + report, + ); + } catch (err) { + report.downloadUrl = ''; + } } return report; } diff --git a/packages/ts-common/src/index.ts b/packages/ts-common/src/index.ts index c5689768..c68ad1bb 100644 --- a/packages/ts-common/src/index.ts +++ b/packages/ts-common/src/index.ts @@ -61,6 +61,7 @@ export { FileCreatableInterface } from './file/interfaces/file-creatable.interfa export { ReportInterface } from './report/interfaces/report.interface'; export { ReportCreatableInterface } from './report/interfaces/report-creatable.interface'; +export { ReportUpdatableInterface } from './report/interfaces/report-updatable.interface'; export { ReportStatusEnum } from './report/enum/report-status.enum'; export { diff --git a/packages/ts-common/src/report/interfaces/report-creatable.interface.ts b/packages/ts-common/src/report/interfaces/report-creatable.interface.ts index 269896a5..bac92d55 100644 --- a/packages/ts-common/src/report/interfaces/report-creatable.interface.ts +++ b/packages/ts-common/src/report/interfaces/report-creatable.interface.ts @@ -1,4 +1,5 @@ import { ReportInterface } from './report.interface'; export interface ReportCreatableInterface - extends Pick {} + extends Pick, + Partial>{ } diff --git a/packages/ts-common/src/report/interfaces/report-updatable.interface.ts b/packages/ts-common/src/report/interfaces/report-updatable.interface.ts new file mode 100644 index 00000000..4d0c440a --- /dev/null +++ b/packages/ts-common/src/report/interfaces/report-updatable.interface.ts @@ -0,0 +1,5 @@ +import { ReportInterface } from './report.interface'; + +export interface ReportUpdatableInterface + extends Pick, + Partial>{ }