diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..df76e41 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,34 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ContainerEntity } from '@/entities/container.entity'; +import { LiveTrackingEntity } from '@/entities/liveTracking.entity'; +import { ShipmentEntity } from '@/entities/shipment.entity'; +import { TrackingModule } from './tracking/tracking.module'; +import { ShipmentsModule } from './shipments/shipments.module'; @Module({ - imports: [], + imports: [ + TypeOrmModule.forRoot({ + type: 'mysql', + host: 'localhost', + port: 13306, + username: 'root', + password: 'myfreight', + database: 'myfreight', + entities: [ContainerEntity, LiveTrackingEntity, ShipmentEntity], + synchronize: true, + logging: false, + }), + TypeOrmModule.forFeature([ + ContainerEntity, + LiveTrackingEntity, + ShipmentEntity, + ]), + TrackingModule, + ShipmentsModule, + ], controllers: [AppController], providers: [AppService], }) diff --git a/src/dtos/container.dto.ts b/src/dtos/container.dto.ts new file mode 100644 index 0000000..991a69c --- /dev/null +++ b/src/dtos/container.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ContainerEntity } from '@/entities/container.entity'; +import { instanceToPlain, plainToClass } from 'class-transformer'; + +export class ContainerDto { + @ApiProperty() + id: string; + + @ApiProperty() + containerNumber: string; + + @ApiProperty() + shipmentId: string; + + constructor(entity: ContainerEntity) { + this.id = entity.id; + this.containerNumber = entity.containerNumber; + this.shipmentId = entity.shipmentId; + } + + static fromEntity(entity: ContainerEntity): ContainerDto { + const data = instanceToPlain(entity); + return plainToClass(ContainerDto, data); + } + + toEntity(): ContainerEntity { + const data = instanceToPlain(this); + return plainToClass(ContainerEntity, data); + } +} diff --git a/src/dtos/shipment.dto.ts b/src/dtos/shipment.dto.ts new file mode 100644 index 0000000..84057e4 --- /dev/null +++ b/src/dtos/shipment.dto.ts @@ -0,0 +1,91 @@ +import { ShipmentEntity } from '@/entities/shipment.entity'; +import { ContainerEntity } from '@/entities/container.entity'; +import { LiveTrackingEntity } from '@/entities/liveTracking.entity'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class ShipmentContainerDto { + @ApiProperty() + id: string; + @ApiProperty() + containerNumber: string; + + constructor(entity: ContainerEntity) { + this.id = entity.id; + this.containerNumber = entity.containerNumber; + } + static bulk(entities: ContainerEntity[]) { + return entities.map((entity) => new ShipmentContainerDto(entity)); + } +} + +export class ShipmentLiveTrackingDto { + @ApiProperty({ + format: 'date-time', + }) + createdAt: string; + @ApiProperty() + latitude: number; + @ApiProperty() + longitude: number; + + constructor(entity: LiveTrackingEntity) { + this.createdAt = entity.createdAt.toISOString(); + this.latitude = entity.latitude; + this.longitude = entity.longitude; + } +} +export class ShipmentDto { + @ApiProperty() + id: string; + @ApiProperty({ + format: 'date-time', + }) + createdAt: string; + @ApiProperty() + carrierName: string; + @ApiProperty() + originPort: string; + @ApiProperty() + destinationPort: string; + @ApiProperty({ + format: 'date-time', + }) + departureDate: string; + @ApiProperty({ + format: 'date-time', + }) + arrivalDateOriginal: string; + @ApiProperty({ + format: 'date-time', + }) + arrivalDateEstimate: string; + @ApiPropertyOptional({ + isArray: true, + type: ShipmentContainerDto, + }) + containers: ShipmentContainerDto[] | null; + @ApiPropertyOptional({ + type: ShipmentLiveTrackingDto, + }) + liveTracking: ShipmentLiveTrackingDto | null; + + constructor(entity: ShipmentEntity) { + this.id = entity.id; + this.createdAt = entity.createdAt.toISOString(); + this.carrierName = entity.carrierName; + this.originPort = entity.originPort; + this.destinationPort = entity.destinationPort; + this.departureDate = entity.departureDate.toISOString(); + this.arrivalDateOriginal = entity.arrivalDateOriginal.toISOString(); + this.arrivalDateEstimate = entity.arrivalDateEstimate.toISOString(); + + this.containers = Array.isArray(entity.containers) + ? ShipmentContainerDto.bulk(entity.containers) + : null; + this.liveTracking = null; + } + + static bulk(entities: ShipmentEntity[]) { + return entities.map((entity) => new ShipmentDto(entity)); + } +} diff --git a/src/dtos/tracking.dto.ts b/src/dtos/tracking.dto.ts new file mode 100644 index 0000000..c555427 --- /dev/null +++ b/src/dtos/tracking.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsDate, IsNotEmpty } from 'class-validator'; + +export class TrackingDto { + @ApiProperty() + @IsNotEmpty() + carrierName!: string; + + @ApiProperty() + @IsNotEmpty() + destinationUnLoCode!: string; + + @ApiProperty({ + type: 'string', + format: 'date-time', + }) + @Type(() => Date) + @IsDate() + @IsNotEmpty() + departureDate!: Date; + + @ApiProperty({ + type: 'string', + format: 'date-time', + }) + @IsDate() + @IsNotEmpty() + @Type(() => Date) + arrivalDateEstimate!: Date; + + @ApiProperty() + @IsNotEmpty() + containerNumber!: string; +} diff --git a/src/shipments/shipments.controller.spec.ts b/src/shipments/shipments.controller.spec.ts new file mode 100644 index 0000000..49f3ce4 --- /dev/null +++ b/src/shipments/shipments.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ShipmentsController } from './shipments.controller'; + +describe('ShipmentsController', () => { + let controller: ShipmentsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ShipmentsController], + }).compile(); + + controller = module.get(ShipmentsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/shipments/shipments.controller.ts b/src/shipments/shipments.controller.ts new file mode 100644 index 0000000..2682e63 --- /dev/null +++ b/src/shipments/shipments.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Get } from '@nestjs/common'; +import { ShipmentsService } from '@/shipments/shipments.service'; +import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'; +import { + ShipmentContainerDto, + ShipmentDto, + ShipmentLiveTrackingDto, +} from '@/dtos/shipment.dto'; + +@Controller('shipments') +@ApiExtraModels(ShipmentDto, ShipmentContainerDto, ShipmentLiveTrackingDto) +export class ShipmentsController { + constructor(private readonly shipmentsService: ShipmentsService) {} + + @Get() + @ApiOkResponse({ + description: 'Returns a list of shipments', + schema: { + items: { + $ref: getSchemaPath(ShipmentDto), + }, + }, + }) + getShipments() { + return this.shipmentsService.getShipments(); + } +} diff --git a/src/shipments/shipments.module.ts b/src/shipments/shipments.module.ts new file mode 100644 index 0000000..acc6c5f --- /dev/null +++ b/src/shipments/shipments.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { ShipmentsController } from './shipments.controller'; +import { ShipmentsService } from './shipments.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ContainerEntity } from '@/entities/container.entity'; +import { LiveTrackingEntity } from '@/entities/liveTracking.entity'; +import { ShipmentEntity } from '@/entities/shipment.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + ContainerEntity, + LiveTrackingEntity, + ShipmentEntity, + ]), + ], + controllers: [ShipmentsController], + providers: [ShipmentsService], +}) +export class ShipmentsModule {} diff --git a/src/shipments/shipments.service.spec.ts b/src/shipments/shipments.service.spec.ts new file mode 100644 index 0000000..5db7f2f --- /dev/null +++ b/src/shipments/shipments.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ShipmentsService } from './shipments.service'; + +describe('ShipmentsService', () => { + let service: ShipmentsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ShipmentsService], + }).compile(); + + service = module.get(ShipmentsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/shipments/shipments.service.ts b/src/shipments/shipments.service.ts new file mode 100644 index 0000000..bdbd1a5 --- /dev/null +++ b/src/shipments/shipments.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ShipmentEntity } from '@/entities/shipment.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class ShipmentsService { + constructor( + @InjectRepository(ShipmentEntity) + private readonly shipmentRepository: Repository, + ) {} + + // @todo add pagination + getShipments() { + return this.shipmentRepository.find({ + relations: ['liveTracking', 'containers'], + }); + } +} diff --git a/src/tracking/tracking.controller.spec.ts b/src/tracking/tracking.controller.spec.ts new file mode 100644 index 0000000..3dffc2a --- /dev/null +++ b/src/tracking/tracking.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TrackingController } from './tracking.controller'; + +describe('TrackingController', () => { + let controller: TrackingController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TrackingController], + }).compile(); + + controller = module.get(TrackingController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/tracking/tracking.controller.ts b/src/tracking/tracking.controller.ts new file mode 100644 index 0000000..ef91aae --- /dev/null +++ b/src/tracking/tracking.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('tracking') +export class TrackingController {} diff --git a/src/tracking/tracking.module.ts b/src/tracking/tracking.module.ts new file mode 100644 index 0000000..a18f2fe --- /dev/null +++ b/src/tracking/tracking.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TrackingController } from './tracking.controller'; +import { TrackingService } from './tracking.service'; + +@Module({ + controllers: [TrackingController], + providers: [TrackingService], +}) +export class TrackingModule {} diff --git a/src/tracking/tracking.service.spec.ts b/src/tracking/tracking.service.spec.ts new file mode 100644 index 0000000..f916200 --- /dev/null +++ b/src/tracking/tracking.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TrackingService } from './tracking.service'; + +describe('TrackingService', () => { + let service: TrackingService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TrackingService], + }).compile(); + + service = module.get(TrackingService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/tracking/tracking.service.ts b/src/tracking/tracking.service.ts new file mode 100644 index 0000000..d768c99 --- /dev/null +++ b/src/tracking/tracking.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TrackingService {}