save results to redis
This commit is contained in:
		
							parent
							
								
									5c802df529
								
							
						
					
					
						commit
						09efe313ba
					
				| 
						 | 
					@ -15,14 +15,14 @@ MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
				
			||||||
REDIS_HOST=v3-redis
 | 
					REDIS_HOST=v3-redis
 | 
				
			||||||
REDIS_PASSWORD=redis
 | 
					REDIS_PASSWORD=redis
 | 
				
			||||||
REDIS_PORT=6379
 | 
					REDIS_PORT=6379
 | 
				
			||||||
 | 
					REDIS_MATCHING_KEY=MATCHER:MATCHING
 | 
				
			||||||
 | 
					REDIS_MATCHING_TTL=900
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# CACHE
 | 
					# CACHE
 | 
				
			||||||
CACHE_TTL=5000
 | 
					CACHE_TTL=5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# DEFAULT CONFIGURATION
 | 
					# DEFAULT CONFIGURATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# default identifier used for match requests
 | 
					 | 
				
			||||||
DEFAULT_UUID=00000000-0000-0000-0000-000000000000
 | 
					 | 
				
			||||||
# algorithm type
 | 
					# algorithm type
 | 
				
			||||||
ALGORITHM=PASSENGER_ORIENTED
 | 
					ALGORITHM=PASSENGER_ORIENTED
 | 
				
			||||||
# max distance in metres between driver
 | 
					# max distance in metres between driver
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@
 | 
				
			||||||
        "@grpc/proto-loader": "^0.7.6",
 | 
					        "@grpc/proto-loader": "^0.7.6",
 | 
				
			||||||
        "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
					        "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
				
			||||||
        "@mobicoop/configuration-module": "^1.2.0",
 | 
					        "@mobicoop/configuration-module": "^1.2.0",
 | 
				
			||||||
        "@mobicoop/ddd-library": "^1.3.0",
 | 
					        "@mobicoop/ddd-library": "^1.5.0",
 | 
				
			||||||
        "@mobicoop/health-module": "^2.0.0",
 | 
					        "@mobicoop/health-module": "^2.0.0",
 | 
				
			||||||
        "@mobicoop/message-broker-module": "^1.2.0",
 | 
					        "@mobicoop/message-broker-module": "^1.2.0",
 | 
				
			||||||
        "@nestjs/axios": "^2.0.0",
 | 
					        "@nestjs/axios": "^2.0.0",
 | 
				
			||||||
| 
						 | 
					@ -1505,9 +1505,9 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@mobicoop/ddd-library": {
 | 
					    "node_modules/@mobicoop/ddd-library": {
 | 
				
			||||||
      "version": "1.3.0",
 | 
					      "version": "1.5.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.5.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-WQTOIzGvsoh3o43Kukb9NIbJw18lsfSqu3k3cMZxc2mmgaYD7MtS4Yif/+KayQ6Ea4Ve3Hc6BVDls2X6svsoOg==",
 | 
					      "integrity": "sha512-CX/V2+vSXrGtKobsyBfVpMW323ZT8tHrgUl1qrvU1XjRKNShvwsKyC7739x7CNgkJ9sr3XV+75JrOXEnqU83zw==",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@nestjs/event-emitter": "^1.4.2",
 | 
					        "@nestjs/event-emitter": "^1.4.2",
 | 
				
			||||||
        "@nestjs/microservices": "^9.4.0",
 | 
					        "@nestjs/microservices": "^9.4.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@
 | 
				
			||||||
    "@grpc/proto-loader": "^0.7.6",
 | 
					    "@grpc/proto-loader": "^0.7.6",
 | 
				
			||||||
    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
					    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
				
			||||||
    "@mobicoop/configuration-module": "^1.2.0",
 | 
					    "@mobicoop/configuration-module": "^1.2.0",
 | 
				
			||||||
    "@mobicoop/ddd-library": "^1.3.0",
 | 
					    "@mobicoop/ddd-library": "^1.5.0",
 | 
				
			||||||
    "@mobicoop/health-module": "^2.0.0",
 | 
					    "@mobicoop/health-module": "^2.0.0",
 | 
				
			||||||
    "@mobicoop/message-broker-module": "^1.2.0",
 | 
					    "@mobicoop/message-broker-module": "^1.2.0",
 | 
				
			||||||
    "@nestjs/axios": "^2.0.0",
 | 
					    "@nestjs/axios": "^2.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
 | 
					export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
 | 
				
			||||||
 | 
					export const MATCHING_REPOSITORY = Symbol('MATCHING_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(
 | 
					export const AD_GET_BASIC_ROUTE_CONTROLLER = Symbol(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import {
 | 
				
			||||||
  INPUT_DATETIME_TRANSFORMER,
 | 
					  INPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
  AD_GET_DETAILED_ROUTE_CONTROLLER,
 | 
					  AD_GET_DETAILED_ROUTE_CONTROLLER,
 | 
				
			||||||
  OUTPUT_DATETIME_TRANSFORMER,
 | 
					  OUTPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
 | 
					  MATCHING_REPOSITORY,
 | 
				
			||||||
} from './ad.di-tokens';
 | 
					} from './ad.di-tokens';
 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
import { AdRepository } from './infrastructure/ad.repository';
 | 
					import { AdRepository } from './infrastructure/ad.repository';
 | 
				
			||||||
| 
						 | 
					@ -32,6 +33,8 @@ import { InputDateTimeTransformer } from './infrastructure/input-datetime-transf
 | 
				
			||||||
import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller';
 | 
					import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller';
 | 
				
			||||||
import { MatchMapper } from './match.mapper';
 | 
					import { MatchMapper } from './match.mapper';
 | 
				
			||||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
 | 
					import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
 | 
				
			||||||
 | 
					import { MatchingRepository } from './infrastructure/matching.repository';
 | 
				
			||||||
 | 
					import { MatchingMapper } from './matching.mapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const grpcControllers = [MatchGrpcController];
 | 
					const grpcControllers = [MatchGrpcController];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,13 +44,17 @@ const commandHandlers: Provider[] = [CreateAdService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
					const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mappers: Provider[] = [AdMapper, MatchMapper];
 | 
					const mappers: Provider[] = [AdMapper, MatchMapper, MatchingMapper];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const repositories: Provider[] = [
 | 
					const repositories: Provider[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    provide: AD_REPOSITORY,
 | 
					    provide: AD_REPOSITORY,
 | 
				
			||||||
    useClass: AdRepository,
 | 
					    useClass: AdRepository,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: MATCHING_REPOSITORY,
 | 
				
			||||||
 | 
					    useClass: MatchingRepository,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messagePublishers: Provider[] = [
 | 
					const messagePublishers: Provider[] = [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { MatchingEntity } from '../../domain/matching.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MatchingRepositoryPort = {
 | 
				
			||||||
 | 
					  get(id: string): Promise<MatchingEntity>;
 | 
				
			||||||
 | 
					  save(matching: MatchingEntity): Promise<void>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
					import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
				
			||||||
import { MatchQuery } from './match.query';
 | 
					import { MatchQuery, ScheduleItem } from './match.query';
 | 
				
			||||||
import { Algorithm } from './algorithm.abstract';
 | 
					import { Algorithm } from './algorithm.abstract';
 | 
				
			||||||
import { PassengerOrientedAlgorithm } from './passenger-oriented-algorithm';
 | 
					import { PassengerOrientedAlgorithm } from './passenger-oriented-algorithm';
 | 
				
			||||||
import { AlgorithmType } from '../../types/algorithm.types';
 | 
					import { AlgorithmType } from '../../types/algorithm.types';
 | 
				
			||||||
| 
						 | 
					@ -8,12 +8,16 @@ import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.reposito
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AD_REPOSITORY,
 | 
					  AD_REPOSITORY,
 | 
				
			||||||
  INPUT_DATETIME_TRANSFORMER,
 | 
					  INPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
 | 
					  MATCHING_REPOSITORY,
 | 
				
			||||||
  PARAMS_PROVIDER,
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
} from '@modules/ad/ad.di-tokens';
 | 
					} from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
					import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
				
			||||||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
 | 
					import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
 | 
				
			||||||
import { DefaultParams } from '../../ports/default-params.type';
 | 
					import { DefaultParams } from '../../ports/default-params.type';
 | 
				
			||||||
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
 | 
					import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
 | 
				
			||||||
 | 
					import { Paginator } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
 | 
				
			||||||
 | 
					import { MatchingRepositoryPort } from '../../ports/matching.repository.port';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@QueryHandler(MatchQuery)
 | 
					@QueryHandler(MatchQuery)
 | 
				
			||||||
export class MatchQueryHandler implements IQueryHandler {
 | 
					export class MatchQueryHandler implements IQueryHandler {
 | 
				
			||||||
| 
						 | 
					@ -22,14 +26,16 @@ export class MatchQueryHandler implements IQueryHandler {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(PARAMS_PROVIDER)
 | 
					    @Inject(PARAMS_PROVIDER)
 | 
				
			||||||
    private readonly defaultParamsProvider: DefaultParamsProviderPort,
 | 
					    private readonly defaultParamsProvider: DefaultParamsProviderPort,
 | 
				
			||||||
    @Inject(AD_REPOSITORY) private readonly repository: AdRepositoryPort,
 | 
					    @Inject(AD_REPOSITORY) private readonly adRepository: AdRepositoryPort,
 | 
				
			||||||
 | 
					    @Inject(MATCHING_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly matchingRepository: MatchingRepositoryPort,
 | 
				
			||||||
    @Inject(INPUT_DATETIME_TRANSFORMER)
 | 
					    @Inject(INPUT_DATETIME_TRANSFORMER)
 | 
				
			||||||
    private readonly datetimeTransformer: DateTimeTransformerPort,
 | 
					    private readonly datetimeTransformer: DateTimeTransformerPort,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this._defaultParams = defaultParamsProvider.getParams();
 | 
					    this._defaultParams = defaultParamsProvider.getParams();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  execute = async (query: MatchQuery): Promise<MatchEntity[]> => {
 | 
					  execute = async (query: MatchQuery): Promise<MatchingResult> => {
 | 
				
			||||||
    query
 | 
					    query
 | 
				
			||||||
      .setMissingMarginDurations(this._defaultParams.DEPARTURE_TIME_MARGIN)
 | 
					      .setMissingMarginDurations(this._defaultParams.DEPARTURE_TIME_MARGIN)
 | 
				
			||||||
      .setMissingStrict(this._defaultParams.STRICT)
 | 
					      .setMissingStrict(this._defaultParams.STRICT)
 | 
				
			||||||
| 
						 | 
					@ -60,8 +66,74 @@ export class MatchQueryHandler implements IQueryHandler {
 | 
				
			||||||
    switch (query.algorithmType) {
 | 
					    switch (query.algorithmType) {
 | 
				
			||||||
      case AlgorithmType.PASSENGER_ORIENTED:
 | 
					      case AlgorithmType.PASSENGER_ORIENTED:
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        algorithm = new PassengerOrientedAlgorithm(query, this.repository);
 | 
					        algorithm = new PassengerOrientedAlgorithm(query, this.adRepository);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return algorithm.match();
 | 
					
 | 
				
			||||||
 | 
					    const matches: MatchEntity[] = await algorithm.match();
 | 
				
			||||||
 | 
					    const perPage: number = query.perPage as number;
 | 
				
			||||||
 | 
					    const page: number = Paginator.pageNumber(
 | 
				
			||||||
 | 
					      matches.length,
 | 
				
			||||||
 | 
					      perPage,
 | 
				
			||||||
 | 
					      query.page as number,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    // create Matching Entity for persistence
 | 
				
			||||||
 | 
					    const matchingEntity: MatchingEntity = MatchingEntity.create({
 | 
				
			||||||
 | 
					      matches: matches.map((matchEntity: MatchEntity) => ({
 | 
				
			||||||
 | 
					        adId: matchEntity.getProps().adId,
 | 
				
			||||||
 | 
					        role: matchEntity.getProps().role,
 | 
				
			||||||
 | 
					        frequency: matchEntity.getProps().frequency,
 | 
				
			||||||
 | 
					        distance: matchEntity.getProps().distance,
 | 
				
			||||||
 | 
					        duration: matchEntity.getProps().duration,
 | 
				
			||||||
 | 
					        initialDistance: matchEntity.getProps().initialDistance,
 | 
				
			||||||
 | 
					        initialDuration: matchEntity.getProps().initialDuration,
 | 
				
			||||||
 | 
					        distanceDetour: matchEntity.getProps().distanceDetour,
 | 
				
			||||||
 | 
					        durationDetour: matchEntity.getProps().durationDetour,
 | 
				
			||||||
 | 
					        distanceDetourPercentage:
 | 
				
			||||||
 | 
					          matchEntity.getProps().distanceDetourPercentage,
 | 
				
			||||||
 | 
					        durationDetourPercentage:
 | 
				
			||||||
 | 
					          matchEntity.getProps().durationDetourPercentage,
 | 
				
			||||||
 | 
					        journeys: matchEntity.getProps().journeys,
 | 
				
			||||||
 | 
					      })),
 | 
				
			||||||
 | 
					      query: {
 | 
				
			||||||
 | 
					        driver: query.driver as boolean,
 | 
				
			||||||
 | 
					        passenger: query.passenger as boolean,
 | 
				
			||||||
 | 
					        frequency: query.frequency,
 | 
				
			||||||
 | 
					        fromDate: query.fromDate,
 | 
				
			||||||
 | 
					        toDate: query.toDate,
 | 
				
			||||||
 | 
					        schedule: query.schedule.map((scheduleItem: ScheduleItem) => ({
 | 
				
			||||||
 | 
					          day: scheduleItem.day as number,
 | 
				
			||||||
 | 
					          time: scheduleItem.time,
 | 
				
			||||||
 | 
					          margin: scheduleItem.margin as number,
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					        seatsProposed: query.seatsProposed as number,
 | 
				
			||||||
 | 
					        seatsRequested: query.seatsRequested as number,
 | 
				
			||||||
 | 
					        strict: query.strict as boolean,
 | 
				
			||||||
 | 
					        waypoints: query.waypoints,
 | 
				
			||||||
 | 
					        algorithmType: query.algorithmType as AlgorithmType,
 | 
				
			||||||
 | 
					        remoteness: query.remoteness as number,
 | 
				
			||||||
 | 
					        useProportion: query.useProportion as boolean,
 | 
				
			||||||
 | 
					        proportion: query.proportion as number,
 | 
				
			||||||
 | 
					        useAzimuth: query.useAzimuth as boolean,
 | 
				
			||||||
 | 
					        azimuthMargin: query.azimuthMargin as number,
 | 
				
			||||||
 | 
					        maxDetourDistanceRatio: query.maxDetourDistanceRatio as number,
 | 
				
			||||||
 | 
					        maxDetourDurationRatio: query.maxDetourDurationRatio as number,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await this.matchingRepository.save(matchingEntity);
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      id: matchingEntity.id,
 | 
				
			||||||
 | 
					      matches: Paginator.pageItems(matches, page, perPage),
 | 
				
			||||||
 | 
					      total: matches.length,
 | 
				
			||||||
 | 
					      page,
 | 
				
			||||||
 | 
					      perPage,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MatchingResult = {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  matches: MatchEntity[];
 | 
				
			||||||
 | 
					  total: number;
 | 
				
			||||||
 | 
					  page: number;
 | 
				
			||||||
 | 
					  perPage: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					import { CreateMatchingProps, MatchingProps } from './matching.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatchingEntity extends AggregateRoot<MatchingProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (create: CreateMatchingProps): MatchingEntity => {
 | 
				
			||||||
 | 
					    const id = v4();
 | 
				
			||||||
 | 
					    const props: MatchingProps = {
 | 
				
			||||||
 | 
					      ...create,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return new MatchingEntity({ id, props });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { ExceptionBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatchingNotFoundException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Matching error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'MATCHER.MATCHING_NOT_FOUND';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(MatchingNotFoundException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import { MatchProps } from './match.types';
 | 
				
			||||||
 | 
					import { MatchQueryProps } from './value-objects/match-query.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// All properties that a Matching has
 | 
				
			||||||
 | 
					export interface MatchingProps {
 | 
				
			||||||
 | 
					  query: MatchQueryProps; // the query that induced the matches
 | 
				
			||||||
 | 
					  matches: MatchProps[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Properties that are needed for a Matching creation
 | 
				
			||||||
 | 
					export interface CreateMatchingProps {
 | 
				
			||||||
 | 
					  query: MatchQueryProps;
 | 
				
			||||||
 | 
					  matches: MatchProps[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,109 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { Frequency } from '../ad.types';
 | 
				
			||||||
 | 
					import { ScheduleItemProps } from './schedule-item.value-object';
 | 
				
			||||||
 | 
					import { PointProps } from './point.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MatchQueryProps {
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  frequency: Frequency;
 | 
				
			||||||
 | 
					  fromDate: string;
 | 
				
			||||||
 | 
					  toDate: string;
 | 
				
			||||||
 | 
					  schedule: ScheduleItemProps[];
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					  strict: boolean;
 | 
				
			||||||
 | 
					  waypoints: PointProps[];
 | 
				
			||||||
 | 
					  algorithmType: string;
 | 
				
			||||||
 | 
					  remoteness: number;
 | 
				
			||||||
 | 
					  useProportion: boolean;
 | 
				
			||||||
 | 
					  proportion: number;
 | 
				
			||||||
 | 
					  useAzimuth: boolean;
 | 
				
			||||||
 | 
					  azimuthMargin: number;
 | 
				
			||||||
 | 
					  maxDetourDistanceRatio: number;
 | 
				
			||||||
 | 
					  maxDetourDurationRatio: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatchQuery extends ValueObject<MatchQueryProps> {
 | 
				
			||||||
 | 
					  get driver(): boolean {
 | 
				
			||||||
 | 
					    return this.props.driver;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get passenger(): boolean {
 | 
				
			||||||
 | 
					    return this.props.passenger;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get frequency(): Frequency {
 | 
				
			||||||
 | 
					    return this.props.frequency;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get fromDate(): string {
 | 
				
			||||||
 | 
					    return this.props.fromDate;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get toDate(): string {
 | 
				
			||||||
 | 
					    return this.props.toDate;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get schedule(): ScheduleItemProps[] {
 | 
				
			||||||
 | 
					    return this.props.schedule;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get seatsProposed(): number {
 | 
				
			||||||
 | 
					    return this.props.seatsProposed;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get seatsRequested(): number {
 | 
				
			||||||
 | 
					    return this.props.seatsRequested;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get strict(): boolean {
 | 
				
			||||||
 | 
					    return this.props.strict;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get waypoints(): PointProps[] {
 | 
				
			||||||
 | 
					    return this.props.waypoints;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get algorithmType(): string {
 | 
				
			||||||
 | 
					    return this.props.algorithmType;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get remoteness(): number {
 | 
				
			||||||
 | 
					    return this.props.remoteness;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get useProportion(): boolean {
 | 
				
			||||||
 | 
					    return this.props.useProportion;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get proportion(): number {
 | 
				
			||||||
 | 
					    return this.props.proportion;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get useAzimuth(): boolean {
 | 
				
			||||||
 | 
					    return this.props.useAzimuth;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get azimuthMargin(): number {
 | 
				
			||||||
 | 
					    return this.props.azimuthMargin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get maxDetourDistanceRatio(): number {
 | 
				
			||||||
 | 
					    return this.props.maxDetourDistanceRatio;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get maxDetourDurationRatio(): number {
 | 
				
			||||||
 | 
					    return this.props.maxDetourDurationRatio;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: MatchQueryProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import { InjectRedis } from '@liaoliaots/nestjs-redis';
 | 
				
			||||||
 | 
					import { MatchingRepositoryPort } from '../core/application/ports/matching.repository.port';
 | 
				
			||||||
 | 
					import { MatchingEntity } from '../core/domain/matching.entity';
 | 
				
			||||||
 | 
					import { Redis } from 'ioredis';
 | 
				
			||||||
 | 
					import { MatchingMapper } from '../matching.mapper';
 | 
				
			||||||
 | 
					import { ConfigService } from '@nestjs/config';
 | 
				
			||||||
 | 
					import { MatchingNotFoundException } from '../core/domain/matching.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const REDIS_MATCHING_TTL = 900;
 | 
				
			||||||
 | 
					const REDIS_MATCHING_KEY = 'MATCHER:MATCHING';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatchingRepository implements MatchingRepositoryPort {
 | 
				
			||||||
 | 
					  private _redisKey: string;
 | 
				
			||||||
 | 
					  private _redisTtl: number;
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @InjectRedis() private readonly redis: Redis,
 | 
				
			||||||
 | 
					    private readonly configService: ConfigService,
 | 
				
			||||||
 | 
					    private readonly mapper: MatchingMapper,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this._redisKey =
 | 
				
			||||||
 | 
					      this.configService.get('REDIS_MATCHING_KEY') !== undefined
 | 
				
			||||||
 | 
					        ? (this.configService.get('REDIS_MATCHING_KEY') as string)
 | 
				
			||||||
 | 
					        : REDIS_MATCHING_KEY;
 | 
				
			||||||
 | 
					    this._redisTtl =
 | 
				
			||||||
 | 
					      this.configService.get('REDIS_MATCHING_TTL') !== undefined
 | 
				
			||||||
 | 
					        ? (this.configService.get('REDIS_MATCHING_TTL') as number)
 | 
				
			||||||
 | 
					        : REDIS_MATCHING_TTL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get = async (matchingId: string): Promise<MatchingEntity> => {
 | 
				
			||||||
 | 
					    const matching: string | null = await this.redis.get(
 | 
				
			||||||
 | 
					      `${this._redisKey}:${matchingId}`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (matching) return this.mapper.toDomain(matching);
 | 
				
			||||||
 | 
					    throw new MatchingNotFoundException(new Error('Matching not found'));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  save = async (matching: MatchingEntity): Promise<void> => {
 | 
				
			||||||
 | 
					    await this.redis.set(
 | 
				
			||||||
 | 
					      `${this._redisKey}:${matching.id}`,
 | 
				
			||||||
 | 
					      this.mapper.toPersistence(matching),
 | 
				
			||||||
 | 
					      'EX',
 | 
				
			||||||
 | 
					      this._redisTtl,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ export class TimeConverter implements TimeConverterPort {
 | 
				
			||||||
    date: string,
 | 
					    date: string,
 | 
				
			||||||
    time: string,
 | 
					    time: string,
 | 
				
			||||||
    timezone: string,
 | 
					    timezone: string,
 | 
				
			||||||
    dst?: boolean,
 | 
					    dst = false,
 | 
				
			||||||
  ): string =>
 | 
					  ): string =>
 | 
				
			||||||
    new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
 | 
					    new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
 | 
				
			||||||
      .convert(TimeZone.zone(timezone, dst))
 | 
					      .convert(TimeZone.zone(timezone, dst))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { PaginatedResponseDto } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class IdPaginatedResponseDto<
 | 
				
			||||||
 | 
					  T,
 | 
				
			||||||
 | 
					> extends PaginatedResponseDto<T> {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { MatchResponseDto } from './match.response.dto';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class MatchPaginatedResponseDto extends PaginatedResponseDto<MatchResponseDto> {
 | 
					 | 
				
			||||||
  readonly data: readonly MatchResponseDto[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { MatchResponseDto } from './match.response.dto';
 | 
				
			||||||
 | 
					import { IdPaginatedResponseDto } from './id-paginated.reponse.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatchingPaginatedResponseDto extends IdPaginatedResponseDto<MatchResponseDto> {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					  readonly data: readonly MatchResponseDto[];
 | 
				
			||||||
 | 
					  constructor(props: IdPaginatedResponseDto<MatchResponseDto>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.id = props.id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import { Controller, Inject, UsePipes } from '@nestjs/common';
 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
					import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
 | 
					import { MatchingPaginatedResponseDto } from '../dtos/matching.paginated.response.dto';
 | 
				
			||||||
import { QueryBus } from '@nestjs/cqrs';
 | 
					import { QueryBus } from '@nestjs/cqrs';
 | 
				
			||||||
import { MatchRequestDto } from './dtos/match.request.dto';
 | 
					import { MatchRequestDto } from './dtos/match.request.dto';
 | 
				
			||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
					import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
				
			||||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
					import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
					import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
				
			||||||
import { MatchMapper } from '@modules/ad/match.mapper';
 | 
					import { MatchMapper } from '@modules/ad/match.mapper';
 | 
				
			||||||
 | 
					import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@UsePipes(
 | 
					@UsePipes(
 | 
				
			||||||
  new RpcValidationPipe({
 | 
					  new RpcValidationPipe({
 | 
				
			||||||
| 
						 | 
					@ -27,18 +28,19 @@ export class MatchGrpcController {
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @GrpcMethod('MatcherService', 'Match')
 | 
					  @GrpcMethod('MatcherService', 'Match')
 | 
				
			||||||
  async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> {
 | 
					  async match(data: MatchRequestDto): Promise<MatchingPaginatedResponseDto> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const matches: MatchEntity[] = await this.queryBus.execute(
 | 
					      const matchingResult: MatchingResult = await this.queryBus.execute(
 | 
				
			||||||
        new MatchQuery(data, this.routeProvider),
 | 
					        new MatchQuery(data, this.routeProvider),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      return new MatchPaginatedResponseDto({
 | 
					      return new MatchingPaginatedResponseDto({
 | 
				
			||||||
        data: matches.map((match: MatchEntity) =>
 | 
					        id: matchingResult.id,
 | 
				
			||||||
 | 
					        data: matchingResult.matches.map((match: MatchEntity) =>
 | 
				
			||||||
          this.matchMapper.toResponse(match),
 | 
					          this.matchMapper.toResponse(match),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        page: 1,
 | 
					        page: matchingResult.page,
 | 
				
			||||||
        perPage: 5,
 | 
					        perPage: matchingResult.perPage,
 | 
				
			||||||
        total: matches.length,
 | 
					        total: matchingResult.total,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      throw new RpcException({
 | 
					      throw new RpcException({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,8 @@ message MatchRequest {
 | 
				
			||||||
  float maxDetourDistanceRatio = 15;
 | 
					  float maxDetourDistanceRatio = 15;
 | 
				
			||||||
  float maxDetourDurationRatio = 16;
 | 
					  float maxDetourDurationRatio = 16;
 | 
				
			||||||
  int32 identifier = 22;
 | 
					  int32 identifier = 22;
 | 
				
			||||||
 | 
					  optional int32 page = 23;
 | 
				
			||||||
 | 
					  optional int32 perPage = 24;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message ScheduleItem {
 | 
					message ScheduleItem {
 | 
				
			||||||
| 
						 | 
					@ -90,6 +92,9 @@ message Actor {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message Matches {
 | 
					message Matches {
 | 
				
			||||||
  repeated Match data = 1;
 | 
					  string id = 1;
 | 
				
			||||||
  int32 total = 2;
 | 
					  repeated Match data = 2;
 | 
				
			||||||
 | 
					  int32 total = 3;
 | 
				
			||||||
 | 
					  int32 page = 4;
 | 
				
			||||||
 | 
					  int32 perPage = 5;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { Mapper } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { MatchingEntity } from './core/domain/matching.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class MatchingMapper
 | 
				
			||||||
 | 
					  implements Mapper<MatchingEntity, string, string, undefined>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  toPersistence = (entity: MatchingEntity): string => JSON.stringify(entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toDomain = (record: string): MatchingEntity =>
 | 
				
			||||||
 | 
					    new MatchingEntity(JSON.parse(record));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
 | 
					import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { MatchQuery } from '@modules/ad/core/domain/value-objects/match-query.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Match Query value object', () => {
 | 
				
			||||||
 | 
					  it('should create a match query value object', () => {
 | 
				
			||||||
 | 
					    const matchQueryVO = new MatchQuery({
 | 
				
			||||||
 | 
					      driver: false,
 | 
				
			||||||
 | 
					      passenger: true,
 | 
				
			||||||
 | 
					      frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					      fromDate: '2023-09-01',
 | 
				
			||||||
 | 
					      toDate: '2023-09-01',
 | 
				
			||||||
 | 
					      schedule: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          day: 5,
 | 
				
			||||||
 | 
					          time: '07:10',
 | 
				
			||||||
 | 
					          margin: 900,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      seatsProposed: 3,
 | 
				
			||||||
 | 
					      seatsRequested: 1,
 | 
				
			||||||
 | 
					      strict: false,
 | 
				
			||||||
 | 
					      waypoints: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          lat: 48.689445,
 | 
				
			||||||
 | 
					          lon: 6.17651,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          lat: 48.21548,
 | 
				
			||||||
 | 
					          lon: 5.65874,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      algorithmType: AlgorithmType.PASSENGER_ORIENTED,
 | 
				
			||||||
 | 
					      remoteness: 15000,
 | 
				
			||||||
 | 
					      useProportion: true,
 | 
				
			||||||
 | 
					      proportion: 0.3,
 | 
				
			||||||
 | 
					      useAzimuth: true,
 | 
				
			||||||
 | 
					      azimuthMargin: 10,
 | 
				
			||||||
 | 
					      maxDetourDistanceRatio: 0.3,
 | 
				
			||||||
 | 
					      maxDetourDurationRatio: 0.3,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(matchQueryVO.driver).toBe(false);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.passenger).toBe(true);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.frequency).toBe(Frequency.PUNCTUAL);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.fromDate).toBe('2023-09-01');
 | 
				
			||||||
 | 
					    expect(matchQueryVO.toDate).toBe('2023-09-01');
 | 
				
			||||||
 | 
					    expect(matchQueryVO.schedule.length).toBe(1);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.seatsProposed).toBe(3);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.seatsRequested).toBe(1);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.strict).toBe(false);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.waypoints.length).toBe(2);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.algorithmType).toBe(AlgorithmType.PASSENGER_ORIENTED);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.remoteness).toBe(15000);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.useProportion).toBe(true);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.proportion).toBe(0.3);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.useAzimuth).toBe(true);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.azimuthMargin).toBe(10);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.maxDetourDistanceRatio).toBe(0.3);
 | 
				
			||||||
 | 
					    expect(matchQueryVO.maxDetourDurationRatio).toBe(0.3);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,22 @@
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AD_REPOSITORY,
 | 
					  AD_REPOSITORY,
 | 
				
			||||||
  INPUT_DATETIME_TRANSFORMER,
 | 
					  INPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
 | 
					  MATCHING_REPOSITORY,
 | 
				
			||||||
  PARAMS_PROVIDER,
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
} from '@modules/ad/ad.di-tokens';
 | 
					} from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
 | 
					import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
 | 
				
			||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
 | 
					import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
 | 
				
			||||||
 | 
					import { MatchingRepositoryPort } from '@modules/ad/core/application/ports/matching.repository.port';
 | 
				
			||||||
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
					import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
				
			||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
					import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
 | 
				
			||||||
import { MatchQueryHandler } from '@modules/ad/core/application/queries/match/match.query-handler';
 | 
					import {
 | 
				
			||||||
 | 
					  MatchQueryHandler,
 | 
				
			||||||
 | 
					  MatchingResult,
 | 
				
			||||||
 | 
					} from '@modules/ad/core/application/queries/match/match.query-handler';
 | 
				
			||||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
 | 
					import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
 | 
				
			||||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
					import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const originWaypoint: Waypoint = {
 | 
					const originWaypoint: Waypoint = {
 | 
				
			||||||
| 
						 | 
					@ -51,9 +56,30 @@ const mockAdRepository = {
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      })),
 | 
					      })),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      id: '4431adea-2e10-4032-a743-01d537058914',
 | 
				
			||||||
 | 
					      getProps: jest.fn().mockImplementation(() => ({
 | 
				
			||||||
 | 
					        role: Role.DRIVER,
 | 
				
			||||||
 | 
					        waypoints: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            lat: 48.698754,
 | 
				
			||||||
 | 
					            lon: 6.159874,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            lat: 48.969874,
 | 
				
			||||||
 | 
					            lon: 2.449875,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      })),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ]),
 | 
					  ]),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMatchingRepository: MatchingRepositoryPort = {
 | 
				
			||||||
 | 
					  get: jest.fn(),
 | 
				
			||||||
 | 
					  save: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
 | 
					const mockDefaultParamsProvider: DefaultParamsProviderPort = {
 | 
				
			||||||
  getParams: () => {
 | 
					  getParams: () => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
| 
						 | 
					@ -107,6 +133,10 @@ describe('Match Query Handler', () => {
 | 
				
			||||||
          provide: AD_REPOSITORY,
 | 
					          provide: AD_REPOSITORY,
 | 
				
			||||||
          useValue: mockAdRepository,
 | 
					          useValue: mockAdRepository,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: MATCHING_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockMatchingRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: PARAMS_PROVIDER,
 | 
					          provide: PARAMS_PROVIDER,
 | 
				
			||||||
          useValue: mockDefaultParamsProvider,
 | 
					          useValue: mockDefaultParamsProvider,
 | 
				
			||||||
| 
						 | 
					@ -125,7 +155,8 @@ describe('Match Query Handler', () => {
 | 
				
			||||||
    expect(matchQueryHandler).toBeDefined();
 | 
					    expect(matchQueryHandler).toBeDefined();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should return a Match entity', async () => {
 | 
					  it('should return a Matching', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(MatchingEntity, 'create');
 | 
				
			||||||
    const matchQuery = new MatchQuery(
 | 
					    const matchQuery = new MatchQuery(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        algorithmType: AlgorithmType.PASSENGER_ORIENTED,
 | 
					        algorithmType: AlgorithmType.PASSENGER_ORIENTED,
 | 
				
			||||||
| 
						 | 
					@ -146,7 +177,10 @@ describe('Match Query Handler', () => {
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      mockRouteProvider,
 | 
					      mockRouteProvider,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const matches: MatchEntity[] = await matchQueryHandler.execute(matchQuery);
 | 
					    const matching: MatchingResult = await matchQueryHandler.execute(
 | 
				
			||||||
    expect(matches.length).toBeGreaterThanOrEqual(0);
 | 
					      matchQuery,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(matching.id).toHaveLength(36);
 | 
				
			||||||
 | 
					    expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -52,7 +52,7 @@ describe('Time Converter', () => {
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('localStringDateTimeToUtcDate', () => {
 | 
					  describe('localStringDateTimeToUtcDate', () => {
 | 
				
			||||||
    it('should convert a summer paris date and time to a utc date', () => {
 | 
					    it('should convert a summer paris date and time to a utc date with dst', () => {
 | 
				
			||||||
      const timeConverter: TimeConverter = new TimeConverter();
 | 
					      const timeConverter: TimeConverter = new TimeConverter();
 | 
				
			||||||
      const parisDate = '2023-06-22';
 | 
					      const parisDate = '2023-06-22';
 | 
				
			||||||
      const parisTime = '12:00';
 | 
					      const parisTime = '12:00';
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ describe('Time Converter', () => {
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
 | 
					      expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('should convert a winter paris date and time to a utc date', () => {
 | 
					    it('should convert a winter paris date and time to a utc date with dst', () => {
 | 
				
			||||||
      const timeConverter: TimeConverter = new TimeConverter();
 | 
					      const timeConverter: TimeConverter = new TimeConverter();
 | 
				
			||||||
      const parisDate = '2023-02-02';
 | 
					      const parisDate = '2023-02-02';
 | 
				
			||||||
      const parisTime = '12:00';
 | 
					      const parisTime = '12:00';
 | 
				
			||||||
| 
						 | 
					@ -72,6 +72,7 @@ describe('Time Converter', () => {
 | 
				
			||||||
        parisDate,
 | 
					        parisDate,
 | 
				
			||||||
        parisTime,
 | 
					        parisTime,
 | 
				
			||||||
        'Europe/Paris',
 | 
					        'Europe/Paris',
 | 
				
			||||||
 | 
					        true,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
 | 
					      expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -83,7 +84,6 @@ describe('Time Converter', () => {
 | 
				
			||||||
        parisDate,
 | 
					        parisDate,
 | 
				
			||||||
        parisTime,
 | 
					        parisTime,
 | 
				
			||||||
        'Europe/Paris',
 | 
					        'Europe/Paris',
 | 
				
			||||||
        false,
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
 | 
					      expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -148,6 +148,30 @@ describe('Time Converter', () => {
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('utcStringDateTimeToLocalIsoString', () => {
 | 
					  describe('utcStringDateTimeToLocalIsoString', () => {
 | 
				
			||||||
 | 
					    it('should convert a utc string date and time to a summer paris date isostring with dst', () => {
 | 
				
			||||||
 | 
					      const timeConverter: TimeConverter = new TimeConverter();
 | 
				
			||||||
 | 
					      const utcDate = '2023-06-22';
 | 
				
			||||||
 | 
					      const utcTime = '10:00';
 | 
				
			||||||
 | 
					      const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
 | 
				
			||||||
 | 
					        utcDate,
 | 
				
			||||||
 | 
					        utcTime,
 | 
				
			||||||
 | 
					        'Europe/Paris',
 | 
				
			||||||
 | 
					        true,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should convert a utc string date and time to a winter paris date isostring with dst', () => {
 | 
				
			||||||
 | 
					      const timeConverter: TimeConverter = new TimeConverter();
 | 
				
			||||||
 | 
					      const utcDate = '2023-02-02';
 | 
				
			||||||
 | 
					      const utcTime = '10:00';
 | 
				
			||||||
 | 
					      const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
 | 
				
			||||||
 | 
					        utcDate,
 | 
				
			||||||
 | 
					        utcTime,
 | 
				
			||||||
 | 
					        'Europe/Paris',
 | 
				
			||||||
 | 
					        true,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    it('should convert a utc string date and time to a summer paris date isostring', () => {
 | 
					    it('should convert a utc string date and time to a summer paris date isostring', () => {
 | 
				
			||||||
      const timeConverter: TimeConverter = new TimeConverter();
 | 
					      const timeConverter: TimeConverter = new TimeConverter();
 | 
				
			||||||
      const utcDate = '2023-06-22';
 | 
					      const utcDate = '2023-06-22';
 | 
				
			||||||
| 
						 | 
					@ -157,29 +181,6 @@ describe('Time Converter', () => {
 | 
				
			||||||
        utcTime,
 | 
					        utcTime,
 | 
				
			||||||
        'Europe/Paris',
 | 
					        'Europe/Paris',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should convert a utc string date and time to a winter paris date isostring', () => {
 | 
					 | 
				
			||||||
      const timeConverter: TimeConverter = new TimeConverter();
 | 
					 | 
				
			||||||
      const utcDate = '2023-02-02';
 | 
					 | 
				
			||||||
      const utcTime = '10:00';
 | 
					 | 
				
			||||||
      const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
 | 
					 | 
				
			||||||
        utcDate,
 | 
					 | 
				
			||||||
        utcTime,
 | 
					 | 
				
			||||||
        'Europe/Paris',
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should convert a utc string date and time to a summer paris date isostring without dst', () => {
 | 
					 | 
				
			||||||
      const timeConverter: TimeConverter = new TimeConverter();
 | 
					 | 
				
			||||||
      const utcDate = '2023-06-22';
 | 
					 | 
				
			||||||
      const utcTime = '10:00';
 | 
					 | 
				
			||||||
      const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
 | 
					 | 
				
			||||||
        utcDate,
 | 
					 | 
				
			||||||
        utcTime,
 | 
					 | 
				
			||||||
        'Europe/Paris',
 | 
					 | 
				
			||||||
        false,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00');
 | 
					      expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('should convert a utc date to a tonga date isostring', () => {
 | 
					    it('should convert a utc date to a tonga date isostring', () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,14 @@
 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
					import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
					import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
 | 
				
			||||||
 | 
					import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
 | 
				
			||||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
					import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
 | 
				
			||||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
import { Target } from '@modules/ad/core/domain/candidate.types';
 | 
					import { Target } from '@modules/ad/core/domain/candidate.types';
 | 
				
			||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
					import { MatchEntity } from '@modules/ad/core/domain/match.entity';
 | 
				
			||||||
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
 | 
					import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
 | 
				
			||||||
import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object';
 | 
					import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object';
 | 
				
			||||||
 | 
					import { MatchingPaginatedResponseDto } from '@modules/ad/interface/dtos/matching.paginated.response.dto';
 | 
				
			||||||
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
 | 
					import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
 | 
				
			||||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
 | 
					import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
 | 
				
			||||||
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
 | 
					import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
 | 
				
			||||||
| 
						 | 
					@ -55,117 +57,126 @@ const recurrentMatchRequestDto: MatchRequestDto = {
 | 
				
			||||||
const mockQueryBus = {
 | 
					const mockQueryBus = {
 | 
				
			||||||
  execute: jest
 | 
					  execute: jest
 | 
				
			||||||
    .fn()
 | 
					    .fn()
 | 
				
			||||||
    .mockImplementationOnce(() => [
 | 
					    .mockImplementationOnce(
 | 
				
			||||||
      MatchEntity.create({
 | 
					      () =>
 | 
				
			||||||
        adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
 | 
					        <MatchingResult>{
 | 
				
			||||||
        role: Role.DRIVER,
 | 
					          id: '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
 | 
				
			||||||
        frequency: Frequency.RECURRENT,
 | 
					          page: 1,
 | 
				
			||||||
        distance: 356041,
 | 
					          perPage: 10,
 | 
				
			||||||
        duration: 12647,
 | 
					          matches: [
 | 
				
			||||||
        initialDistance: 349251,
 | 
					            MatchEntity.create({
 | 
				
			||||||
        initialDuration: 12103,
 | 
					              adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
 | 
				
			||||||
        journeys: [
 | 
					              role: Role.DRIVER,
 | 
				
			||||||
          {
 | 
					              frequency: Frequency.RECURRENT,
 | 
				
			||||||
            firstDate: new Date('2023-09-01'),
 | 
					              distance: 356041,
 | 
				
			||||||
            lastDate: new Date('2024-08-30'),
 | 
					              duration: 12647,
 | 
				
			||||||
            journeyItems: [
 | 
					              initialDistance: 349251,
 | 
				
			||||||
              new JourneyItem({
 | 
					              initialDuration: 12103,
 | 
				
			||||||
                lat: 48.689445,
 | 
					              journeys: [
 | 
				
			||||||
                lon: 6.17651,
 | 
					                {
 | 
				
			||||||
                duration: 0,
 | 
					                  firstDate: new Date('2023-09-01'),
 | 
				
			||||||
                distance: 0,
 | 
					                  lastDate: new Date('2024-08-30'),
 | 
				
			||||||
                actorTimes: [
 | 
					                  journeyItems: [
 | 
				
			||||||
                  new ActorTime({
 | 
					                    new JourneyItem({
 | 
				
			||||||
                    role: Role.DRIVER,
 | 
					                      lat: 48.689445,
 | 
				
			||||||
                    target: Target.START,
 | 
					                      lon: 6.17651,
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 07:00'),
 | 
					                      duration: 0,
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 06:45'),
 | 
					                      distance: 0,
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 07:15'),
 | 
					                      actorTimes: [
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 07:00'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 06:45'),
 | 
					                          role: Role.DRIVER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 07:15'),
 | 
					                          target: Target.START,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 07:00'),
 | 
				
			||||||
                ],
 | 
					                          firstMinDatetime: new Date('2023-09-01 06:45'),
 | 
				
			||||||
              }),
 | 
					                          firstMaxDatetime: new Date('2023-09-01 07:15'),
 | 
				
			||||||
              new JourneyItem({
 | 
					                          lastDatetime: new Date('2024-08-30 07:00'),
 | 
				
			||||||
                lat: 48.369445,
 | 
					                          lastMinDatetime: new Date('2024-08-30 06:45'),
 | 
				
			||||||
                lon: 6.67487,
 | 
					                          lastMaxDatetime: new Date('2024-08-30 07:15'),
 | 
				
			||||||
                duration: 2100,
 | 
					                        }),
 | 
				
			||||||
                distance: 56878,
 | 
					                      ],
 | 
				
			||||||
                actorTimes: [
 | 
					                    }),
 | 
				
			||||||
                  new ActorTime({
 | 
					                    new JourneyItem({
 | 
				
			||||||
                    role: Role.DRIVER,
 | 
					                      lat: 48.369445,
 | 
				
			||||||
                    target: Target.NEUTRAL,
 | 
					                      lon: 6.67487,
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 07:35'),
 | 
					                      duration: 2100,
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 07:20'),
 | 
					                      distance: 56878,
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 07:50'),
 | 
					                      actorTimes: [
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 07:35'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 07:20'),
 | 
					                          role: Role.DRIVER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 07:50'),
 | 
					                          target: Target.NEUTRAL,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 07:35'),
 | 
				
			||||||
                  new ActorTime({
 | 
					                          firstMinDatetime: new Date('2023-09-01 07:20'),
 | 
				
			||||||
                    role: Role.PASSENGER,
 | 
					                          firstMaxDatetime: new Date('2023-09-01 07:50'),
 | 
				
			||||||
                    target: Target.START,
 | 
					                          lastDatetime: new Date('2024-08-30 07:35'),
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 07:32'),
 | 
					                          lastMinDatetime: new Date('2024-08-30 07:20'),
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 07:17'),
 | 
					                          lastMaxDatetime: new Date('2024-08-30 07:50'),
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 07:47'),
 | 
					                        }),
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 07:32'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 07:17'),
 | 
					                          role: Role.PASSENGER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 07:47'),
 | 
					                          target: Target.START,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 07:32'),
 | 
				
			||||||
                ],
 | 
					                          firstMinDatetime: new Date('2023-09-01 07:17'),
 | 
				
			||||||
              }),
 | 
					                          firstMaxDatetime: new Date('2023-09-01 07:47'),
 | 
				
			||||||
              new JourneyItem({
 | 
					                          lastDatetime: new Date('2024-08-30 07:32'),
 | 
				
			||||||
                lat: 47.98487,
 | 
					                          lastMinDatetime: new Date('2024-08-30 07:17'),
 | 
				
			||||||
                lon: 6.9427,
 | 
					                          lastMaxDatetime: new Date('2024-08-30 07:47'),
 | 
				
			||||||
                duration: 3840,
 | 
					                        }),
 | 
				
			||||||
                distance: 76491,
 | 
					                      ],
 | 
				
			||||||
                actorTimes: [
 | 
					                    }),
 | 
				
			||||||
                  new ActorTime({
 | 
					                    new JourneyItem({
 | 
				
			||||||
                    role: Role.DRIVER,
 | 
					                      lat: 47.98487,
 | 
				
			||||||
                    target: Target.NEUTRAL,
 | 
					                      lon: 6.9427,
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 08:04'),
 | 
					                      duration: 3840,
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 07:51'),
 | 
					                      distance: 76491,
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 08:19'),
 | 
					                      actorTimes: [
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 08:04'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 07:51'),
 | 
					                          role: Role.DRIVER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 08:19'),
 | 
					                          target: Target.NEUTRAL,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 08:04'),
 | 
				
			||||||
                  new ActorTime({
 | 
					                          firstMinDatetime: new Date('2023-09-01 07:51'),
 | 
				
			||||||
                    role: Role.PASSENGER,
 | 
					                          firstMaxDatetime: new Date('2023-09-01 08:19'),
 | 
				
			||||||
                    target: Target.FINISH,
 | 
					                          lastDatetime: new Date('2024-08-30 08:04'),
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 08:01'),
 | 
					                          lastMinDatetime: new Date('2024-08-30 07:51'),
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 07:46'),
 | 
					                          lastMaxDatetime: new Date('2024-08-30 08:19'),
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 08:16'),
 | 
					                        }),
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 08:01'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 07:46'),
 | 
					                          role: Role.PASSENGER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 08:16'),
 | 
					                          target: Target.FINISH,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 08:01'),
 | 
				
			||||||
                ],
 | 
					                          firstMinDatetime: new Date('2023-09-01 07:46'),
 | 
				
			||||||
              }),
 | 
					                          firstMaxDatetime: new Date('2023-09-01 08:16'),
 | 
				
			||||||
              new JourneyItem({
 | 
					                          lastDatetime: new Date('2024-08-30 08:01'),
 | 
				
			||||||
                lat: 47.365987,
 | 
					                          lastMinDatetime: new Date('2024-08-30 07:46'),
 | 
				
			||||||
                lon: 7.02154,
 | 
					                          lastMaxDatetime: new Date('2024-08-30 08:16'),
 | 
				
			||||||
                duration: 4980,
 | 
					                        }),
 | 
				
			||||||
                distance: 96475,
 | 
					                      ],
 | 
				
			||||||
                actorTimes: [
 | 
					                    }),
 | 
				
			||||||
                  new ActorTime({
 | 
					                    new JourneyItem({
 | 
				
			||||||
                    role: Role.DRIVER,
 | 
					                      lat: 47.365987,
 | 
				
			||||||
                    target: Target.FINISH,
 | 
					                      lon: 7.02154,
 | 
				
			||||||
                    firstDatetime: new Date('2023-09-01 08:23'),
 | 
					                      duration: 4980,
 | 
				
			||||||
                    firstMinDatetime: new Date('2023-09-01 08:08'),
 | 
					                      distance: 96475,
 | 
				
			||||||
                    firstMaxDatetime: new Date('2023-09-01 08:38'),
 | 
					                      actorTimes: [
 | 
				
			||||||
                    lastDatetime: new Date('2024-08-30 08:23'),
 | 
					                        new ActorTime({
 | 
				
			||||||
                    lastMinDatetime: new Date('2024-08-30 08:08'),
 | 
					                          role: Role.DRIVER,
 | 
				
			||||||
                    lastMaxDatetime: new Date('2024-08-30 08:38'),
 | 
					                          target: Target.FINISH,
 | 
				
			||||||
                  }),
 | 
					                          firstDatetime: new Date('2023-09-01 08:23'),
 | 
				
			||||||
                ],
 | 
					                          firstMinDatetime: new Date('2023-09-01 08:08'),
 | 
				
			||||||
              }),
 | 
					                          firstMaxDatetime: new Date('2023-09-01 08:38'),
 | 
				
			||||||
            ],
 | 
					                          lastDatetime: new Date('2024-08-30 08:23'),
 | 
				
			||||||
          },
 | 
					                          lastMinDatetime: new Date('2024-08-30 08:08'),
 | 
				
			||||||
        ],
 | 
					                          lastMaxDatetime: new Date('2024-08-30 08:38'),
 | 
				
			||||||
      }),
 | 
					                        }),
 | 
				
			||||||
    ])
 | 
					                      ],
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          total: 1,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
      throw new Error();
 | 
					      throw new Error();
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
| 
						 | 
					@ -319,12 +330,16 @@ describe('Match Grpc Controller', () => {
 | 
				
			||||||
    expect(matchGrpcController).toBeDefined();
 | 
					    expect(matchGrpcController).toBeDefined();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should return matches', async () => {
 | 
					  it('should return a matching', async () => {
 | 
				
			||||||
    jest.spyOn(mockQueryBus, 'execute');
 | 
					    jest.spyOn(mockQueryBus, 'execute');
 | 
				
			||||||
    const matchPaginatedResponseDto = await matchGrpcController.match(
 | 
					    const matchingPaginatedResponseDto: MatchingPaginatedResponseDto =
 | 
				
			||||||
      recurrentMatchRequestDto,
 | 
					      await matchGrpcController.match(recurrentMatchRequestDto);
 | 
				
			||||||
 | 
					    expect(matchingPaginatedResponseDto.id).toBe(
 | 
				
			||||||
 | 
					      '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    expect(matchPaginatedResponseDto.data).toHaveLength(1);
 | 
					    expect(matchingPaginatedResponseDto.data).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(matchingPaginatedResponseDto.page).toBe(1);
 | 
				
			||||||
 | 
					    expect(matchingPaginatedResponseDto.perPage).toBe(10);
 | 
				
			||||||
    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
					    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { Target } from '@modules/ad/core/domain/candidate.types';
 | 
				
			||||||
 | 
					import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
 | 
				
			||||||
 | 
					import { MatchingMapper } from '@modules/ad/matching.mapper';
 | 
				
			||||||
 | 
					import { Test } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Matching Mapper', () => {
 | 
				
			||||||
 | 
					  let matchingMapper: MatchingMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [MatchingMapper],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					    matchingMapper = module.get<MatchingMapper>(MatchingMapper);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(matchingMapper).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map domain entity to persistence', async () => {
 | 
				
			||||||
 | 
					    const matchingEntity: MatchingEntity = new MatchingEntity({
 | 
				
			||||||
 | 
					      id: '644a7cb3-6436-4db5-850d-b4c7421d4b97',
 | 
				
			||||||
 | 
					      createdAt: new Date('2023-08-20T09:48:00Z'),
 | 
				
			||||||
 | 
					      updatedAt: new Date('2023-08-20T09:48:00Z'),
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        matches: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            adId: 'dd937edf-1264-4868-b073-d1952abe30b1',
 | 
				
			||||||
 | 
					            role: Role.DRIVER,
 | 
				
			||||||
 | 
					            frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					            distance: 356041,
 | 
				
			||||||
 | 
					            duration: 12647,
 | 
				
			||||||
 | 
					            initialDistance: 348745,
 | 
				
			||||||
 | 
					            initialDuration: 12105,
 | 
				
			||||||
 | 
					            distanceDetour: 7296,
 | 
				
			||||||
 | 
					            durationDetour: 542,
 | 
				
			||||||
 | 
					            distanceDetourPercentage: 4.1,
 | 
				
			||||||
 | 
					            durationDetourPercentage: 3.8,
 | 
				
			||||||
 | 
					            journeys: [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                firstDate: new Date('2023-09-01'),
 | 
				
			||||||
 | 
					                lastDate: new Date('2023-09-01'),
 | 
				
			||||||
 | 
					                journeyItems: [
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                    lon: 6.35484,
 | 
				
			||||||
 | 
					                    lat: 48.26587,
 | 
				
			||||||
 | 
					                    duration: 0,
 | 
				
			||||||
 | 
					                    distance: 0,
 | 
				
			||||||
 | 
					                    actorTimes: [
 | 
				
			||||||
 | 
					                      {
 | 
				
			||||||
 | 
					                        role: Role.DRIVER,
 | 
				
			||||||
 | 
					                        target: Target.START,
 | 
				
			||||||
 | 
					                        firstDatetime: new Date('2023-09-01T07:00:00Z'),
 | 
				
			||||||
 | 
					                        firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
 | 
				
			||||||
 | 
					                        firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
 | 
				
			||||||
 | 
					                        lastDatetime: new Date('2023-09-01T07:00:00Z'),
 | 
				
			||||||
 | 
					                        lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
 | 
				
			||||||
 | 
					                        lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            // ...
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        query: {
 | 
				
			||||||
 | 
					          driver: false,
 | 
				
			||||||
 | 
					          passenger: true,
 | 
				
			||||||
 | 
					          frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					          fromDate: '2023-09-01',
 | 
				
			||||||
 | 
					          toDate: '2023-09-01',
 | 
				
			||||||
 | 
					          schedule: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              day: 5,
 | 
				
			||||||
 | 
					              time: '06:40',
 | 
				
			||||||
 | 
					              margin: 900,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          seatsProposed: 3,
 | 
				
			||||||
 | 
					          seatsRequested: 1,
 | 
				
			||||||
 | 
					          strict: true,
 | 
				
			||||||
 | 
					          waypoints: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              lon: 6.389745,
 | 
				
			||||||
 | 
					              lat: 48.32644,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              lon: 6.984567,
 | 
				
			||||||
 | 
					              lat: 48.021548,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          algorithmType: 'PASSENGER_ORIENTED',
 | 
				
			||||||
 | 
					          remoteness: 15000,
 | 
				
			||||||
 | 
					          useProportion: true,
 | 
				
			||||||
 | 
					          proportion: 0.3,
 | 
				
			||||||
 | 
					          useAzimuth: true,
 | 
				
			||||||
 | 
					          azimuthMargin: 10,
 | 
				
			||||||
 | 
					          maxDetourDistanceRatio: 0.3,
 | 
				
			||||||
 | 
					          maxDetourDurationRatio: 0.3,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const mapped: string = matchingMapper.toPersistence(matchingEntity);
 | 
				
			||||||
 | 
					    expect(mapped).toBe(
 | 
				
			||||||
 | 
					      '{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map persisted string to domain entity', async () => {
 | 
				
			||||||
 | 
					    const matchingEntity: MatchingEntity = matchingMapper.toDomain(
 | 
				
			||||||
 | 
					      '{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(matchingEntity.getProps().query.fromDate).toBe('2023-09-01');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue