diff --git a/src/modules/database/src/domain/territory-repository.ts b/src/modules/database/src/domain/territory-repository.ts index 1b153dd..79c203a 100644 --- a/src/modules/database/src/domain/territory-repository.ts +++ b/src/modules/database/src/domain/territory-repository.ts @@ -15,6 +15,18 @@ export class TerritoryRepository extends PrismaRepository { ); } + async findForPoints(points: Point[]): Promise> { + const multipoint = points + .map((point) => '(' + point.lon + ' ' + point.lat + ')') + .join(', '); + return await this.findAllByQuery( + ['uuid', 'name'], + [ + `ST_Intersects(ST_GeomFromText('MULTIPOINT(${multipoint})',4326),shape) = true`, + ], + ); + } + async createTerritory(territory: Partial): Promise { try { const affectedRowNumber = await this.createWithFields({ diff --git a/src/modules/database/tests/unit/territory-repository.spec.ts b/src/modules/database/tests/unit/territory-repository.spec.ts index b26fe92..dd7194e 100644 --- a/src/modules/database/tests/unit/territory-repository.spec.ts +++ b/src/modules/database/tests/unit/territory-repository.spec.ts @@ -86,6 +86,19 @@ describe('TerritoryRepository', () => { }); }); + describe('findForPoints', () => { + it('should return an array of entities', async () => { + const entities = await repository.findForPoints([ + new Point(6.1, 48.2), + new Point(6.2, 48.3), + ]); + expect(entities).toStrictEqual({ + data: mockTerritories, + total: mockTerritories.length, + }); + }); + }); + describe('createTerritory', () => { it('should create a new territory', async () => { const territory = await repository.createTerritory({ diff --git a/src/modules/territories/adapters/primaries/territories.controller.ts b/src/modules/territories/adapters/primaries/territories.controller.ts index 19bd5ce..aeaa6ed 100644 --- a/src/modules/territories/adapters/primaries/territories.controller.ts +++ b/src/modules/territories/adapters/primaries/territories.controller.ts @@ -19,6 +19,8 @@ import { DatabaseException } from 'src/modules/database/src/exceptions/database. import { UpdateTerritoryRequest } from '../../domain/dtos/update-territory.request'; import { UpdateTerritoryCommand } from '../../commands/update-territory.command'; import { DeleteTerritoryCommand } from '../../commands/delete-territory.command'; +import { FindAllTerritoriesForPointsRequest } from '../../domain/dtos/find-all-territories-for-points.request'; +import { FindAllTerritoriesForPointsQuery } from '../../queries/find-all-territories-for-points.query'; @UsePipes( new RpcValidationPipe({ @@ -49,6 +51,21 @@ export class TerritoriesController { }); } + @GrpcMethod('TerritoriesService', 'FindAllForPoints') + async findAllTerritoriesForPoints( + data: FindAllTerritoriesForPointsRequest, + ): Promise> { + const territoryCollection = await this._queryBus.execute( + new FindAllTerritoriesForPointsQuery(data), + ); + return Promise.resolve({ + data: territoryCollection.data.map((territory: Territory) => + this._mapper.map(territory, Territory, TerritoryPresenter), + ), + total: territoryCollection.total, + }); + } + @GrpcMethod('TerritoriesService', 'FindAll') async findAll( data: FindAllTerritoriesRequest, diff --git a/src/modules/territories/adapters/primaries/territory.proto b/src/modules/territories/adapters/primaries/territory.proto index 5c57ba6..e9bb2e8 100644 --- a/src/modules/territories/adapters/primaries/territory.proto +++ b/src/modules/territories/adapters/primaries/territory.proto @@ -6,6 +6,7 @@ service TerritoriesService { rpc FindOneByUuid(TerritoryByUuid) returns (Territory); rpc FindAll(TerritoryFilter) returns (Territories); rpc FindAllForPoint(Point) returns (Territories); + rpc FindAllForPoints(Points) returns (Territories); rpc Create(TerritoryShape) returns (Territory); rpc Update(TerritoryShape) returns (Territory); rpc Delete(TerritoryByUuid) returns (Empty); @@ -41,4 +42,8 @@ message Point { float lat = 2; } +message Points { + repeated Point points = 1; +} + message Empty {} diff --git a/src/modules/territories/domain/dtos/find-all-territories-for-points.request.ts b/src/modules/territories/domain/dtos/find-all-territories-for-points.request.ts new file mode 100644 index 0000000..2b2432b --- /dev/null +++ b/src/modules/territories/domain/dtos/find-all-territories-for-points.request.ts @@ -0,0 +1,9 @@ +import { Type } from 'class-transformer'; +import { IsArray } from 'class-validator'; +import { Point } from '../entities/point'; + +export class FindAllTerritoriesForPointsRequest { + @IsArray() + @Type(() => Point) + points: Point[]; +} diff --git a/src/modules/territories/domain/usecases/find-all-territories-for-points.usecase.ts b/src/modules/territories/domain/usecases/find-all-territories-for-points.usecase.ts new file mode 100644 index 0000000..8b103d7 --- /dev/null +++ b/src/modules/territories/domain/usecases/find-all-territories-for-points.usecase.ts @@ -0,0 +1,18 @@ +import { QueryHandler } from '@nestjs/cqrs'; +import { ICollection } from 'src/modules/database/src/interfaces/collection.interface'; +import { TerritoriesRepository } from '../../adapters/secondaries/territories.repository'; +import { FindAllTerritoriesForPointsQuery } from '../../queries/find-all-territories-for-points.query'; +import { Territory } from '../entities/territory'; + +@QueryHandler(FindAllTerritoriesForPointsQuery) +export class FindAllTerritoriesForPointsUseCase { + constructor(private readonly _repository: TerritoriesRepository) {} + + async execute( + findAllTerritoriesForPointsQuery: FindAllTerritoriesForPointsQuery, + ): Promise> { + return this._repository.findForPoints( + findAllTerritoriesForPointsQuery.points, + ); + } +} diff --git a/src/modules/territories/queries/find-all-territories-for-points.query.ts b/src/modules/territories/queries/find-all-territories-for-points.query.ts new file mode 100644 index 0000000..26411d7 --- /dev/null +++ b/src/modules/territories/queries/find-all-territories-for-points.query.ts @@ -0,0 +1,12 @@ +import { FindAllTerritoriesForPointsRequest } from '../domain/dtos/find-all-territories-for-points.request'; +import { Point } from '../domain/entities/point'; + +export class FindAllTerritoriesForPointsQuery { + points: Point[]; + + constructor( + findAllTerritoriesForPointsRequest?: FindAllTerritoriesForPointsRequest, + ) { + this.points = findAllTerritoriesForPointsRequest.points; + } +} diff --git a/src/modules/territories/territories.module.ts b/src/modules/territories/territories.module.ts index 7f1081c..a5e7838 100644 --- a/src/modules/territories/territories.module.ts +++ b/src/modules/territories/territories.module.ts @@ -12,6 +12,7 @@ import { TerritoryMessager } from './adapters/secondaries/territory.messager'; import { CreateTerritoryUseCase } from './domain/usecases/create-territory.usecase'; import { DeleteTerritoryUseCase } from './domain/usecases/delete-territory.usecase'; import { FindAllTerritoriesForPointUseCase } from './domain/usecases/find-all-territories-for-point.usecase'; +import { FindAllTerritoriesForPointsUseCase } from './domain/usecases/find-all-territories-for-points.usecase'; import { FindAllTerritoriesUseCase } from './domain/usecases/find-all-territories.usecase'; import { FindTerritoryByUuidUseCase } from './domain/usecases/find-territory-by-uuid.usecase'; import { UpdateTerritoryUseCase } from './domain/usecases/update-territory.usecase'; @@ -58,6 +59,7 @@ import { TerritoryProfile } from './mappers/territory.profile'; TerritoryMessager, LoggingMessager, FindAllTerritoriesForPointUseCase, + FindAllTerritoriesForPointsUseCase, FindAllTerritoriesUseCase, FindTerritoryByUuidUseCase, CreateTerritoryUseCase, diff --git a/src/modules/territories/tests/unit/find-all-territories-for-points.usecase.spec.ts b/src/modules/territories/tests/unit/find-all-territories-for-points.usecase.spec.ts new file mode 100644 index 0000000..a0cb2c7 --- /dev/null +++ b/src/modules/territories/tests/unit/find-all-territories-for-points.usecase.spec.ts @@ -0,0 +1,71 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TerritoriesRepository } from '../../adapters/secondaries/territories.repository'; +import { FindAllTerritoriesForPointsRequest } from '../../domain/dtos/find-all-territories-for-points.request'; +import { Point } from '../../domain/entities/point'; +import { FindAllTerritoriesForPointsUseCase } from '../../domain/usecases/find-all-territories-for-points.usecase'; +import { FindAllTerritoriesForPointsQuery } from '../../queries/find-all-territories-for-points.query'; + +const findAllTerritoriesForPointsRequest: FindAllTerritoriesForPointsRequest = + new FindAllTerritoriesForPointsRequest(); +findAllTerritoriesForPointsRequest.points = [ + new Point(6.181455, 48.685689), + new Point(6.191455, 48.695689), +]; + +const findforPointsQuery: FindAllTerritoriesForPointsQuery = + new FindAllTerritoriesForPointsQuery(findAllTerritoriesForPointsRequest); + +const mockTerritories = [ + { + uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', + name: 'Nancy', + }, + { + uuid: 'bb281075-1b98-4456-89d6-c643d3044a92', + name: 'Meurthe-et-Moselle', + }, +]; + +const mockTerritoriesRepository = { + findForPoints: jest + .fn() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementation((query?: FindAllTerritoriesForPointsQuery) => { + return Promise.resolve(mockTerritories); + }), +}; + +describe('FindAllTerritoriesforPointsUseCase', () => { + let findAllTerritoriesForPointsUseCase: FindAllTerritoriesForPointsUseCase; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: TerritoriesRepository, + useValue: mockTerritoriesRepository, + }, + FindAllTerritoriesForPointsUseCase, + ], + }).compile(); + + findAllTerritoriesForPointsUseCase = + module.get( + FindAllTerritoriesForPointsUseCase, + ); + }); + + it('should be defined', () => { + expect(findAllTerritoriesForPointsUseCase).toBeDefined(); + }); + + describe('execute', () => { + it('should return an array filled with territories', async () => { + const territories = await findAllTerritoriesForPointsUseCase.execute( + findforPointsQuery, + ); + + expect(territories).toBe(mockTerritories); + }); + }); +});