basic ad entity without direction
This commit is contained in:
		
							parent
							
								
									ce48890a66
								
							
						
					
					
						commit
						db13f4d87e
					
				| 
						 | 
					@ -23,8 +23,6 @@ CACHE_TTL=5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# default identifier used for match requests
 | 
					# default identifier used for match requests
 | 
				
			||||||
DEFAULT_UUID=00000000-0000-0000-0000-000000000000
 | 
					DEFAULT_UUID=00000000-0000-0000-0000-000000000000
 | 
				
			||||||
# default timezone
 | 
					 | 
				
			||||||
DEFAULT_TIMEZONE=Europe/Paris
 | 
					 | 
				
			||||||
# default number of seats proposed as driver
 | 
					# default number of seats proposed as driver
 | 
				
			||||||
DEFAULT_SEATS=3
 | 
					DEFAULT_SEATS=3
 | 
				
			||||||
# algorithm type
 | 
					# algorithm type
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,6 @@ datasource db {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
model Ad {
 | 
					model Ad {
 | 
				
			||||||
  uuid              String                                @id @db.Uuid
 | 
					  uuid              String                                @id @db.Uuid
 | 
				
			||||||
  userUuid          String                                @db.Uuid
 | 
					 | 
				
			||||||
  driver            Boolean
 | 
					  driver            Boolean
 | 
				
			||||||
  passenger         Boolean
 | 
					  passenger         Boolean
 | 
				
			||||||
  frequency         Frequency
 | 
					  frequency         Frequency
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
 | 
					import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
 | 
				
			||||||
import { HealthModuleOptions } from '@mobicoop/health-module/dist/core/domain/types/health.types';
 | 
					import { HealthModuleOptions } from '@mobicoop/health-module/dist/core/domain/types/health.types';
 | 
				
			||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { GeographyModule } from '@modules/geography/geography.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,7 @@ import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    AdModule,
 | 
					    AdModule,
 | 
				
			||||||
 | 
					    GeographyModule,
 | 
				
			||||||
    MessagerModule,
 | 
					    MessagerModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  exports: [AdModule, MessagerModule],
 | 
					  exports: [AdModule, MessagerModule],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,3 @@
 | 
				
			||||||
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
 | 
					 | 
				
			||||||
export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
 | 
					 | 
				
			||||||
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
 | 
					 | 
				
			||||||
export const GEOROUTER_CREATOR = Symbol('GEOROUTER_CREATOR');
 | 
					 | 
				
			||||||
export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
 | 
					export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
 | 
				
			||||||
 | 
					export const GEOROUTER = Symbol('GEOROUTER');
 | 
				
			||||||
 | 
					export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,6 @@ export class AdMapper
 | 
				
			||||||
    const now = new Date();
 | 
					    const now = new Date();
 | 
				
			||||||
    const record: AdWriteModel = {
 | 
					    const record: AdWriteModel = {
 | 
				
			||||||
      uuid: copy.id,
 | 
					      uuid: copy.id,
 | 
				
			||||||
      userUuid: copy.userId,
 | 
					 | 
				
			||||||
      driver: copy.driver,
 | 
					      driver: copy.driver,
 | 
				
			||||||
      passenger: copy.passenger,
 | 
					      passenger: copy.passenger,
 | 
				
			||||||
      frequency: copy.frequency,
 | 
					      frequency: copy.frequency,
 | 
				
			||||||
| 
						 | 
					@ -55,8 +54,8 @@ export class AdMapper
 | 
				
			||||||
      driverDistance: copy.driverDistance,
 | 
					      driverDistance: copy.driverDistance,
 | 
				
			||||||
      passengerDuration: copy.passengerDuration,
 | 
					      passengerDuration: copy.passengerDuration,
 | 
				
			||||||
      passengerDistance: copy.passengerDistance,
 | 
					      passengerDistance: copy.passengerDistance,
 | 
				
			||||||
      waypoints: copy.waypoints,
 | 
					      waypoints: '',
 | 
				
			||||||
      direction: copy.direction,
 | 
					      direction: '',
 | 
				
			||||||
      fwdAzimuth: copy.fwdAzimuth,
 | 
					      fwdAzimuth: copy.fwdAzimuth,
 | 
				
			||||||
      backAzimuth: copy.backAzimuth,
 | 
					      backAzimuth: copy.backAzimuth,
 | 
				
			||||||
      createdAt: copy.createdAt,
 | 
					      createdAt: copy.createdAt,
 | 
				
			||||||
| 
						 | 
					@ -71,7 +70,6 @@ export class AdMapper
 | 
				
			||||||
      createdAt: new Date(record.createdAt),
 | 
					      createdAt: new Date(record.createdAt),
 | 
				
			||||||
      updatedAt: new Date(record.updatedAt),
 | 
					      updatedAt: new Date(record.updatedAt),
 | 
				
			||||||
      props: {
 | 
					      props: {
 | 
				
			||||||
        userId: record.userUuid,
 | 
					 | 
				
			||||||
        driver: record.driver,
 | 
					        driver: record.driver,
 | 
				
			||||||
        passenger: record.passenger,
 | 
					        passenger: record.passenger,
 | 
				
			||||||
        frequency: Frequency[record.frequency],
 | 
					        frequency: Frequency[record.frequency],
 | 
				
			||||||
| 
						 | 
					@ -95,8 +93,7 @@ export class AdMapper
 | 
				
			||||||
        driverDistance: record.driverDistance,
 | 
					        driverDistance: record.driverDistance,
 | 
				
			||||||
        passengerDuration: record.passengerDuration,
 | 
					        passengerDuration: record.passengerDuration,
 | 
				
			||||||
        passengerDistance: record.passengerDistance,
 | 
					        passengerDistance: record.passengerDistance,
 | 
				
			||||||
        waypoints: record.waypoints,
 | 
					        waypoints: [],
 | 
				
			||||||
        direction: record.direction,
 | 
					 | 
				
			||||||
        fwdAzimuth: record.fwdAzimuth,
 | 
					        fwdAzimuth: record.fwdAzimuth,
 | 
				
			||||||
        backAzimuth: record.backAzimuth,
 | 
					        backAzimuth: record.backAzimuth,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,13 @@
 | 
				
			||||||
import { Module, Provider } from '@nestjs/common';
 | 
					import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
					import { CqrsModule } from '@nestjs/cqrs';
 | 
				
			||||||
import {
 | 
					import { AD_MESSAGE_PUBLISHER, AD_REPOSITORY } from './ad.di-tokens';
 | 
				
			||||||
  AD_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
  AD_REPOSITORY,
 | 
					 | 
				
			||||||
  PARAMS_PROVIDER,
 | 
					 | 
				
			||||||
  TIMEZONE_FINDER,
 | 
					 | 
				
			||||||
} 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';
 | 
				
			||||||
import { PrismaService } from './infrastructure/prisma.service';
 | 
					import { PrismaService } from './infrastructure/prisma.service';
 | 
				
			||||||
import { DefaultParamsProvider } from './infrastructure/default-params-provider';
 | 
					 | 
				
			||||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
 | 
					 | 
				
			||||||
import { AdMapper } from './ad.mapper';
 | 
					import { AdMapper } from './ad.mapper';
 | 
				
			||||||
 | 
					import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messageHandlers = [AdCreatedMessageHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mappers: Provider[] = [AdMapper];
 | 
					const mappers: Provider[] = [AdMapper];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,32 +26,15 @@ const messagePublishers: Provider[] = [
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
const orms: Provider[] = [PrismaService];
 | 
					const orms: Provider[] = [PrismaService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const adapters: Provider[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    provide: PARAMS_PROVIDER,
 | 
					 | 
				
			||||||
    useClass: DefaultParamsProvider,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    provide: TIMEZONE_FINDER,
 | 
					 | 
				
			||||||
    useClass: TimezoneFinder,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [CqrsModule],
 | 
					  imports: [CqrsModule],
 | 
				
			||||||
  providers: [
 | 
					  providers: [
 | 
				
			||||||
 | 
					    ...messageHandlers,
 | 
				
			||||||
    ...mappers,
 | 
					    ...mappers,
 | 
				
			||||||
    ...repositories,
 | 
					    ...repositories,
 | 
				
			||||||
    ...messagePublishers,
 | 
					    ...messagePublishers,
 | 
				
			||||||
    ...orms,
 | 
					    ...orms,
 | 
				
			||||||
    ...adapters,
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  exports: [
 | 
					 | 
				
			||||||
    PrismaService,
 | 
					 | 
				
			||||||
    AdMapper,
 | 
					 | 
				
			||||||
    AD_REPOSITORY,
 | 
					 | 
				
			||||||
    PARAMS_PROVIDER,
 | 
					 | 
				
			||||||
    TIMEZONE_FINDER,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
					  exports: [PrismaService, AdMapper, AD_REPOSITORY],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AdModule {}
 | 
					export class AdModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { ScheduleItem } from '../../types/schedule-item.type';
 | 
				
			||||||
 | 
					import { Waypoint } from '../../types/waypoint.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CreateAdCommand extends Command {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					  readonly driver: boolean;
 | 
				
			||||||
 | 
					  readonly passenger: boolean;
 | 
				
			||||||
 | 
					  readonly frequency: Frequency;
 | 
				
			||||||
 | 
					  readonly fromDate: string;
 | 
				
			||||||
 | 
					  readonly toDate: string;
 | 
				
			||||||
 | 
					  readonly schedule: ScheduleItem[];
 | 
				
			||||||
 | 
					  readonly seatsProposed: number;
 | 
				
			||||||
 | 
					  readonly seatsRequested: number;
 | 
				
			||||||
 | 
					  readonly strict: boolean;
 | 
				
			||||||
 | 
					  readonly waypoints: Waypoint[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<CreateAdCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.id = props.id;
 | 
				
			||||||
 | 
					    this.driver = props.driver;
 | 
				
			||||||
 | 
					    this.passenger = props.passenger;
 | 
				
			||||||
 | 
					    this.frequency = props.frequency;
 | 
				
			||||||
 | 
					    this.fromDate = props.fromDate;
 | 
				
			||||||
 | 
					    this.toDate = props.toDate;
 | 
				
			||||||
 | 
					    this.schedule = props.schedule;
 | 
				
			||||||
 | 
					    this.seatsProposed = props.seatsProposed;
 | 
				
			||||||
 | 
					    this.seatsRequested = props.seatsRequested;
 | 
				
			||||||
 | 
					    this.strict = props.strict;
 | 
				
			||||||
 | 
					    this.waypoints = props.waypoints;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { CreateAdCommand } from './create-ad.command';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
 | 
					import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
				
			||||||
 | 
					import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(CreateAdCommand)
 | 
				
			||||||
 | 
					export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly repository: AdRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: CreateAdCommand): Promise<AggregateID> {
 | 
				
			||||||
 | 
					    const ad = AdEntity.create({
 | 
				
			||||||
 | 
					      id: command.id,
 | 
				
			||||||
 | 
					      driver: command.driver,
 | 
				
			||||||
 | 
					      passenger: command.passenger,
 | 
				
			||||||
 | 
					      frequency: command.frequency,
 | 
				
			||||||
 | 
					      fromDate: command.fromDate,
 | 
				
			||||||
 | 
					      toDate: command.toDate,
 | 
				
			||||||
 | 
					      schedule: command.schedule,
 | 
				
			||||||
 | 
					      seatsProposed: command.seatsProposed,
 | 
				
			||||||
 | 
					      seatsRequested: command.seatsRequested,
 | 
				
			||||||
 | 
					      strict: command.strict,
 | 
				
			||||||
 | 
					      waypoints: command.waypoints,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.repository.insert(ad);
 | 
				
			||||||
 | 
					      return ad.id;
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error instanceof ConflictException) {
 | 
				
			||||||
 | 
					        throw new AdAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
export interface TimezoneFinderPort {
 | 
					 | 
				
			||||||
  timezones(lon: number, lat: number, defaultTimezone?: string): string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					export type ScheduleItem = {
 | 
				
			||||||
 | 
					  day: number;
 | 
				
			||||||
 | 
					  time: string;
 | 
				
			||||||
 | 
					  margin: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					import { PointContext } from '../../domain/ad.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Waypoint = {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static create = (create: CreateAdProps): AdEntity => {
 | 
					  static create = (create: CreateAdProps): AdEntity => {
 | 
				
			||||||
    const props: AdProps = { ...create };
 | 
					    const props: AdProps = { ...create };
 | 
				
			||||||
    const ad = new AdEntity({ id: create.id, props });
 | 
					    return new AdEntity({ id: create.id, props });
 | 
				
			||||||
    return ad;
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validate(): void {
 | 
					  validate(): void {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { ExceptionBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AdAlreadyExistsException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Ad already exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'AD.ALREADY_EXISTS';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(AdAlreadyExistsException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// All properties that an Ad has
 | 
					// All properties that an Ad has
 | 
				
			||||||
export interface AdProps {
 | 
					export interface AdProps {
 | 
				
			||||||
  userId: string;
 | 
					 | 
				
			||||||
  driver: boolean;
 | 
					  driver: boolean;
 | 
				
			||||||
  passenger: boolean;
 | 
					  passenger: boolean;
 | 
				
			||||||
  frequency: Frequency;
 | 
					  frequency: Frequency;
 | 
				
			||||||
| 
						 | 
					@ -12,20 +12,18 @@ export interface AdProps {
 | 
				
			||||||
  seatsProposed: number;
 | 
					  seatsProposed: number;
 | 
				
			||||||
  seatsRequested: number;
 | 
					  seatsRequested: number;
 | 
				
			||||||
  strict: boolean;
 | 
					  strict: boolean;
 | 
				
			||||||
  driverDuration: number;
 | 
					  driverDuration?: number;
 | 
				
			||||||
  driverDistance: number;
 | 
					  driverDistance?: number;
 | 
				
			||||||
  passengerDuration: number;
 | 
					  passengerDuration?: number;
 | 
				
			||||||
  passengerDistance: number;
 | 
					  passengerDistance?: number;
 | 
				
			||||||
  waypoints: string;
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
  direction: string;
 | 
					  fwdAzimuth?: number;
 | 
				
			||||||
  fwdAzimuth: number;
 | 
					  backAzimuth?: number;
 | 
				
			||||||
  backAzimuth: number;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Properties that are needed for an Ad creation
 | 
					// Properties that are needed for an Ad creation
 | 
				
			||||||
export interface CreateAdProps {
 | 
					export interface CreateAdProps {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  userId: string;
 | 
					 | 
				
			||||||
  driver: boolean;
 | 
					  driver: boolean;
 | 
				
			||||||
  passenger: boolean;
 | 
					  passenger: boolean;
 | 
				
			||||||
  frequency: Frequency;
 | 
					  frequency: Frequency;
 | 
				
			||||||
| 
						 | 
					@ -35,17 +33,18 @@ export interface CreateAdProps {
 | 
				
			||||||
  seatsProposed: number;
 | 
					  seatsProposed: number;
 | 
				
			||||||
  seatsRequested: number;
 | 
					  seatsRequested: number;
 | 
				
			||||||
  strict: boolean;
 | 
					  strict: boolean;
 | 
				
			||||||
  driverDuration: number;
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
  driverDistance: number;
 | 
					 | 
				
			||||||
  passengerDuration: number;
 | 
					 | 
				
			||||||
  passengerDistance: number;
 | 
					 | 
				
			||||||
  waypoints: string;
 | 
					 | 
				
			||||||
  direction: string;
 | 
					 | 
				
			||||||
  fwdAzimuth: number;
 | 
					 | 
				
			||||||
  backAzimuth: number;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum Frequency {
 | 
					export enum Frequency {
 | 
				
			||||||
  PUNCTUAL = 'PUNCTUAL',
 | 
					  PUNCTUAL = 'PUNCTUAL',
 | 
				
			||||||
  RECURRENT = 'RECURRENT',
 | 
					  RECURRENT = 'RECURRENT',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum PointContext {
 | 
				
			||||||
 | 
					  HOUSE_NUMBER = 'HOUSE_NUMBER',
 | 
				
			||||||
 | 
					  STREET_ADDRESS = 'STREET_ADDRESS',
 | 
				
			||||||
 | 
					  LOCALITY = 'LOCALITY',
 | 
				
			||||||
 | 
					  VENUE = 'VENUE',
 | 
				
			||||||
 | 
					  OTHER = 'OTHER',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,8 @@
 | 
				
			||||||
import { ValueObject } from '@mobicoop/ddd-library';
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					  ValueObject,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Note:
 | 
					/** Note:
 | 
				
			||||||
 * Value Objects with multiple properties can contain
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
| 
						 | 
					@ -6,9 +10,9 @@ import { ValueObject } from '@mobicoop/ddd-library';
 | 
				
			||||||
 * */
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ScheduleItemProps {
 | 
					export interface ScheduleItemProps {
 | 
				
			||||||
  day?: number;
 | 
					  day: number;
 | 
				
			||||||
  time: string;
 | 
					  time: string;
 | 
				
			||||||
  margin?: number;
 | 
					  margin: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ScheduleItem extends ValueObject<ScheduleItemProps> {
 | 
					export class ScheduleItem extends ValueObject<ScheduleItemProps> {
 | 
				
			||||||
| 
						 | 
					@ -26,6 +30,19 @@ export class ScheduleItem extends ValueObject<ScheduleItemProps> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
  protected validate(props: ScheduleItemProps): void {
 | 
					  protected validate(props: ScheduleItemProps): void {
 | 
				
			||||||
    return;
 | 
					    if (props.day < 0 || props.day > 6)
 | 
				
			||||||
 | 
					      throw new ArgumentOutOfRangeException('day must be between 0 and 6');
 | 
				
			||||||
 | 
					    if (props.time.split(':').length != 2)
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException('time is invalid');
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      parseInt(props.time.split(':')[0]) < 0 ||
 | 
				
			||||||
 | 
					      parseInt(props.time.split(':')[0]) > 23
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException('time is invalid');
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      parseInt(props.time.split(':')[1]) < 0 ||
 | 
				
			||||||
 | 
					      parseInt(props.time.split(':')[1]) > 59
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException('time is invalid');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					  ValueObject,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { PointContext } from '../ad.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WaypointProps {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Waypoint extends ValueObject<WaypointProps> {
 | 
				
			||||||
 | 
					  get position(): number {
 | 
				
			||||||
 | 
					    return this.props.position;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lon(): number {
 | 
				
			||||||
 | 
					    return this.props.lon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lat(): number {
 | 
				
			||||||
 | 
					    return this.props.lat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get context(): PointContext {
 | 
				
			||||||
 | 
					    return this.props.context;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected validate(props: WaypointProps): void {
 | 
				
			||||||
 | 
					    if (props.position < 0)
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException(
 | 
				
			||||||
 | 
					        'position must be greater than or equal to 0',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    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');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import { AdMapper } from '../ad.mapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AdBaseModel = {
 | 
					export type AdBaseModel = {
 | 
				
			||||||
  uuid: string;
 | 
					  uuid: string;
 | 
				
			||||||
  userUuid: string;
 | 
					 | 
				
			||||||
  driver: boolean;
 | 
					  driver: boolean;
 | 
				
			||||||
  passenger: boolean;
 | 
					  passenger: boolean;
 | 
				
			||||||
  frequency: string;
 | 
					  frequency: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { find } from 'geo-tz';
 | 
					 | 
				
			||||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class TimezoneFinder implements TimezoneFinderPort {
 | 
					 | 
				
			||||||
  timezones = (
 | 
					 | 
				
			||||||
    lon: number,
 | 
					 | 
				
			||||||
    lat: number,
 | 
					 | 
				
			||||||
    defaultTimezone?: string,
 | 
					 | 
				
			||||||
  ): string[] => {
 | 
					 | 
				
			||||||
    const foundTimezones = find(lat, lon);
 | 
					 | 
				
			||||||
    if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
 | 
					 | 
				
			||||||
    return foundTimezones;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { RabbitSubscribe } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
 | 
				
			||||||
 | 
					import { Ad } from './ad.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AdCreatedMessageHandler {
 | 
				
			||||||
 | 
					  constructor(private readonly commandBus: CommandBus) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @RabbitSubscribe({
 | 
				
			||||||
 | 
					    name: 'adCreated',
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  public async adCreated(message: string) {
 | 
				
			||||||
 | 
					    const createdAd: Ad = JSON.parse(message);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.commandBus.execute(
 | 
				
			||||||
 | 
					        new CreateAdCommand({
 | 
				
			||||||
 | 
					          id: createdAd.id,
 | 
				
			||||||
 | 
					          driver: createdAd.driver,
 | 
				
			||||||
 | 
					          passenger: createdAd.passenger,
 | 
				
			||||||
 | 
					          frequency: createdAd.frequency,
 | 
				
			||||||
 | 
					          fromDate: createdAd.fromDate,
 | 
				
			||||||
 | 
					          toDate: createdAd.toDate,
 | 
				
			||||||
 | 
					          schedule: createdAd.schedule,
 | 
				
			||||||
 | 
					          seatsProposed: createdAd.seatsProposed,
 | 
				
			||||||
 | 
					          seatsRequested: createdAd.seatsRequested,
 | 
				
			||||||
 | 
					          strict: createdAd.strict,
 | 
				
			||||||
 | 
					          waypoints: createdAd.waypoints,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } catch (e: any) {}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					import { Frequency, PointContext } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Ad = {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  frequency: Frequency;
 | 
				
			||||||
 | 
					  fromDate: string;
 | 
				
			||||||
 | 
					  toDate: string;
 | 
				
			||||||
 | 
					  schedule: ScheduleItem[];
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					  strict: boolean;
 | 
				
			||||||
 | 
					  waypoints: Waypoint[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ScheduleItem = {
 | 
				
			||||||
 | 
					  day: number;
 | 
				
			||||||
 | 
					  time: string;
 | 
				
			||||||
 | 
					  margin: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Waypoint = {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  houseNumber?: string;
 | 
				
			||||||
 | 
					  street?: string;
 | 
				
			||||||
 | 
					  locality?: string;
 | 
				
			||||||
 | 
					  postalCode?: string;
 | 
				
			||||||
 | 
					  country: string;
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,107 @@
 | 
				
			||||||
 | 
					import { AdMapper } from '@modules/ad/ad.mapper';
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
 | 
					import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AdReadModel,
 | 
				
			||||||
 | 
					  AdWriteModel,
 | 
				
			||||||
 | 
					} from '@modules/ad/infrastructure/ad.repository';
 | 
				
			||||||
 | 
					import { Test } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = new Date('2023-06-21 06:00:00');
 | 
				
			||||||
 | 
					const adEntity: AdEntity = new AdEntity({
 | 
				
			||||||
 | 
					  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    driver: true,
 | 
				
			||||||
 | 
					    passenger: true,
 | 
				
			||||||
 | 
					    frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					    fromDate: '2023-06-21',
 | 
				
			||||||
 | 
					    toDate: '2023-06-21',
 | 
				
			||||||
 | 
					    schedule: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        day: 3,
 | 
				
			||||||
 | 
					        time: '07:15',
 | 
				
			||||||
 | 
					        margin: 900,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    waypoints: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        position: 0,
 | 
				
			||||||
 | 
					        lat: 48.689445,
 | 
				
			||||||
 | 
					        lon: 6.1765102,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        position: 1,
 | 
				
			||||||
 | 
					        lat: 48.8566,
 | 
				
			||||||
 | 
					        lon: 2.3522,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    strict: false,
 | 
				
			||||||
 | 
					    seatsProposed: 3,
 | 
				
			||||||
 | 
					    seatsRequested: 1,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const adReadModel: AdReadModel = {
 | 
				
			||||||
 | 
					  uuid: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
 | 
					  driver: true,
 | 
				
			||||||
 | 
					  passenger: true,
 | 
				
			||||||
 | 
					  frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					  fromDate: new Date('2023-06-21'),
 | 
				
			||||||
 | 
					  toDate: new Date('2023-06-21'),
 | 
				
			||||||
 | 
					  schedule: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      uuid: '3978f3d6-560f-4a8f-83ba-9bf5aa9a2d27',
 | 
				
			||||||
 | 
					      day: 3,
 | 
				
			||||||
 | 
					      time: new Date('2023-06-21T07:05:00Z'),
 | 
				
			||||||
 | 
					      margin: 900,
 | 
				
			||||||
 | 
					      createdAt: now,
 | 
				
			||||||
 | 
					      updatedAt: now,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  waypoints: "'LINESTRING(6.1765102 48.689445,2.3522 48.8566)'",
 | 
				
			||||||
 | 
					  direction:
 | 
				
			||||||
 | 
					    "'LINESTRING(6.1765102 48.689445,5.12345 48.76543,2.3522 48.8566)'",
 | 
				
			||||||
 | 
					  driverDistance: 350000,
 | 
				
			||||||
 | 
					  driverDuration: 14400,
 | 
				
			||||||
 | 
					  passengerDistance: 350000,
 | 
				
			||||||
 | 
					  passengerDuration: 14400,
 | 
				
			||||||
 | 
					  fwdAzimuth: 273,
 | 
				
			||||||
 | 
					  backAzimuth: 93,
 | 
				
			||||||
 | 
					  strict: false,
 | 
				
			||||||
 | 
					  seatsProposed: 3,
 | 
				
			||||||
 | 
					  seatsRequested: 1,
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Ad Mapper', () => {
 | 
				
			||||||
 | 
					  let adMapper: AdMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [AdMapper],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					    adMapper = module.get<AdMapper>(AdMapper);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(adMapper).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map domain entity to persistence data', async () => {
 | 
				
			||||||
 | 
					    const mapped: AdWriteModel = adMapper.toPersistence(adEntity);
 | 
				
			||||||
 | 
					    expect(mapped.schedule.create.length).toBe(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map persisted data to domain entity', async () => {
 | 
				
			||||||
 | 
					    const mapped: AdEntity = adMapper.toDomain(adReadModel);
 | 
				
			||||||
 | 
					    expect(mapped.getProps().schedule.length).toBe(1);
 | 
				
			||||||
 | 
					    expect(mapped.getProps().schedule[0].time).toBe('07:05');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map domain entity to response', async () => {
 | 
				
			||||||
 | 
					    expect(adMapper.toResponse(adEntity)).toBeUndefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
 | 
					import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const originWaypointProps: WaypointProps = {
 | 
				
			||||||
 | 
					  position: 0,
 | 
				
			||||||
 | 
					  lon: 48.689445,
 | 
				
			||||||
 | 
					  lat: 6.17651,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const destinationWaypointProps: WaypointProps = {
 | 
				
			||||||
 | 
					  position: 1,
 | 
				
			||||||
 | 
					  lon: 48.8566,
 | 
				
			||||||
 | 
					  lat: 2.3522,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createAdProps: CreateAdProps = {
 | 
				
			||||||
 | 
					  id: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
 | 
				
			||||||
 | 
					  driver: true,
 | 
				
			||||||
 | 
					  passenger: true,
 | 
				
			||||||
 | 
					  fromDate: '2023-06-21',
 | 
				
			||||||
 | 
					  toDate: '2023-06-21',
 | 
				
			||||||
 | 
					  schedule: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      day: 3,
 | 
				
			||||||
 | 
					      time: '08:30',
 | 
				
			||||||
 | 
					      margin: 900,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					  seatsProposed: 3,
 | 
				
			||||||
 | 
					  seatsRequested: 1,
 | 
				
			||||||
 | 
					  strict: false,
 | 
				
			||||||
 | 
					  waypoints: [originWaypointProps, destinationWaypointProps],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Ad entity create', () => {
 | 
				
			||||||
 | 
					  it('should create a new entity', async () => {
 | 
				
			||||||
 | 
					    const ad: AdEntity = AdEntity.create(createAdProps);
 | 
				
			||||||
 | 
					    expect(ad.id.length).toBe(36);
 | 
				
			||||||
 | 
					    expect(ad.getProps().schedule.length).toBe(1);
 | 
				
			||||||
 | 
					    expect(ad.getProps().schedule[0].day).toBe(3);
 | 
				
			||||||
 | 
					    expect(ad.getProps().schedule[0].time).toBe('08:30');
 | 
				
			||||||
 | 
					    expect(ad.getProps().driver).toBeTruthy();
 | 
				
			||||||
 | 
					    expect(ad.getProps().passenger).toBeTruthy();
 | 
				
			||||||
 | 
					    expect(ad.getProps().driverDistance).toBeUndefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
 | 
					import { ConflictException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
 | 
				
			||||||
 | 
					import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
 | 
				
			||||||
 | 
					import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
				
			||||||
 | 
					import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const originWaypoint: WaypointProps = {
 | 
				
			||||||
 | 
					  position: 0,
 | 
				
			||||||
 | 
					  lon: 48.689445,
 | 
				
			||||||
 | 
					  lat: 6.17651,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const destinationWaypoint: WaypointProps = {
 | 
				
			||||||
 | 
					  position: 1,
 | 
				
			||||||
 | 
					  lon: 48.8566,
 | 
				
			||||||
 | 
					  lat: 2.3522,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const createAdProps: CreateAdProps = {
 | 
				
			||||||
 | 
					  id: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
 | 
				
			||||||
 | 
					  fromDate: '2023-12-21',
 | 
				
			||||||
 | 
					  toDate: '2023-12-21',
 | 
				
			||||||
 | 
					  schedule: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      day: 4,
 | 
				
			||||||
 | 
					      time: '08:15',
 | 
				
			||||||
 | 
					      margin: 900,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  driver: true,
 | 
				
			||||||
 | 
					  passenger: true,
 | 
				
			||||||
 | 
					  seatsProposed: 3,
 | 
				
			||||||
 | 
					  seatsRequested: 1,
 | 
				
			||||||
 | 
					  strict: false,
 | 
				
			||||||
 | 
					  frequency: Frequency.PUNCTUAL,
 | 
				
			||||||
 | 
					  waypoints: [originWaypoint, destinationWaypoint],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockAdRepository = {
 | 
				
			||||||
 | 
					  insert: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => ({}))
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new ConflictException('already exists');
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('create-ad.service', () => {
 | 
				
			||||||
 | 
					  let createAdService: CreateAdService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: AD_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockAdRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        CreateAdService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createAdService = module.get<CreateAdService>(CreateAdService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(createAdService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('execution', () => {
 | 
				
			||||||
 | 
					    const createAdCommand = new CreateAdCommand(createAdProps);
 | 
				
			||||||
 | 
					    it('should create a new ad', async () => {
 | 
				
			||||||
 | 
					      AdEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const result: AggregateID = await createAdService.execute(
 | 
				
			||||||
 | 
					        createAdCommand,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an error if something bad happens', async () => {
 | 
				
			||||||
 | 
					      AdEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if Ad already exists', async () => {
 | 
				
			||||||
 | 
					      AdEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createAdService.execute(createAdCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(AdAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { ScheduleItem } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Schedule item value object', () => {
 | 
				
			||||||
 | 
					  it('should create a schedule item value object', () => {
 | 
				
			||||||
 | 
					    const scheduleItemVO = new ScheduleItem({
 | 
				
			||||||
 | 
					      day: 0,
 | 
				
			||||||
 | 
					      time: '07:00',
 | 
				
			||||||
 | 
					      margin: 900,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(scheduleItemVO.day).toBe(0);
 | 
				
			||||||
 | 
					    expect(scheduleItemVO.time).toBe('07:00');
 | 
				
			||||||
 | 
					    expect(scheduleItemVO.margin).toBe(900);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if day is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new ScheduleItem({
 | 
				
			||||||
 | 
					        day: 7,
 | 
				
			||||||
 | 
					        time: '07:00',
 | 
				
			||||||
 | 
					        margin: 900,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if time is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new ScheduleItem({
 | 
				
			||||||
 | 
					        day: 0,
 | 
				
			||||||
 | 
					        time: '07,00',
 | 
				
			||||||
 | 
					        margin: 900,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentInvalidException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if the hour of the time is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new ScheduleItem({
 | 
				
			||||||
 | 
					        day: 0,
 | 
				
			||||||
 | 
					        time: '25:00',
 | 
				
			||||||
 | 
					        margin: 900,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentInvalidException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if the minutes of the time are invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new ScheduleItem({
 | 
				
			||||||
 | 
					        day: 0,
 | 
				
			||||||
 | 
					        time: '07:63',
 | 
				
			||||||
 | 
					        margin: 900,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentInvalidException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { PointContext } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { Waypoint } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Waypoint value object', () => {
 | 
				
			||||||
 | 
					  it('should create a waypoint value object without context', () => {
 | 
				
			||||||
 | 
					    const waypointVO = new Waypoint({
 | 
				
			||||||
 | 
					      position: 0,
 | 
				
			||||||
 | 
					      lon: 48.689445,
 | 
				
			||||||
 | 
					      lat: 6.17651,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(waypointVO.position).toBe(0);
 | 
				
			||||||
 | 
					    expect(waypointVO.lon).toBe(48.689445);
 | 
				
			||||||
 | 
					    expect(waypointVO.lat).toBe(6.17651);
 | 
				
			||||||
 | 
					    expect(waypointVO.context).toBeUndefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should create a waypoint value object with context', () => {
 | 
				
			||||||
 | 
					    const waypointVO = new Waypoint({
 | 
				
			||||||
 | 
					      position: 0,
 | 
				
			||||||
 | 
					      lon: 48.689445,
 | 
				
			||||||
 | 
					      lat: 6.17651,
 | 
				
			||||||
 | 
					      context: PointContext.HOUSE_NUMBER,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(waypointVO.position).toBe(0);
 | 
				
			||||||
 | 
					    expect(waypointVO.lon).toBe(48.689445);
 | 
				
			||||||
 | 
					    expect(waypointVO.lat).toBe(6.17651);
 | 
				
			||||||
 | 
					    expect(waypointVO.context).toBe(PointContext.HOUSE_NUMBER);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if position is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new Waypoint({
 | 
				
			||||||
 | 
					        position: -1,
 | 
				
			||||||
 | 
					        lon: 48.689445,
 | 
				
			||||||
 | 
					        lat: 6.17651,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentInvalidException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if longitude is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new Waypoint({
 | 
				
			||||||
 | 
					        position: 0,
 | 
				
			||||||
 | 
					        lon: 348.689445,
 | 
				
			||||||
 | 
					        lat: 6.17651,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new Waypoint({
 | 
				
			||||||
 | 
					        position: 0,
 | 
				
			||||||
 | 
					        lon: -348.689445,
 | 
				
			||||||
 | 
					        lat: 6.17651,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if longitude is invalid', () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new Waypoint({
 | 
				
			||||||
 | 
					        position: 0,
 | 
				
			||||||
 | 
					        lon: 48.689445,
 | 
				
			||||||
 | 
					        lat: 96.17651,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      new Waypoint({
 | 
				
			||||||
 | 
					        position: 0,
 | 
				
			||||||
 | 
					        lon: 48.689445,
 | 
				
			||||||
 | 
					        lat: -96.17651,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { AdMapper } from '@modules/ad/ad.mapper';
 | 
				
			||||||
 | 
					import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
 | 
				
			||||||
 | 
					import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
 | 
				
			||||||
 | 
					import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Ad repository', () => {
 | 
				
			||||||
 | 
					  let prismaService: PrismaService;
 | 
				
			||||||
 | 
					  let adMapper: AdMapper;
 | 
				
			||||||
 | 
					  let eventEmitter: EventEmitter2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      imports: [EventEmitterModule.forRoot()],
 | 
				
			||||||
 | 
					      providers: [PrismaService, AdMapper],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prismaService = module.get<PrismaService>(PrismaService);
 | 
				
			||||||
 | 
					    adMapper = module.get<AdMapper>(AdMapper);
 | 
				
			||||||
 | 
					    eventEmitter = module.get<EventEmitter2>(EventEmitter2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      new AdRepository(
 | 
				
			||||||
 | 
					        prismaService,
 | 
				
			||||||
 | 
					        adMapper,
 | 
				
			||||||
 | 
					        eventEmitter,
 | 
				
			||||||
 | 
					        mockMessagePublisher,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					import { AdCreatedMessageHandler } from '@modules/ad/interface/message-handlers/ad-created.message-handler';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const adCreatedMessage =
 | 
				
			||||||
 | 
					  '{"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4","driver":"true","passenger":"true","frequency":"PUNCTUAL","fromDate":"2023-08-18","toDate":"2023-08-18","schedule":[{"day":"5","time":"10:00","margin":"900"}],"seatsProposed":"3","seatsRequested":"1","strict":"false","waypoints":[{"position":"0","houseNumber":"5","street":"rue de la monnaie","locality":"Nancy","postalCode":"54000","country":"France","lon":"48.689445","lat":"6.17651"},{"position":"1","locality":"Paris","postalCode":"75000","country":"France","lon":"48.8566","lat":"2.3522"}]}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockCommandBus = {
 | 
				
			||||||
 | 
					  execute: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Ad Created Message Handler', () => {
 | 
				
			||||||
 | 
					  let adCreatedMessageHandler: AdCreatedMessageHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: CommandBus,
 | 
				
			||||||
 | 
					          useValue: mockCommandBus,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        AdCreatedMessageHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    adCreatedMessageHandler = module.get<AdCreatedMessageHandler>(
 | 
				
			||||||
 | 
					      AdCreatedMessageHandler,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(adCreatedMessageHandler).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should create an ad', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    await adCreatedMessageHandler.adCreated(adCreatedMessage);
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { DefaultParams } from './default-params.type';
 | 
					import { DefaultParams } from '../types/default-params.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DefaultParamsProviderPort {
 | 
					export interface DefaultParamsProviderPort {
 | 
				
			||||||
  getParams(): DefaultParams;
 | 
					  getParams(): DefaultParams;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { Coordinates } from '../types/coordinates.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DirectionEncoderPort {
 | 
				
			||||||
 | 
					  encode(coordinates: Coordinates[]): string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					export interface GeodesicPort {
 | 
				
			||||||
 | 
					  inverse(
 | 
				
			||||||
 | 
					    lon1: number,
 | 
				
			||||||
 | 
					    lat1: number,
 | 
				
			||||||
 | 
					    lon2: number,
 | 
				
			||||||
 | 
					    lat2: number,
 | 
				
			||||||
 | 
					  ): {
 | 
				
			||||||
 | 
					    azimuth: number;
 | 
				
			||||||
 | 
					    distance: number;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { GeorouterPort } from './georouter.port';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GeorouterCreatorPort {
 | 
				
			||||||
 | 
					  create(type: string, url: string): GeorouterPort;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { GeorouterSettings } from '../types/georouter-settings.type';
 | 
				
			||||||
 | 
					import { Path } from '../types/path.type';
 | 
				
			||||||
 | 
					import { Route } from '../types/route.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GeorouterPort {
 | 
				
			||||||
 | 
					  routes(paths: Path[], settings: GeorouterSettings): Promise<Route[]>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					export type Coordinates = {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,4 @@
 | 
				
			||||||
export type DefaultParams = {
 | 
					export type DefaultParams = {
 | 
				
			||||||
  DEFAULT_TIMEZONE: string;
 | 
					 | 
				
			||||||
  GEOROUTER_TYPE: string;
 | 
					  GEOROUTER_TYPE: string;
 | 
				
			||||||
  GEOROUTER_URL: string;
 | 
					  GEOROUTER_URL: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					export type GeorouterSettings = {
 | 
				
			||||||
 | 
					  withPoints: boolean;
 | 
				
			||||||
 | 
					  withTime: boolean;
 | 
				
			||||||
 | 
					  withDistance: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { PathType } from '../../domain/route.types';
 | 
				
			||||||
 | 
					import { Point } from './point.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Path = {
 | 
				
			||||||
 | 
					  type: PathType;
 | 
				
			||||||
 | 
					  points: Point[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { PointContext } from '../../domain/route.types';
 | 
				
			||||||
 | 
					import { Coordinates } from './coordinates.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Point = Coordinates & {
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { Point } from './point.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Route = {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  distance: number;
 | 
				
			||||||
 | 
					  duration: number;
 | 
				
			||||||
 | 
					  fwdAzimuth: number;
 | 
				
			||||||
 | 
					  backAzimuth: number;
 | 
				
			||||||
 | 
					  distanceAzimuth: number;
 | 
				
			||||||
 | 
					  points: Point[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,117 @@
 | 
				
			||||||
 | 
					import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CreateRouteProps,
 | 
				
			||||||
 | 
					  Path,
 | 
				
			||||||
 | 
					  Role,
 | 
				
			||||||
 | 
					  RouteProps,
 | 
				
			||||||
 | 
					  PathType,
 | 
				
			||||||
 | 
					} from './route.types';
 | 
				
			||||||
 | 
					import { WaypointProps } from './value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class RouteEntity extends AggregateRoot<RouteProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = async (create: CreateRouteProps): Promise<RouteEntity> => {
 | 
				
			||||||
 | 
					    const props: RouteProps = await create.georouter.routes(
 | 
				
			||||||
 | 
					      this.getPaths(create.roles, create.waypoints),
 | 
				
			||||||
 | 
					      create.georouterSettings,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const route = new RouteEntity({ props });
 | 
				
			||||||
 | 
					    return route;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static getPaths = (
 | 
				
			||||||
 | 
					    roles: Role[],
 | 
				
			||||||
 | 
					    waypoints: WaypointProps[],
 | 
				
			||||||
 | 
					  ): Path[] => {
 | 
				
			||||||
 | 
					    const paths: Path[] = [];
 | 
				
			||||||
 | 
					    if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
 | 
				
			||||||
 | 
					      if (waypoints.length == 2) {
 | 
				
			||||||
 | 
					        // 2 points => same route for driver and passenger
 | 
				
			||||||
 | 
					        const commonPath: Path = {
 | 
				
			||||||
 | 
					          type: PathType.COMMON,
 | 
				
			||||||
 | 
					          points: waypoints,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        paths.push(commonPath);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const driverPath: Path = RouteEntity.createDriverPath(waypoints);
 | 
				
			||||||
 | 
					        const passengerPath: Path = RouteEntity.createPassengerPath(waypoints);
 | 
				
			||||||
 | 
					        paths.push(driverPath, passengerPath);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (roles.includes(Role.DRIVER)) {
 | 
				
			||||||
 | 
					      const driverPath: Path = RouteEntity.createDriverPath(waypoints);
 | 
				
			||||||
 | 
					      paths.push(driverPath);
 | 
				
			||||||
 | 
					    } else if (roles.includes(Role.PASSENGER)) {
 | 
				
			||||||
 | 
					      const passengerPath: Path = RouteEntity.createPassengerPath(waypoints);
 | 
				
			||||||
 | 
					      paths.push(passengerPath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return paths;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static createDriverPath = (waypoints: WaypointProps[]): Path => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      type: PathType.DRIVER,
 | 
				
			||||||
 | 
					      points: waypoints,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static createPassengerPath = (waypoints: WaypointProps[]): Path => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      type: PathType.PASSENGER,
 | 
				
			||||||
 | 
					      points: [waypoints[0], waypoints[waypoints.length - 1]],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// import { IGeodesic } from '../interfaces/geodesic.interface';
 | 
				
			||||||
 | 
					// import { Point } from '../types/point.type';
 | 
				
			||||||
 | 
					// import { SpacetimePoint } from './spacetime-point';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export class Route {
 | 
				
			||||||
 | 
					//   distance: number;
 | 
				
			||||||
 | 
					//   duration: number;
 | 
				
			||||||
 | 
					//   fwdAzimuth: number;
 | 
				
			||||||
 | 
					//   backAzimuth: number;
 | 
				
			||||||
 | 
					//   distanceAzimuth: number;
 | 
				
			||||||
 | 
					//   points: Point[];
 | 
				
			||||||
 | 
					//   spacetimePoints: SpacetimePoint[];
 | 
				
			||||||
 | 
					//   private geodesic: IGeodesic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   constructor(geodesic: IGeodesic) {
 | 
				
			||||||
 | 
					//     this.distance = undefined;
 | 
				
			||||||
 | 
					//     this.duration = undefined;
 | 
				
			||||||
 | 
					//     this.fwdAzimuth = undefined;
 | 
				
			||||||
 | 
					//     this.backAzimuth = undefined;
 | 
				
			||||||
 | 
					//     this.distanceAzimuth = undefined;
 | 
				
			||||||
 | 
					//     this.points = [];
 | 
				
			||||||
 | 
					//     this.spacetimePoints = [];
 | 
				
			||||||
 | 
					//     this.geodesic = geodesic;
 | 
				
			||||||
 | 
					//   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   setPoints = (points: Point[]): void => {
 | 
				
			||||||
 | 
					//     this.points = points;
 | 
				
			||||||
 | 
					//     this.setAzimuth(points);
 | 
				
			||||||
 | 
					//   };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   setSpacetimePoints = (spacetimePoints: SpacetimePoint[]): void => {
 | 
				
			||||||
 | 
					//     this.spacetimePoints = spacetimePoints;
 | 
				
			||||||
 | 
					//   };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   protected setAzimuth = (points: Point[]): void => {
 | 
				
			||||||
 | 
					//     const inverse = this.geodesic.inverse(
 | 
				
			||||||
 | 
					//       points[0].lon,
 | 
				
			||||||
 | 
					//       points[0].lat,
 | 
				
			||||||
 | 
					//       points[points.length - 1].lon,
 | 
				
			||||||
 | 
					//       points[points.length - 1].lat,
 | 
				
			||||||
 | 
					//     );
 | 
				
			||||||
 | 
					//     this.fwdAzimuth =
 | 
				
			||||||
 | 
					//       inverse.azimuth >= 0 ? inverse.azimuth : 360 - Math.abs(inverse.azimuth);
 | 
				
			||||||
 | 
					//     this.backAzimuth =
 | 
				
			||||||
 | 
					//       this.fwdAzimuth > 180 ? this.fwdAzimuth - 180 : this.fwdAzimuth + 180;
 | 
				
			||||||
 | 
					//     this.distanceAzimuth = inverse.distance;
 | 
				
			||||||
 | 
					//   };
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { GeorouterPort } from '../application/ports/georouter.port';
 | 
				
			||||||
 | 
					import { GeorouterSettings } from '../application/types/georouter-settings.type';
 | 
				
			||||||
 | 
					import { SpacetimePointProps } from './value-objects/timepoint.value-object';
 | 
				
			||||||
 | 
					import { WaypointProps } from './value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// All properties that a Route has
 | 
				
			||||||
 | 
					export interface RouteProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  distance: number;
 | 
				
			||||||
 | 
					  duration: number;
 | 
				
			||||||
 | 
					  fwdAzimuth: number;
 | 
				
			||||||
 | 
					  backAzimuth: number;
 | 
				
			||||||
 | 
					  distanceAzimuth: number;
 | 
				
			||||||
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
 | 
					  spacetimePoints: SpacetimePointProps[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Properties that are needed for a Route creation
 | 
				
			||||||
 | 
					export interface CreateRouteProps {
 | 
				
			||||||
 | 
					  roles: Role[];
 | 
				
			||||||
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
 | 
					  georouter: GeorouterPort;
 | 
				
			||||||
 | 
					  georouterSettings: GeorouterSettings;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Path = {
 | 
				
			||||||
 | 
					  type: PathType;
 | 
				
			||||||
 | 
					  points: Point[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Point = {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum Role {
 | 
				
			||||||
 | 
					  DRIVER = 'DRIVER',
 | 
				
			||||||
 | 
					  PASSENGER = 'PASSENGER',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum PointContext {
 | 
				
			||||||
 | 
					  HOUSE_NUMBER = 'HOUSE_NUMBER',
 | 
				
			||||||
 | 
					  STREET_ADDRESS = 'STREET_ADDRESS',
 | 
				
			||||||
 | 
					  LOCALITY = 'LOCALITY',
 | 
				
			||||||
 | 
					  VENUE = 'VENUE',
 | 
				
			||||||
 | 
					  OTHER = 'OTHER',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum PathType {
 | 
				
			||||||
 | 
					  COMMON = 'common',
 | 
				
			||||||
 | 
					  DRIVER = 'driver',
 | 
				
			||||||
 | 
					  PASSENGER = 'passenger',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SpacetimePointProps {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  duration: number;
 | 
				
			||||||
 | 
					  distance: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class SpacetimePoint extends ValueObject<SpacetimePointProps> {
 | 
				
			||||||
 | 
					  get lon(): number {
 | 
				
			||||||
 | 
					    return this.props.lon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lat(): number {
 | 
				
			||||||
 | 
					    return this.props.lat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get duration(): number {
 | 
				
			||||||
 | 
					    return this.props.duration;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get distance(): number {
 | 
				
			||||||
 | 
					    return this.props.distance;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected validate(props: SpacetimePointProps): void {
 | 
				
			||||||
 | 
					    if (props.duration < 0)
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException(
 | 
				
			||||||
 | 
					        'duration must be greater than or equal to 0',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    if (props.distance < 0)
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException(
 | 
				
			||||||
 | 
					        'distance must be greater than or equal to 0',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					  ValueObject,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { PointContext } from '../route.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WaypointProps {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					  context?: PointContext;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Waypoint extends ValueObject<WaypointProps> {
 | 
				
			||||||
 | 
					  get position(): number {
 | 
				
			||||||
 | 
					    return this.props.position;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lon(): number {
 | 
				
			||||||
 | 
					    return this.props.lon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lat(): number {
 | 
				
			||||||
 | 
					    return this.props.lat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get context(): PointContext {
 | 
				
			||||||
 | 
					    return this.props.context;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected validate(props: WaypointProps): void {
 | 
				
			||||||
 | 
					    if (props.position < 0)
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException(
 | 
				
			||||||
 | 
					        'position must be greater than or equal to 0',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    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,2 @@
 | 
				
			||||||
 | 
					export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
 | 
				
			||||||
 | 
					export const DIRECTION_ENCODER = Symbol('DIRECTION_ENCODER');
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CqrsModule } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { DIRECTION_ENCODER, PARAMS_PROVIDER } from './geography.di-tokens';
 | 
				
			||||||
 | 
					import { DefaultParamsProvider } from './infrastructure/default-params-provider';
 | 
				
			||||||
 | 
					import { PostgresDirectionEncoder } from './infrastructure/postgres-direction-encoder';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const adapters: Provider[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					    useClass: DefaultParamsProvider,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					    useClass: PostgresDirectionEncoder,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  imports: [CqrsModule],
 | 
				
			||||||
 | 
					  providers: [...adapters],
 | 
				
			||||||
 | 
					  exports: [DIRECTION_ENCODER],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class GeographyModule {}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { ConfigService } from '@nestjs/config';
 | 
					import { ConfigService } from '@nestjs/config';
 | 
				
			||||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
 | 
					import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
 | 
				
			||||||
import { DefaultParams } from '../core/application/ports/default-params.type';
 | 
					import { DefaultParams } from '../core/application/types/default-params.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class DefaultParamsProvider implements DefaultParamsProviderPort {
 | 
					export class DefaultParamsProvider implements DefaultParamsProviderPort {
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,5 @@ export class DefaultParamsProvider implements DefaultParamsProviderPort {
 | 
				
			||||||
  getParams = (): DefaultParams => ({
 | 
					  getParams = (): DefaultParams => ({
 | 
				
			||||||
    GEOROUTER_TYPE: this._configService.get('GEOROUTER_TYPE'),
 | 
					    GEOROUTER_TYPE: this._configService.get('GEOROUTER_TYPE'),
 | 
				
			||||||
    GEOROUTER_URL: this._configService.get('GEOROUTER_URL'),
 | 
					    GEOROUTER_URL: this._configService.get('GEOROUTER_URL'),
 | 
				
			||||||
    DEFAULT_TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'),
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { Coordinates } from '../core/application/types/coordinates.type';
 | 
				
			||||||
 | 
					import { DirectionEncoderPort } from '../core/application/ports/direction-encoder.port';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PostgresDirectionEncoder implements DirectionEncoderPort {
 | 
				
			||||||
 | 
					  encode = (coordinates: Coordinates[]): string =>
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      "'LINESTRING(",
 | 
				
			||||||
 | 
					      coordinates.map((point) => [point.lon, point.lat].join(' ')).join(),
 | 
				
			||||||
 | 
					      ")'",
 | 
				
			||||||
 | 
					    ].join('');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,20 @@ const imports = [
 | 
				
			||||||
      uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
					      uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
				
			||||||
      exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
					      exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
				
			||||||
      name: 'matcher',
 | 
					      name: 'matcher',
 | 
				
			||||||
 | 
					      handlers: {
 | 
				
			||||||
 | 
					        adCreated: {
 | 
				
			||||||
 | 
					          routingKey: 'ad.created',
 | 
				
			||||||
 | 
					          queue: 'matcher-ad-created',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        adUpdated: {
 | 
				
			||||||
 | 
					          routingKey: 'ad.updated',
 | 
				
			||||||
 | 
					          queue: 'matcher-ad-updated',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        adDeleted: {
 | 
				
			||||||
 | 
					          routingKey: 'ad.deleted',
 | 
				
			||||||
 | 
					          queue: 'matcher-ad-deleted',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue