Skip to content

Commit

Permalink
Get leads (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarchois authored Mar 29, 2021
1 parent 379f309 commit f8d0ec2
Show file tree
Hide file tree
Showing 18 changed files with 496 additions and 16 deletions.
14 changes: 14 additions & 0 deletions api/migrations/1617026957510-Lead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";

export class Lead1617026957510 implements MigrationInterface {
name = 'Lead1617026957510'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "lead" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "reference" character varying NOT NULL, "name" character varying NOT NULL, "address" character varying NOT NULL, "zipCode" character varying NOT NULL, "city" character varying NOT NULL, "phoneNumber" character varying NOT NULL, "email" character varying NOT NULL, "numberOfStudents" integer DEFAULT 0, "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "PK_ca96c1888f7dcfccab72b72fffa" PRIMARY KEY ("id"))`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "lead"`);
}

}
7 changes: 7 additions & 0 deletions api/src/Application/Lead/Query/GetLeadsQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IQuery } from 'src/Application/IQuery';

export class GetLeadsQuery implements IQuery {
constructor(
public readonly page: number,
) {}
}
71 changes: 71 additions & 0 deletions api/src/Application/Lead/Query/GetLeadsQueryHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { mock, instance, when, verify } from 'ts-mockito';
import { GetLeadsQueryHandler } from 'src/Application/Lead/Query/GetLeadsQueryHandler';
import { LeadRepository } from 'src/Infrastructure/Lead/Repository/LeadRepository';
import { GetLeadsQuery } from 'src/Application/Lead/Query/GetLeadsQuery';
import { Lead } from 'src/Domain/Lead/Lead.entity';
import { LeadView } from 'src/Application/Lead/View/LeadView';
import { Pagination } from 'src/Application/Common/Pagination';

describe('GetLeadsQueryHandler', () => {
it('testGetLeads', async () => {
const leadRepository = mock(LeadRepository);

const lead1 = mock(Lead);
when(lead1.getId()).thenReturn('eb9e1d9b-dce2-48a9-b64f-f0872f3157d2');
when(lead1.getName()).thenReturn('Ecole élementaire Belliard');
when(lead1.getReference()).thenReturn('xLKJSs');
when(lead1.getAddress()).thenReturn('127 Rue Belliard');
when(lead1.getCity()).thenReturn('Paris');
when(lead1.getZipCode()).thenReturn('75010');
when(lead1.getEmail()).thenReturn('email@email.com');
when(lead1.getPhoneNumber()).thenReturn('0102030405');
when(lead1.getNumberOfStudents()).thenReturn(200);

const lead2 = mock(Lead);
when(lead2.getId()).thenReturn('d54f15d6-1a1d-47e8-8672-9f46018f9960');
when(lead2.getName()).thenReturn('Ecole Primaire les Landes');
when(lead2.getReference()).thenReturn('abcDes');
when(lead2.getAddress()).thenReturn('12 Rue des Lampes');
when(lead2.getCity()).thenReturn('Paris');
when(lead2.getZipCode()).thenReturn('75018');
when(lead2.getEmail()).thenReturn('email2@email.com');
when(lead2.getPhoneNumber()).thenReturn('0102030406');

when(leadRepository.findLeads(1)).thenResolve([
[instance(lead2), instance(lead1)],
2
]);

const queryHandler = new GetLeadsQueryHandler(instance(leadRepository));
const expectedResult = new Pagination<LeadView>(
[
new LeadView(
'd54f15d6-1a1d-47e8-8672-9f46018f9960',
'Ecole Primaire les Landes',
'abcDes',
'12 Rue des Lampes',
'Paris',
'75018',
'email2@email.com',
'0102030406',
null
),
new LeadView(
'eb9e1d9b-dce2-48a9-b64f-f0872f3157d2',
'Ecole élementaire Belliard',
'xLKJSs',
'127 Rue Belliard',
'Paris',
'75010',
'email@email.com',
'0102030405',
200
)
],
2
);

expect(await queryHandler.execute(new GetLeadsQuery(1))).toMatchObject(expectedResult);
verify(leadRepository.findLeads(1)).once();
});
});
38 changes: 38 additions & 0 deletions api/src/Application/Lead/Query/GetLeadsQueryHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { QueryHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common';
import { GetLeadsQuery } from './GetLeadsQuery';
import { Pagination } from 'src/Application/Common/Pagination';
import { ILeadRepository } from 'src/Domain/Lead/Repository/ILeadRepository';
import { LeadView } from '../View/LeadView';

@QueryHandler(GetLeadsQuery)
export class GetLeadsQueryHandler {
constructor(
@Inject('ILeadRepository')
private readonly leadRepository: ILeadRepository
) {}

public async execute(query: GetLeadsQuery): Promise<Pagination<LeadView>> {
const { page } = query;
const leadViews: LeadView[] = [];
const [ leads, total ] = await this.leadRepository.findLeads(page);

for (const lead of leads) {
leadViews.push(
new LeadView(
lead.getId(),
lead.getName(),
lead.getReference(),
lead.getAddress(),
lead.getCity(),
lead.getZipCode(),
lead.getEmail(),
lead.getPhoneNumber(),
lead.getNumberOfStudents(),
)
);
}

return new Pagination<LeadView>(leadViews, total);
}
}
13 changes: 13 additions & 0 deletions api/src/Application/Lead/View/LeadView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class LeadView {
constructor(
public readonly id: string,
public readonly reference: string,
public readonly name: string,
public readonly address: string,
public readonly zipCode: string,
public readonly city: string,
public readonly email: string,
public readonly phoneNumber: string,
public readonly numberOfStudents?: number,
) {}
}
26 changes: 26 additions & 0 deletions api/src/Domain/Lead/Lead.entity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Lead } from './Lead.entity';

describe('Lead', () => {
it('testGetters', () => {
const lead = new Lead(
'LM120I',
'Belliard',
'127 Rue Belliard',
'75018',
'Paris',
'test@test.com',
'010101010101',
200
);
expect(lead.getId()).toBeUndefined();
expect(lead.getName()).toBe('Belliard');
expect(lead.getReference()).toBe('LM120I');
expect(lead.getCity()).toBe('Paris');
expect(lead.getZipCode()).toBe('75018');
expect(lead.getAddress()).toBe('127 Rue Belliard');
expect(lead.getPhoneNumber()).toBe('010101010101');
expect(lead.getEmail()).toBe('test@test.com');
expect(lead.getNumberOfStudents()).toBe(200);
expect(lead.getCreatedAt()).toBeUndefined();
});
});
94 changes: 94 additions & 0 deletions api/src/Domain/Lead/Lead.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Lead {
@PrimaryGeneratedColumn('uuid')
private id: string;

@Column({ type: 'varchar', nullable: false })
private reference: string;

@Column({ type: 'varchar', nullable: false })
private name: string;

@Column({ type: 'varchar', nullable: false })
private address: string;

@Column({ type: 'varchar', nullable: false })
private zipCode: string;

@Column({ type: 'varchar', nullable: false })
private city: string;

@Column({ type: 'varchar', nullable: false })
private phoneNumber: string;

@Column({ type: 'varchar', nullable: false })
private email: string;

@Column({ type: 'integer', nullable: true, default: 0 })
private numberOfStudents: number;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
private createdAt: number;

constructor(
reference: string,
name: string,
address: string,
zipCode: string,
city: string,
email: string,
phoneNumber: string,
numberOfStudents?: number,
) {
this.reference = reference;
this.name = name;
this.address = address;
this.zipCode = zipCode;
this.city = city;
this.email = email;
this.phoneNumber = phoneNumber;
this.numberOfStudents = numberOfStudents;
}

public getId(): string {
return this.id;
}

public getReference(): string {
return this.reference;
}

public getName(): string {
return this.name;
}

public getZipCode(): string {
return this.zipCode;
}

public getAddress(): string {
return this.address;
}

public getCity(): string {
return this.city;
}

public getEmail(): string {
return this.email;
}

public getPhoneNumber(): string {
return this.phoneNumber;
}

public getNumberOfStudents(): number {
return this.numberOfStudents;
}

public getCreatedAt(): number {
return this.createdAt;
}
}
6 changes: 6 additions & 0 deletions api/src/Domain/Lead/Repository/ILeadRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Lead } from '../Lead.entity';

export interface ILeadRepository {
save(lead: Lead): Promise<Lead>;
findLeads(page: number): Promise<[Lead[], number]>;
}
31 changes: 31 additions & 0 deletions api/src/Infrastructure/Lead/Action/GetSchoolsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller, Inject, UseGuards, Get, Query } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { LeadView } from 'src/Application/Lead/View/LeadView';
import { IQueryBus } from 'src/Application/IQueryBus';
import { GetLeadsQuery } from 'src/Application/Lead/Query/GetLeadsQuery';
import { Pagination } from 'src/Application/Common/Pagination';
import { PaginationDTO } from 'src/Infrastructure/Common/DTO/PaginationDTO';
import { RolesGuard } from 'src/Infrastructure/User/Security/RolesGuard';
import { Roles } from 'src/Infrastructure/User/Decorator/Roles';
import { UserRole } from 'src/Domain/User/User.entity';

@Controller('leads')
@ApiTags('Lead')
@ApiBearerAuth()
@UseGuards(AuthGuard('bearer'), RolesGuard)
export class GetLeadsAction {
constructor(
@Inject('IQueryBus')
private readonly queryBus: IQueryBus
) {}

@Get()
@Roles(UserRole.PHOTOGRAPHER)
@ApiOperation({summary: 'Get all leads'})
public async index(
@Query() { page }: PaginationDTO,
): Promise<Pagination<LeadView>> {
return await this.queryBus.execute(new GetLeadsQuery(page));
}
}
38 changes: 38 additions & 0 deletions api/src/Infrastructure/Lead/Repository/LeadRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Lead } from 'src/Domain/Lead/Lead.entity';
import { ILeadRepository } from 'src/Domain/Lead/Repository/ILeadRepository';
import { MAX_ITEMS_PER_PAGE } from 'src/Application/Common/Pagination';

@Injectable()
export class LeadRepository implements ILeadRepository {
constructor(
@InjectRepository(Lead)
private readonly repository: Repository<Lead>
) {}

public save(lead: Lead): Promise<Lead> {
return this.repository.save(lead);
}

public findLeads(page = 1): Promise<[Lead[], number]> {
return this.repository
.createQueryBuilder('lead')
.select([
'lead.id',
'lead.name',
'lead.reference',
'lead.address',
'lead.city',
'lead.zipCode',
'lead.email',
'lead.phoneNumber',
'lead.numberOfStudents'
])
.orderBy('lead.createdAt', 'DESC')
.limit(MAX_ITEMS_PER_PAGE)
.offset((page - 1) * MAX_ITEMS_PER_PAGE)
.getManyAndCount();
}
}
24 changes: 24 additions & 0 deletions api/src/Infrastructure/Lead/lead.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GetLeadsQueryHandler } from 'src/Application/Lead/Query/GetLeadsQueryHandler';
import { Lead } from 'src/Domain/Lead/Lead.entity';
import { BusModule } from '../bus.module';
import { GetLeadsAction } from './Action/GetSchoolsAction';
import { LeadRepository } from './Repository/LeadRepository';

@Module({
imports: [
BusModule,
TypeOrmModule.forFeature([
Lead
])
],
controllers: [
GetLeadsAction
],
providers: [
{ provide: 'ILeadRepository', useClass: LeadRepository },
GetLeadsQueryHandler
]
})
export class LeadModule {}
9 changes: 8 additions & 1 deletion api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LeadModule } from './Infrastructure/Lead/lead.module';
import { ProductModule } from './Infrastructure/Product/product.module';
import { SchoolModule } from './Infrastructure/School/school.module';
import { UserModule } from './Infrastructure/User/user.module';

@Module({
imports: [TypeOrmModule.forRoot(), UserModule, SchoolModule, ProductModule],
imports: [
TypeOrmModule.forRoot(),
UserModule,
SchoolModule,
ProductModule,
LeadModule
],
controllers: [],
providers: []
})
Expand Down
Loading

0 comments on commit f8d0ec2

Please sign in to comment.