working crud

This commit is contained in:
sbriat 2023-02-08 15:06:49 +01:00
parent 4e1fb9a8d6
commit 368ce98174
8 changed files with 108 additions and 27 deletions

View File

@ -215,4 +215,25 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
} }
} }
} }
async updateWithFields(uuid: string, entity: Partial<T>): Promise<number> {
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();
}
}
}
} }

View File

@ -32,4 +32,24 @@ export class TerritoryRepository<T> extends PrismaRepository<T> {
throw e; throw e;
} }
} }
async updateTerritory(uuid: string, territory: Territory): Promise<T> {
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;
}
}
} }

View File

@ -72,6 +72,18 @@ const mockPrismaService = {
}); });
}) })
// eslint-disable-next-line @typescript-eslint/no-unused-vars // 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) => { .mockImplementationOnce((fields: object) => {
throw new Error('an unknown error'); throw new Error('an unknown error');
}), }),
@ -454,7 +466,7 @@ describe('PrismaRepository', () => {
}); });
}); });
describe('createwithFields', () => { describe('createWithFields', () => {
it('should create an entity', async () => { it('should create an entity', async () => {
jest.spyOn(prisma, '$queryRawUnsafe'); jest.spyOn(prisma, '$queryRawUnsafe');
@ -483,4 +495,41 @@ describe('PrismaRepository', () => {
).rejects.toBeInstanceOf(DatabaseException); ).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);
});
});
}); });

View File

@ -1,12 +1,6 @@
import { Mapper } from '@automapper/core'; import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs'; import { InjectMapper } from '@automapper/nestjs';
import { import { Controller, UsePipes } from '@nestjs/common';
CacheInterceptor,
CacheKey,
Controller,
UseInterceptors,
UsePipes,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { Territory } from '../../domain/entities/territory'; import { Territory } from '../../domain/entities/territory';
@ -41,8 +35,6 @@ export class TerritoriesController {
) {} ) {}
@GrpcMethod('TerritoriesService', 'FindAllForPoint') @GrpcMethod('TerritoriesService', 'FindAllForPoint')
@UseInterceptors(CacheInterceptor)
@CacheKey('TerritoriesServiceFindAllForPoint')
async findAllTerritoriesForPoint( async findAllTerritoriesForPoint(
data: FindAllTerritoriesForPointRequest, data: FindAllTerritoriesForPointRequest,
): Promise<ICollection<Territory>> { ): Promise<ICollection<Territory>> {
@ -58,8 +50,6 @@ export class TerritoriesController {
} }
@GrpcMethod('TerritoriesService', 'FindAll') @GrpcMethod('TerritoriesService', 'FindAll')
@UseInterceptors(CacheInterceptor)
@CacheKey('TerritoriesServiceFindAll')
async findAll( async findAll(
data: FindAllTerritoriesRequest, data: FindAllTerritoriesRequest,
): Promise<ICollection<Territory>> { ): Promise<ICollection<Territory>> {
@ -75,8 +65,6 @@ export class TerritoriesController {
} }
@GrpcMethod('TerritoriesService', 'FindOneByUuid') @GrpcMethod('TerritoriesService', 'FindOneByUuid')
@UseInterceptors(CacheInterceptor)
@CacheKey('TerritoriesServiceFindOneByUuid')
async findOneByUuid( async findOneByUuid(
data: FindTerritoryByUuidRequest, data: FindTerritoryByUuidRequest,
): Promise<TerritoryPresenter> { ): Promise<TerritoryPresenter> {

View File

@ -6,8 +6,8 @@ service TerritoriesService {
rpc FindOneByUuid(TerritoryByUuid) returns (Territory); rpc FindOneByUuid(TerritoryByUuid) returns (Territory);
rpc FindAll(TerritoryFilter) returns (Territories); rpc FindAll(TerritoryFilter) returns (Territories);
rpc FindAllForPoint(Point) returns (Territories); rpc FindAllForPoint(Point) returns (Territories);
rpc Create(Territory) returns (Territory); rpc Create(TerritoryShape) returns (Territory);
rpc Update(Territory) returns (Territory); rpc Update(TerritoryShape) returns (Territory);
rpc Delete(TerritoryByUuid) returns (Empty); rpc Delete(TerritoryByUuid) returns (Empty);
} }
@ -15,12 +15,17 @@ message TerritoryByUuid {
string uuid = 1; string uuid = 1;
} }
message Territory { message TerritoryShape {
string uuid = 1; string uuid = 1;
string name = 2; string name = 2;
string shape = 3; string shape = 3;
} }
message Territory {
string uuid = 1;
string name = 2;
}
message TerritoryFilter { message TerritoryFilter {
optional int32 page = 1; optional int32 page = 1;
optional int32 perPage = 2; optional int32 perPage = 2;

View File

@ -18,16 +18,14 @@ export class UpdateTerritoryUseCase {
) {} ) {}
async execute(command: UpdateTerritoryCommand): Promise<Territory> { async execute(command: UpdateTerritoryCommand): Promise<Territory> {
const entity = this._mapper.map(
command.updateTerritoryRequest,
UpdateTerritoryRequest,
Territory,
);
try { try {
const territory = await this._repository.update( const territory = await this._repository.updateTerritory(
command.updateTerritoryRequest.uuid, command.updateTerritoryRequest.uuid,
entity, this._mapper.map(
command.updateTerritoryRequest,
UpdateTerritoryRequest,
Territory,
),
); );
this._territoryMessager.publish( this._territoryMessager.publish(
'update', 'update',

View File

@ -45,7 +45,7 @@ import { TerritoryProfile } from './mappers/territory.profile';
store: await redisStore({ store: await redisStore({
host: configService.get<string>('REDIS_HOST'), host: configService.get<string>('REDIS_HOST'),
port: configService.get<number>('REDIS_PORT'), port: configService.get<number>('REDIS_PORT'),
ttl: configService.get('CACHE_TTL'), ttl: configService.get<number>('CACHE_TTL'),
}), }),
}), }),
inject: [ConfigService], inject: [ConfigService],

View File

@ -23,7 +23,7 @@ const updateTerritoryCommand: UpdateTerritoryCommand =
new UpdateTerritoryCommand(updateTerritoryRequest); new UpdateTerritoryCommand(updateTerritoryRequest);
const mockTerritoriesRepository = { const mockTerritoriesRepository = {
update: jest updateTerritory: jest
.fn() .fn()
.mockImplementationOnce((uuid: string, params: any) => { .mockImplementationOnce((uuid: string, params: any) => {
originalTerritory.name = params.name; originalTerritory.name = params.name;