Implement a DeleteAdCommand
This commit is contained in:
		
							parent
							
								
									a7c281d740
								
							
						
					
					
						commit
						1701fbbeb1
					
				| 
						 | 
					@ -1,49 +1,50 @@
 | 
				
			||||||
import { Module, Provider } from '@nestjs/common';
 | 
					import { ConfigurationRepository } from '@mobicoop/configuration-module';
 | 
				
			||||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  AD_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
  AD_REPOSITORY,
 | 
					 | 
				
			||||||
  AD_DIRECTION_ENCODER,
 | 
					 | 
				
			||||||
  AD_ROUTE_PROVIDER,
 | 
					 | 
				
			||||||
  TIMEZONE_FINDER,
 | 
					 | 
				
			||||||
  TIME_CONVERTER,
 | 
					 | 
				
			||||||
  INPUT_DATETIME_TRANSFORMER,
 | 
					 | 
				
			||||||
  OUTPUT_DATETIME_TRANSFORMER,
 | 
					 | 
				
			||||||
  MATCHING_REPOSITORY,
 | 
					 | 
				
			||||||
  AD_CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
  GEOGRAPHY_PACKAGE,
 | 
					 | 
				
			||||||
} 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 { PrismaService } from './infrastructure/prisma.service';
 | 
					 | 
				
			||||||
import { AdMapper } from './ad.mapper';
 | 
					 | 
				
			||||||
import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
 | 
					 | 
				
			||||||
import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
 | 
					 | 
				
			||||||
import { GeographyModule } from '@modules/geography/geography.module';
 | 
					import { GeographyModule } from '@modules/geography/geography.module';
 | 
				
			||||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
 | 
					import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
 | 
				
			||||||
import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller';
 | 
					 | 
				
			||||||
import { MatchQueryHandler } from './core/application/queries/match/match.query-handler';
 | 
					 | 
				
			||||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
 | 
					 | 
				
			||||||
import { TimeConverter } from './infrastructure/time-converter';
 | 
					 | 
				
			||||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
 | 
					 | 
				
			||||||
import { MatchMapper } from './match.mapper';
 | 
					 | 
				
			||||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
 | 
					 | 
				
			||||||
import { MatchingRepository } from './infrastructure/matching.repository';
 | 
					 | 
				
			||||||
import { MatchingMapper } from './matching.mapper';
 | 
					 | 
				
			||||||
import { CacheModule } from '@nestjs/cache-manager';
 | 
					import { CacheModule } from '@nestjs/cache-manager';
 | 
				
			||||||
 | 
					import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
import { redisStore } from 'cache-manager-ioredis-yet';
 | 
					import { CqrsModule } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { ClientsModule, Transport } from '@nestjs/microservices';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  RedisClientOptions,
 | 
					  RedisClientOptions,
 | 
				
			||||||
  RedisModule,
 | 
					  RedisModule,
 | 
				
			||||||
  RedisModuleOptions,
 | 
					  RedisModuleOptions,
 | 
				
			||||||
} from '@songkeys/nestjs-redis';
 | 
					} from '@songkeys/nestjs-redis';
 | 
				
			||||||
import { ConfigurationRepository } from '@mobicoop/configuration-module';
 | 
					 | 
				
			||||||
import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
 | 
					 | 
				
			||||||
import { Georouter } from './infrastructure/georouter';
 | 
					 | 
				
			||||||
import { ClientsModule, Transport } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { GRPC_GEOGRAPHY_PACKAGE_NAME } from '@src/app.constants';
 | 
					import { GRPC_GEOGRAPHY_PACKAGE_NAME } from '@src/app.constants';
 | 
				
			||||||
 | 
					import { redisStore } from 'cache-manager-ioredis-yet';
 | 
				
			||||||
import { join } from 'path';
 | 
					import { join } from 'path';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AD_CONFIGURATION_REPOSITORY,
 | 
				
			||||||
 | 
					  AD_DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					  AD_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					  AD_REPOSITORY,
 | 
				
			||||||
 | 
					  AD_ROUTE_PROVIDER,
 | 
				
			||||||
 | 
					  GEOGRAPHY_PACKAGE,
 | 
				
			||||||
 | 
					  INPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
 | 
					  MATCHING_REPOSITORY,
 | 
				
			||||||
 | 
					  OUTPUT_DATETIME_TRANSFORMER,
 | 
				
			||||||
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					  TIME_CONVERTER,
 | 
				
			||||||
 | 
					} from './ad.di-tokens';
 | 
				
			||||||
 | 
					import { AdMapper } from './ad.mapper';
 | 
				
			||||||
 | 
					import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
 | 
				
			||||||
 | 
					import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
 | 
				
			||||||
 | 
					import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					import { MatchQueryHandler } from './core/application/queries/match/match.query-handler';
 | 
				
			||||||
 | 
					import { AdRepository } from './infrastructure/ad.repository';
 | 
				
			||||||
 | 
					import { Georouter } from './infrastructure/georouter';
 | 
				
			||||||
 | 
					import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
 | 
				
			||||||
 | 
					import { MatchingRepository } from './infrastructure/matching.repository';
 | 
				
			||||||
 | 
					import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
 | 
				
			||||||
 | 
					import { PrismaService } from './infrastructure/prisma.service';
 | 
				
			||||||
 | 
					import { TimeConverter } from './infrastructure/time-converter';
 | 
				
			||||||
 | 
					import { TimezoneFinder } from './infrastructure/timezone-finder';
 | 
				
			||||||
 | 
					import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller';
 | 
				
			||||||
 | 
					import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
 | 
				
			||||||
 | 
					import { MatchMapper } from './match.mapper';
 | 
				
			||||||
 | 
					import { MatchingMapper } from './matching.mapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const imports = [
 | 
					const imports = [
 | 
				
			||||||
  CqrsModule,
 | 
					  CqrsModule,
 | 
				
			||||||
| 
						 | 
					@ -102,7 +103,7 @@ const eventHandlers: Provider[] = [
 | 
				
			||||||
  PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
					  PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const commandHandlers: Provider[] = [CreateAdService];
 | 
					const commandHandlers: Provider[] = [CreateAdService, DeleteAdService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
					const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class DeleteAdCommand extends Command {
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<DeleteAdCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
				
			||||||
 | 
					import { DeleteAdCommand } from './delete-ad.command';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(DeleteAdCommand)
 | 
				
			||||||
 | 
					export class DeleteAdService implements ICommandHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_REPOSITORY) private readonly adRepository: AdRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: DeleteAdCommand): Promise<boolean> {
 | 
				
			||||||
 | 
					    const ad = await this.adRepository.findOneById(command.id);
 | 
				
			||||||
 | 
					    ad.delete();
 | 
				
			||||||
 | 
					    return this.adRepository.delete(ad);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
					import { AggregateID, AggregateRoot } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AdProps, CreateAdProps } from './ad.types';
 | 
					import { AdProps, CreateAdProps } from './ad.types';
 | 
				
			||||||
 | 
					import { AdDeletedDomainEvent } from './events/ad-delete.domain-event';
 | 
				
			||||||
import { MatcherAdCreatedDomainEvent } from './events/matcher-ad-created.domain-event';
 | 
					import { MatcherAdCreatedDomainEvent } from './events/matcher-ad-created.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AdEntity extends AggregateRoot<AdProps> {
 | 
					export class AdEntity extends AggregateRoot<AdProps> {
 | 
				
			||||||
| 
						 | 
					@ -26,6 +27,14 @@ export class AdEntity extends AggregateRoot<AdProps> {
 | 
				
			||||||
    return ad;
 | 
					    return ad;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete(): void {
 | 
				
			||||||
 | 
					    this.addEvent(
 | 
				
			||||||
 | 
					      new AdDeletedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: this.id,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validate(): void {
 | 
					  validate(): void {
 | 
				
			||||||
    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AdDeletedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<AdDeletedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,52 +1,17 @@
 | 
				
			||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
 | 
					import { createAdProps } from './ad.fixtures';
 | 
				
			||||||
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const originPointProps: PointProps = {
 | 
					 | 
				
			||||||
  lat: 48.689445,
 | 
					 | 
				
			||||||
  lon: 6.17651,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
const destinationPointProps: PointProps = {
 | 
					 | 
				
			||||||
  lat: 48.8566,
 | 
					 | 
				
			||||||
  lon: 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: [originPointProps, destinationPointProps],
 | 
					 | 
				
			||||||
  driverDistance: 23000,
 | 
					 | 
				
			||||||
  driverDuration: 900,
 | 
					 | 
				
			||||||
  passengerDistance: 23000,
 | 
					 | 
				
			||||||
  passengerDuration: 900,
 | 
					 | 
				
			||||||
  fwdAzimuth: 283,
 | 
					 | 
				
			||||||
  backAzimuth: 93,
 | 
					 | 
				
			||||||
  points: [],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Ad entity create', () => {
 | 
					describe('Ad entity create', () => {
 | 
				
			||||||
  it('should create a new entity', async () => {
 | 
					  describe('create', () => {
 | 
				
			||||||
    const ad: AdEntity = AdEntity.create(createAdProps);
 | 
					    it('should create a new entity', async () => {
 | 
				
			||||||
    expect(ad.id.length).toBe(36);
 | 
					      const ad: AdEntity = AdEntity.create(createAdProps());
 | 
				
			||||||
    expect(ad.getProps().schedule.length).toBe(1);
 | 
					      expect(ad.id.length).toBe(36);
 | 
				
			||||||
    expect(ad.getProps().schedule[0].day).toBe(3);
 | 
					      expect(ad.getProps().schedule.length).toBe(1);
 | 
				
			||||||
    expect(ad.getProps().schedule[0].time).toBe('08:30');
 | 
					      expect(ad.getProps().schedule[0].day).toBe(3);
 | 
				
			||||||
    expect(ad.getProps().driver).toBeTruthy();
 | 
					      expect(ad.getProps().schedule[0].time).toBe('08:30');
 | 
				
			||||||
    expect(ad.getProps().passenger).toBeTruthy();
 | 
					      expect(ad.getProps().driver).toBeTruthy();
 | 
				
			||||||
    expect(ad.getProps().driverDistance).toBe(23000);
 | 
					      expect(ad.getProps().passenger).toBeTruthy();
 | 
				
			||||||
 | 
					      expect(ad.getProps().driverDistance).toBe(23000);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
 | 
					import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const originPointProps: PointProps = {
 | 
				
			||||||
 | 
					  lat: 48.689445,
 | 
				
			||||||
 | 
					  lon: 6.17651,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const destinationPointProps: PointProps = {
 | 
				
			||||||
 | 
					  lat: 48.8566,
 | 
				
			||||||
 | 
					  lon: 2.3522,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createAdProps(): CreateAdProps {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    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: [originPointProps, destinationPointProps],
 | 
				
			||||||
 | 
					    driverDistance: 23000,
 | 
				
			||||||
 | 
					    driverDuration: 900,
 | 
				
			||||||
 | 
					    passengerDistance: 23000,
 | 
				
			||||||
 | 
					    passengerDuration: 900,
 | 
				
			||||||
 | 
					    fwdAzimuth: 283,
 | 
				
			||||||
 | 
					    backAzimuth: 93,
 | 
				
			||||||
 | 
					    points: [],
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { DeleteAdCommand } from '@modules/ad/core/application/commands/delete-ad/delete-ad.command';
 | 
				
			||||||
 | 
					import { DeleteAdService } from '@modules/ad/core/application/commands/delete-ad/delete-ad.service';
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { createAdProps } from './ad.fixtures';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ad: AdEntity = AdEntity.create(createAdProps());
 | 
				
			||||||
 | 
					const mockAdRepository = {
 | 
				
			||||||
 | 
					  findOneById: jest.fn().mockImplementation(() => ad),
 | 
				
			||||||
 | 
					  delete: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('DeleteAdService', () => {
 | 
				
			||||||
 | 
					  let deleteAdService: DeleteAdService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: AD_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockAdRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        DeleteAdService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deleteAdService = module.get<DeleteAdService>(DeleteAdService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(deleteAdService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should execute the delete logic and delete the ad from the repository', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(ad, 'delete');
 | 
				
			||||||
 | 
					    await deleteAdService.execute(new DeleteAdCommand(ad.id));
 | 
				
			||||||
 | 
					    expect(ad.delete).toHaveBeenCalled();
 | 
				
			||||||
 | 
					    expect(mockAdRepository.delete).toHaveBeenCalledWith(ad);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue