WIP ad creation

This commit is contained in:
Grégoire Chevalier
2023-05-15 17:00:05 +02:00
parent bf08260403
commit 5ed2c562d5
18 changed files with 216 additions and 52 deletions

View File

@@ -9,6 +9,7 @@ import { AdsRepository } from './adapters/secondaries/ads.repository';
import { Messager } from './adapters/secondaries/messager';
import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase';
import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
@Module({
imports: [
@@ -36,6 +37,10 @@ import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
Messager,
FindAdByUuidUseCase,
CreateAdUseCase,
{
provide: 'ParamsProvider',
useClass: DefaultParamsProvider,
},
],
})
export class AdModule {}

View File

@@ -10,7 +10,7 @@ import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
import { Ad } from '../../domain/entities/ad';
import { CreateAdRequest } from '../../domain/dtos/create-ad.request';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { DatabaseException } from 'src/modules/database/exceptions/database.exception';
import { DatabaseException } from '../../../database/exceptions/database.exception';
@UsePipes(
new RpcValidationPipe({
@@ -41,10 +41,10 @@ export class AdController {
@GrpcMethod('AdsService', 'Create')
async createAd(data: CreateAdRequest): Promise<AdPresenter> {
console.log('presenter');
console.log(data);
console.log('*******************************');
try {
console.log('controler');
console.log(data);
console.log('-----------------------------------------');
const ad = await this._commandBus.execute(new CreateAdCommand(data));
return this._mapper.map(ad, Ad, AdPresenter);
} catch (e) {

View File

@@ -1,8 +1,34 @@
import { Injectable } from '@nestjs/common';
import { AdRepository } from '../../../database/domain/ad-repository';
import { Ad } from '../../domain/entities/ad';
import { DatabaseException } from '../../../database/exceptions/database.exception';
import { Prisma } from '@prisma/client';
@Injectable()
export class AdsRepository extends AdRepository<Ad> {
protected _model = 'ad';
async create(entity: Partial<Ad> | any, include?: any): Promise<Ad> {
console.log('custom ad repo ');
console.log(entity);
console.log('*****************************************');
try {
const res = await this._prisma[this._model].create({
data: entity,
include: include,
});
return res;
} catch (e) {
console.log('repo error ');
console.log(e);
if (e instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseException(
Prisma.PrismaClientKnownRequestError.name,
e.code,
e.message,
);
} else {
throw new DatabaseException();
}
}
}
}

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DefaultParams } from '../../domain/types/default-params.type';
import { IProvideParams } from '../../domain/interfaces/param-provider.interface';
@Injectable()
export class DefaultParamsProvider implements IProvideParams {
constructor(private readonly configService: ConfigService) {}
//TODO adding config service
getParams = (): DefaultParams => {
return {
MON_MARGIN: 900,
TUE_MARGIN: 900,
WED_MARGIN: 900,
THU_MARGIN: 900,
FRI_MARGIN: 900,
SAT_MARGIN: 900,
SUN_MARGIN: 900,
DRIVER: false,
DRIVER_SEAT: 0,
PASSENGER: true,
PASSENGER_SEATS: 1,
STRICT: false,
};
};
}

View File

@@ -5,6 +5,5 @@ export class CreateAdCommand {
constructor(request: CreateAdRequest) {
this.createAdRequest = request;
console.log('req creation');
}
}

View File

@@ -17,8 +17,8 @@ import { mappingKeyToFrequency } from './utils/frequency.mapping';
import { MarginDTO } from './create.margin.dto';
import { ScheduleDTO } from './create.schedule.dto';
import { AddressDTO } from './create.address.dto';
import { HasProperPassengerSeats } from './has-passenger-seats.validator';
import { HasProperDriverSeats } from './has-driver-seats.validator';
import { HasProperPassengerSeats } from './utils/has-passenger-seats.validator';
import { HasProperDriverSeats } from './utils/has-driver-seats.validator';
export class CreateAdRequest {
@IsString()

View File

@@ -7,9 +7,6 @@ import {
// TODO refactor ??
// TODO propely set driver max limit
export function hasProperDriverSeats(value: any, args: ValidationArguments) {
console.log('here ');
console.log(value);
console.log(args.object);
if (value === true && typeof args.object['seatsDriver'] === 'number')
return args.object['seatsDriver'] > 0;
else if (

View File

@@ -7,9 +7,6 @@ import {
// TODO refactor ??
// TODO propely set passenger max limit
export function hasProperPassengerSeats(value: any, args: ValidationArguments) {
console.log('here ');
console.log(value);
console.log(args.object);
if (value === true && typeof args.object['seatsPassenger'] === 'number')
return args.object['seatsPassenger'] > 0;
else if (

View File

@@ -6,9 +6,12 @@ import {
IsDate,
IsInt,
IsEnum,
ValidateNested,
ArrayMinSize,
} from 'class-validator';
import { Address } from '../entities/address';
import { Frequency } from '../types/frequency.enum';
import { Type } from 'class-transformer';
export class Ad {
@IsString()
@@ -21,11 +24,11 @@ export class Ad {
@IsBoolean()
@AutoMap()
driver?: boolean;
driver: boolean;
@IsBoolean()
@AutoMap()
passenger?: boolean;
passenger: boolean;
@IsEnum(Frequency)
@AutoMap()
@@ -110,6 +113,10 @@ export class Ad {
@AutoMap()
seatsPassenger: number;
@IsBoolean()
@AutoMap()
strict: boolean;
@IsDate()
@AutoMap()
createdAt: Date;
@@ -118,6 +125,9 @@ export class Ad {
@AutoMap()
updatedAt?: Date;
@Type(() => Address)
@ArrayMinSize(2)
@ValidateNested({ each: true })
@AutoMap()
addresses?: Array<Address>;
addresses: Address[];
}

View File

@@ -1,3 +1,14 @@
export type DefaultParams = {
DEFAULT_MARGIN: number;
MON_MARGIN: number;
TUE_MARGIN: number;
WED_MARGIN: number;
THU_MARGIN: number;
FRI_MARGIN: number;
SAT_MARGIN: number;
SUN_MARGIN: number;
DRIVER: boolean;
DRIVER_SEAT: number;
PASSENGER: boolean;
PASSENGER_SEATS: number;
STRICT: boolean;
};

View File

@@ -1,19 +1,28 @@
import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { Inject } from '@nestjs/common';
import { CommandHandler } from '@nestjs/cqrs';
import { Messager } from '../../adapters/secondaries/messager';
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { CreateAdRequest } from '../dtos/create-ad.request';
import { Ad } from '../entities/ad';
import { IProvideParams } from '../interfaces/param-provider.interface';
import { DefaultParams } from '../types/default-params.type';
@CommandHandler(CreateAdCommand)
export class CreateAdUseCase {
private readonly defaultParams: DefaultParams;
private ad: Ad;
constructor(
private readonly _repository: AdsRepository,
private readonly _messager: Messager,
@InjectMapper() private readonly _mapper: Mapper,
) {}
@Inject('ParamsProvider')
private readonly defaultParamsProvider: IProvideParams,
) {
this.defaultParams = defaultParamsProvider.getParams();
}
async execute(command: CreateAdCommand): Promise<Ad> {
const entity: Ad = this._mapper.map(
@@ -21,11 +30,38 @@ export class CreateAdUseCase {
CreateAdRequest,
Ad,
);
console.log('usecase');
console.log(entity.addresses[0]);
console.log('-----------------------------------------');
typeof entity.monMargin === 'number'
? entity.monMargin
: (entity.monMargin = this.defaultParams.MON_MARGIN);
typeof entity.tueMargin === 'number'
? entity.tueMargin
: (entity.tueMargin = this.defaultParams.TUE_MARGIN);
typeof entity.wedMargin === 'number'
? entity.wedMargin
: (entity.wedMargin = this.defaultParams.WED_MARGIN);
typeof entity.thuMargin === 'number'
? entity.thuMargin
: (entity.thuMargin = this.defaultParams.THU_MARGIN);
typeof entity.friMargin === 'number'
? entity.friMargin
: (entity.friMargin = this.defaultParams.FRI_MARGIN);
typeof entity.satMargin === 'number'
? entity.satMargin
: (entity.satMargin = this.defaultParams.SAT_MARGIN);
typeof entity.sunMargin === 'number'
? entity.sunMargin
: (entity.sunMargin = this.defaultParams.SUN_MARGIN);
typeof entity.strict === 'boolean'
? entity.strict
: (entity.strict = this.defaultParams.STRICT);
try {
const ad = await this._repository.create(entity);
this.ad = await this._repository.create(entity);
// this._messager.publish('ad.create', JSON.stringify(ad));
// this._messager.publish('logging.ad.create.info', JSON.stringify(ad));
return ad;
return this.ad;
} catch (error) {
let key = 'logging.ad.create.crit';
if (error.message.includes('Already exists')) {

View File

@@ -74,6 +74,10 @@ export class AdProfile extends AutomapperProfile {
(destination) => destination.sunTime,
mapFrom((source) => source.schedule.sun),
),
forMember(
(destination) => destination.addresses,
mapFrom((source) => source.addresses),
),
);
};
}

View File

@@ -10,8 +10,7 @@ import { classes } from '@automapper/classes';
import { Frequency } from '../../domain/types/frequency.enum';
import { Ad } from '../../domain/entities/ad';
import { AdProfile } from '../../mappers/ad.profile';
import { ScheduleDTO } from '../../domain/dtos/create.schedule.dto';
import { ObjectUnsubscribedError } from 'rxjs';
import { DefaultParams } from '../../domain/types/default-params.type';
const mockAddress1: Address = {
position: 0,
@@ -31,7 +30,27 @@ const mockAddress2: Address = {
postalCode: '75000',
country: 'France',
};
const minimalReccurentAdREquest: CreateAdRequest = {
userUuid: '224e0000-0000-4000-a000-000000000000',
driver: undefined,
passenger: undefined,
frequency: Frequency.RECURRENT,
fromDate: new Date('01-05-2023'),
toDate: new Date('01-05-2024'),
schedule: {
mon: '08:00',
},
marginDurations: {
mon: undefined,
tue: undefined,
wed: undefined,
thu: undefined,
fri: undefined,
sat: undefined,
sun: undefined,
},
addresses: [mockAddress1, mockAddress2],
};
const newAdRequest: CreateAdRequest = {
userUuid: '113e0000-0000-4000-a000-000000000000',
driver: true,
@@ -56,12 +75,28 @@ const newAdRequest: CreateAdRequest = {
addresses: [mockAddress1, mockAddress2],
};
const newAdCommand = new CreateAdCommand(newAdRequest);
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
const mockAdRepository = {
const mockDefaultParamsProvider = {
getParams: () => {
return {
MON_MARGIN: 900,
TUE_MARGIN: 900,
WED_MARGIN: 900,
THU_MARGIN: 900,
FRI_MARGIN: 900,
SAT_MARGIN: 900,
SUN_MARGIN: 900,
DRIVER: false,
DRIVER_SEAT: 0,
PASSENGER: true,
PASSENGER_SEATS: 1,
STRICT: false,
};
},
};
let mockAdRepository = {
create: jest
.fn()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -72,9 +107,10 @@ const mockAdRepository = {
createdAt: new Date('01-05-2023'),
});
})
.mockImplementation(() => {
.mockImplementationOnce(() => {
throw new Error('Already exists');
}),
})
.mockImplementation(),
};
describe('CreateAdUseCase', () => {
let createAdUseCase: CreateAdUseCase;
@@ -92,6 +128,10 @@ describe('CreateAdUseCase', () => {
},
CreateAdUseCase,
AdProfile,
{
provide: 'ParamsProvider',
useValue: mockDefaultParamsProvider,
},
],
}).compile();
@@ -100,11 +140,8 @@ describe('CreateAdUseCase', () => {
it('should be defined', () => {
expect(createAdUseCase).toBeDefined();
});
// describe('Ad parameters validation', () => {
// it('should throw an error if ');
// });
describe('execution', () => {
const newAdCommand = new CreateAdCommand(newAdRequest);
it('should create an new ad', async () => {
const newAd: Ad = await createAdUseCase.execute(newAdCommand);
expect(newAd.userUuid).toBe(newAdRequest.userUuid);
@@ -116,4 +153,19 @@ describe('CreateAdUseCase', () => {
).rejects.toBeInstanceOf(Error);
});
});
describe('Ad parameter default setting ', () => {
const newAdCommand = new CreateAdCommand(minimalReccurentAdREquest);
it('should define mimimal ad as 1 passager add', async () => {
const newAd: Ad = await createAdUseCase.execute(newAdCommand);
expect(mockAdRepository).toBeCalledWith({
userUuid: '113e0000-0000-4000-a000-000000000000',
driver: true,
passenger: false,
frequency: Frequency.RECURRENT,
fromDate: new Date('01-05-2023'),
toDate: new Date('20-08-2023'),
});
});
});
});

View File

@@ -1,4 +1,4 @@
import { hasProperDriverSeats } from '../../domain/dtos/has-driver-seats.validator';
import { hasProperDriverSeats } from '../../domain/dtos/utils/has-driver-seats.validator';
describe('driver and/or driver seats validator', () => {
it('should validate if driver and drivers seats is not provided ', () => {

View File

@@ -1,4 +1,4 @@
import { hasProperPassengerSeats } from '../../domain/dtos/has-passenger-seats.validator';
import { hasProperPassengerSeats } from '../../domain/dtos/utils/has-passenger-seats.validator';
describe('driver and/or passenger seats validator', () => {
it('should validate if passenger and passengers seats is not provided ', () => {

View File

@@ -82,12 +82,14 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
async create(entity: Partial<T> | any, include?: any): Promise<T> {
console.log('repo entity ');
console.log(entity);
console.log('-----------------------------------------');
try {
const res = await this._prisma[this._model].create({
data: entity,
include: include,
});
console.log('result');
console.log(res);
return res;
} catch (e) {
console.log('repo error ');