diff --git a/src/entities/container.entity.ts b/src/entities/container.entity.ts new file mode 100644 index 0000000..0daf713 --- /dev/null +++ b/src/entities/container.entity.ts @@ -0,0 +1,37 @@ +import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; +import { uuidTransformer } from '@/utils/uuidTransformer'; +import { ShipmentEntity } from '@/entities/shipment.entity'; + +@Entity({ + name: 'containers', +}) +export class ContainerEntity extends BaseEntity { + @PrimaryColumn({ + type: 'binary', + length: 16, + generated: false, + transformer: uuidTransformer, + default: () => '(UUID_TO_BIN(UUID()))', + }) + id!: string; + + @Column({ + type: 'char', + length: 13, + }) + containerNumber!: string; + + @Column({ + type: 'binary', + length: 16, + generated: false, + transformer: uuidTransformer, + }) + shipmentId!: string; + + @ManyToOne( + () => ShipmentEntity, + (shipment: ShipmentEntity) => shipment.containers, + ) + shipment?: ShipmentEntity; +} diff --git a/src/entities/liveTracking.entity.ts b/src/entities/liveTracking.entity.ts new file mode 100644 index 0000000..662d05d --- /dev/null +++ b/src/entities/liveTracking.entity.ts @@ -0,0 +1,50 @@ +import { + BaseEntity, + Column, + Entity, + JoinColumn, + OneToOne, + PrimaryColumn, +} from 'typeorm'; +import { uuidTransformer } from '@/utils/uuidTransformer'; +import { ShipmentEntity } from '@/entities/shipment.entity'; + +@Entity({ + name: 'live_tracking', +}) +export class LiveTrackingEntity extends BaseEntity { + @PrimaryColumn({ + type: 'binary', + length: 16, + generated: false, + transformer: uuidTransformer, + }) + shipmentId!: string; + + @Column({ + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt!: Date; + + @Column({ + type: 'decimal', + precision: 11, + scale: 7, + }) + latitude!: number; + + @Column({ + type: 'decimal', + precision: 11, + scale: 7, + }) + longitude!: number; + + @OneToOne( + () => ShipmentEntity, + (shipment: ShipmentEntity) => shipment.liveTracking, + ) + @JoinColumn() + shipment?: ShipmentEntity; +} diff --git a/src/entities/shipment.entity.ts b/src/entities/shipment.entity.ts new file mode 100644 index 0000000..8f2e2f1 --- /dev/null +++ b/src/entities/shipment.entity.ts @@ -0,0 +1,77 @@ +import { + BaseEntity, + Column, + Entity, + OneToMany, + OneToOne, + PrimaryColumn, +} from 'typeorm'; +import { uuidTransformer } from '@/utils/uuidTransformer'; +import { ContainerEntity } from '@/entities/container.entity'; +import { LiveTrackingEntity } from '@/entities/liveTracking.entity'; + +@Entity({ + name: 'shipments', +}) +export class ShipmentEntity extends BaseEntity { + @PrimaryColumn({ + type: 'binary', + length: 16, + generated: false, + transformer: uuidTransformer, + default: () => '(UUID_TO_BIN(UUID()))', + }) + id!: string; + + @Column({ + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt!: Date; + + @Column({ + type: 'varchar', + length: 255, + }) + carrierName!: string; + + @Column({ + type: 'char', + length: 5, + }) + originPort!: string; + + @Column({ + type: 'char', + length: 5, + }) + destinationPort!: string; + + @Column({ + type: 'datetime', + }) + departureDate!: Date; + + @Column({ + type: 'datetime', + }) + arrivalDateOriginal!: Date; + + @Column({ + type: 'datetime', + }) + arrivalDateEstimate!: Date; + + @OneToMany( + () => ContainerEntity, + (container: ContainerEntity) => container.shipment, + ) + containers?: ContainerEntity[]; + + @OneToOne( + () => LiveTrackingEntity, + (liveTracking: LiveTrackingEntity) => liveTracking.shipment, + { eager: true }, + ) + liveTracking?: LiveTrackingEntity; +} diff --git a/src/utils/uuidTransformer.spec.ts b/src/utils/uuidTransformer.spec.ts new file mode 100644 index 0000000..20e1d7b --- /dev/null +++ b/src/utils/uuidTransformer.spec.ts @@ -0,0 +1,27 @@ +import { uuidTransformer } from '@/utils/uuidTransformer'; +import { NIL } from 'uuid'; + +describe('uuidTransformer util', () => { + const transformer = uuidTransformer; + beforeEach(() => {}); + + it('Should have a to member', () => { + expect(transformer.to).toBeDefined(); + }); + + it('Should have a from member', () => { + expect(transformer.from).toBeDefined(); + }); + + it('From method should return a nil uuid if null is passed', () => { + expect(transformer.from(null)).toBe(NIL); + }); + + it('From method should return a nil uuid if empty 16 length buffer is passed', () => { + expect(transformer.from(Buffer.alloc(16))).toBe(NIL); + }); + + it('To method should return a buffer matching the uuid', () => { + expect(transformer.to(NIL)).toEqual(Buffer.alloc(16)); + }); +}); diff --git a/src/utils/uuidTransformer.ts b/src/utils/uuidTransformer.ts new file mode 100644 index 0000000..e2a80ee --- /dev/null +++ b/src/utils/uuidTransformer.ts @@ -0,0 +1,26 @@ +import { NIL, parse } from 'uuid'; + +// custom implementation of stringify, as provided uuid's are not valid according to RFC4122 +const stringify = (buffer: Buffer | null): string => { + if (buffer === null) { + return NIL; + } + if (buffer.length != 16) { + throw new Error('Invalid buffer length for uuid: ' + buffer.length); + } + if (buffer.equals(Buffer.alloc(16))) { + return NIL; // If buffer is all zeros, return zero'd uuid + } + + const hex = buffer.toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +}; + +export const uuidTransformer = { + to(uuid: string): Buffer { + return Buffer.from(parse(uuid)); + }, + from(bin: Buffer | null): string { + return stringify(bin); + }, +};