Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ timesheet-in-transit-api/
│-- app/
│ │-- src/
│ │ │-- app.module.ts
│ │ │-- config.env.ts
│ │ │-- database.module.ts
│ │ │-- index.ts
│ │ │-- main.ts
Expand All @@ -47,7 +46,7 @@ timesheet-in-transit-api/
│ │ │ │-- config/
│ │ │ │-- consumers/
│ │ │ │-- web/
│ │ │ │ │-- common/
│ │ │ │ │-- shared/
│ │ │ │ │-- config/
│ │ │ │ │-- rest/
│ │ │-- core/
Expand All @@ -67,6 +66,9 @@ timesheet-in-transit-api/
│ │ │ │-- integrations/
│ │ │ │-- queue/
│ │ │ │-- repositories/
│ │ │-- shared/
│ │ │ │-- audit/
│ │ │ │-- config/
```

### **modules**
Expand Down
6 changes: 4 additions & 2 deletions README.pt-br.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ timesheet-in-transit-api/
│-- app/
│ │-- src/
│ │ │-- app.module.ts
│ │ │-- config.env.ts
│ │ │-- database.module.ts
│ │ │-- index.ts
│ │ │-- main.ts
Expand All @@ -47,7 +46,7 @@ timesheet-in-transit-api/
│ │ │ │-- config/
│ │ │ │-- consumers/
│ │ │ │-- web/
│ │ │ │ │-- common/
│ │ │ │ │-- shared/
│ │ │ │ │-- config/
│ │ │ │ │-- rest/
│ │ │-- core/
Expand All @@ -67,6 +66,9 @@ timesheet-in-transit-api/
│ │ │ │-- integrations/
│ │ │ │-- queue/
│ │ │ │-- repositories/
│ │ │-- shared/
│ │ │ │-- audit/
│ │ │ │-- config/
```

### **modules**
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "timesheet-in-transit-api",
"version": "0.0.3",
"version": "0.0.4",
"description": "timesheet-in-transit-api",
"author": "Hemicharly Thiago",
"private": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NotificationOrderCoreEntity } from 'src/core/domain/entities/notifications';
import { NotificationOrderRegisterUsecase } from '@core/usecases/notification';
import { ProducerQueueProvider } from '@core/providers/queue/producer.queue.provider';
import { configEnv } from '@src/config.env';
import { ProducerQueueProvider } from '@core/providers/queue';
import { configEnv } from '@shared/config';

export class NotificationOrderRegisterUsecaseImpl implements NotificationOrderRegisterUsecase {
constructor(private readonly sendQueueProvider: ProducerQueueProvider) {}
Expand Down
2 changes: 1 addition & 1 deletion app/src/database.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { configEnv } from '@src/config.env';
import { configEnv } from '@src/shared/config';

@Module({
imports: [
Expand Down
1 change: 0 additions & 1 deletion app/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './app.module';
export * from './config.env';
export * from './database.module';
export * from './main';
export * from './seed.module';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DynamicModule, Module, Type } from '@nestjs/common';
* @example
*
* ```typescript
* import { Module } from '@nestjs/common';
* import { Module } from '@nestjs/shared';
* import { WebhookIntegrationClientProviderImpl } from '@infrastructure/integrations/webhook-client/impl';
* import { IntegrationConfigModule } from 'src/infrastructure/integrations/config/abstract';
* import { WebhookConfigModule } from '@infrastructure/integrations/webhook-client/config';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { HttpService } from '@nestjs/axios';
import { Logger } from '@nestjs/common';
import { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { IntegrationLoggerDto } from '@infrastructure/integrations/common/audit';
import { IntegrationLoggerDto } from '@infrastructure/integrations/shared/audit';
import { TracerContextAudit } from '@shared/audit';

/**
* Class responsible for managing API integration audit logs.
Expand All @@ -11,10 +12,10 @@ import { IntegrationLoggerDto } from '@infrastructure/integrations/common/audit'
*
* ```typescript
* import { WebhookIntegrationClientProvider } from '@core/providers/integrations';
* import { Injectable, Logger } from '@nestjs/common';
* import { Injectable, Logger } from '@nestjs/shared';
* import { firstValueFrom } from 'rxjs';
* import { HttpService } from '@nestjs/axios';
* import { IntegrationAuditCommon } from '@infrastructure/integrations/common/audit';
* import { IntegrationAuditCommon } from '@infrastructure/integrations/shared/audit';
*
* @Injectable()
* export class WebhookIntegrationClientProviderImpl implements WebhookIntegrationClientProvider {
Expand Down Expand Up @@ -83,6 +84,7 @@ export class IntegrationAuditCommon {
config.headers = {};
}
config.headers['request-start-time'] = process.hrtime();
config.headers['x-tracer-id'] = TracerContextAudit.getContextTracerId();
return config;
}

Expand Down Expand Up @@ -126,7 +128,7 @@ export class IntegrationAuditCommon {
*/
private logAudit(config: AxiosRequestConfig, responseData: any, status: number, startTime: [number, number], isError: boolean = false): void {
const integrationLoggerDto = this.createIntegrationLoggerDto(config, responseData, status, startTime);
const logMessage = `[AUDIT LOG] ${JSON.stringify(integrationLoggerDto)}`;
const logMessage = JSON.stringify(integrationLoggerDto);
if (isError) {
this.logger.error(logMessage);
return;
Expand All @@ -144,8 +146,9 @@ export class IntegrationAuditCommon {
* @returns An `IntegrationLoggerDto` object containing the audit information.
*/
private createIntegrationLoggerDto(config: AxiosRequestConfig, responseData: any, status: number, startTime: [number, number]): IntegrationLoggerDto {
const tracerId = <string>config.headers['x-tracer-id'];
const method = `${config.method?.toUpperCase()} ${config.url}`;
return new IntegrationLoggerDto(this.application, method, config.headers, config.params, config.data, responseData, status, startTime);
return new IntegrationLoggerDto(tracerId, this.application, method, config.headers, config.params, config.data, responseData, status, startTime);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const SENSITIVE_FIELDS = new Set([
* @class IntegrationLoggerDto
*/
export class IntegrationLoggerDto {
readonly tracerId: string;

/**
* Date and time when the log was created.
* @type {string}
Expand Down Expand Up @@ -84,6 +86,7 @@ export class IntegrationLoggerDto {
* Constructor for the `IntegrationLoggerDto` class.
* Initializes the properties and masks sensitive fields from the provided data.
*
* @param {string} tracerId The tracer id log.
* @param {string} application The name of the application generating the log.
* @param {string} endpoint The API endpoint called.
* @param {any} headers The request headers.
Expand All @@ -93,7 +96,8 @@ export class IntegrationLoggerDto {
* @param {number} statusCode The HTTP status code of the response.
* @param {[number, number]} startTime The start time of the request for calculating duration.
*/
constructor(application: string, endpoint: string, headers: any, queryParameters: any, requestBody: any, responseBody: any, statusCode: number, startTime: [number, number]) {
constructor(tracerId: string, application: string, endpoint: string, headers: any, queryParameters: any, requestBody: any, responseBody: any, statusCode: number, startTime: [number, number]) {
this.tracerId = tracerId; // Sets the tracerId
this.timestamp = new Date().toJSON(); // Marks the timestamp of the log
this.application = application; // Sets the application name
this.endpoint = endpoint; // Sets the accessed endpoint
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { configEnv } from '@src/config.env';
import { configEnv } from '@src/shared/config';

@Module({
imports: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { WebhookIntegrationClientProvider } from '@core/providers/integrations';
import { Injectable, Logger } from '@nestjs/common';
import { firstValueFrom } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import { IntegrationAuditCommon } from '@infrastructure/integrations/common/audit';
import { IntegrationAuditCommon } from '@infrastructure/integrations/shared/audit';

@Injectable()
export class WebhookIntegrationClientProviderImpl implements WebhookIntegrationClientProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DynamicModule, Module, Type } from '@nestjs/common';
* @example
*
* ```typescript
import { Module } from '@nestjs/common';
import { Module } from '@nestjs/shared';
import { SqsProducerQueueProviderImpl } from '@infrastructure/queue/sqs/impl/send.queue.provider.impl';
import { QueueConfigModule } from '@infrastructure/queue/abstract';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { ChangeMessageVisibilityCommand, DeleteMessageCommand, Message, ReceiveMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
import { SQSClientConfig } from '@aws-sdk/client-sqs/dist-types/SQSClient';
import { configEnv } from '@src/config.env';
import { configEnv } from '@src/shared/config';
import { TracerContextAudit } from '@shared/audit';

interface SqsHandler {
(message: Message): Promise<void>;
Expand Down Expand Up @@ -35,13 +36,16 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
}

async onModuleInit() {
this.logger.log('SqsConsumerQueueProviderImpl initialized.');
this.logger.log('SQS Consumer initialized.');
}

async onModuleDestroy() {
this.isShuttingDown = true;
this.logger.log('Shutting down SqsConsumerQueueProviderImpl...');
this.logger.log('Shutting down sqs consumer...');
this.stopAllPolling();

await Promise.all([...this.pollingIntervals.values()].map((interval) => clearTimeout(interval)));
this.logger.log('All polling operations stopped.');
}

registerQueueHandler(queueConfig: QueueConfig, handler: SqsHandler) {
Expand All @@ -68,6 +72,7 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
MaxNumberOfMessages: queueConfig.batchSize || 10,
WaitTimeSeconds: queueConfig.waitTimeSeconds || 20,
VisibilityTimeout: queueConfig.visibilityTimeout || 30,
MessageAttributeNames: ['All'],
});

const response = await this.sqsClient.send(command);
Expand All @@ -81,17 +86,18 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
if (!handler) {
throw new Error(`No handler registered for queue ${name}`);
}
TracerContextAudit.setContextTracerId(message?.MessageAttributes?.TracerId?.StringValue);
await handler(message);
await this.deleteMessage(url, message.ReceiptHandle!);
} catch (error) {
this.logger.error(`[${name}] Error processing message: ${error.message}`);
this.logger.error(`[${name}] Error processing message: ${error.message}`, error.stack);
await this.extendVisibilityTimeout(url, message.ReceiptHandle!, queueConfig.visibilityTimeout || 30);
}
}
backoffTime = 1000;
}
} catch (error) {
this.logger.error(`[${name}] Error receiving messages: ${error.message}`);
this.logger.error(`[${name}] Unhandled error: ${error.message}`, error.stack);
} finally {
if (!this.isShuttingDown) {
backoffTime = Math.min(backoffTime * 2, maxBackoffTime);
Expand All @@ -107,6 +113,11 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
}

private async deleteMessage(queueUrl: string, receiptHandle: string) {
if (!receiptHandle) {
this.logger.warn(`Invalid receipt handle for queue: ${queueUrl}`);
return;
}

try {
const command = new DeleteMessageCommand({
QueueUrl: queueUrl,
Expand All @@ -120,6 +131,11 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
}

private async extendVisibilityTimeout(queueUrl: string, receiptHandle: string, visibilityTimeout: number) {
if (!receiptHandle) {
this.logger.warn(`Invalid receipt handle for queue: ${queueUrl}`);
return;
}

try {
const command = new ChangeMessageVisibilityCommand({
QueueUrl: queueUrl,
Expand All @@ -129,7 +145,7 @@ export class SqsConsumerQueueProviderImpl implements OnModuleInit, OnModuleDestr
await this.sqsClient.send(command);
this.logger.debug(`Extended visibility timeout for message in queue: ${queueUrl}`);
} catch (error) {
this.logger.error(`Error extending visibility timeout for queue: ${queueUrl}, Error: ${error.message}`);
this.logger.error(`Error extending visibility timeout for queue: ${queueUrl}, Error: ${error.message}`, error.stack);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ProducerQueueProvider } from '@core/providers/queue';
import { Injectable, Logger } from '@nestjs/common';
import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
import { configEnv } from '@src/config.env';
import { configEnv } from '@src/shared/config';
import { SQSClientConfig } from '@aws-sdk/client-sqs/dist-types/SQSClient';
import { TracerContextAudit } from '@shared/audit';

@Injectable()
export class SqsProducerQueueProviderImpl implements ProducerQueueProvider {
Expand All @@ -25,6 +26,12 @@ export class SqsProducerQueueProviderImpl implements ProducerQueueProvider {
const sendMessageCommand = new SendMessageCommand({
MessageBody: message,
QueueUrl: `${configEnv.aws.sqs.queueUrl(queueName)}`,
MessageAttributes: {
TracerId: {
DataType: 'String',
StringValue: TracerContextAudit.getContextTracerId(),
},
},
});
this.logger.log(`[QueueName: ${queueName}] Send message to SQS.`);
await this.sqsClient.send(sendMessageCommand);
Expand All @@ -39,6 +46,10 @@ export class SqsProducerQueueProviderImpl implements ProducerQueueProvider {
const sendMessageCommand = new SendMessageCommand({
MessageBody: message,
MessageAttributes: {
TracerId: {
DataType: 'String',
StringValue: TracerContextAudit.getContextTracerId(),
},
AttemptCount: {
DataType: 'String',
StringValue: String(attempt),
Expand Down
4 changes: 2 additions & 2 deletions app/src/infrastructure/queue/sqs/sqs.queue-infra.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { QueueConfigModule } from '@infrastructure/queue/abstract';
import { SqsConsumerQueueProviderImpl, SqsProducerQueueProviderImpl } from '@infrastructure/queue/sqs/impl';
import { SqsProducerQueueProviderImpl } from '@infrastructure/queue/sqs/impl';

const queueConfigModule = QueueConfigModule.forFeature([SqsProducerQueueProviderImpl, SqsConsumerQueueProviderImpl]);
const queueConfigModule = QueueConfigModule.forFeature([SqsProducerQueueProviderImpl]);
@Module({
providers: queueConfigModule.providers,
exports: queueConfigModule.exports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-clas
* @example
*
* ```typescript
* import { Module } from '@nestjs/common';
* import { Module } from '@nestjs/shared';
* import { OrderEntity } from '@infrastructure/repositories/orders/entity';
* import { OrderRepositoryProviderImpl } from '@infrastructure/repositories/orders/impl';
* import { RepositoryConfigModule } from '@infrastructure/repositories/abstract/repository.config.module';
Expand Down
6 changes: 3 additions & 3 deletions app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import 'dotenv/config';
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from '@src/app.module';
import { HttpExceptionFilter } from 'src/modules/web/common/exceptions/filters';
import { CustomValidationPipe } from '@application/web/common/exceptions';
import { HttpExceptionFilter } from '@application/web/shared/middleware/exceptions/filters';
import { CustomValidationPipe } from 'src/modules/web/shared/middleware/exceptions';
import { NestExpressApplication } from '@nestjs/platform-express';
import * as express from 'express';
import * as compression from 'compression';
import helmet from 'helmet';
import { SetupRedoc, SwaggerDoc } from 'src/modules/web/config/swagger';
import { configEnv } from '@src/config.env';
import { configEnv } from '@shared/config';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Provider, Type } from '@nestjs/common';
* Usage example:
*
* ```typescript
* import { Module, Provider } from '@nestjs/common';
* import { Module, Provider } from '@nestjs/shared';
* import { RepositoryInfraModule } from '@infrastructure/repositories';
* import { AuthAppModule } from '@modules/web/middleware/apikey';
* import { OrdersController } from '@modules/web/controllers/orders';
Expand Down
Loading
Loading