move route compute to create service as entity creation is not async
This commit is contained in:
parent
4762c844e1
commit
ac8e459e90
|
@ -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');
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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>;
|
||||||
|
}
|
|
@ -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[];
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
||||||
|
|
|
@ -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;
|
};
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue