move route compute to create service as entity creation is not async

This commit is contained in:
sbriat 2023-08-22 16:14:36 +02:00
parent 4762c844e1
commit ac8e459e90
27 changed files with 403 additions and 94 deletions

View File

@ -1,4 +1,7 @@
export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
export const AD_DIRECTION_ENCODER = Symbol('AD_DIRECTION_ENCODER'); export const AD_DIRECTION_ENCODER = Symbol('AD_DIRECTION_ENCODER');
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER'); export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
export const AD_GET_BASIC_ROUTE_CONTROLLER = Symbol(
'AD_GET_BASIC_ROUTE_CONTROLLER',
);
export const AD_ROUTE_PROVIDER = Symbol('AD_ROUTE_PROVIDER'); export const AD_ROUTE_PROVIDER = Symbol('AD_ROUTE_PROVIDER');

View File

@ -6,13 +6,11 @@ import {
AdReadModel, AdReadModel,
ScheduleItemModel, ScheduleItemModel,
} from './infrastructure/ad.repository'; } from './infrastructure/ad.repository';
import { Frequency, Role } from './core/domain/ad.types'; import { Frequency } from './core/domain/ad.types';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object'; import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port'; import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
import { AD_DIRECTION_ENCODER, AD_ROUTE_PROVIDER } from './ad.di-tokens'; import { AD_DIRECTION_ENCODER } from './ad.di-tokens';
import { RouteProviderPort } from './core/application/ports/route-provider.port';
import { Route } from './core/application/types/route.type';
/** /**
* Mapper constructs objects that are used in different layers: * Mapper constructs objects that are used in different layers:
@ -28,17 +26,11 @@ export class AdMapper
constructor( constructor(
@Inject(AD_DIRECTION_ENCODER) @Inject(AD_DIRECTION_ENCODER)
private readonly directionEncoder: DirectionEncoderPort, private readonly directionEncoder: DirectionEncoderPort,
@Inject(AD_ROUTE_PROVIDER)
private readonly routeProvider: RouteProviderPort,
) {} ) {}
toPersistence = (entity: AdEntity): AdWriteModel => { toPersistence = (entity: AdEntity): AdWriteModel => {
const copy = entity.getProps(); const copy = entity.getProps();
const now = new Date(); const now = new Date();
const roles: Role[] = [];
if (copy.driver) roles.push(Role.DRIVER);
if (copy.passenger) roles.push(Role.PASSENGER);
const route: Route = this.routeProvider.getBasic(roles, copy.waypoints);
const record: AdWriteModel = { const record: AdWriteModel = {
uuid: copy.id, uuid: copy.id,
driver: copy.driver, driver: copy.driver,
@ -65,14 +57,14 @@ export class AdMapper
seatsProposed: copy.seatsProposed, seatsProposed: copy.seatsProposed,
seatsRequested: copy.seatsRequested, seatsRequested: copy.seatsRequested,
strict: copy.strict, strict: copy.strict,
driverDuration: route.driverDuration, driverDuration: copy.driverDuration,
driverDistance: route.driverDistance, driverDistance: copy.driverDistance,
passengerDuration: route.passengerDuration, passengerDuration: copy.passengerDuration,
passengerDistance: route.passengerDistance, passengerDistance: copy.passengerDistance,
waypoints: this.directionEncoder.encode(copy.waypoints), waypoints: this.directionEncoder.encode(copy.waypoints),
direction: this.directionEncoder.encode(route.points), direction: this.directionEncoder.encode(copy.points),
fwdAzimuth: route.fwdAzimuth, fwdAzimuth: copy.fwdAzimuth,
backAzimuth: route.backAzimuth, backAzimuth: copy.backAzimuth,
createdAt: copy.createdAt, createdAt: copy.createdAt,
updatedAt: copy.updatedAt, updatedAt: copy.updatedAt,
}; };
@ -116,6 +108,7 @@ export class AdMapper
})), })),
fwdAzimuth: record.fwdAzimuth, fwdAzimuth: record.fwdAzimuth,
backAzimuth: record.backAzimuth, backAzimuth: record.backAzimuth,
points: [],
}, },
}); });
return entity; return entity;

View File

@ -1,20 +1,32 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateAdCommand } from './create-ad.command'; import { CreateAdCommand } from './create-ad.command';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; import { AD_REPOSITORY, AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { AdRepositoryPort } from '../../ports/ad.repository.port'; import { AdRepositoryPort } from '../../ports/ad.repository.port';
import { AggregateID, ConflictException } from '@mobicoop/ddd-library'; import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
import { RouteProviderPort } from '../../ports/route-provider.port';
import { Role } from '@modules/ad/core/domain/ad.types';
import { Route } from '../../types/route.type';
@CommandHandler(CreateAdCommand) @CommandHandler(CreateAdCommand)
export class CreateAdService implements ICommandHandler { export class CreateAdService implements ICommandHandler {
constructor( constructor(
@Inject(AD_REPOSITORY) @Inject(AD_REPOSITORY)
private readonly repository: AdRepositoryPort, private readonly repository: AdRepositoryPort,
@Inject(AD_ROUTE_PROVIDER)
private readonly routeProvider: RouteProviderPort,
) {} ) {}
async execute(command: CreateAdCommand): Promise<AggregateID> { async execute(command: CreateAdCommand): Promise<AggregateID> {
const roles: Role[] = [];
if (command.driver) roles.push(Role.DRIVER);
if (command.passenger) roles.push(Role.PASSENGER);
const route: Route = await this.routeProvider.getBasic(
roles,
command.waypoints,
);
const ad = AdEntity.create({ const ad = AdEntity.create({
id: command.id, id: command.id,
driver: command.driver, driver: command.driver,
@ -27,6 +39,13 @@ export class CreateAdService implements ICommandHandler {
seatsRequested: command.seatsRequested, seatsRequested: command.seatsRequested,
strict: command.strict, strict: command.strict,
waypoints: command.waypoints, waypoints: command.waypoints,
points: route.points,
driverDistance: route.driverDistance,
driverDuration: route.driverDuration,
passengerDistance: route.passengerDistance,
passengerDuration: route.passengerDuration,
fwdAzimuth: route.fwdAzimuth,
backAzimuth: route.backAzimuth,
}); });
try { try {

View File

@ -6,5 +6,5 @@ export interface RouteProviderPort {
/** /**
* Get a basic route with points and overall duration / distance * Get a basic route with points and overall duration / distance
*/ */
getBasic(roles: Role[], waypoints: Waypoint[]): Route; getBasic(roles: Role[], waypoints: Waypoint[]): Promise<Route>;
} }

View File

@ -1,3 +1,4 @@
import { PointProps } from './value-objects/point.value-object';
import { ScheduleItemProps } from './value-objects/schedule-item.value-object'; import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
import { WaypointProps } from './value-objects/waypoint.value-object'; import { WaypointProps } from './value-objects/waypoint.value-object';
@ -17,8 +18,9 @@ export interface AdProps {
passengerDuration?: number; passengerDuration?: number;
passengerDistance?: number; passengerDistance?: number;
waypoints: WaypointProps[]; waypoints: WaypointProps[];
fwdAzimuth?: number; points: PointProps[];
backAzimuth?: number; fwdAzimuth: number;
backAzimuth: number;
} }
// Properties that are needed for an Ad creation // Properties that are needed for an Ad creation
@ -34,6 +36,13 @@ export interface CreateAdProps {
seatsRequested: number; seatsRequested: number;
strict: boolean; strict: boolean;
waypoints: WaypointProps[]; waypoints: WaypointProps[];
driverDuration?: number;
driverDistance?: number;
passengerDuration?: number;
passengerDistance?: number;
fwdAzimuth: number;
backAzimuth: number;
points: PointProps[];
} }
export enum Frequency { export enum Frequency {

View File

@ -0,0 +1,31 @@
import {
ArgumentOutOfRangeException,
ValueObject,
} from '@mobicoop/ddd-library';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface PointProps {
lon: number;
lat: number;
}
export class Point extends ValueObject<PointProps> {
get lon(): number {
return this.props.lon;
}
get lat(): number {
return this.props.lat;
}
protected validate(props: PointProps): void {
if (props.lon > 180 || props.lon < -180)
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
if (props.lat > 90 || props.lat < -90)
throw new ArgumentOutOfRangeException('lat must be between -90 and 90');
}
}

View File

@ -0,0 +1,21 @@
import { Inject, Injectable } from '@nestjs/common';
import { RouteProviderPort } from '../core/application/ports/route-provider.port';
import { Route } from '../core/application/types/route.type';
import { Waypoint } from '../core/application/types/waypoint.type';
import { Role } from '../core/domain/ad.types';
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens';
@Injectable()
export class RouteProvider implements RouteProviderPort {
constructor(
@Inject(AD_GET_BASIC_ROUTE_CONTROLLER)
private readonly getBasicRouteController: GetBasicRouteControllerPort,
) {}
getBasic = async (roles: Role[], waypoints: Waypoint[]): Promise<Route> =>
await this.getBasicRouteController.get({
roles,
waypoints,
});
}

View File

@ -1,9 +1,5 @@
import { import { AD_DIRECTION_ENCODER } from '@modules/ad/ad.di-tokens';
AD_DIRECTION_ENCODER,
AD_ROUTE_PROVIDER,
} from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper'; import { AdMapper } from '@modules/ad/ad.mapper';
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Frequency } from '@modules/ad/core/domain/ad.types'; import { Frequency } from '@modules/ad/core/domain/ad.types';
import { import {
@ -44,6 +40,26 @@ const adEntity: AdEntity = new AdEntity({
strict: false, strict: false,
seatsProposed: 3, seatsProposed: 3,
seatsRequested: 1, seatsRequested: 1,
driverDistance: 350101,
driverDuration: 14422,
passengerDistance: 350101,
passengerDuration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
points: [
{
lon: 6.1765102,
lat: 48.689445,
},
{
lon: 4.984578,
lat: 48.725687,
},
{
lon: 2.3522,
lat: 48.8566,
},
],
}, },
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
@ -104,32 +120,6 @@ const mockDirectionEncoder: DirectionEncoderPort = {
]), ]),
}; };
const mockRouteProvider: RouteProviderPort = {
getBasic: jest.fn().mockImplementation(() => ({
driverDistance: 350101,
driverDuration: 14422,
passengerDistance: 350101,
passengerDuration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [
{
lon: 6.1765102,
lat: 48.689445,
},
{
lon: 4.984578,
lat: 48.725687,
},
{
lon: 2.3522,
lat: 48.8566,
},
],
})),
};
describe('Ad Mapper', () => { describe('Ad Mapper', () => {
let adMapper: AdMapper; let adMapper: AdMapper;
@ -141,10 +131,6 @@ describe('Ad Mapper', () => {
provide: AD_DIRECTION_ENCODER, provide: AD_DIRECTION_ENCODER,
useValue: mockDirectionEncoder, useValue: mockDirectionEncoder,
}, },
{
provide: AD_ROUTE_PROVIDER,
useValue: mockRouteProvider,
},
], ],
}).compile(); }).compile();
adMapper = module.get<AdMapper>(AdMapper); adMapper = module.get<AdMapper>(AdMapper);

View File

@ -31,6 +31,13 @@ const createAdProps: CreateAdProps = {
seatsRequested: 1, seatsRequested: 1,
strict: false, strict: false,
waypoints: [originWaypointProps, destinationWaypointProps], waypoints: [originWaypointProps, destinationWaypointProps],
driverDistance: 23000,
driverDuration: 900,
passengerDistance: 23000,
passengerDuration: 900,
fwdAzimuth: 283,
backAzimuth: 93,
points: [],
}; };
describe('Ad entity create', () => { describe('Ad entity create', () => {
@ -42,6 +49,6 @@ describe('Ad entity create', () => {
expect(ad.getProps().schedule[0].time).toBe('08:30'); expect(ad.getProps().schedule[0].time).toBe('08:30');
expect(ad.getProps().driver).toBeTruthy(); expect(ad.getProps().driver).toBeTruthy();
expect(ad.getProps().passenger).toBeTruthy(); expect(ad.getProps().passenger).toBeTruthy();
expect(ad.getProps().driverDistance).toBeUndefined(); expect(ad.getProps().driverDistance).toBe(23000);
}); });
}); });

View File

@ -1,5 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; import { AD_REPOSITORY, AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
import { AggregateID } from '@mobicoop/ddd-library'; import { AggregateID } from '@mobicoop/ddd-library';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { ConflictException } from '@mobicoop/ddd-library'; import { ConflictException } from '@mobicoop/ddd-library';
@ -8,6 +8,7 @@ import { CreateAdService } from '@modules/ad/core/application/commands/create-ad
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command'; import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object'; import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
const originWaypoint: WaypointProps = { const originWaypoint: WaypointProps = {
position: 0, position: 0,
@ -37,6 +38,13 @@ const createAdProps: CreateAdProps = {
strict: false, strict: false,
frequency: Frequency.PUNCTUAL, frequency: Frequency.PUNCTUAL,
waypoints: [originWaypoint, destinationWaypoint], waypoints: [originWaypoint, destinationWaypoint],
driverDistance: 23000,
driverDuration: 900,
passengerDistance: 23000,
passengerDuration: 900,
fwdAzimuth: 283,
backAzimuth: 93,
points: [],
}; };
const mockAdRepository = { const mockAdRepository = {
@ -51,6 +59,32 @@ const mockAdRepository = {
}), }),
}; };
const mockRouteProvider: RouteProviderPort = {
getBasic: jest.fn().mockImplementation(() => ({
driverDistance: 350101,
driverDuration: 14422,
passengerDistance: 350101,
passengerDuration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [
{
lon: 6.1765102,
lat: 48.689445,
},
{
lon: 4.984578,
lat: 48.725687,
},
{
lon: 2.3522,
lat: 48.8566,
},
],
})),
};
describe('create-ad.service', () => { describe('create-ad.service', () => {
let createAdService: CreateAdService; let createAdService: CreateAdService;
@ -61,6 +95,10 @@ describe('create-ad.service', () => {
provide: AD_REPOSITORY, provide: AD_REPOSITORY,
useValue: mockAdRepository, useValue: mockAdRepository,
}, },
{
provide: AD_ROUTE_PROVIDER,
useValue: mockRouteProvider,
},
CreateAdService, CreateAdService,
], ],
}).compile(); }).compile();

View File

@ -0,0 +1,49 @@
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
describe('Point value object', () => {
it('should create a point value object', () => {
const pointVO = new Point({
lon: 48.689445,
lat: 6.17651,
});
expect(pointVO.lon).toBe(48.689445);
expect(pointVO.lat).toBe(6.17651);
});
it('should throw an exception if longitude is invalid', () => {
try {
new Point({
lon: 348.689445,
lat: 6.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Point({
lon: -348.689445,
lat: 6.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
});
it('should throw an exception if latitude is invalid', () => {
try {
new Point({
lon: 48.689445,
lat: 96.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Point({
lon: 48.689445,
lat: -96.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
});
});

View File

@ -46,7 +46,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException); expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
} }
}); });
it('should throw an exception if longitude is invalid', () => { it('should throw an exception if latitude is invalid', () => {
try { try {
new Waypoint({ new Waypoint({
position: 0, position: 0,

View File

@ -0,0 +1,69 @@
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '@modules/ad/ad.di-tokens';
import { Route } from '@modules/ad/core/application/types/route.type';
import { Role } from '@modules/ad/core/domain/ad.types';
import { RouteProvider } from '@modules/ad/infrastructure/route-provider';
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
import { Test, TestingModule } from '@nestjs/testing';
const mockGetBasicRouteController: GetBasicRouteControllerPort = {
get: jest.fn().mockImplementation(() => ({
driverDistance: 23000,
driverDuration: 900,
passengerDistance: 23000,
passengerDuration: 900,
fwdAzimuth: 283,
backAzimuth: 93,
distanceAzimuth: 19840,
points: [
{
lon: 6.1765103,
lat: 48.689446,
},
{
lon: 2.3523,
lat: 48.8567,
},
],
})),
};
describe('Route provider', () => {
let routeProvider: RouteProvider;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
RouteProvider,
{
provide: AD_GET_BASIC_ROUTE_CONTROLLER,
useValue: mockGetBasicRouteController,
},
],
}).compile();
routeProvider = module.get<RouteProvider>(RouteProvider);
});
it('should be defined', () => {
expect(routeProvider).toBeDefined();
});
it('should provide a route', async () => {
const route: Route = await routeProvider.getBasic(
[Role.DRIVER],
[
{
position: 0,
lat: 48.689445,
lon: 6.1765102,
},
{
position: 1,
lat: 48.8566,
lon: 2.3522,
},
],
);
expect(route.driverDistance).toBe(23000);
});
});

View File

@ -0,0 +1,6 @@
import { GetRouteRequestDto } from '@modules/geography/interface/controllers/dtos/get-route.request.dto';
import { RouteResponseDto } from '@modules/geography/interface/dtos/route.response.dto';
export interface GetBasicRouteControllerPort {
get(data: GetRouteRequestDto): Promise<RouteResponseDto>;
}

View File

@ -9,6 +9,5 @@ export type Route = {
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
points: Coordinates[]; points: Coordinates[] | SpacetimePoint[];
spacetimePoints: SpacetimePoint[];
}; };

View File

@ -60,9 +60,7 @@ export class RouteEntity extends AggregateRoot<RouteProps> {
? driverRoute.distanceAzimuth ? driverRoute.distanceAzimuth
: passengerRoute.distanceAzimuth, : passengerRoute.distanceAzimuth,
waypoints: create.waypoints, waypoints: create.waypoints,
spacetimePoints: driverRoute points: driverRoute ? driverRoute.points : passengerRoute.points,
? driverRoute.spacetimePoints
: passengerRoute.spacetimePoints,
}; };
return new RouteEntity({ return new RouteEntity({
id: v4(), id: v4(),

View File

@ -1,5 +1,6 @@
import { GeorouterPort } from '../application/ports/georouter.port'; import { GeorouterPort } from '../application/ports/georouter.port';
import { GeorouterSettings } from '../application/types/georouter-settings.type'; import { GeorouterSettings } from '../application/types/georouter-settings.type';
import { CoordinatesProps } from './value-objects/coordinates.value-object';
import { SpacetimePointProps } from './value-objects/spacetime-point.value-object'; import { SpacetimePointProps } from './value-objects/spacetime-point.value-object';
import { WaypointProps } from './value-objects/waypoint.value-object'; import { WaypointProps } from './value-objects/waypoint.value-object';
@ -13,7 +14,7 @@ export interface RouteProps {
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
waypoints: WaypointProps[]; waypoints: WaypointProps[];
spacetimePoints: SpacetimePointProps[]; points: SpacetimePointProps[] | CoordinatesProps[];
} }
// Properties that are needed for a Route creation // Properties that are needed for a Route creation
@ -31,8 +32,7 @@ export type Direction = {
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
points: Point[]; points: SpacetimePoint[] | Point[];
spacetimePoints: SpacetimePoint[];
}; };
export type Path = { export type Path = {

View File

@ -0,0 +1,31 @@
import {
ArgumentOutOfRangeException,
ValueObject,
} from '@mobicoop/ddd-library';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface CoordinatesProps {
lon: number;
lat: number;
}
export class Coordinates extends ValueObject<CoordinatesProps> {
get lon(): number {
return this.props.lon;
}
get lat(): number {
return this.props.lat;
}
protected validate(props: CoordinatesProps): void {
if (props.lon > 180 || props.lon < -180)
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
if (props.lat > 90 || props.lat < -90)
throw new ArgumentOutOfRangeException('lat must be between -90 and 90');
}
}

View File

@ -3,7 +3,7 @@ import { CqrsModule } from '@nestjs/cqrs';
import { DIRECTION_ENCODER, PARAMS_PROVIDER } from './geography.di-tokens'; import { DIRECTION_ENCODER, PARAMS_PROVIDER } from './geography.di-tokens';
import { DefaultParamsProvider } from './infrastructure/default-params-provider'; import { DefaultParamsProvider } from './infrastructure/default-params-provider';
import { PostgresDirectionEncoder } from './infrastructure/postgres-direction-encoder'; import { PostgresDirectionEncoder } from './infrastructure/postgres-direction-encoder';
import { GetRouteController } from './interface/controllers/get-route.controller'; import { GetBasicRouteController } from './interface/controllers/get-basic-route.controller';
const adapters: Provider[] = [ const adapters: Provider[] = [
{ {
@ -14,12 +14,12 @@ const adapters: Provider[] = [
provide: DIRECTION_ENCODER, provide: DIRECTION_ENCODER,
useClass: PostgresDirectionEncoder, useClass: PostgresDirectionEncoder,
}, },
GetRouteController, GetBasicRouteController,
]; ];
@Module({ @Module({
imports: [CqrsModule], imports: [CqrsModule],
providers: [...adapters], providers: [...adapters],
exports: [DIRECTION_ENCODER, GetRouteController], exports: [DIRECTION_ENCODER, GetBasicRouteController],
}) })
export class GeographyModule {} export class GeographyModule {}

View File

@ -1,9 +1,7 @@
import { GeorouterSettings } from '@modules/geography/core/application/types/georouter-settings.type';
import { Waypoint } from '@modules/geography/core/application/types/waypoint.type'; import { Waypoint } from '@modules/geography/core/application/types/waypoint.type';
import { Role } from '@modules/geography/core/domain/route.types'; import { Role } from '@modules/geography/core/domain/route.types';
export class GetRouteRequestDto { export type GetRouteRequestDto = {
roles: Role[]; roles: Role[];
waypoints: Waypoint[]; waypoints: Waypoint[];
georouterSettings: GeorouterSettings; };
}

View File

@ -4,10 +4,11 @@ import { GetRouteRequestDto } from './dtos/get-route.request.dto';
import { RouteEntity } from '@modules/geography/core/domain/route.entity'; import { RouteEntity } from '@modules/geography/core/domain/route.entity';
import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query'; import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query';
import { RouteMapper } from '@modules/geography/route.mapper'; import { RouteMapper } from '@modules/geography/route.mapper';
import { Injectable } from '@nestjs/common'; import { Controller } from '@nestjs/common';
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
@Injectable() @Controller()
export class GetRouteController { export class GetBasicRouteController implements GetBasicRouteControllerPort {
constructor( constructor(
private readonly queryBus: QueryBus, private readonly queryBus: QueryBus,
private readonly mapper: RouteMapper, private readonly mapper: RouteMapper,
@ -15,7 +16,11 @@ export class GetRouteController {
async get(data: GetRouteRequestDto): Promise<RouteResponseDto> { async get(data: GetRouteRequestDto): Promise<RouteResponseDto> {
const route: RouteEntity = await this.queryBus.execute( const route: RouteEntity = await this.queryBus.execute(
new GetRouteQuery(data.roles, data.waypoints, data.georouterSettings), new GetRouteQuery(data.roles, data.waypoints, {
detailedDistance: false,
detailedDuration: false,
points: true,
}),
); );
return this.mapper.toResponse(route); return this.mapper.toResponse(route);
} }

View File

@ -1,3 +1,4 @@
import { Coordinates } from '@modules/geography/core/application/types/coordinates.type';
import { SpacetimePoint } from '@modules/geography/core/application/types/spacetime-point.type'; import { SpacetimePoint } from '@modules/geography/core/application/types/spacetime-point.type';
export class RouteResponseDto { export class RouteResponseDto {
@ -8,5 +9,5 @@ export class RouteResponseDto {
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
spacetimePoints: SpacetimePoint[]; points: SpacetimePoint[] | Coordinates[];
} }

View File

@ -33,7 +33,7 @@ export class RouteMapper
response.fwdAzimuth = entity.getProps().fwdAzimuth; response.fwdAzimuth = entity.getProps().fwdAzimuth;
response.backAzimuth = entity.getProps().backAzimuth; response.backAzimuth = entity.getProps().backAzimuth;
response.distanceAzimuth = entity.getProps().distanceAzimuth; response.distanceAzimuth = entity.getProps().distanceAzimuth;
response.spacetimePoints = entity.getProps().spacetimePoints; response.points = entity.getProps().points;
return response; return response;
}; };
} }

View File

@ -0,0 +1,49 @@
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
import { Coordinates } from '@modules/geography/core/domain/value-objects/coordinates.value-object';
describe('Waypoint value object', () => {
it('should create a waypoint value object', () => {
const coordinatesVO = new Coordinates({
lon: 48.689445,
lat: 6.17651,
});
expect(coordinatesVO.lon).toBe(48.689445);
expect(coordinatesVO.lat).toBe(6.17651);
});
it('should throw an exception if longitude is invalid', () => {
try {
new Coordinates({
lon: 348.689445,
lat: 6.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Coordinates({
lon: -348.689445,
lat: 6.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
});
it('should throw an exception if latitude is invalid', () => {
try {
new Coordinates({
lon: 48.689445,
lat: 96.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Coordinates({
lon: 48.689445,
lat: -96.17651,
});
} catch (e: any) {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
});
});

View File

@ -46,7 +46,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException); expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
} }
}); });
it('should throw an exception if longitude is invalid', () => { it('should throw an exception if latitude is invalid', () => {
try { try {
new Waypoint({ new Waypoint({
position: 0, position: 0,

View File

@ -1,5 +1,5 @@
import { Role } from '@modules/geography/core/domain/route.types'; import { Role } from '@modules/geography/core/domain/route.types';
import { GetRouteController } from '@modules/geography/interface/controllers/get-route.controller'; import { GetBasicRouteController } from '@modules/geography/interface/controllers/get-basic-route.controller';
import { RouteMapper } from '@modules/geography/route.mapper'; import { RouteMapper } from '@modules/geography/route.mapper';
import { QueryBus } from '@nestjs/cqrs'; import { QueryBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
@ -14,8 +14,8 @@ const mockRouteMapper = {
toResponse: jest.fn(), toResponse: jest.fn(),
}; };
describe('Get Route Controller', () => { describe('Get Basic Route Controller', () => {
let getRouteController: GetRouteController; let getBasicRouteController: GetBasicRouteController;
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -28,11 +28,13 @@ describe('Get Route Controller', () => {
provide: RouteMapper, provide: RouteMapper,
useValue: mockRouteMapper, useValue: mockRouteMapper,
}, },
GetRouteController, GetBasicRouteController,
], ],
}).compile(); }).compile();
getRouteController = module.get<GetRouteController>(GetRouteController); getBasicRouteController = module.get<GetBasicRouteController>(
GetBasicRouteController,
);
}); });
afterEach(async () => { afterEach(async () => {
@ -40,12 +42,12 @@ describe('Get Route Controller', () => {
}); });
it('should be defined', () => { it('should be defined', () => {
expect(getRouteController).toBeDefined(); expect(getBasicRouteController).toBeDefined();
}); });
it('should get a route', async () => { it('should get a route', async () => {
jest.spyOn(mockQueryBus, 'execute'); jest.spyOn(mockQueryBus, 'execute');
await getRouteController.get({ await getBasicRouteController.get({
roles: [Role.DRIVER], roles: [Role.DRIVER],
waypoints: [ waypoints: [
{ {
@ -59,11 +61,6 @@ describe('Get Route Controller', () => {
lat: 2.3522, lat: 2.3522,
}, },
], ],
georouterSettings: {
points: true,
detailedDistance: false,
detailedDuration: false,
},
}); });
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
}); });

View File

@ -38,7 +38,7 @@ describe('Route Mapper', () => {
fwdAzimuth: 283, fwdAzimuth: 283,
backAzimuth: 93, backAzimuth: 93,
distanceAzimuth: 19840, distanceAzimuth: 19840,
spacetimePoints: [], points: [],
waypoints: [ waypoints: [
{ {
position: 0, position: 0,