Skip to content

Commit

Permalink
Merge branch 'master' into feat-hotwire
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Nov 13, 2023
2 parents c263197 + 13846aa commit ebb5be6
Show file tree
Hide file tree
Showing 37 changed files with 178 additions and 448 deletions.
21 changes: 21 additions & 0 deletions migrations/1699290758562-dropAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class DropAddress1699290758562 implements MigrationInterface {
name = 'DropAddress1699290758562'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "customer" DROP CONSTRAINT "FK_7697a356e1f4b79ab3819839e95"`);
await queryRunner.query(`ALTER TABLE "cooperative" DROP CONSTRAINT "FK_76076f95b4afaa794ca4a974661"`);
await queryRunner.query(`ALTER TABLE "customer" DROP COLUMN "addressId"`);
await queryRunner.query(`ALTER TABLE "cooperative" DROP COLUMN "addressId"`);
await queryRunner.query(`DROP TABLE "address"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "address" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "street" character varying NOT NULL, "city" character varying NOT NULL, "zipCode" character varying(6) NOT NULL, "country" character varying(2) NOT NULL, CONSTRAINT "PK_d92de1f82754668b5f5f5dd4fd5" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "cooperative" ADD "addressId" uuid NOT NULL`);
await queryRunner.query(`ALTER TABLE "customer" ADD "addressId" uuid`);
await queryRunner.query(`ALTER TABLE "cooperative" ADD CONSTRAINT "FK_76076f95b4afaa794ca4a974661" FOREIGN KEY ("addressId") REFERENCES "address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "customer" ADD CONSTRAINT "FK_7697a356e1f4b79ab3819839e95" FOREIGN KEY ("addressId") REFERENCES "address"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
}
}
42 changes: 42 additions & 0 deletions server/src/Infrastructure/Customer/Action/CreateCustomerAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
Body,
Post,
Controller,
Inject,
BadRequestException,
UseGuards
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { CreateCustomerCommand } from 'src/Application/Customer/Command/CreateCustomerCommand';
import { ICommandBus } from 'src/Application/ICommandBus';
import { CustomerDTO } from '../DTO/CustomerDTO';
import { RolesGuard } from 'src/Infrastructure/HumanResource/User/Security/RolesGuard';
import { UserRole } from 'src/Domain/HumanResource/User/User.entity';
import { Roles } from 'src/Infrastructure/HumanResource/User/Decorator/Roles';

@Controller('customers')
@ApiTags('Customer')
@ApiBearerAuth()
@UseGuards(AuthGuard('bearer'), RolesGuard)
export class CreateCustomerAction {
constructor(
@Inject('ICommandBus')
private readonly commandBus: ICommandBus
) {}

@Post()
@Roles(UserRole.COOPERATOR, UserRole.EMPLOYEE)
@ApiOperation({ summary: 'Create new customer' })
public async index(@Body() customerDto: CustomerDTO) {
const { name } = customerDto;

try {
const id = await this.commandBus.execute(new CreateCustomerCommand(name));

return { id };
} catch (e) {
throw new BadRequestException(e.message);
}
}
}
45 changes: 45 additions & 0 deletions server/src/Infrastructure/Customer/Action/UpdateCustomerAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
Body,
Controller,
Inject,
BadRequestException,
UseGuards,
Put,
Param
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { ICommandBus } from 'src/Application/ICommandBus';
import { UpdateCustomerCommand } from 'src/Application/Customer/Command/UpdateCustomerCommand';
import { CustomerDTO } from '../DTO/CustomerDTO';
import { IdDTO } from 'src/Infrastructure/Common/DTO/IdDTO';
import { RolesGuard } from 'src/Infrastructure/HumanResource/User/Security/RolesGuard';
import { Roles } from 'src/Infrastructure/HumanResource/User/Decorator/Roles';
import { UserRole } from 'src/Domain/HumanResource/User/User.entity';

@Controller('customers')
@ApiTags('Customer')
@ApiBearerAuth()
@UseGuards(AuthGuard('bearer'), RolesGuard)
export class UpdateCustomerAction {
constructor(
@Inject('ICommandBus')
private readonly commandBus: ICommandBus
) {}

@Put(':id')
@Roles(UserRole.COOPERATOR, UserRole.EMPLOYEE)
@ApiOperation({ summary: 'Update customer' })
public async index(@Param() dto: IdDTO, @Body() customerDto: CustomerDTO) {
try {
const { name } = customerDto;
const { id } = dto;

await this.commandBus.execute(new UpdateCustomerCommand(id, name));

return { id };
} catch (e) {
throw new BadRequestException(e.message);
}
}
}
23 changes: 23 additions & 0 deletions server/src/Infrastructure/Customer/DTO/CustomerDTO.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CustomerDTO } from './CustomerDTO';
import { validate } from 'class-validator';

describe('CustomerDTO', () => {
it('testValidDTO', async () => {
const dto = new CustomerDTO();
dto.name = 'Customer';

const validation = await validate(dto);
expect(validation).toHaveLength(0);
});

it('testInvalidDTO', async () => {
const dto = new CustomerDTO();
dto.name = '';

const validation = await validate(dto);
expect(validation).toHaveLength(1);
expect(validation[0].constraints).toMatchObject({
isNotEmpty: 'name should not be empty'
});
});
});
8 changes: 8 additions & 0 deletions server/src/Infrastructure/Customer/DTO/CustomerDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';

export class CustomerDTO {
@ApiProperty()
@IsNotEmpty()
public name: string;
}
8 changes: 1 addition & 7 deletions src/Application/Customer/Command/CreateCustomerCommand.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { ICommand } from 'src/Application/ICommand';

export class CreateCustomerCommand implements ICommand {
constructor(
public readonly name: string,
public readonly street: string,
public readonly city: string,
public readonly zipCode: string,
public readonly country: string
) {}
constructor(public readonly name: string) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,22 @@ import { Customer } from 'src/Domain/Customer/Customer.entity';
import { CreateCustomerCommandHandler } from 'src/Application/Customer/Command/CreateCustomerCommandHandler';
import { CreateCustomerCommand } from 'src/Application/Customer/Command/CreateCustomerCommand';
import { CustomerAlreadyExistException } from 'src/Domain/Customer/Exception/CustomerAlreadyExistException';
import { AddressRepository } from 'src/Infrastructure/Customer/Repository/AddressRepository';
import { Address } from 'src/Domain/Customer/Address.entity';

describe('CreateCustomerCommandHandler', () => {
let customerRepository: CustomerRepository;
let addressRepository: AddressRepository;
let isCustomerAlreadyExist: IsCustomerAlreadyExist;
let createdCustomer: Customer;
let createdAddress: Address;
let handler: CreateCustomerCommandHandler;

const command = new CreateCustomerCommand(
'Customer',
'2 rue Dieu',
'Paris',
'75010',
'FR'
);
const command = new CreateCustomerCommand('Customer');

beforeEach(() => {
customerRepository = mock(CustomerRepository);
addressRepository = mock(AddressRepository);
isCustomerAlreadyExist = mock(IsCustomerAlreadyExist);
createdCustomer = mock(Customer);
createdAddress = mock(Address);

handler = new CreateCustomerCommandHandler(
instance(customerRepository),
instance(addressRepository),
instance(isCustomerAlreadyExist)
);
});
Expand All @@ -45,26 +32,15 @@ describe('CreateCustomerCommandHandler', () => {
);
when(createdCustomer.getName()).thenReturn('Customer');
when(
addressRepository.save(
deepEqual(new Address('2 rue Dieu', 'Paris', '75010', 'FR'))
)
).thenResolve(instance(createdAddress));
when(
customerRepository.save(
deepEqual(new Customer('Customer', instance(createdAddress)))
)
customerRepository.save(deepEqual(new Customer('Customer')))
).thenResolve(instance(createdCustomer));

expect(await handler.execute(command)).toBe(
'2d5fb4da-12c2-11ea-8d71-362b9e155667'
);

verify(isCustomerAlreadyExist.isSatisfiedBy('Customer')).once();
verify(
customerRepository.save(
deepEqual(new Customer('Customer', instance(createdAddress)))
)
).once();
verify(customerRepository.save(deepEqual(new Customer('Customer')))).once();
verify(createdCustomer.getId()).once();
});

Expand Down
14 changes: 2 additions & 12 deletions src/Application/Customer/Command/CreateCustomerCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,23 @@ import { CreateCustomerCommand } from './CreateCustomerCommand';
import { ICustomerRepository } from 'src/Domain/Customer/Repository/ICustomerRepository';
import { CustomerAlreadyExistException } from 'src/Domain/Customer/Exception/CustomerAlreadyExistException';
import { IsCustomerAlreadyExist } from 'src/Domain/Customer/Specification/IsCustomerAlreadyExist';
import { IAddressRepository } from 'src/Domain/Customer/Repository/IAddressRepository';
import { Address } from 'src/Domain/Customer/Address.entity';

@CommandHandler(CreateCustomerCommand)
export class CreateCustomerCommandHandler {
constructor(
@Inject('ICustomerRepository')
private readonly customerRepository: ICustomerRepository,
@Inject('IAddressRepository')
private readonly addressRepository: IAddressRepository,
private readonly isCustomerAlreadyExist: IsCustomerAlreadyExist
) {}

public async execute(command: CreateCustomerCommand): Promise<string> {
const { name, street, city, country, zipCode } = command;
const { name } = command;

if (true === (await this.isCustomerAlreadyExist.isSatisfiedBy(name))) {
throw new CustomerAlreadyExistException();
}

const address = await this.addressRepository.save(
new Address(street, city, zipCode, country)
);

const customer = await this.customerRepository.save(
new Customer(name, address)
);
const customer = await this.customerRepository.save(new Customer(name));

return customer.getId();
}
Expand Down
9 changes: 1 addition & 8 deletions src/Application/Customer/Command/UpdateCustomerCommand.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { ICommand } from 'src/Application/ICommand';

export class UpdateCustomerCommand implements ICommand {
constructor(
public readonly id: string,
public readonly name: string,
public readonly street: string,
public readonly city: string,
public readonly zipCode: string,
public readonly country: string
) {}
constructor(public readonly id: string, public readonly name: string) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,25 @@ import { UpdateCustomerCommand } from './UpdateCustomerCommand';
import { CustomerNotFoundException } from 'src/Domain/Customer/Exception/CustomerNotFoundException';
import { CustomerAlreadyExistException } from 'src/Domain/Customer/Exception/CustomerAlreadyExistException';
import { UpdateCustomerCommandHandler } from './UpdateCustomerCommandHandler';
import { AddressRepository } from 'src/Infrastructure/Customer/Repository/AddressRepository';
import { Address } from 'src/Domain/Customer/Address.entity';

describe('UpdateCustomerCommandHandler', () => {
let customerRepository: CustomerRepository;
let addressRepository: AddressRepository;
let isCustomerAlreadyExist: IsCustomerAlreadyExist;
let updatedCustomer: Customer;
let updatedAddress: Address;
let handler: UpdateCustomerCommandHandler;

const command = new UpdateCustomerCommand(
'afda00b1-bf49-4102-9bc2-bce17f3acd48',
'Customer',
'2 rue Dieu',
'Paris',
'75010',
'FR'
'Customer'
);

beforeEach(() => {
customerRepository = mock(CustomerRepository);
addressRepository = mock(AddressRepository);
isCustomerAlreadyExist = mock(IsCustomerAlreadyExist);
updatedCustomer = mock(Customer);
updatedAddress = mock(Address);

handler = new UpdateCustomerCommandHandler(
instance(customerRepository),
instance(addressRepository),
instance(isCustomerAlreadyExist)
);
});
Expand All @@ -45,7 +34,6 @@ describe('UpdateCustomerCommandHandler', () => {
customerRepository.findOneById('afda00b1-bf49-4102-9bc2-bce17f3acd48')
).thenResolve(instance(updatedCustomer));

when(updatedCustomer.getAddress()).thenReturn(instance(updatedAddress));
when(updatedCustomer.getName()).thenReturn('Old customer');
when(isCustomerAlreadyExist.isSatisfiedBy('Customer')).thenResolve(false);

Expand All @@ -54,13 +42,7 @@ describe('UpdateCustomerCommandHandler', () => {

verify(isCustomerAlreadyExist.isSatisfiedBy('Customer')).once();
verify(customerRepository.save(instance(updatedCustomer))).once();
verify(addressRepository.save(instance(updatedAddress))).once();
verify(updatedCustomer.getAddress()).once();
verify(updatedAddress.update('2 rue Dieu', 'Paris', '75010', 'FR')).once();
verify(updatedCustomer.updateName('Customer')).once();
verify(
updatedAddress.update('2 rue Dieu', 'Paris', '75010', 'FR')
).calledBefore(addressRepository.save(instance(updatedAddress)));
verify(updatedCustomer.updateName('Customer')).calledBefore(
customerRepository.save(instance(updatedCustomer))
);
Expand All @@ -78,10 +60,6 @@ describe('UpdateCustomerCommandHandler', () => {
expect(e).toBeInstanceOf(CustomerNotFoundException);
expect(e.message).toBe('crm.customers.errors.not_found');
verify(isCustomerAlreadyExist.isSatisfiedBy(anything())).never();
verify(addressRepository.save(anything())).never();
verify(
updatedAddress.update(anything(), anything(), anything(), anything())
).never();
verify(customerRepository.save(anything())).never();
verify(updatedCustomer.updateName(anything())).never();
verify(updatedCustomer.getName()).never();
Expand All @@ -102,10 +80,6 @@ describe('UpdateCustomerCommandHandler', () => {
verify(isCustomerAlreadyExist.isSatisfiedBy('Customer')).once();
verify(customerRepository.save(anything())).never();
verify(updatedCustomer.updateName(anything())).never();
verify(addressRepository.save(anything())).never();
verify(
updatedAddress.update(anything(), anything(), anything(), anything())
).never();
verify(updatedCustomer.getName()).once();
}
});
Expand Down
12 changes: 2 additions & 10 deletions src/Application/Customer/Command/UpdateCustomerCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ import { ICustomerRepository } from 'src/Domain/Customer/Repository/ICustomerRep
import { CustomerNotFoundException } from 'src/Domain/Customer/Exception/CustomerNotFoundException';
import { IsCustomerAlreadyExist } from 'src/Domain/Customer/Specification/IsCustomerAlreadyExist';
import { CustomerAlreadyExistException } from 'src/Domain/Customer/Exception/CustomerAlreadyExistException';
import { IAddressRepository } from 'src/Domain/Customer/Repository/IAddressRepository';

@CommandHandler(UpdateCustomerCommand)
export class UpdateCustomerCommandHandler {
constructor(
@Inject('ICustomerRepository')
private readonly customerRepository: ICustomerRepository,
@Inject('IAddressRepository')
private readonly addressRepository: IAddressRepository,
private readonly isCustomerAlreadyExist: IsCustomerAlreadyExist
) {}

public async execute(command: UpdateCustomerCommand): Promise<void> {
const { id, name, city, street, country, zipCode } = command;
const { id, name } = command;

const customer = await this.customerRepository.findOneById(id);
if (!customer) {
Expand All @@ -32,13 +29,8 @@ export class UpdateCustomerCommandHandler {
throw new CustomerAlreadyExistException();
}

const address = customer.getAddress();
customer.updateName(name);
address.update(street, city, zipCode, country);

await Promise.all([
this.customerRepository.save(customer),
this.addressRepository.save(address)
]);
await this.customerRepository.save(customer);
}
}
Loading

0 comments on commit ebb5be6

Please sign in to comment.