From 68e321a08fef2fcd69ea650e5798d70077fd1039 Mon Sep 17 00:00:00 2001 From: David-Henri ARNAUD Date: Wed, 15 Oct 2025 15:14:49 +0200 Subject: [PATCH] fix --- .claude/settings.local.json | 6 +- apps/backend/.dockerignore | 2 +- apps/backend/docker-compose.yaml | 19 +++++ .../src/application/auth/auth.module.ts | 16 ++-- .../src/application/auth/auth.service.ts | 20 ++--- .../controllers/users.controller.ts | 63 +++++---------- .../organizations/organizations.module.ts | 18 ++--- .../src/application/rates/rates.module.ts | 21 +++-- .../src/application/users/users.module.ts | 20 ++--- .../src/domain/services/booking.service.ts | 21 +++-- .../carriers/base-carrier.connector.ts | 80 ++++++------------- 11 files changed, 115 insertions(+), 171 deletions(-) create mode 100644 apps/backend/docker-compose.yaml diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2f33d77..d94058d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,11 @@ "Bash(git commit:*)", "Bash(k6:*)", "Bash(npx playwright:*)", - "Bash(npx newman:*)" + "Bash(npx newman:*)", + "Bash(chmod:*)", + "Bash(netstat -ano)", + "Bash(findstr \":5432\")", + "Bash(findstr \"LISTENING\")" ], "deny": [], "ask": [] diff --git a/apps/backend/.dockerignore b/apps/backend/.dockerignore index 9b89c86..da0d9d2 100644 --- a/apps/backend/.dockerignore +++ b/apps/backend/.dockerignore @@ -69,7 +69,7 @@ temp # Docker Dockerfile .dockerignore -docker-compose*.yml +docker-compose.yaml # CI/CD .gitlab-ci.yml diff --git a/apps/backend/docker-compose.yaml b/apps/backend/docker-compose.yaml new file mode 100644 index 0000000..70c904f --- /dev/null +++ b/apps/backend/docker-compose.yaml @@ -0,0 +1,19 @@ +services: + postgres: + image: postgres:latest + container_name: xpeditis-postgres + environment: + POSTGRES_USER: xpeditis + POSTGRES_PASSWORD: xpeditis_dev_password + POSTGRES_DB: xpeditis_dev + ports: + - "5432:5432" + + redis: + image: redis:7 + container_name: xpeditis-redis + command: redis-server --requirepass xpeditis_redis_password + environment: + REDIS_PASSWORD: xpeditis_redis_password + ports: + - "6379:6379" diff --git a/apps/backend/src/application/auth/auth.module.ts b/apps/backend/src/application/auth/auth.module.ts index 827f5b7..2068765 100644 --- a/apps/backend/src/application/auth/auth.module.ts +++ b/apps/backend/src/application/auth/auth.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthService } from './auth.service'; import { JwtStrategy } from './jwt.strategy'; import { AuthController } from '../controllers/auth.controller'; @@ -9,18 +10,8 @@ import { AuthController } from '../controllers/auth.controller'; // Import domain and infrastructure dependencies import { USER_REPOSITORY } from '../../domain/ports/out/user.repository'; import { TypeOrmUserRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-user.repository'; +import { UserOrmEntity } from '../../infrastructure/persistence/typeorm/entities/user.orm-entity'; -/** - * Authentication Module - * - * Wires together the authentication system: - * - JWT configuration with access/refresh tokens - * - Passport JWT strategy - * - Auth service and controller - * - User repository for database access - * - * This module should be imported in AppModule. - */ @Module({ imports: [ // Passport configuration @@ -37,6 +28,9 @@ import { TypeOrmUserRepository } from '../../infrastructure/persistence/typeorm/ }, }), }), + + // 👇 Add this to register TypeORM repository for UserOrmEntity + TypeOrmModule.forFeature([UserOrmEntity]), ], controllers: [AuthController], providers: [ diff --git a/apps/backend/src/application/auth/auth.service.ts b/apps/backend/src/application/auth/auth.service.ts index 3f8115e..bc9c704 100644 --- a/apps/backend/src/application/auth/auth.service.ts +++ b/apps/backend/src/application/auth/auth.service.ts @@ -1,8 +1,8 @@ -import { Injectable, UnauthorizedException, ConflictException, Logger } from '@nestjs/common'; +import { Injectable, UnauthorizedException, ConflictException, Logger, Inject } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import * as argon2 from 'argon2'; -import { UserRepository } from '../../domain/ports/out/user.repository'; +import { UserRepository, USER_REPOSITORY } from '../../domain/ports/out/user.repository'; import { User, UserRole } from '../../domain/entities/user.entity'; import { v4 as uuidv4 } from 'uuid'; @@ -19,7 +19,8 @@ export class AuthService { private readonly logger = new Logger(AuthService.name); constructor( - private readonly userRepository: UserRepository, + @Inject(USER_REPOSITORY) + private readonly userRepository: UserRepository, // ✅ Correct injection private readonly jwtService: JwtService, private readonly configService: ConfigService, ) {} @@ -36,14 +37,12 @@ export class AuthService { ): Promise<{ accessToken: string; refreshToken: string; user: any }> { this.logger.log(`Registering new user: ${email}`); - // Check if user already exists const existingUser = await this.userRepository.findByEmail(email); if (existingUser) { throw new ConflictException('User with this email already exists'); } - // Hash password with Argon2 const passwordHash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 65536, // 64 MB @@ -51,7 +50,6 @@ export class AuthService { parallelism: 4, }); - // Create user entity const user = User.create({ id: uuidv4(), organizationId, @@ -59,13 +57,11 @@ export class AuthService { passwordHash, firstName, lastName, - role: UserRole.USER, // Default role + role: UserRole.USER, }); - // Save to database const savedUser = await this.userRepository.save(user); - // Generate tokens const tokens = await this.generateTokens(savedUser); this.logger.log(`User registered successfully: ${email}`); @@ -92,7 +88,6 @@ export class AuthService { ): Promise<{ accessToken: string; refreshToken: string; user: any }> { this.logger.log(`Login attempt for: ${email}`); - // Find user by email const user = await this.userRepository.findByEmail(email); if (!user) { @@ -103,14 +98,12 @@ export class AuthService { throw new UnauthorizedException('User account is inactive'); } - // Verify password const isPasswordValid = await argon2.verify(user.passwordHash, password); if (!isPasswordValid) { throw new UnauthorizedException('Invalid credentials'); } - // Generate tokens const tokens = await this.generateTokens(user); this.logger.log(`User logged in successfully: ${email}`); @@ -133,7 +126,6 @@ export class AuthService { */ async refreshAccessToken(refreshToken: string): Promise<{ accessToken: string; refreshToken: string }> { try { - // Verify refresh token const payload = await this.jwtService.verifyAsync(refreshToken, { secret: this.configService.get('JWT_SECRET'), }); @@ -142,14 +134,12 @@ export class AuthService { throw new UnauthorizedException('Invalid token type'); } - // Get user const user = await this.userRepository.findById(payload.sub); if (!user || !user.isActive) { throw new UnauthorizedException('User not found or inactive'); } - // Generate new tokens const tokens = await this.generateTokens(user); this.logger.log(`Access token refreshed for user: ${user.email}`); diff --git a/apps/backend/src/application/controllers/users.controller.ts b/apps/backend/src/application/controllers/users.controller.ts index c51dc10..21b2c48 100644 --- a/apps/backend/src/application/controllers/users.controller.ts +++ b/apps/backend/src/application/controllers/users.controller.ts @@ -101,17 +101,13 @@ export class UsersController { }) async createUser( @Body() dto: CreateUserDto, - @CurrentUser() user: UserPayload, + @CurrentUser() user: UserPayload ): Promise { - this.logger.log( - `[User: ${user.email}] Creating user: ${dto.email} (${dto.role})`, - ); + this.logger.log(`[User: ${user.email}] Creating user: ${dto.email} (${dto.role})`); // Authorization: Managers can only create users in their own organization if (user.role === 'manager' && dto.organizationId !== user.organizationId) { - throw new ForbiddenException( - 'You can only create users in your own organization', - ); + throw new ForbiddenException('You can only create users in your own organization'); } // Check if user already exists @@ -121,8 +117,7 @@ export class UsersController { } // Generate temporary password if not provided - const tempPassword = - dto.password || this.generateTemporaryPassword(); + const tempPassword = dto.password || this.generateTemporaryPassword(); // Hash password with Argon2id const passwordHash = await argon2.hash(tempPassword, { @@ -153,7 +148,7 @@ export class UsersController { // TODO: Send invitation email with temporary password this.logger.warn( - `TODO: Send invitation email to ${dto.email} with temp password: ${tempPassword}`, + `TODO: Send invitation email to ${dto.email} with temp password: ${tempPassword}` ); return UserMapper.toDto(savedUser); @@ -165,8 +160,7 @@ export class UsersController { @Get(':id') @ApiOperation({ summary: 'Get user by ID', - description: - 'Retrieve user details. Users can view users in their org, admins can view any.', + description: 'Retrieve user details. Users can view users in their org, admins can view any.', }) @ApiParam({ name: 'id', @@ -183,7 +177,7 @@ export class UsersController { }) async getUser( @Param('id', ParseUUIDPipe) id: string, - @CurrentUser() currentUser: UserPayload, + @CurrentUser() currentUser: UserPayload ): Promise { this.logger.log(`[User: ${currentUser.email}] Fetching user: ${id}`); @@ -193,10 +187,7 @@ export class UsersController { } // Authorization: Can only view users in same organization (unless admin) - if ( - currentUser.role !== 'admin' && - user.organizationId !== currentUser.organizationId - ) { + if (currentUser.role !== 'admin' && user.organizationId !== currentUser.organizationId) { throw new ForbiddenException('You can only view users in your organization'); } @@ -211,8 +202,7 @@ export class UsersController { @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) @ApiOperation({ summary: 'Update user', - description: - 'Update user details (name, role, status). Admin/manager only.', + description: 'Update user details (name, role, status). Admin/manager only.', }) @ApiParam({ name: 'id', @@ -233,7 +223,7 @@ export class UsersController { async updateUser( @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateUserDto, - @CurrentUser() currentUser: UserPayload, + @CurrentUser() currentUser: UserPayload ): Promise { this.logger.log(`[User: ${currentUser.email}] Updating user: ${id}`); @@ -243,13 +233,8 @@ export class UsersController { } // Authorization: Managers can only update users in their own organization - if ( - currentUser.role === 'manager' && - user.organizationId !== currentUser.organizationId - ) { - throw new ForbiddenException( - 'You can only update users in your own organization', - ); + if (currentUser.role === 'manager' && user.organizationId !== currentUser.organizationId) { + throw new ForbiddenException('You can only update users in your own organization'); } // Update fields @@ -308,7 +293,7 @@ export class UsersController { }) async deleteUser( @Param('id', ParseUUIDPipe) id: string, - @CurrentUser() currentUser: UserPayload, + @CurrentUser() currentUser: UserPayload ): Promise { this.logger.log(`[Admin: ${currentUser.email}] Deactivating user: ${id}`); @@ -360,21 +345,17 @@ export class UsersController { @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('pageSize', new DefaultValuePipe(20), ParseIntPipe) pageSize: number, @Query('role') role: string | undefined, - @CurrentUser() currentUser: UserPayload, + @CurrentUser() currentUser: UserPayload ): Promise { this.logger.log( - `[User: ${currentUser.email}] Listing users: page=${page}, pageSize=${pageSize}, role=${role}`, + `[User: ${currentUser.email}] Listing users: page=${page}, pageSize=${pageSize}, role=${role}` ); // Fetch users by organization - const users = await this.userRepository.findByOrganization( - currentUser.organizationId, - ); + const users = await this.userRepository.findByOrganization(currentUser.organizationId); // Filter by role if provided - const filteredUsers = role - ? users.filter(u => u.role === role) - : users; + const filteredUsers = role ? users.filter(u => u.role === role) : users; // Paginate const startIndex = (page - 1) * pageSize; @@ -418,7 +399,7 @@ export class UsersController { }) async updatePassword( @Body() dto: UpdatePasswordDto, - @CurrentUser() currentUser: UserPayload, + @CurrentUser() currentUser: UserPayload ): Promise<{ message: string }> { this.logger.log(`[User: ${currentUser.email}] Updating password`); @@ -428,10 +409,7 @@ export class UsersController { } // Verify current password - const isPasswordValid = await argon2.verify( - user.passwordHash, - dto.currentPassword, - ); + const isPasswordValid = await argon2.verify(user.passwordHash, dto.currentPassword); if (!isPasswordValid) { throw new ForbiddenException('Current password is incorrect'); @@ -459,8 +437,7 @@ export class UsersController { */ private generateTemporaryPassword(): string { const length = 16; - const charset = - 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; + const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; let password = ''; const randomBytes = crypto.randomBytes(length); diff --git a/apps/backend/src/application/organizations/organizations.module.ts b/apps/backend/src/application/organizations/organizations.module.ts index 9ef8b47..0560b4d 100644 --- a/apps/backend/src/application/organizations/organizations.module.ts +++ b/apps/backend/src/application/organizations/organizations.module.ts @@ -1,20 +1,16 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { OrganizationsController } from '../controllers/organizations.controller'; // Import domain ports import { ORGANIZATION_REPOSITORY } from '../../domain/ports/out/organization.repository'; import { TypeOrmOrganizationRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-organization.repository'; +import { OrganizationOrmEntity } from '../../infrastructure/persistence/typeorm/entities/organization.orm-entity'; -/** - * Organizations Module - * - * Handles organization management functionality: - * - Create organizations (admin only) - * - View organization details - * - Update organization (admin/manager) - * - List organizations - */ @Module({ + imports: [ + TypeOrmModule.forFeature([OrganizationOrmEntity]), // 👈 This line registers the repository provider + ], controllers: [OrganizationsController], providers: [ { @@ -22,6 +18,8 @@ import { TypeOrmOrganizationRepository } from '../../infrastructure/persistence/ useClass: TypeOrmOrganizationRepository, }, ], - exports: [], + exports: [ + ORGANIZATION_REPOSITORY, // optional, if other modules need it + ], }) export class OrganizationsModule {} diff --git a/apps/backend/src/application/rates/rates.module.ts b/apps/backend/src/application/rates/rates.module.ts index f88427d..b071fa9 100644 --- a/apps/backend/src/application/rates/rates.module.ts +++ b/apps/backend/src/application/rates/rates.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { RatesController } from '../controllers/rates.controller'; import { CacheModule } from '../../infrastructure/cache/cache.module'; import { CarrierModule } from '../../infrastructure/carriers/carrier.module'; @@ -6,18 +7,14 @@ import { CarrierModule } from '../../infrastructure/carriers/carrier.module'; // Import domain ports import { RATE_QUOTE_REPOSITORY } from '../../domain/ports/out/rate-quote.repository'; import { TypeOrmRateQuoteRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-rate-quote.repository'; +import { RateQuoteOrmEntity } from '../../infrastructure/persistence/typeorm/entities/rate-quote.orm-entity'; -/** - * Rates Module - * - * Handles rate search functionality: - * - Rate search API endpoint - * - Integration with carrier APIs - * - Redis caching for rate quotes - * - Rate quote persistence - */ @Module({ - imports: [CacheModule, CarrierModule], + imports: [ + CacheModule, + CarrierModule, + TypeOrmModule.forFeature([RateQuoteOrmEntity]), // 👈 Add this + ], controllers: [RatesController], providers: [ { @@ -25,6 +22,8 @@ import { TypeOrmRateQuoteRepository } from '../../infrastructure/persistence/typ useClass: TypeOrmRateQuoteRepository, }, ], - exports: [], + exports: [ + RATE_QUOTE_REPOSITORY, // optional, if used in other modules + ], }) export class RatesModule {} diff --git a/apps/backend/src/application/users/users.module.ts b/apps/backend/src/application/users/users.module.ts index 65983e9..5d14d16 100644 --- a/apps/backend/src/application/users/users.module.ts +++ b/apps/backend/src/application/users/users.module.ts @@ -1,22 +1,16 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersController } from '../controllers/users.controller'; // Import domain ports import { USER_REPOSITORY } from '../../domain/ports/out/user.repository'; import { TypeOrmUserRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-user.repository'; +import { UserOrmEntity } from '../../infrastructure/persistence/typeorm/entities/user.orm-entity'; -/** - * Users Module - * - * Handles user management functionality: - * - Create/invite users (admin/manager) - * - View user details - * - Update user (admin/manager) - * - Deactivate user (admin) - * - List users in organization - * - Update own password - */ @Module({ + imports: [ + TypeOrmModule.forFeature([UserOrmEntity]), // 👈 Add this line + ], controllers: [UsersController], providers: [ { @@ -24,6 +18,8 @@ import { TypeOrmUserRepository } from '../../infrastructure/persistence/typeorm/ useClass: TypeOrmUserRepository, }, ], - exports: [], + exports: [ + USER_REPOSITORY, // optional, export if other modules need it + ], }) export class UsersModule {} diff --git a/apps/backend/src/domain/services/booking.service.ts b/apps/backend/src/domain/services/booking.service.ts index 679d180..57bafa2 100644 --- a/apps/backend/src/domain/services/booking.service.ts +++ b/apps/backend/src/domain/services/booking.service.ts @@ -1,15 +1,9 @@ -/** - * BookingService (Domain Service) - * - * Business logic for booking management - */ - -import { Injectable } from '@nestjs/common'; -import { Booking, BookingContainer } from '../entities/booking.entity'; -import { BookingNumber } from '../value-objects/booking-number.vo'; -import { BookingStatus } from '../value-objects/booking-status.vo'; +import { Injectable, Inject, NotFoundException } from '@nestjs/common'; +import { Booking } from '../entities/booking.entity'; import { BookingRepository } from '../ports/out/booking.repository'; import { RateQuoteRepository } from '../ports/out/rate-quote.repository'; +import { BOOKING_REPOSITORY } from '../ports/out/booking.repository'; +import { RATE_QUOTE_REPOSITORY } from '../ports/out/rate-quote.repository'; import { v4 as uuidv4 } from 'uuid'; export interface CreateBookingInput { @@ -24,7 +18,10 @@ export interface CreateBookingInput { @Injectable() export class BookingService { constructor( + @Inject(BOOKING_REPOSITORY) private readonly bookingRepository: BookingRepository, + + @Inject(RATE_QUOTE_REPOSITORY) private readonly rateQuoteRepository: RateQuoteRepository ) {} @@ -35,7 +32,7 @@ export class BookingService { // Validate rate quote exists const rateQuote = await this.rateQuoteRepository.findById(input.rateQuoteId); if (!rateQuote) { - throw new Error(`Rate quote ${input.rateQuoteId} not found`); + throw new NotFoundException(`Rate quote ${input.rateQuoteId} not found`); } // TODO: Get userId and organizationId from context @@ -51,7 +48,7 @@ export class BookingService { shipper: input.shipper, consignee: input.consignee, cargoDescription: input.cargoDescription, - containers: input.containers.map((c) => ({ + containers: input.containers.map(c => ({ id: uuidv4(), type: c.type, containerNumber: c.containerNumber, diff --git a/apps/backend/src/infrastructure/carriers/base-carrier.connector.ts b/apps/backend/src/infrastructure/carriers/base-carrier.connector.ts index e9900d5..8126d87 100644 --- a/apps/backend/src/infrastructure/carriers/base-carrier.connector.ts +++ b/apps/backend/src/infrastructure/carriers/base-carrier.connector.ts @@ -7,7 +7,7 @@ import { Logger } from '@nestjs/common'; import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import CircuitBreaker from 'opossum'; +import * as CircuitBreaker from 'opossum'; // ✅ Correction ici import { CarrierConnectorPort, CarrierRateSearchInput, @@ -45,28 +45,28 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { }, }); - // Add request interceptor for logging + // Request interceptor this.httpClient.interceptors.request.use( - (request: any) => { + request => { this.logger.debug( `Request: ${request.method?.toUpperCase()} ${request.url}`, request.data ? JSON.stringify(request.data).substring(0, 200) : '' ); return request; }, - (error: any) => { + error => { this.logger.error(`Request error: ${error?.message || 'Unknown error'}`); return Promise.reject(error); } ); - // Add response interceptor for logging + // Response interceptor this.httpClient.interceptors.response.use( - (response: any) => { + response => { this.logger.debug(`Response: ${response.status} ${response.statusText}`); return response; }, - (error: any) => { + error => { if (error?.code === 'ECONNABORTED') { this.logger.warn(`Request timeout after ${config.timeout}ms`); throw new CarrierTimeoutException(config.name, config.timeout); @@ -76,7 +76,7 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { } ); - // Create circuit breaker + // Circuit breaker this.circuitBreaker = new CircuitBreaker(this.makeRequest.bind(this), { timeout: config.timeout, errorThresholdPercentage: config.circuitBreakerThreshold, @@ -84,18 +84,15 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { name: `${config.name}-circuit-breaker`, }); - // Circuit breaker event handlers - this.circuitBreaker.on('open', () => { - this.logger.warn('Circuit breaker opened - carrier unavailable'); - }); - - this.circuitBreaker.on('halfOpen', () => { - this.logger.log('Circuit breaker half-open - testing carrier availability'); - }); - - this.circuitBreaker.on('close', () => { - this.logger.log('Circuit breaker closed - carrier available'); - }); + this.circuitBreaker.on('open', () => + this.logger.warn('Circuit breaker opened - carrier unavailable') + ); + this.circuitBreaker.on('halfOpen', () => + this.logger.log('Circuit breaker half-open - testing carrier availability') + ); + this.circuitBreaker.on('close', () => + this.logger.log('Circuit breaker closed - carrier available') + ); } getCarrierName(): string { @@ -106,9 +103,6 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { return this.config.code; } - /** - * Make HTTP request with retry logic - */ protected async makeRequest( config: AxiosRequestConfig, retries = this.config.maxRetries @@ -126,41 +120,27 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { } } - /** - * Determine if error is retryable - */ protected isRetryableError(error: any): boolean { - // Retry on network errors, timeouts, and 5xx server errors - if (error.code === 'ECONNABORTED') return false; // Don't retry timeouts - if (error.code === 'ENOTFOUND') return false; // Don't retry DNS errors + if (error.code === 'ECONNABORTED') return false; + if (error.code === 'ENOTFOUND') return false; if (error.response) { const status = error.response.status; return status >= 500 && status < 600; } - return true; // Retry network errors + return true; } - /** - * Calculate retry delay with exponential backoff - */ protected calculateRetryDelay(attempt: number): number { - const baseDelay = 1000; // 1 second - const maxDelay = 5000; // 5 seconds + const baseDelay = 1000; + const maxDelay = 5000; const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay); - // Add jitter to prevent thundering herd - return delay + Math.random() * 1000; + return delay + Math.random() * 1000; // jitter } - /** - * Sleep utility - */ protected sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } - /** - * Make request with circuit breaker protection - */ protected async requestWithCircuitBreaker( config: AxiosRequestConfig ): Promise> { @@ -174,16 +154,9 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { } } - /** - * Health check implementation - */ async healthCheck(): Promise { try { - await this.requestWithCircuitBreaker({ - method: 'GET', - url: '/health', - timeout: 5000, - }); + await this.requestWithCircuitBreaker({ method: 'GET', url: '/health', timeout: 5000 }); return true; } catch (error: any) { this.logger.warn(`Health check failed: ${error?.message || 'Unknown error'}`); @@ -191,9 +164,6 @@ export abstract class BaseCarrierConnector implements CarrierConnectorPort { } } - /** - * Abstract methods to be implemented by specific carriers - */ abstract searchRates(input: CarrierRateSearchInput): Promise; abstract checkAvailability(input: CarrierAvailabilityInput): Promise; }