Skip to content

Commit

Permalink
List and count shootings (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarchois authored Apr 18, 2021
1 parent d35d867 commit 6703910
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 14 deletions.
2 changes: 1 addition & 1 deletion api/migrations/1618659343512-Shooting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export class Shooting1618659343512 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "photo" DROP CONSTRAINT "FK_92a92cf6e76017c70a95170a540"`);
await queryRunner.query(`CREATE TYPE "shooting_status_enum" AS ENUM('enabled', 'disabled')`);
await queryRunner.query(`CREATE TABLE "shooting" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "shoointingDate" date NOT NULL, "closingDate" date NOT NULL, "status" "shooting_status_enum" NOT NULL DEFAULT 'disabled', "schoolId" uuid NOT NULL, CONSTRAINT "PK_f41f1588cd6d69dca794f93c8f4" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "shooting" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "shootingDate" date NOT NULL, "closingDate" date NOT NULL, "status" "shooting_status_enum" NOT NULL DEFAULT 'disabled', "schoolId" uuid NOT NULL, CONSTRAINT "PK_f41f1588cd6d69dca794f93c8f4" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "school" DROP COLUMN "pdv"`);
await queryRunner.query(`ALTER TABLE "photo" DROP COLUMN "schoolId"`);
await queryRunner.query(`ALTER TABLE "photo" ADD "token" character varying NOT NULL`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GetSchoolProductsQueryHandler } from './GetSchoolProductsQueryHandler';
import { SchoolProductRepository } from 'src/Infrastructure/School/Repository/SchoolProductRepository';
import { ProductSummaryView } from 'src/Application/Product/View/ProductSummaryView';

describe('GetSchoolsQueryHandler', () => {
describe('GetSchoolProductsQueryHandler', () => {
it('testGetSchoolProducts', async () => {
const schoolProductRepository = mock(SchoolProductRepository);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IQuery } from 'src/Application/IQuery';

export class CountShootingsBySchoolQuery implements IQuery {
constructor(public readonly schoolId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { mock, instance, when, verify } from 'ts-mockito';
import { ShootingRepository } from 'src/Infrastructure/School/Repository/ShootingRepository';
import { CountShootingsBySchoolQueryHandler } from './CountShootingsBySchoolQueryHandler';
import { CountShootingsBySchoolQuery } from './CountShootingsBySchoolQuery';

describe('CountSchoolsQueryHandler', () => {
it('testCountShootings', async () => {
const shootingRepository = mock(ShootingRepository);
when(
shootingRepository.countBySchool('5eb3173b-97ab-4bbc-b31c-878d4bfafbc1')
).thenResolve(2);

const queryHandler = new CountShootingsBySchoolQueryHandler(
instance(shootingRepository)
);

expect(
await queryHandler.execute(
new CountShootingsBySchoolQuery('5eb3173b-97ab-4bbc-b31c-878d4bfafbc1')
)
).toBe(2);
verify(
shootingRepository.countBySchool(
'5eb3173b-97ab-4bbc-b31c-878d4bfafbc1'
)
).once();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { QueryHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common';
import { CountShootingsBySchoolQuery } from './CountShootingsBySchoolQuery';
import { IShootingRepository } from 'src/Domain/School/Repository/IShootingRepository';

@QueryHandler(CountShootingsBySchoolQuery)
export class CountShootingsBySchoolQueryHandler {
constructor(
@Inject('IShootingRepository')
private readonly shootingRepository: IShootingRepository
) {}

public execute({ schoolId }: CountShootingsBySchoolQuery): Promise<number> {
return this.shootingRepository.countBySchool(schoolId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IQuery } from 'src/Application/IQuery';

export class GetShootingsBySchoolQuery implements IQuery {
constructor(public readonly schoolId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { mock, instance, when, verify } from 'ts-mockito';
import { Shooting, ShootingStatus } from 'src/Domain/School/Shooting.entity';
import { ShootingView } from '../../View/ShootingView';
import { GetShootingsBySchoolQuery } from './GetShootingsBySchoolQuery';
import { GetShootingsBySchoolQueryHandler } from './GetShootingsBySchoolQueryHandler';
import { ShootingRepository } from 'src/Infrastructure/School/Repository/ShootingRepository';
import { School } from 'src/Domain/School/School.entity';

describe('GetShootingBySchoolQueryHandler', () => {
it('testGetShootings', async () => {
const shootingRepository = mock(ShootingRepository);

const school = mock(School);
const shooting = mock(Shooting);
when(shooting.getId()).thenReturn(
'4de2ffc4-e835-44c8-95b7-17c171c09873'
);
when(shooting.getName()).thenReturn('Prise de vue fin année');
when(shooting.getClosingDate()).thenReturn(new Date('2021-09-01'));
when(shooting.getShootingDate()).thenReturn(new Date('2021-04-18'));
when(shooting.getSchool()).thenReturn(instance(school));
when(shooting.getStatus()).thenReturn(ShootingStatus.DISABLED);

when(
shootingRepository.findBySchool(
'5eb3173b-97ab-4bbc-b31c-878d4bfafbc1'
)
).thenResolve([instance(shooting)]);

const queryHandler = new GetShootingsBySchoolQueryHandler(
instance(shootingRepository)
);

const expectedResult = [
new ShootingView(
'4de2ffc4-e835-44c8-95b7-17c171c09873',
'Prise de vue fin année',
ShootingStatus.DISABLED,
new Date('2021-04-18'),
new Date('2021-09-01')
)
];

expect(
await queryHandler.execute(
new GetShootingsBySchoolQuery('5eb3173b-97ab-4bbc-b31c-878d4bfafbc1')
)
).toMatchObject(expectedResult);
verify(
shootingRepository.findBySchool(
'5eb3173b-97ab-4bbc-b31c-878d4bfafbc1'
)
).once();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { QueryHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common';
import { GetShootingsBySchoolQuery } from './GetShootingsBySchoolQuery';
import { IShootingRepository } from 'src/Domain/School/Repository/IShootingRepository';
import { ShootingView } from '../../View/ShootingView';

@QueryHandler(GetShootingsBySchoolQuery)
export class GetShootingsBySchoolQueryHandler {
constructor(
@Inject('IShootingRepository')
private readonly shootingRepository: IShootingRepository
) {}

public async execute({ schoolId }: GetShootingsBySchoolQuery): Promise<ShootingView[]> {
const shootingViews: ShootingView[] = [];
const shootings = await this.shootingRepository.findBySchool(
schoolId
);

for (const shooting of shootings) {
shootingViews.push(
new ShootingView(
shooting.getId(),
shooting.getName(),
shooting.getStatus(),
shooting.getShootingDate(),
shooting.getClosingDate(),
)
);
}

return shootingViews;
}
}
11 changes: 11 additions & 0 deletions api/src/Application/School/View/ShootingView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ShootingStatus } from 'src/Domain/School/Shooting.entity';

export class ShootingView {
constructor(
public readonly id: string,
public readonly name: string,
public readonly status: ShootingStatus,
public readonly shootingDate: Date,
public readonly closingDate: Date,
) {}
}
2 changes: 2 additions & 0 deletions api/src/Domain/School/Repository/IShootingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import { Shooting } from '../Shooting.entity';

export interface IShootingRepository {
save(shooting: Shooting): Promise<Shooting>;
findBySchool(schoolId: string): Promise<Shooting[]>;
countBySchool(schoolId: string): Promise<number>;
}
2 changes: 1 addition & 1 deletion api/src/Domain/School/Shooting.entity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Shooting', () => {
);
expect(shooting.getId()).toBeUndefined();
expect(shooting.getName()).toBe('Prise de vue début année');
expect(shooting.getShoointingDate()).toMatchObject(new Date('2021-04-17T00:00:00.000Z'));
expect(shooting.getShootingDate()).toMatchObject(new Date('2021-04-17T00:00:00.000Z'));
expect(shooting.getClosingDate()).toMatchObject(new Date('2021-09-01T00:00:00.000Z'));
expect(shooting.getStatus()).toBe(ShootingStatus.DISABLED);
expect(shooting.getSchool()).toBe(instance(school));
Expand Down
10 changes: 5 additions & 5 deletions api/src/Domain/School/Shooting.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Shooting {
private name: string;

@Column({ type: 'date', nullable: false })
private shoointingDate: Date;
private shootingDate: Date;

@Column({ type: 'date', nullable: false })
private closingDate: Date;
Expand All @@ -28,13 +28,13 @@ export class Shooting {

constructor(
name: string,
shoointingDate: Date,
shootingDate: Date,
closingDate: Date,
status: ShootingStatus,
school: School
) {
this.name = name;
this.shoointingDate = shoointingDate;
this.shootingDate = shootingDate;
this.closingDate = closingDate;
this.status = status;
this.school = school;
Expand All @@ -48,8 +48,8 @@ export class Shooting {
return this.name;
}

public getShoointingDate(): Date {
return this.shoointingDate;
public getShootingDate(): Date {
return this.shootingDate;
}

public getClosingDate(): Date {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller, Inject, UseGuards, Get, Param, NotFoundException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { IQueryBus } from 'src/Application/IQueryBus';
import { CountShootingsBySchoolQuery } from 'src/Application/School/Query/Shooting/CountShootingsBySchoolQuery';
import { UserRole } from 'src/Domain/User/User.entity';
import { IdDTO } from 'src/Infrastructure/Common/DTO/IdDTO';
import { Roles } from 'src/Infrastructure/User/Decorator/Roles';
import { RolesGuard } from 'src/Infrastructure/User/Security/RolesGuard';

@Controller('schools')
@ApiTags('School shooting')
@ApiBearerAuth()
@UseGuards(AuthGuard('bearer'), RolesGuard)
export class CountSchoolShootingsAction {
constructor(
@Inject('IQueryBus')
private readonly queryBus: IQueryBus
) {}

@Get(':id/count-shootings')
@Roles(UserRole.PHOTOGRAPHER)
@ApiOperation({ summary: 'Count school shootings' })
public async index(@Param() { id }: IdDTO) {
try {
const total = await this.queryBus.execute(new CountShootingsBySchoolQuery(id));

return { total };
} catch (e) {
throw new NotFoundException(e.message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Controller, Inject, UseGuards, Get, Param, NotFoundException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { IQueryBus } from 'src/Application/IQueryBus';
import { IdDTO } from 'src/Infrastructure/Common/DTO/IdDTO';
import { Roles } from 'src/Infrastructure/User/Decorator/Roles';
import { RolesGuard } from 'src/Infrastructure/User/Security/RolesGuard';
import { UserRole } from 'src/Domain/User/User.entity';
import { ShootingView } from 'src/Application/School/View/ShootingView';
import { GetShootingsBySchoolQuery } from 'src/Application/School/Query/Shooting/GetShootingsBySchoolQuery';

@Controller('schools')
@ApiTags('School shooting')
@ApiBearerAuth()
@UseGuards(AuthGuard('bearer'), RolesGuard)
export class GetSchoolShootingsAction {
constructor(
@Inject('IQueryBus')
private readonly queryBus: IQueryBus
) {}

@Get(':id/shootings')
@Roles(UserRole.PHOTOGRAPHER)
@ApiOperation({ summary: 'Get school shootings' })
public async index(@Param() dto: IdDTO): Promise<ShootingView[]> {
try {
return await this.queryBus.execute(new GetShootingsBySchoolQuery(dto.id));
} catch (e) {
throw new NotFoundException(e.message);
}
}
}
23 changes: 23 additions & 0 deletions api/src/Infrastructure/School/Repository/ShootingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,27 @@ export class ShootingRepository implements IShootingRepository {
public save(shooting: Shooting): Promise<Shooting> {
return this.repository.save(shooting);
}

public findBySchool(schoolId: string): Promise<Shooting[]> {
return this.repository
.createQueryBuilder('shooting')
.select([
'shooting.id',
'shooting.name',
'shooting.status',
'shooting.shootingDate',
'shooting.closingDate'
])
.innerJoin('shooting.school', 'school', 'school.id = :schoolId', { schoolId })
.orderBy('shooting.shootingDate', 'DESC')
.getMany();
}

public countBySchool(id: string): Promise<number> {
return this.repository
.createQueryBuilder('shooting')
.select('shooting.id')
.innerJoin('shooting.school', 'school', 'school.id = :id', { id })
.getCount();
}
}
10 changes: 9 additions & 1 deletion api/src/Infrastructure/School/school.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ import { Shooting } from 'src/Domain/School/Shooting.entity';
import { ShootingRepository } from './Repository/ShootingRepository';
import { CreateShootingAction } from './Action/Shooting/CreateShootingAction';
import { CreateShootingCommandHandler } from 'src/Application/School/Command/Shooting/CreateShootingCommandHandler';
import { GetShootingsBySchoolQueryHandler } from 'src/Application/School/Query/Shooting/GetShootingsBySchoolQueryHandler';
import { GetSchoolShootingsAction } from './Action/Shooting/GetSchoolShootingsAction';
import { CountShootingsBySchoolQueryHandler } from 'src/Application/School/Query/Shooting/CountShootingsBySchoolQueryHandler';
import { CountSchoolShootingsAction } from './Action/Shooting/CountSchoolShootingsAction';

@Module({
imports: [
Expand Down Expand Up @@ -89,7 +93,9 @@ import { CreateShootingCommandHandler } from 'src/Application/School/Command/Sho
RemoveSchoolProductAction,
ConsumeVoucherAction,
RemoveVoucherAction,
GetSchoolShootingsAction,
CreateShootingAction,
CountSchoolShootingsAction
],
providers: [
{ provide: 'ICodeGenerator', useClass: CodeGeneratorAdapter },
Expand Down Expand Up @@ -124,7 +130,9 @@ import { CreateShootingCommandHandler } from 'src/Application/School/Command/Sho
RemoveVoucherCommandHandler,
RemoveSchoolUserCommandHandler,
GetVoucherByCodeQueryHandler,
CreateShootingCommandHandler
CreateShootingCommandHandler,
GetShootingsBySchoolQueryHandler,
CountShootingsBySchoolQueryHandler
]
})
export class SchoolModule {}
12 changes: 12 additions & 0 deletions client/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@
"name": "Nom de la prise de vue",
"closing_date": "Date de la fermeture des commandes",
"shooting_date": "Date de la prise de vue"
},
"status": {
"enabled": "Publiée",
"disabled": "En attente de publication"
},
"list": {
"name": "Prise de vue",
"closing_date": "Fermeture commandes",
"shooting_date": "Date",
"status": "Etat",
"class": "Nb classes",
"photos": "Nb photos"
}
}
},
Expand Down
Loading

0 comments on commit 6703910

Please sign in to comment.