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

@ -10,13 +10,13 @@ CREATE TABLE "ad" (
"frequency" "Frequency" NOT NULL,
"fromDate" DATE NOT NULL,
"toDate" DATE NOT NULL,
"monTime" TIMESTAMPTZ,
"tueTime" TIMESTAMPTZ,
"wedTime" TIMESTAMPTZ,
"thuTime" TIMESTAMPTZ,
"friTime" TIMESTAMPTZ,
"satTime" TIMESTAMPTZ,
"sunTime" TIMESTAMPTZ,
"monTime" TEXT,
"tueTime" TEXT,
"wedTime" TEXT,
"thuTime" TEXT,
"friTime" TEXT,
"satTime" TEXT,
"sunTime" TEXT,
"monMargin" INTEGER NOT NULL,
"tueMargin" INTEGER NOT NULL,
"wedMargin" INTEGER NOT NULL,
@ -45,8 +45,7 @@ CREATE TABLE "address" (
"street" TEXT,
"locality" TEXT,
"postalCode" TEXT,
"country" TEXT,
"countryCode" TEXT,
"country" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@ -18,13 +18,13 @@ model Ad {
frequency Frequency
fromDate DateTime @db.Date
toDate DateTime @db.Date
monTime DateTime? @db.Timestamptz()
tueTime DateTime? @db.Timestamptz()
wedTime DateTime? @db.Timestamptz()
thuTime DateTime? @db.Timestamptz()
friTime DateTime? @db.Timestamptz()
satTime DateTime? @db.Timestamptz()
sunTime DateTime? @db.Timestamptz()
monTime String?
tueTime String?
wedTime String?
thuTime String?
friTime String?
satTime String?
sunTime String?
monMargin Int
tueMargin Int
wedMargin Int

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 ');