diff --git a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts index c43225e..7a90a1d 100644 --- a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts +++ b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts @@ -215,4 +215,25 @@ export abstract class PrismaRepository implements IRepository { } } } + + async updateWithFields(uuid: string, entity: Partial): Promise { + entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; + const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); + try { + const command = `UPDATE ${this._model} SET ${values.join( + ', ', + )} WHERE uuid = '${uuid}'`; + return await this._prisma.$executeRawUnsafe(command); + } catch (e) { + if (e instanceof PrismaClientKnownRequestError) { + throw new DatabaseException( + PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + } } diff --git a/src/modules/database/src/domain/territory-repository.ts b/src/modules/database/src/domain/territory-repository.ts index 1e4c1ca..a53b0b2 100644 --- a/src/modules/database/src/domain/territory-repository.ts +++ b/src/modules/database/src/domain/territory-repository.ts @@ -32,4 +32,24 @@ export class TerritoryRepository extends PrismaRepository { throw e; } } + + async updateTerritory(uuid: string, territory: Territory): Promise { + try { + const fields = {}; + if (territory.name) fields['name'] = `'${territory.name}'`; + if (territory.shape) + fields[ + 'shape' + ] = `ST_GeomFromGeoJSON('{"type":"MultiPolygon","coordinates":${territory.shape}}')`; + const affectedRowNumber = await this.updateWithFields(uuid, fields); + if (affectedRowNumber == 1) { + return this.findOne({ + name: territory.name, + }); + } + throw new DatabaseException(); + } catch (e) { + throw e; + } + } } diff --git a/src/modules/database/tests/unit/prisma-repository.spec.ts b/src/modules/database/tests/unit/prisma-repository.spec.ts index cb585be..08323e2 100644 --- a/src/modules/database/tests/unit/prisma-repository.spec.ts +++ b/src/modules/database/tests/unit/prisma-repository.spec.ts @@ -72,6 +72,18 @@ const mockPrismaService = { }); }) // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new Error('an unknown error'); + }) + .mockResolvedValueOnce(fakeEntityCreated) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new PrismaClientKnownRequestError('unknown request', { + code: 'code', + clientVersion: 'version', + }); + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((fields: object) => { throw new Error('an unknown error'); }), @@ -454,7 +466,7 @@ describe('PrismaRepository', () => { }); }); - describe('createwithFields', () => { + describe('createWithFields', () => { it('should create an entity', async () => { jest.spyOn(prisma, '$queryRawUnsafe'); @@ -483,4 +495,41 @@ describe('PrismaRepository', () => { ).rejects.toBeInstanceOf(DatabaseException); }); }); + + describe('updateWithFields', () => { + it('should update an entity', async () => { + jest.spyOn(prisma, '$queryRawUnsafe'); + + const updatedEntity = await fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ); + expect(updatedEntity).toBe(fakeEntityCreated); + expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1); + }); + + it('should throw a DatabaseException for client error', async () => { + await expect( + fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ), + ).rejects.toBeInstanceOf(DatabaseException); + }); + + it('should throw a DatabaseException if uuid is not found', async () => { + await expect( + fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ), + ).rejects.toBeInstanceOf(DatabaseException); + }); + }); }); diff --git a/src/modules/territories/adapters/primaries/territories.controller.ts b/src/modules/territories/adapters/primaries/territories.controller.ts index d662662..19bd5ce 100644 --- a/src/modules/territories/adapters/primaries/territories.controller.ts +++ b/src/modules/territories/adapters/primaries/territories.controller.ts @@ -1,12 +1,6 @@ import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; -import { - CacheInterceptor, - CacheKey, - Controller, - UseInterceptors, - UsePipes, -} from '@nestjs/common'; +import { Controller, UsePipes } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { Territory } from '../../domain/entities/territory'; @@ -41,8 +35,6 @@ export class TerritoriesController { ) {} @GrpcMethod('TerritoriesService', 'FindAllForPoint') - @UseInterceptors(CacheInterceptor) - @CacheKey('TerritoriesServiceFindAllForPoint') async findAllTerritoriesForPoint( data: FindAllTerritoriesForPointRequest, ): Promise> { @@ -58,8 +50,6 @@ export class TerritoriesController { } @GrpcMethod('TerritoriesService', 'FindAll') - @UseInterceptors(CacheInterceptor) - @CacheKey('TerritoriesServiceFindAll') async findAll( data: FindAllTerritoriesRequest, ): Promise> { @@ -75,8 +65,6 @@ export class TerritoriesController { } @GrpcMethod('TerritoriesService', 'FindOneByUuid') - @UseInterceptors(CacheInterceptor) - @CacheKey('TerritoriesServiceFindOneByUuid') async findOneByUuid( data: FindTerritoryByUuidRequest, ): Promise { diff --git a/src/modules/territories/adapters/primaries/territory.proto b/src/modules/territories/adapters/primaries/territory.proto index 6191db0..5c57ba6 100644 --- a/src/modules/territories/adapters/primaries/territory.proto +++ b/src/modules/territories/adapters/primaries/territory.proto @@ -6,8 +6,8 @@ service TerritoriesService { rpc FindOneByUuid(TerritoryByUuid) returns (Territory); rpc FindAll(TerritoryFilter) returns (Territories); rpc FindAllForPoint(Point) returns (Territories); - rpc Create(Territory) returns (Territory); - rpc Update(Territory) returns (Territory); + rpc Create(TerritoryShape) returns (Territory); + rpc Update(TerritoryShape) returns (Territory); rpc Delete(TerritoryByUuid) returns (Empty); } @@ -15,12 +15,17 @@ message TerritoryByUuid { string uuid = 1; } -message Territory { +message TerritoryShape { string uuid = 1; string name = 2; string shape = 3; } +message Territory { + string uuid = 1; + string name = 2; +} + message TerritoryFilter { optional int32 page = 1; optional int32 perPage = 2; diff --git a/src/modules/territories/domain/usecases/update-territory.usecase.ts b/src/modules/territories/domain/usecases/update-territory.usecase.ts index 833de08..b2c509f 100644 --- a/src/modules/territories/domain/usecases/update-territory.usecase.ts +++ b/src/modules/territories/domain/usecases/update-territory.usecase.ts @@ -18,16 +18,14 @@ export class UpdateTerritoryUseCase { ) {} async execute(command: UpdateTerritoryCommand): Promise { - const entity = this._mapper.map( - command.updateTerritoryRequest, - UpdateTerritoryRequest, - Territory, - ); - try { - const territory = await this._repository.update( + const territory = await this._repository.updateTerritory( command.updateTerritoryRequest.uuid, - entity, + this._mapper.map( + command.updateTerritoryRequest, + UpdateTerritoryRequest, + Territory, + ), ); this._territoryMessager.publish( 'update', diff --git a/src/modules/territories/territories.module.ts b/src/modules/territories/territories.module.ts index fc2a78b..7f1081c 100644 --- a/src/modules/territories/territories.module.ts +++ b/src/modules/territories/territories.module.ts @@ -45,7 +45,7 @@ import { TerritoryProfile } from './mappers/territory.profile'; store: await redisStore({ host: configService.get('REDIS_HOST'), port: configService.get('REDIS_PORT'), - ttl: configService.get('CACHE_TTL'), + ttl: configService.get('CACHE_TTL'), }), }), inject: [ConfigService], diff --git a/src/modules/territories/tests/unit/update-territory.usecase.spec.ts b/src/modules/territories/tests/unit/update-territory.usecase.spec.ts index b13876b..32cba50 100644 --- a/src/modules/territories/tests/unit/update-territory.usecase.spec.ts +++ b/src/modules/territories/tests/unit/update-territory.usecase.spec.ts @@ -23,7 +23,7 @@ const updateTerritoryCommand: UpdateTerritoryCommand = new UpdateTerritoryCommand(updateTerritoryRequest); const mockTerritoriesRepository = { - update: jest + updateTerritory: jest .fn() .mockImplementationOnce((uuid: string, params: any) => { originalTerritory.name = params.name;