fix v1.0.0
Some checks failed
CI/CD Pipeline / Discord Notification (Failure) (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Portainer (push) Blocked by required conditions
CI/CD Pipeline / Discord Notification (Success) (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m20s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Discord Notification (Failure) (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Portainer (push) Blocked by required conditions
CI/CD Pipeline / Discord Notification (Success) (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m20s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled
This commit is contained in:
parent
c19af3b119
commit
a1e255e816
@ -5,20 +5,22 @@ module.exports = {
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'unused-imports'],
|
||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', 'dist/**', 'node_modules/**'],
|
||||
ignorePatterns: ['.eslintrc.js', 'dist/**', 'node_modules/**', 'apps/**'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'@typescript-eslint/no-explicit-any': 'off', // Désactivé pour projet existant en production
|
||||
'@typescript-eslint/no-unused-vars': 'off', // Désactivé car remplacé par unused-imports
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
|
||||
17
apps/backend/package-lock.json
generated
17
apps/backend/package-lock.json
generated
@ -81,6 +81,7 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"ioredis-mock": "^8.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.1",
|
||||
@ -8211,6 +8212,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unused-imports": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz",
|
||||
"integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
|
||||
"eslint": "^9.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
|
||||
|
||||
@ -97,6 +97,7 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"ioredis-mock": "^8.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.1",
|
||||
|
||||
@ -1,48 +1,42 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
// Controller
|
||||
import { AdminController } from '../controllers/admin.controller';
|
||||
|
||||
// ORM Entities
|
||||
import { UserOrmEntity } from '@infrastructure/persistence/typeorm/entities/user.orm-entity';
|
||||
import { OrganizationOrmEntity } from '@infrastructure/persistence/typeorm/entities/organization.orm-entity';
|
||||
import { CsvBookingOrmEntity } from '@infrastructure/persistence/typeorm/entities/csv-booking.orm-entity';
|
||||
|
||||
// Repositories
|
||||
import { TypeOrmUserRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-user.repository';
|
||||
import { TypeOrmOrganizationRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-organization.repository';
|
||||
import { TypeOrmCsvBookingRepository } from '@infrastructure/persistence/typeorm/repositories/csv-booking.repository';
|
||||
|
||||
// Repository tokens
|
||||
import { USER_REPOSITORY } from '@domain/ports/out/user.repository';
|
||||
import { ORGANIZATION_REPOSITORY } from '@domain/ports/out/organization.repository';
|
||||
|
||||
/**
|
||||
* Admin Module
|
||||
*
|
||||
* Provides admin-only endpoints for managing all data in the system.
|
||||
* All endpoints require ADMIN role.
|
||||
*/
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
UserOrmEntity,
|
||||
OrganizationOrmEntity,
|
||||
CsvBookingOrmEntity,
|
||||
]),
|
||||
],
|
||||
controllers: [AdminController],
|
||||
providers: [
|
||||
{
|
||||
provide: USER_REPOSITORY,
|
||||
useClass: TypeOrmUserRepository,
|
||||
},
|
||||
{
|
||||
provide: ORGANIZATION_REPOSITORY,
|
||||
useClass: TypeOrmOrganizationRepository,
|
||||
},
|
||||
TypeOrmCsvBookingRepository,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
// Controller
|
||||
import { AdminController } from '../controllers/admin.controller';
|
||||
|
||||
// ORM Entities
|
||||
import { UserOrmEntity } from '@infrastructure/persistence/typeorm/entities/user.orm-entity';
|
||||
import { OrganizationOrmEntity } from '@infrastructure/persistence/typeorm/entities/organization.orm-entity';
|
||||
import { CsvBookingOrmEntity } from '@infrastructure/persistence/typeorm/entities/csv-booking.orm-entity';
|
||||
|
||||
// Repositories
|
||||
import { TypeOrmUserRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-user.repository';
|
||||
import { TypeOrmOrganizationRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-organization.repository';
|
||||
import { TypeOrmCsvBookingRepository } from '@infrastructure/persistence/typeorm/repositories/csv-booking.repository';
|
||||
|
||||
// Repository tokens
|
||||
import { USER_REPOSITORY } from '@domain/ports/out/user.repository';
|
||||
import { ORGANIZATION_REPOSITORY } from '@domain/ports/out/organization.repository';
|
||||
|
||||
/**
|
||||
* Admin Module
|
||||
*
|
||||
* Provides admin-only endpoints for managing all data in the system.
|
||||
* All endpoints require ADMIN role.
|
||||
*/
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([UserOrmEntity, OrganizationOrmEntity, CsvBookingOrmEntity])],
|
||||
controllers: [AdminController],
|
||||
providers: [
|
||||
{
|
||||
provide: USER_REPOSITORY,
|
||||
useClass: TypeOrmUserRepository,
|
||||
},
|
||||
{
|
||||
provide: ORGANIZATION_REPOSITORY,
|
||||
useClass: TypeOrmOrganizationRepository,
|
||||
},
|
||||
TypeOrmCsvBookingRepository,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -15,9 +15,8 @@ import {
|
||||
OrganizationRepository,
|
||||
ORGANIZATION_REPOSITORY,
|
||||
} from '@domain/ports/out/organization.repository';
|
||||
import { Organization, OrganizationType } from '@domain/entities/organization.entity';
|
||||
import { Organization } from '@domain/entities/organization.entity';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { DEFAULT_ORG_ID } from '@infrastructure/persistence/typeorm/seeds/test-organizations.seed';
|
||||
import { RegisterOrganizationDto } from '../dto/auth-login.dto';
|
||||
|
||||
export interface JwtPayload {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -38,7 +38,7 @@ class AuditLogResponseDto {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
class AuditLogQueryDto {
|
||||
class _AuditLogQueryDto {
|
||||
userId?: string;
|
||||
action?: AuditAction[];
|
||||
status?: AuditStatus[];
|
||||
|
||||
@ -34,7 +34,7 @@ import {
|
||||
import { Response } from 'express';
|
||||
import { CreateBookingRequestDto, BookingResponseDto, BookingListResponseDto } from '../dto';
|
||||
import { BookingFilterDto } from '../dto/booking-filter.dto';
|
||||
import { BookingExportDto, ExportFormat } from '../dto/booking-export.dto';
|
||||
import { BookingExportDto } from '../dto/booking-export.dto';
|
||||
import { BookingMapper } from '../mappers';
|
||||
import { BookingService } from '@domain/services/booking.service';
|
||||
import { BookingRepository, BOOKING_REPOSITORY } from '@domain/ports/out/booking.repository';
|
||||
@ -48,7 +48,7 @@ import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
|
||||
import { ExportService } from '../services/export.service';
|
||||
import { FuzzySearchService } from '../services/fuzzy-search.service';
|
||||
import { AuditService } from '../services/audit.service';
|
||||
import { AuditAction, AuditStatus } from '@domain/entities/audit-log.entity';
|
||||
import { AuditAction } from '@domain/entities/audit-log.entity';
|
||||
import { NotificationService } from '../services/notification.service';
|
||||
import { NotificationsGateway } from '../gateways/notifications.gateway';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
|
||||
@ -13,8 +13,6 @@ import {
|
||||
BadRequestException,
|
||||
ParseIntPipe,
|
||||
DefaultValuePipe,
|
||||
Res,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { FilesInterceptor } from '@nestjs/platform-express';
|
||||
import {
|
||||
@ -27,14 +25,12 @@ import {
|
||||
ApiQuery,
|
||||
ApiParam,
|
||||
} from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||
import { Public } from '../decorators/public.decorator';
|
||||
import { CsvBookingService } from '../services/csv-booking.service';
|
||||
import {
|
||||
CreateCsvBookingDto,
|
||||
CsvBookingResponseDto,
|
||||
UpdateCsvBookingStatusDto,
|
||||
CsvBookingListResponseDto,
|
||||
CsvBookingStatsDto,
|
||||
} from '../dto/csv-booking.dto';
|
||||
|
||||
@ -8,15 +8,10 @@ import {
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger';
|
||||
import { InvitationService } from '../services/invitation.service';
|
||||
import {
|
||||
CreateInvitationDto,
|
||||
InvitationResponseDto,
|
||||
VerifyInvitationDto,
|
||||
} from '../dto/invitation.dto';
|
||||
import { CreateInvitationDto, InvitationResponseDto } from '../dto/invitation.dto';
|
||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../guards/roles.guard';
|
||||
import { Roles } from '../decorators/roles.decorator';
|
||||
|
||||
@ -371,7 +371,9 @@ export class UsersController {
|
||||
);
|
||||
|
||||
// Fetch users from current user's organization
|
||||
this.logger.log(`[User: ${currentUser.email}] Fetching users from organization: ${currentUser.organizationId}`);
|
||||
this.logger.log(
|
||||
`[User: ${currentUser.email}] Fetching users from organization: ${currentUser.organizationId}`
|
||||
);
|
||||
let users = await this.userRepository.findByOrganization(currentUser.organizationId);
|
||||
|
||||
// Security: Non-admin users cannot see ADMIN users
|
||||
@ -379,7 +381,9 @@ export class UsersController {
|
||||
users = users.filter(u => u.role !== DomainUserRole.ADMIN);
|
||||
this.logger.log(`[SECURITY] Non-admin user ${currentUser.email} - filtered out ADMIN users`);
|
||||
} else {
|
||||
this.logger.log(`[ADMIN] User ${currentUser.email} can see all users including ADMINs in their organization`);
|
||||
this.logger.log(
|
||||
`[ADMIN] User ${currentUser.email} can see all users including ADMINs in their organization`
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by role if provided
|
||||
|
||||
@ -17,11 +17,7 @@ import {
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import {
|
||||
WebhookService,
|
||||
CreateWebhookInput,
|
||||
UpdateWebhookInput,
|
||||
} from '../services/webhook.service';
|
||||
import { WebhookService, CreateWebhookInput } from '../services/webhook.service';
|
||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../guards/roles.guard';
|
||||
import { Roles } from '../decorators/roles.decorator';
|
||||
|
||||
@ -6,14 +6,9 @@ import {
|
||||
Min,
|
||||
IsOptional,
|
||||
IsEnum,
|
||||
IsArray,
|
||||
ValidateNested,
|
||||
IsUUID,
|
||||
IsDateString,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
/**
|
||||
* Create CSV Booking DTO
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
IsArray,
|
||||
IsNumber,
|
||||
Min,
|
||||
Max,
|
||||
IsEnum,
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
IsUUID,
|
||||
|
||||
@ -23,7 +23,7 @@ export class CustomThrottlerGuard extends ThrottlerGuard {
|
||||
/**
|
||||
* Custom error message (override for new API)
|
||||
*/
|
||||
protected async throwThrottlingException(context: ExecutionContext): Promise<void> {
|
||||
protected async throwThrottlingException(_context: ExecutionContext): Promise<void> {
|
||||
throw new ThrottlerException('Too many requests. Please try again later.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export class PerformanceMonitoringInterceptor implements NestInterceptor {
|
||||
const startTime = Date.now();
|
||||
|
||||
return next.handle().pipe(
|
||||
tap(data => {
|
||||
tap(_data => {
|
||||
const duration = Date.now() - startTime;
|
||||
const response = context.switchToHttp().getResponse();
|
||||
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import { Booking } from '@domain/entities/booking.entity';
|
||||
import { RateQuote } from '@domain/entities/rate-quote.entity';
|
||||
import {
|
||||
BookingResponseDto,
|
||||
BookingAddressDto,
|
||||
BookingPartyDto,
|
||||
BookingContainerDto,
|
||||
BookingRateQuoteDto,
|
||||
BookingListItemDto,
|
||||
} from '../dto/booking-response.dto';
|
||||
import {
|
||||
CreateBookingRequestDto,
|
||||
PartyDto,
|
||||
AddressDto,
|
||||
ContainerDto,
|
||||
} from '../dto/create-booking-request.dto';
|
||||
import { BookingResponseDto, BookingListItemDto } from '../dto/booking-response.dto';
|
||||
import { CreateBookingRequestDto } from '../dto/create-booking-request.dto';
|
||||
|
||||
export class BookingMapper {
|
||||
/**
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CsvRate } from '@domain/entities/csv-rate.entity';
|
||||
import { Volume } from '@domain/value-objects/volume.vo';
|
||||
import { CsvRateResultDto, CsvRateSearchResponseDto } from '../dto/csv-rate-search.dto';
|
||||
import {
|
||||
CsvRateSearchInput,
|
||||
CsvRateSearchOutput,
|
||||
CsvRateSearchResult,
|
||||
RateSearchFilters,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Port } from '@domain/entities/port.entity';
|
||||
import { PortResponseDto, PortCoordinatesDto, PortSearchResponseDto } from '../dto/port.dto';
|
||||
import { PortResponseDto, PortSearchResponseDto } from '../dto/port.dto';
|
||||
|
||||
@Injectable()
|
||||
export class PortMapper {
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import { RateQuote } from '@domain/entities/rate-quote.entity';
|
||||
import {
|
||||
RateQuoteDto,
|
||||
PortDto,
|
||||
SurchargeDto,
|
||||
PricingDto,
|
||||
RouteSegmentDto,
|
||||
} from '../dto/rate-search-response.dto';
|
||||
import { RateQuoteDto } from '../dto/rate-search-response.dto';
|
||||
|
||||
export class RateQuoteMapper {
|
||||
/**
|
||||
|
||||
@ -4,13 +4,7 @@
|
||||
* Handles carrier authentication and automatic account creation
|
||||
*/
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
ConflictException,
|
||||
Inject,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, Logger, UnauthorizedException, Inject } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Validates uploaded files for security
|
||||
*/
|
||||
|
||||
import { Injectable, BadRequestException, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { fileUploadConfig } from '../../infrastructure/security/security.config';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import { HttpService } from '@nestjs/axios';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import * as crypto from 'crypto';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Webhook, WebhookEvent, WebhookStatus } from '@domain/entities/webhook.entity';
|
||||
import { Webhook, WebhookEvent } from '@domain/entities/webhook.entity';
|
||||
import {
|
||||
WebhookRepository,
|
||||
WEBHOOK_REPOSITORY,
|
||||
|
||||
@ -9,7 +9,7 @@ import { PortCode } from '../value-objects/port-code.vo';
|
||||
describe('CsvBooking Entity', () => {
|
||||
// Test data factory
|
||||
const createValidBooking = (
|
||||
overrides?: Partial<ConstructorParameters<typeof CsvBooking>[0]>
|
||||
_overrides?: Partial<ConstructorParameters<typeof CsvBooking>[0]>
|
||||
): CsvBooking => {
|
||||
const documents: CsvBookingDocument[] = [
|
||||
{
|
||||
|
||||
@ -2,7 +2,7 @@ import { PortCode } from '../value-objects/port-code.vo';
|
||||
import { ContainerType } from '../value-objects/container-type.vo';
|
||||
import { Money } from '../value-objects/money.vo';
|
||||
import { Volume } from '../value-objects/volume.vo';
|
||||
import { Surcharge, SurchargeCollection } from '../value-objects/surcharge.vo';
|
||||
import { SurchargeCollection } from '../value-objects/surcharge.vo';
|
||||
import { DateRange } from '../value-objects/date-range.vo';
|
||||
|
||||
/**
|
||||
|
||||
@ -64,7 +64,7 @@ describe('RateQuote Entity', () => {
|
||||
it('should set validUntil to 15 minutes from now', () => {
|
||||
const before = new Date();
|
||||
const rateQuote = RateQuote.create(validProps);
|
||||
const after = new Date();
|
||||
const _after = new Date();
|
||||
|
||||
const expectedValidUntil = new Date(before.getTime() + 15 * 60 * 1000);
|
||||
const diff = Math.abs(rateQuote.validUntil.getTime() - expectedValidUntil.getTime());
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { CsvRate } from '../../entities/csv-rate.entity';
|
||||
import { PortCode } from '../../value-objects/port-code.vo';
|
||||
import { Volume } from '../../value-objects/volume.vo';
|
||||
import { ServiceLevel } from '../../services/rate-offer-generator.service';
|
||||
|
||||
/**
|
||||
|
||||
@ -2,7 +2,6 @@ import { CsvRate } from '../entities/csv-rate.entity';
|
||||
import { PortCode } from '../value-objects/port-code.vo';
|
||||
import { ContainerType } from '../value-objects/container-type.vo';
|
||||
import { Volume } from '../value-objects/volume.vo';
|
||||
import { Money } from '../value-objects/money.vo';
|
||||
import {
|
||||
SearchCsvRatesPort,
|
||||
CsvRateSearchInput,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Implements CarrierConnectorPort for CMA CGM WebAccess API integration
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
CarrierConnectorPort,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Implements CarrierConnectorPort for Hapag-Lloyd Quick Quotes API
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
CarrierConnectorPort,
|
||||
|
||||
@ -25,8 +25,8 @@ export class MaerskResponseMapper {
|
||||
*/
|
||||
private static toRateQuote(
|
||||
result: MaerskRateResult,
|
||||
originCode: string,
|
||||
destinationCode: string
|
||||
_originCode: string,
|
||||
_destinationCode: string
|
||||
): RateQuote {
|
||||
const surcharges = result.pricing.charges.map(charge => ({
|
||||
type: charge.chargeCode,
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { BaseCarrierConnector, CarrierConfig } from '../base-carrier.connector';
|
||||
import {
|
||||
CarrierRateSearchInput,
|
||||
@ -15,7 +14,7 @@ import {
|
||||
import { RateQuote } from '@domain/entities/rate-quote.entity';
|
||||
import { MaerskRequestMapper } from './maersk-request.mapper';
|
||||
import { MaerskResponseMapper } from './maersk-response.mapper';
|
||||
import { MaerskRateSearchRequest, MaerskRateSearchResponse } from './maersk.types';
|
||||
import { MaerskRateSearchResponse } from './maersk.types';
|
||||
|
||||
@Injectable()
|
||||
export class MaerskConnector extends BaseCarrierConnector {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Implements CarrierConnectorPort for MSC API integration
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
CarrierConnectorPort,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Implements CarrierConnectorPort for ONE API
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
CarrierConnectorPort,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, LessThan, MoreThan } from 'typeorm';
|
||||
import { CsvBooking, CsvBookingStatus } from '@domain/entities/csv-booking.entity';
|
||||
import { CsvBooking } from '@domain/entities/csv-booking.entity';
|
||||
import { CsvBookingRepositoryPort } from '@domain/ports/out/csv-booking.repository';
|
||||
import { CsvBookingOrmEntity } from '../entities/csv-booking.orm-entity';
|
||||
import { CsvBookingMapper } from '../mappers/csv-booking.mapper';
|
||||
@ -110,7 +110,6 @@ export class TypeOrmCsvBookingRepository implements CsvBookingRepositoryPort {
|
||||
async findExpiringSoon(daysUntilExpiration: number): Promise<CsvBooking[]> {
|
||||
this.logger.log(`Finding CSV bookings expiring in ${daysUntilExpiration} days`);
|
||||
|
||||
const now = new Date();
|
||||
const expirationDate = new Date();
|
||||
expirationDate.setDate(expirationDate.getDate() + daysUntilExpiration);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, In, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AuditLogRepository, AuditLogFilters } from '@domain/ports/out/audit-log.repository';
|
||||
import { AuditLog, AuditStatus, AuditAction } from '@domain/entities/audit-log.entity';
|
||||
import { AuditLogOrmEntity } from '../entities/audit-log.orm-entity';
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, ILike } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Port } from '@domain/entities/port.entity';
|
||||
import { PortRepository } from '@domain/ports/out/port.repository';
|
||||
import { PortOrmEntity } from '../entities/port.orm-entity';
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
* Seeds test organizations for development
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export interface OrganizationSeed {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@ -16,7 +16,7 @@ import { AppModule } from '../src/app.module';
|
||||
describe('Carrier Portal (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let carrierAccessToken: string;
|
||||
let carrierId: string;
|
||||
let _carrierId: string;
|
||||
let bookingId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -53,7 +53,7 @@ describe('Carrier Portal (e2e)', () => {
|
||||
|
||||
// Save tokens for subsequent tests
|
||||
carrierAccessToken = res.body.accessToken;
|
||||
carrierId = res.body.carrier.id;
|
||||
_carrierId = res.body.carrier.id;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { acceptCsvBooking, type CsvBookingResponse } from '@/lib/api/bookings';
|
||||
|
||||
@ -21,18 +21,7 @@ export default function BookingConfirmPage() {
|
||||
const [booking, setBooking] = useState<CsvBookingResponse | null>(null);
|
||||
const [isAccepting, setIsAccepting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setError('Token de confirmation invalide');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-accept the booking
|
||||
handleAccept();
|
||||
}, [token]);
|
||||
|
||||
const handleAccept = async () => {
|
||||
const handleAccept = useCallback(async () => {
|
||||
setIsAccepting(true);
|
||||
setError(null);
|
||||
|
||||
@ -50,7 +39,18 @@ export default function BookingConfirmPage() {
|
||||
setIsLoading(false);
|
||||
setIsAccepting(false);
|
||||
}
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setError('Token de confirmation invalide');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-accept the booking
|
||||
handleAccept();
|
||||
}, [token, handleAccept]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
||||
@ -34,8 +34,8 @@ interface Booking {
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
requestedAt?: string;
|
||||
organizationId: string;
|
||||
userId: string;
|
||||
organizationId?: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export default function AdminBookingsPage() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { getAllBookings, getAllUsers } from '@/lib/api/admin';
|
||||
|
||||
interface Document {
|
||||
@ -21,12 +21,12 @@ interface Booking {
|
||||
bookingNumber?: string;
|
||||
bookingId?: string;
|
||||
type?: string;
|
||||
userId: string;
|
||||
organizationId: string;
|
||||
userId?: string;
|
||||
organizationId?: string;
|
||||
origin?: string;
|
||||
destination?: string;
|
||||
carrierName?: string;
|
||||
documents: Document[];
|
||||
documents?: Document[];
|
||||
requestedAt?: string;
|
||||
status: string;
|
||||
}
|
||||
@ -39,7 +39,6 @@ interface DocumentWithBooking extends Document {
|
||||
organizationId: string;
|
||||
route: string;
|
||||
status: string;
|
||||
fileName?: string;
|
||||
fileType?: string;
|
||||
}
|
||||
|
||||
@ -94,11 +93,7 @@ export default function AdminDocumentsPage() {
|
||||
return typeMap[ext] || ext.toUpperCase();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchBookingsAndDocuments();
|
||||
}, []);
|
||||
|
||||
const fetchBookingsAndDocuments = async () => {
|
||||
const fetchBookingsAndDocuments = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getAllBookings();
|
||||
@ -191,7 +186,11 @@ export default function AdminDocumentsPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchBookingsAndDocuments();
|
||||
}, [fetchBookingsAndDocuments]);
|
||||
|
||||
// Get unique users for filter (with names)
|
||||
const uniqueUsers = Array.from(
|
||||
|
||||
@ -34,7 +34,23 @@ export default function AdminOrganizationsPage() {
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState({
|
||||
const [formData, setFormData] = useState<{
|
||||
name: string;
|
||||
type: string;
|
||||
scac: string;
|
||||
siren: string;
|
||||
eori: string;
|
||||
contact_phone: string;
|
||||
contact_email: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state?: string;
|
||||
postalCode: string;
|
||||
country: string;
|
||||
};
|
||||
logoUrl: string;
|
||||
}>({
|
||||
name: '',
|
||||
type: 'FREIGHT_FORWARDER',
|
||||
scac: '',
|
||||
@ -72,7 +88,19 @@ export default function AdminOrganizationsPage() {
|
||||
const handleCreate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await createOrganization(formData);
|
||||
// Transform formData to match API expected format
|
||||
const apiData = {
|
||||
name: formData.name,
|
||||
type: formData.type as any, // OrganizationType
|
||||
address_street: formData.address.street,
|
||||
address_city: formData.address.city,
|
||||
address_postal_code: formData.address.postalCode,
|
||||
address_country: formData.address.country,
|
||||
contact_email: formData.contact_email || undefined,
|
||||
contact_phone: formData.contact_phone || undefined,
|
||||
logo_url: formData.logoUrl || undefined,
|
||||
};
|
||||
await createOrganization(apiData);
|
||||
await fetchOrganizations();
|
||||
setShowCreateModal(false);
|
||||
resetForm();
|
||||
|
||||
@ -4,13 +4,14 @@ import { useState, useEffect } from 'react';
|
||||
import { getAllUsers, updateAdminUser, deleteAdminUser } from '@/lib/api/admin';
|
||||
import { createUser } from '@/lib/api/users';
|
||||
import { getAllOrganizations } from '@/lib/api/admin';
|
||||
import type { UserRole } from '@/types/api';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
role: UserRole;
|
||||
organizationId: string;
|
||||
organizationName?: string;
|
||||
isActive: boolean;
|
||||
@ -33,7 +34,14 @@ export default function AdminUsersPage() {
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState({
|
||||
const [formData, setFormData] = useState<{
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole;
|
||||
organizationId: string;
|
||||
password: string;
|
||||
}>({
|
||||
email: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
@ -290,7 +298,7 @@ export default function AdminUsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700">Role</label>
|
||||
<select
|
||||
value={formData.role}
|
||||
onChange={e => setFormData({ ...formData, role: e.target.value })}
|
||||
onChange={e => setFormData({ ...formData, role: e.target.value as UserRole })}
|
||||
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="USER">User</option>
|
||||
@ -388,7 +396,7 @@ export default function AdminUsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700">Role</label>
|
||||
<select
|
||||
value={formData.role}
|
||||
onChange={e => setFormData({ ...formData, role: e.target.value })}
|
||||
onChange={e => setFormData({ ...formData, role: e.target.value as UserRole })}
|
||||
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="USER">User</option>
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { createBooking } from '@/lib/api';
|
||||
|
||||
@ -274,9 +275,11 @@ export default function NewBookingPage() {
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
{preselectedQuote.carrier.logoUrl ? (
|
||||
<img
|
||||
<Image
|
||||
src={preselectedQuote.carrier.logoUrl}
|
||||
alt={preselectedQuote.carrier.name}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-12 w-12 object-contain"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@ -208,8 +208,8 @@ export default function DashboardPage() {
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={({ name, percent }) =>
|
||||
`${name} ${(percent * 100).toFixed(0)}%`
|
||||
label={({ name, percent }: any) =>
|
||||
`${name} ${((percent || 0) * 100).toFixed(0)}%`
|
||||
}
|
||||
outerRadius={70}
|
||||
fill="#8884d8"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { searchCsvRatesWithOffers } from '@/lib/api/rates';
|
||||
import type { CsvRateSearchResult } from '@/types/rates';
|
||||
@ -25,16 +25,7 @@ export default function SearchResultsPage() {
|
||||
const weightKG = parseFloat(searchParams.get('weightKG') || '0');
|
||||
const palletCount = parseInt(searchParams.get('palletCount') || '0');
|
||||
|
||||
useEffect(() => {
|
||||
if (!origin || !destination || !volumeCBM || !weightKG) {
|
||||
router.push('/dashboard/search-advanced');
|
||||
return;
|
||||
}
|
||||
|
||||
performSearch();
|
||||
}, [origin, destination, volumeCBM, weightKG, palletCount]);
|
||||
|
||||
const performSearch = async () => {
|
||||
const performSearch = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
@ -61,7 +52,16 @@ export default function SearchResultsPage() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [origin, destination, volumeCBM, weightKG, palletCount, searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!origin || !destination || !volumeCBM || !weightKG) {
|
||||
router.push('/dashboard/search-advanced');
|
||||
return;
|
||||
}
|
||||
|
||||
performSearch();
|
||||
}, [origin, destination, volumeCBM, weightKG, performSearch, router]);
|
||||
|
||||
const getBestOptions = (): BestOptions | null => {
|
||||
if (results.length === 0) return null;
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Image from 'next/image';
|
||||
import { searchRates } from '@/lib/api';
|
||||
import { searchPorts, Port } from '@/lib/api/ports';
|
||||
|
||||
@ -433,9 +434,11 @@ export default function RateSearchPage() {
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
{quote.carrier.logoUrl ? (
|
||||
<img
|
||||
<Image
|
||||
src={quote.carrier.logoUrl}
|
||||
alt={quote.carrier.name}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-12 w-12 object-contain"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useAuth } from '@/lib/context/auth-context';
|
||||
import { getOrganization, updateOrganization } from '@/lib/api/organizations';
|
||||
import type { OrganizationResponse } from '@/types/api';
|
||||
@ -42,17 +42,13 @@ export default function OrganizationSettingsPage() {
|
||||
// Check if user can edit organization (only ADMIN and MANAGER)
|
||||
const canEdit = user?.role === 'ADMIN' || user?.role === 'MANAGER';
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.organizationId) {
|
||||
loadOrganization();
|
||||
}
|
||||
}, [user?.organizationId]);
|
||||
const loadOrganization = useCallback(async () => {
|
||||
if (!user?.organizationId) return;
|
||||
|
||||
const loadOrganization = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const org = await getOrganization(user!.organizationId);
|
||||
const org = await getOrganization(user.organizationId);
|
||||
setOrganization(org);
|
||||
setFormData({
|
||||
name: org.name || '',
|
||||
@ -71,7 +67,13 @@ export default function OrganizationSettingsPage() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.organizationId) {
|
||||
loadOrganization();
|
||||
}
|
||||
}, [user?.organizationId, loadOrganization]);
|
||||
|
||||
const handleChange = (field: keyof OrganizationForm, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function TestImagePage() {
|
||||
return (
|
||||
<div className="min-h-screen p-8">
|
||||
@ -5,10 +7,12 @@ export default function TestImagePage() {
|
||||
|
||||
{/* Test 1: Direct img tag */}
|
||||
<div className="mb-8">
|
||||
<h2 className="font-semibold mb-2">Test 1: Direct img tag</h2>
|
||||
<img
|
||||
<h2 className="font-semibold mb-2">Test 1: Direct Image component</h2>
|
||||
<Image
|
||||
src="/assets/images/background-section-1-landingpage.png"
|
||||
alt="test"
|
||||
width={256}
|
||||
height={128}
|
||||
className="w-64 h-32 object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* Carrier Monitoring Dashboard
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { CarrierStats, CarrierHealthCheck } from '@/types/carrier';
|
||||
|
||||
export const CarrierMonitoring: React.FC = () => {
|
||||
@ -12,14 +12,7 @@ export const CarrierMonitoring: React.FC = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [timeRange, setTimeRange] = useState('24h');
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitoringData();
|
||||
// Refresh every 30 seconds
|
||||
const interval = setInterval(fetchMonitoringData, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [timeRange]);
|
||||
|
||||
const fetchMonitoringData = async () => {
|
||||
const fetchMonitoringData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
@ -50,7 +43,14 @@ export const CarrierMonitoring: React.FC = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [timeRange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitoringData();
|
||||
// Refresh every 30 seconds
|
||||
const interval = setInterval(fetchMonitoringData, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [timeRange, fetchMonitoringData]);
|
||||
|
||||
const getHealthStatus = (carrierId: string): CarrierHealthCheck | undefined => {
|
||||
return health.find(h => h.carrierId === carrierId);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user