improve tests, handle coordinates precision
This commit is contained in:
parent
4ad00b96c0
commit
211bee2c70
|
@ -38,8 +38,8 @@ CREATE TABLE "waypoint" (
|
||||||
"uuid" UUID NOT NULL,
|
"uuid" UUID NOT NULL,
|
||||||
"adUuid" UUID NOT NULL,
|
"adUuid" UUID NOT NULL,
|
||||||
"position" SMALLINT NOT NULL,
|
"position" SMALLINT NOT NULL,
|
||||||
"lon" DOUBLE PRECISION NOT NULL,
|
"lon" DECIMAL(9,6) NOT NULL,
|
||||||
"lat" DOUBLE PRECISION NOT NULL,
|
"lat" DECIMAL(8,6) NOT NULL,
|
||||||
"name" TEXT,
|
"name" TEXT,
|
||||||
"houseNumber" TEXT,
|
"houseNumber" TEXT,
|
||||||
"street" TEXT,
|
"street" TEXT,
|
|
@ -47,8 +47,8 @@ model Waypoint {
|
||||||
uuid String @id @default(uuid()) @db.Uuid
|
uuid String @id @default(uuid()) @db.Uuid
|
||||||
adUuid String @db.Uuid
|
adUuid String @db.Uuid
|
||||||
position Int @db.SmallInt
|
position Int @db.SmallInt
|
||||||
lon Float
|
lon Decimal @db.Decimal(9, 6)
|
||||||
lat Float
|
lat Decimal @db.Decimal(8, 6)
|
||||||
name String?
|
name String?
|
||||||
houseNumber String?
|
houseNumber String?
|
||||||
street String?
|
street String?
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Paginated } from '../ddd';
|
import { Paginated } from '../ddd';
|
||||||
|
|
||||||
export abstract class PaginatedResponseDto<T> extends Paginated<T> {
|
export abstract class PaginatedResponseDto<T> extends Paginated<T> {
|
||||||
readonly count: number;
|
readonly total: number;
|
||||||
readonly limit: number;
|
readonly perPage: number;
|
||||||
readonly page: number;
|
readonly page: number;
|
||||||
abstract readonly data: readonly T[];
|
abstract readonly data: readonly T[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { AggregateRoot, Mapper, RepositoryPort } from '../ddd';
|
import { AggregateRoot, Mapper, RepositoryPort } from '../ddd';
|
||||||
import { ObjectLiteral } from '../types';
|
import { ObjectLiteral } from '../types';
|
||||||
import { LoggerPort } from '../ports/logger.port';
|
import { LoggerPort } from '../ports/logger.port';
|
||||||
import { None, Option, Some } from 'oxide.ts';
|
|
||||||
import {
|
import {
|
||||||
PrismaRawRepositoryPort,
|
PrismaRawRepositoryPort,
|
||||||
PrismaRepositoryPort,
|
PrismaRepositoryPort,
|
||||||
} from '../ports/prisma-repository.port';
|
} from '../ports/prisma-repository.port';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { ConflictException, DatabaseErrorException } from '@libs/exceptions';
|
import {
|
||||||
|
ConflictException,
|
||||||
|
DatabaseErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@libs/exceptions';
|
||||||
|
|
||||||
export abstract class PrismaRepositoryBase<
|
export abstract class PrismaRepositoryBase<
|
||||||
Aggregate extends AggregateRoot<any>,
|
Aggregate extends AggregateRoot<any>,
|
||||||
|
@ -24,12 +27,13 @@ export abstract class PrismaRepositoryBase<
|
||||||
protected readonly logger: LoggerPort,
|
protected readonly logger: LoggerPort,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findOneById(id: string, include?: any): Promise<Option<Aggregate>> {
|
async findOneById(id: string, include?: any): Promise<Aggregate> {
|
||||||
const entity = await this.prisma.findUnique({
|
const entity = await this.prisma.findUnique({
|
||||||
where: { uuid: id },
|
where: { uuid: id },
|
||||||
include,
|
include,
|
||||||
});
|
});
|
||||||
return entity ? Some(this.mapper.toDomain(entity)) : None;
|
if (entity) return this.mapper.toDomain(entity);
|
||||||
|
throw new NotFoundException('Record not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
async insert(entity: Aggregate): Promise<void> {
|
async insert(entity: Aggregate): Promise<void> {
|
||||||
|
@ -52,7 +56,6 @@ export abstract class PrismaRepositoryBase<
|
||||||
await this.prisma.$queryRaw`SELECT 1`;
|
await this.prisma.$queryRaw`SELECT 1`;
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseErrorException(e.message);
|
throw new DatabaseErrorException(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { OrderBy, PaginatedQueryParams } from './repository.port';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for regular queries
|
||||||
|
*/
|
||||||
|
export abstract class QueryBase {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for paginated queries
|
||||||
|
*/
|
||||||
|
export abstract class PaginatedQueryBase extends QueryBase {
|
||||||
|
perPage: number;
|
||||||
|
offset: number;
|
||||||
|
orderBy: OrderBy;
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
constructor(props: PaginatedParams<PaginatedQueryBase>) {
|
||||||
|
super();
|
||||||
|
this.perPage = props.perPage || 10;
|
||||||
|
this.offset = props.page ? props.page * this.perPage : 0;
|
||||||
|
this.page = props.page || 0;
|
||||||
|
this.orderBy = props.orderBy || { field: true, param: 'desc' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paginated query parameters
|
||||||
|
export type PaginatedParams<T> = Omit<
|
||||||
|
T,
|
||||||
|
'perPage' | 'offset' | 'orderBy' | 'page'
|
||||||
|
> &
|
||||||
|
Partial<Omit<PaginatedQueryParams, 'offset'>>;
|
|
@ -1,5 +1,3 @@
|
||||||
import { Option } from 'oxide.ts';
|
|
||||||
|
|
||||||
/* Most of repositories will probably need generic
|
/* Most of repositories will probably need generic
|
||||||
save/find/delete operations, so it's easier
|
save/find/delete operations, so it's easier
|
||||||
to have some shared interfaces.
|
to have some shared interfaces.
|
||||||
|
@ -8,14 +6,14 @@ import { Option } from 'oxide.ts';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Paginated<T> {
|
export class Paginated<T> {
|
||||||
readonly count: number;
|
readonly total: number;
|
||||||
readonly limit: number;
|
readonly perPage: number;
|
||||||
readonly page: number;
|
readonly page: number;
|
||||||
readonly data: readonly T[];
|
readonly data: readonly T[];
|
||||||
|
|
||||||
constructor(props: Paginated<T>) {
|
constructor(props: Paginated<T>) {
|
||||||
this.count = props.count;
|
this.total = props.total;
|
||||||
this.limit = props.limit;
|
this.perPage = props.perPage;
|
||||||
this.page = props.page;
|
this.page = props.page;
|
||||||
this.data = props.data;
|
this.data = props.data;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +22,7 @@ export class Paginated<T> {
|
||||||
export type OrderBy = { field: string | true; param: 'asc' | 'desc' };
|
export type OrderBy = { field: string | true; param: 'asc' | 'desc' };
|
||||||
|
|
||||||
export type PaginatedQueryParams = {
|
export type PaginatedQueryParams = {
|
||||||
limit: number;
|
perPage: number;
|
||||||
page: number;
|
page: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
orderBy: OrderBy;
|
orderBy: OrderBy;
|
||||||
|
@ -32,7 +30,7 @@ export type PaginatedQueryParams = {
|
||||||
|
|
||||||
export interface RepositoryPort<Entity> {
|
export interface RepositoryPort<Entity> {
|
||||||
insert(entity: Entity | Entity[]): Promise<void>;
|
insert(entity: Entity | Entity[]): Promise<void>;
|
||||||
findOneById(id: string): Promise<Option<Entity>>;
|
findOneById(id: string, include?: any): Promise<Entity>;
|
||||||
healthCheck(): Promise<boolean>;
|
healthCheck(): Promise<boolean>;
|
||||||
// findAll(): Promise<Entity[]>;
|
// findAll(): Promise<Entity[]>;
|
||||||
// findAllPaginated(params: PaginatedQueryParams): Promise<Paginated<Entity>>;
|
// findAllPaginated(params: PaginatedQueryParams): Promise<Paginated<Entity>>;
|
||||||
|
|
|
@ -62,49 +62,49 @@ export class AdMapper
|
||||||
fromDate: new Date(copy.fromDate),
|
fromDate: new Date(copy.fromDate),
|
||||||
toDate: new Date(copy.toDate),
|
toDate: new Date(copy.toDate),
|
||||||
monTime: copy.schedule.mon
|
monTime: copy.schedule.mon
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.mon,
|
copy.schedule.mon,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
tueTime: copy.schedule.tue
|
tueTime: copy.schedule.tue
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.tue,
|
copy.schedule.tue,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
wedTime: copy.schedule.wed
|
wedTime: copy.schedule.wed
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.wed,
|
copy.schedule.wed,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
thuTime: copy.schedule.thu
|
thuTime: copy.schedule.thu
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.thu,
|
copy.schedule.thu,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
friTime: copy.schedule.fri
|
friTime: copy.schedule.fri
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.fri,
|
copy.schedule.fri,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
satTime: copy.schedule.sat
|
satTime: copy.schedule.sat
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.sat,
|
copy.schedule.sat,
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
sunTime: copy.schedule.sun
|
sunTime: copy.schedule.sun
|
||||||
? this.timeConverter.dateTimeToUtc(
|
? this.timeConverter.localDateTimeToUtc(
|
||||||
copy.fromDate,
|
copy.fromDate,
|
||||||
copy.schedule.sun,
|
copy.schedule.sun,
|
||||||
timezone,
|
timezone,
|
||||||
|
@ -143,6 +143,11 @@ export class AdMapper
|
||||||
};
|
};
|
||||||
|
|
||||||
toDomain = (record: AdReadModel): AdEntity => {
|
toDomain = (record: AdReadModel): AdEntity => {
|
||||||
|
const timezone = this.timezoneFinder.timezones(
|
||||||
|
record.waypoints[0].lon,
|
||||||
|
record.waypoints[0].lat,
|
||||||
|
this.defaultParams.DEFAULT_TIMEZONE,
|
||||||
|
)[0];
|
||||||
const entity = new AdEntity({
|
const entity = new AdEntity({
|
||||||
id: record.uuid,
|
id: record.uuid,
|
||||||
createdAt: new Date(record.createdAt),
|
createdAt: new Date(record.createdAt),
|
||||||
|
@ -152,13 +157,23 @@ export class AdMapper
|
||||||
driver: record.driver,
|
driver: record.driver,
|
||||||
passenger: record.passenger,
|
passenger: record.passenger,
|
||||||
frequency: Frequency[record.frequency],
|
frequency: Frequency[record.frequency],
|
||||||
fromDate: record.fromDate.toISOString(),
|
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||||
toDate: record.toDate.toISOString(),
|
toDate: record.toDate.toISOString().split('T')[0],
|
||||||
schedule: {
|
schedule: {
|
||||||
mon: record.monTime?.toISOString(),
|
mon: record.monTime?.toISOString(),
|
||||||
tue: record.tueTime?.toISOString(),
|
tue: record.tueTime?.toISOString(),
|
||||||
wed: record.wedTime?.toISOString(),
|
wed: record.wedTime
|
||||||
thu: record.thuTime?.toISOString(),
|
? this.timeConverter.utcDatetimeToLocalTime(
|
||||||
|
record.wedTime.toISOString(),
|
||||||
|
timezone,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
thu: record.thuTime
|
||||||
|
? this.timeConverter.utcDatetimeToLocalTime(
|
||||||
|
record.thuTime.toISOString(),
|
||||||
|
timezone,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
fri: record.friTime?.toISOString(),
|
fri: record.friTime?.toISOString(),
|
||||||
sat: record.satTime?.toISOString(),
|
sat: record.satTime?.toISOString(),
|
||||||
sun: record.sunTime?.toISOString(),
|
sun: record.sunTime?.toISOString(),
|
||||||
|
@ -198,7 +213,27 @@ export class AdMapper
|
||||||
toResponse = (entity: AdEntity): AdResponseDto => {
|
toResponse = (entity: AdEntity): AdResponseDto => {
|
||||||
const props = entity.getProps();
|
const props = entity.getProps();
|
||||||
const response = new AdResponseDto(entity);
|
const response = new AdResponseDto(entity);
|
||||||
response.uuid = props.id;
|
response.userId = props.userId;
|
||||||
|
response.driver = props.driver;
|
||||||
|
response.passenger = props.passenger;
|
||||||
|
response.frequency = props.frequency;
|
||||||
|
response.fromDate = props.fromDate;
|
||||||
|
response.toDate = props.toDate;
|
||||||
|
response.schedule = { ...props.schedule };
|
||||||
|
response.marginDurations = { ...props.marginDurations };
|
||||||
|
response.seatsProposed = props.seatsProposed;
|
||||||
|
response.seatsRequested = props.seatsRequested;
|
||||||
|
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||||
|
position: waypoint.position,
|
||||||
|
name: waypoint.address.name,
|
||||||
|
houseNumber: waypoint.address.houseNumber,
|
||||||
|
street: waypoint.address.street,
|
||||||
|
postalCode: waypoint.address.postalCode,
|
||||||
|
locality: waypoint.address.locality,
|
||||||
|
country: waypoint.address.country,
|
||||||
|
lon: waypoint.address.coordinates.lon,
|
||||||
|
lat: waypoint.address.coordinates.lat,
|
||||||
|
}));
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,15 @@ import { CreateAdService } from './core/commands/create-ad/create-ad.service';
|
||||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||||
import { PrismaService } from '@libs/db/prisma.service';
|
import { PrismaService } from '@libs/db/prisma.service';
|
||||||
import { TimeConverter } from './infrastructure/time-converter';
|
import { TimeConverter } from './infrastructure/time-converter';
|
||||||
|
import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller';
|
||||||
|
import { FindAdByIdQueryHandler } from './core/queries/find-ad-by-id/find-ad-by-id.query-handler';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CqrsModule],
|
imports: [CqrsModule],
|
||||||
controllers: [CreateAdGrpcController],
|
controllers: [CreateAdGrpcController, FindAdByIdGrpcController],
|
||||||
providers: [
|
providers: [
|
||||||
CreateAdService,
|
CreateAdService,
|
||||||
|
FindAdByIdQueryHandler,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
AdMapper,
|
AdMapper,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
export interface TimeConverterPort {
|
export interface TimeConverterPort {
|
||||||
dateTimeToUtc(
|
localDateTimeToUtc(
|
||||||
date: string,
|
date: string,
|
||||||
time: string,
|
time: string,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
dst?: boolean,
|
dst?: boolean,
|
||||||
): Date;
|
): Date;
|
||||||
|
utcDatetimeToLocalTime(isoString: string, timezone: string): string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { FindAdByIdQuery } from './find-ad-by-id.query';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { AdEntity } from '../../ad.entity';
|
||||||
|
|
||||||
|
@QueryHandler(FindAdByIdQuery)
|
||||||
|
export class FindAdByIdQueryHandler implements IQueryHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
) {}
|
||||||
|
async execute(query: FindAdByIdQuery): Promise<AdEntity> {
|
||||||
|
return await this.repository.findOneById(query.id, { waypoints: true });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import { FindAdByIdRequestDTO } from '../../../interface/queries/find-ad-by-id/dtos/find-ad-by-id.request.dto';
|
import { QueryBase } from '@libs/ddd/query.base';
|
||||||
|
|
||||||
export class FindAdByIdQuery {
|
export class FindAdByIdQuery extends QueryBase {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
|
||||||
constructor(findAdByIdRequestDTO: FindAdByIdRequestDTO) {
|
constructor(id: string) {
|
||||||
this.id = findAdByIdRequestDTO.id;
|
super();
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { DateTime, TimeZone } from 'timezonecomplete';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimeConverter implements TimeConverterPort {
|
export class TimeConverter implements TimeConverterPort {
|
||||||
dateTimeToUtc = (
|
localDateTimeToUtc = (
|
||||||
date: string,
|
date: string,
|
||||||
time: string,
|
time: string,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
|
@ -21,4 +21,16 @@ export class TimeConverter implements TimeConverterPort {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
utcDatetimeToLocalTime = (isoString: string, timezone: string): string => {
|
||||||
|
try {
|
||||||
|
return new DateTime(isoString)
|
||||||
|
.convert(TimeZone.zone(timezone))
|
||||||
|
.toString()
|
||||||
|
.split('T')[1]
|
||||||
|
.substring(0, 5);
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
|
||||||
|
|
||||||
export class AdPresenter {
|
|
||||||
@AutoMap()
|
|
||||||
id: string;
|
|
||||||
}
|
|
|
@ -1,5 +1,43 @@
|
||||||
import { ResponseBase } from '@libs/api/response.base';
|
import { ResponseBase } from '@libs/api/response.base';
|
||||||
|
import { Frequency } from '@modules/ad/core/ad.types';
|
||||||
|
|
||||||
export class AdResponseDto extends ResponseBase {
|
export class AdResponseDto extends ResponseBase {
|
||||||
uuid: string;
|
userId: string;
|
||||||
|
driver: boolean;
|
||||||
|
passenger: boolean;
|
||||||
|
frequency: Frequency;
|
||||||
|
fromDate: string;
|
||||||
|
toDate: string;
|
||||||
|
schedule: {
|
||||||
|
mon?: string;
|
||||||
|
tue?: string;
|
||||||
|
wed?: string;
|
||||||
|
thu?: string;
|
||||||
|
fri?: string;
|
||||||
|
sat?: string;
|
||||||
|
sun?: string;
|
||||||
|
};
|
||||||
|
marginDurations: {
|
||||||
|
mon?: number;
|
||||||
|
tue?: number;
|
||||||
|
wed?: number;
|
||||||
|
thu?: number;
|
||||||
|
fri?: number;
|
||||||
|
sat?: number;
|
||||||
|
sun?: number;
|
||||||
|
};
|
||||||
|
seatsProposed: number;
|
||||||
|
seatsRequested: number;
|
||||||
|
strict: boolean;
|
||||||
|
waypoints: {
|
||||||
|
position: number;
|
||||||
|
name?: string;
|
||||||
|
houseNumber?: string;
|
||||||
|
street?: string;
|
||||||
|
postalCode?: string;
|
||||||
|
locality?: string;
|
||||||
|
country: string;
|
||||||
|
lon: number;
|
||||||
|
lat: number;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
|
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
|
||||||
import { AdPresenter } from '../ad.presenter';
|
import { CreateAdRequestDto } from './dtos/create-ad.request.dto';
|
||||||
import { CreateAdRequestDTO } from './dtos/create-ad.request.dto';
|
|
||||||
import { CreateAdCommand } from '../../core/commands/create-ad/create-ad.command';
|
import { CreateAdCommand } from '../../core/commands/create-ad/create-ad.command';
|
||||||
import { Result, match } from 'oxide.ts';
|
import { Result, match } from 'oxide.ts';
|
||||||
import { AggregateID } from '@libs/ddd';
|
import { AggregateID } from '@libs/ddd';
|
||||||
|
@ -21,7 +20,7 @@ export class CreateAdGrpcController {
|
||||||
constructor(private readonly commandBus: CommandBus) {}
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
@GrpcMethod('AdsService', 'Create')
|
@GrpcMethod('AdsService', 'Create')
|
||||||
async create(data: CreateAdRequestDTO): Promise<AdPresenter> {
|
async create(data: CreateAdRequestDto): Promise<IdResponse> {
|
||||||
const result: Result<AggregateID, AdAlreadyExistsError> =
|
const result: Result<AggregateID, AdAlreadyExistsError> =
|
||||||
await this.commandBus.execute(new CreateAdCommand(data));
|
await this.commandBus.execute(new CreateAdCommand(data));
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
import { AutoMap } from '@automapper/classes';
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import { CoordinatesDTO } from './coordinates.dto';
|
import { CoordinatesDto as CoordinatesDto } from './coordinates.dto';
|
||||||
|
|
||||||
export class AddressDTO extends CoordinatesDTO {
|
export class AddressDto extends CoordinatesDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
import { AutoMap } from '@automapper/classes';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
import { IsLatitude, IsLongitude } from 'class-validator';
|
import { IsLatitude, IsLongitude } from 'class-validator';
|
||||||
|
import { toPrecision } from './validators/to-precision';
|
||||||
|
|
||||||
export class CoordinatesDTO {
|
export class CoordinatesDto {
|
||||||
|
@Transform(({ value }) => toPrecision(value, 6), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
@IsLongitude()
|
@IsLongitude()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
lon: number;
|
lon: number;
|
||||||
|
|
||||||
|
@Transform(({ value }) => toPrecision(value, 6), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
@IsLatitude()
|
@IsLatitude()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
lat: number;
|
lat: number;
|
||||||
|
|
|
@ -11,15 +11,15 @@ import {
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Transform, Type } from 'class-transformer';
|
import { Transform, Type } from 'class-transformer';
|
||||||
import { ScheduleDTO } from './schedule.dto';
|
import { ScheduleDto } from './schedule.dto';
|
||||||
import { MarginDurationsDTO } from './margin-durations.dto';
|
import { MarginDurationsDto } from './margin-durations.dto';
|
||||||
import { WaypointDTO } from './waypoint.dto';
|
import { WaypointDto } from './waypoint.dto';
|
||||||
import { intToFrequency } from './validators/frequency.mapping';
|
import { intToFrequency } from './validators/frequency.mapping';
|
||||||
import { IsSchedule } from './validators/decorators/is-schedule.validator';
|
import { IsSchedule } from './validators/decorators/is-schedule.validator';
|
||||||
import { HasValidPositionIndexes } from './validators/decorators/valid-position-indexes.validator';
|
import { HasValidPositionIndexes } from './validators/decorators/valid-position-indexes.validator';
|
||||||
import { Frequency } from '@modules/ad/core/ad.types';
|
import { Frequency } from '@modules/ad/core/ad.types';
|
||||||
|
|
||||||
export class CreateAdRequestDTO {
|
export class CreateAdRequestDto {
|
||||||
@IsUUID(4)
|
@IsUUID(4)
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -55,17 +55,17 @@ export class CreateAdRequestDTO {
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
toDate: string;
|
toDate: string;
|
||||||
|
|
||||||
@Type(() => ScheduleDTO)
|
@Type(() => ScheduleDto)
|
||||||
@IsSchedule()
|
@IsSchedule()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
schedule: ScheduleDTO;
|
schedule: ScheduleDto;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => MarginDurationsDTO)
|
@Type(() => MarginDurationsDto)
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
marginDurations?: MarginDurationsDTO;
|
marginDurations?: MarginDurationsDto;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
@ -82,10 +82,11 @@ export class CreateAdRequestDTO {
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
strict?: boolean;
|
strict?: boolean;
|
||||||
|
|
||||||
|
@Type(() => WaypointDto)
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ArrayMinSize(2)
|
@ArrayMinSize(2)
|
||||||
@HasValidPositionIndexes()
|
@HasValidPositionIndexes()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
waypoints: WaypointDTO[];
|
waypoints: WaypointDto[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class FindAdByIdRequestDTO {
|
export class FindAdByIdRequestDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
id: string;
|
id: string;
|
|
@ -1,7 +1,7 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
import { AutoMap } from '@automapper/classes';
|
||||||
import { IsInt, IsOptional } from 'class-validator';
|
import { IsInt, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
export class MarginDurationsDTO {
|
export class MarginDurationsDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
import { AutoMap } from '@automapper/classes';
|
||||||
import { IsOptional, IsMilitaryTime } from 'class-validator';
|
import { IsOptional, IsMilitaryTime } from 'class-validator';
|
||||||
|
|
||||||
export class ScheduleDTO {
|
export class ScheduleDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsMilitaryTime()
|
@IsMilitaryTime()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator';
|
import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator';
|
||||||
import { hasValidPositionIndexes } from '../waypoint-position';
|
import { hasValidPositionIndexes } from '../waypoint-position';
|
||||||
import { WaypointDTO } from '../../waypoint.dto';
|
import { WaypointDto } from '../../waypoint.dto';
|
||||||
|
|
||||||
export const HasValidPositionIndexes = (
|
export const HasValidPositionIndexes = (
|
||||||
validationOptions?: ValidationOptions,
|
validationOptions?: ValidationOptions,
|
||||||
|
@ -10,7 +10,7 @@ export const HasValidPositionIndexes = (
|
||||||
name: '',
|
name: '',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
validator: {
|
validator: {
|
||||||
validate: (waypoints: WaypointDTO[]): boolean =>
|
validate: (waypoints: WaypointDto[]): boolean =>
|
||||||
hasValidPositionIndexes(waypoints),
|
hasValidPositionIndexes(waypoints),
|
||||||
defaultMessage: buildMessage(
|
defaultMessage: buildMessage(
|
||||||
() => `invalid waypoints positions`,
|
() => `invalid waypoints positions`,
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const toPrecision = (input: number, precision: number): number => {
|
||||||
|
const multiplier = 10 ** precision;
|
||||||
|
return Math.round((input + Number.EPSILON) * multiplier) / multiplier;
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import { WaypointDTO } from '../waypoint.dto';
|
import { WaypointDto } from '../waypoint.dto';
|
||||||
|
|
||||||
export const hasValidPositionIndexes = (waypoints: WaypointDTO[]): boolean => {
|
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
||||||
if (!waypoints) return;
|
if (!waypoints) return;
|
||||||
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
import { AutoMap } from '@automapper/classes';
|
||||||
import { IsInt, IsOptional } from 'class-validator';
|
import { IsInt, IsOptional } from 'class-validator';
|
||||||
import { AddressDTO } from './address.dto';
|
import { AddressDto } from './address.dto';
|
||||||
|
|
||||||
export class WaypointDTO extends AddressDTO {
|
export class WaypointDto extends AddressDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@AutoMap()
|
@AutoMap()
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
|
import { RpcValidationPipe } from '@utils/pipes/rpc.validation-pipe';
|
||||||
|
import { FindAdByIdRequestDto } from './dtos/find-ad-by-id.request.dto';
|
||||||
|
import { FindAdByIdQuery } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query';
|
||||||
|
import { AdResponseDto } from '../dtos/ad.response.dto';
|
||||||
|
import { AdEntity } from '@modules/ad/core/ad.entity';
|
||||||
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: false,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
@Controller()
|
||||||
|
export class FindAdByIdGrpcController {
|
||||||
|
constructor(
|
||||||
|
protected readonly mapper: AdMapper,
|
||||||
|
private readonly queryBus: QueryBus,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@GrpcMethod('AdsService', 'FindOneById')
|
||||||
|
async findOnebyId(data: FindAdByIdRequestDto): Promise<AdResponseDto> {
|
||||||
|
try {
|
||||||
|
const ad: AdEntity = await this.queryBus.execute(
|
||||||
|
new FindAdByIdQuery(data.id),
|
||||||
|
);
|
||||||
|
return this.mapper.toResponse(ad);
|
||||||
|
} catch (e) {
|
||||||
|
throw new RpcException({
|
||||||
|
code: e.code,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
import { Mapper } from '@automapper/core';
|
|
||||||
import { InjectMapper } from '@automapper/nestjs';
|
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
|
||||||
import { RpcValidationPipe } from '../../../../../utils/pipes/rpc.validation-pipe';
|
|
||||||
import { FindAdByIdRequestDTO } from './dtos/find-ad-by-id.request.dto';
|
|
||||||
import { AdPresenter } from '../../ad.presenter';
|
|
||||||
import { FindAdByIdQuery } from '../../../core/queries/find-ad-by-id/find-ad-by-id.query';
|
|
||||||
import { AdEntity } from '../../../core/ad.entity';
|
|
||||||
|
|
||||||
@UsePipes(
|
|
||||||
new RpcValidationPipe({
|
|
||||||
whitelist: false,
|
|
||||||
forbidUnknownValues: false,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@Controller()
|
|
||||||
export class FindAdByIdGrpcController {
|
|
||||||
constructor(
|
|
||||||
private readonly queryBus: QueryBus,
|
|
||||||
@InjectMapper() private readonly mapper: Mapper,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@GrpcMethod('AdsService', 'FindOneById')
|
|
||||||
async findOnebyId(data: FindAdByIdRequestDTO): Promise<AdPresenter> {
|
|
||||||
try {
|
|
||||||
const ad = await this.queryBus.execute(new FindAdByIdQuery(data));
|
|
||||||
return this.mapper.map(ad, AdEntity, AdPresenter);
|
|
||||||
} catch (e) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: e.code,
|
|
||||||
message: e.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,12 @@ import {
|
||||||
AD_REPOSITORY,
|
AD_REPOSITORY,
|
||||||
PARAMS_PROVIDER,
|
PARAMS_PROVIDER,
|
||||||
TIMEZONE_FINDER,
|
TIMEZONE_FINDER,
|
||||||
|
TIME_CONVERTER,
|
||||||
} from '@modules/ad/ad.di-tokens';
|
} from '@modules/ad/ad.di-tokens';
|
||||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||||
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
|
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
|
||||||
|
import { TimeConverter } from '@modules/ad/infrastructure/time-converter';
|
||||||
import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder';
|
import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
@ -274,6 +276,10 @@ describe('Ad Repository', () => {
|
||||||
provide: TIMEZONE_FINDER,
|
provide: TIMEZONE_FINDER,
|
||||||
useClass: TimezoneFinder,
|
useClass: TimezoneFinder,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: TIME_CONVERTER,
|
||||||
|
useClass: TimeConverter,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
prismaService = module.get<PrismaService>(PrismaService);
|
prismaService = module.get<PrismaService>(PrismaService);
|
||||||
|
@ -416,7 +422,7 @@ describe('Ad Repository', () => {
|
||||||
waypoints: true,
|
waypoints: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.unwrap().id).toBe(baseUuid.uuid);
|
expect(result.id).toBe(baseUuid.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
// it('should return null', async () => {
|
// it('should return null', async () => {
|
||||||
|
|
|
@ -45,8 +45,8 @@ const adEntity: AdEntity = new AdEntity({
|
||||||
postalCode: '54000',
|
postalCode: '54000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lat: 48.68944505415954,
|
lat: 48.689445,
|
||||||
lon: 6.176510296462267,
|
lon: 6.1765102,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -103,8 +103,8 @@ const adReadModel: AdReadModel = {
|
||||||
locality: 'Nancy',
|
locality: 'Nancy',
|
||||||
postalCode: '54000',
|
postalCode: '54000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
lat: 48.68944505415954,
|
lat: 48.689445,
|
||||||
lon: 6.176510296462267,
|
lon: 6.1765102,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
},
|
},
|
||||||
|
@ -163,12 +163,13 @@ const mockTimezoneFinder: TimezoneFinderPort = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockTimeConverter: TimeConverterPort = {
|
const mockTimeConverter: TimeConverterPort = {
|
||||||
dateTimeToUtc: jest
|
localDateTimeToUtc: jest
|
||||||
.fn()
|
.fn()
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
|
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
|
||||||
return datetime;
|
return datetime;
|
||||||
}),
|
}),
|
||||||
|
utcDatetimeToLocalTime: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Ad Mapper', () => {
|
describe('Ad Mapper', () => {
|
||||||
|
@ -208,13 +209,13 @@ describe('Ad Mapper', () => {
|
||||||
it('should map persisted data to domain entity', async () => {
|
it('should map persisted data to domain entity', async () => {
|
||||||
const mapped: AdEntity = adMapper.toDomain(adReadModel);
|
const mapped: AdEntity = adMapper.toDomain(adReadModel);
|
||||||
expect(mapped.getProps().waypoints[0].address.coordinates.lat).toBe(
|
expect(mapped.getProps().waypoints[0].address.coordinates.lat).toBe(
|
||||||
48.68944505415954,
|
48.689445,
|
||||||
);
|
);
|
||||||
expect(mapped.getProps().waypoints[1].address.coordinates.lon).toBe(2.3522);
|
expect(mapped.getProps().waypoints[1].address.coordinates.lon).toBe(2.3522);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map domain entity to response', async () => {
|
it('should map domain entity to response', async () => {
|
||||||
const mapped: AdResponseDto = adMapper.toResponse(adEntity);
|
const mapped: AdResponseDto = adMapper.toResponse(adEntity);
|
||||||
expect(mapped.uuid).toBe('c160cf8c-f057-4962-841f-3ad68346df44');
|
expect(mapped.id).toBe('c160cf8c-f057-4962-841f-3ad68346df44');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { CreateAdService } from '@modules/ad/core/commands/create-ad/create-ad.service';
|
import { CreateAdService } from '@modules/ad/core/commands/create-ad/create-ad.service';
|
||||||
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
|
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port';
|
import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port';
|
||||||
import { WaypointDTO } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
import { CreateAdRequestDTO } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
||||||
import { Frequency } from '@modules/ad/core/ad.types';
|
import { Frequency } from '@modules/ad/core/ad.types';
|
||||||
import { CreateAdCommand } from '@modules/ad/core/commands/create-ad/create-ad.command';
|
import { CreateAdCommand } from '@modules/ad/core/commands/create-ad/create-ad.command';
|
||||||
import { Result } from 'oxide.ts';
|
import { Result } from 'oxide.ts';
|
||||||
|
@ -12,7 +12,7 @@ import { AdAlreadyExistsError } from '@modules/ad/core/ad.errors';
|
||||||
import { AdEntity } from '@modules/ad/core/ad.entity';
|
import { AdEntity } from '@modules/ad/core/ad.entity';
|
||||||
import { ConflictException } from '@libs/exceptions';
|
import { ConflictException } from '@libs/exceptions';
|
||||||
|
|
||||||
const originWaypoint: WaypointDTO = {
|
const originWaypoint: WaypointDto = {
|
||||||
position: 0,
|
position: 0,
|
||||||
lon: 48.68944505415954,
|
lon: 48.68944505415954,
|
||||||
lat: 6.176510296462267,
|
lat: 6.176510296462267,
|
||||||
|
@ -22,7 +22,7 @@ const originWaypoint: WaypointDTO = {
|
||||||
postalCode: '54000',
|
postalCode: '54000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const destinationWaypoint: WaypointDTO = {
|
const destinationWaypoint: WaypointDto = {
|
||||||
position: 1,
|
position: 1,
|
||||||
lon: 48.8566,
|
lon: 48.8566,
|
||||||
lat: 2.3522,
|
lat: 2.3522,
|
||||||
|
@ -30,7 +30,7 @@ const destinationWaypoint: WaypointDTO = {
|
||||||
postalCode: '75000',
|
postalCode: '75000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const punctualCreateAdRequest: CreateAdRequestDTO = {
|
const punctualCreateAdRequest: CreateAdRequestDto = {
|
||||||
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
|
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
|
||||||
fromDate: '2023-12-21',
|
fromDate: '2023-12-21',
|
||||||
toDate: '2023-12-21',
|
toDate: '2023-12-21',
|
||||||
|
|
|
@ -5,48 +5,81 @@ describe('Time Converter', () => {
|
||||||
const timeConverter: TimeConverter = new TimeConverter();
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
expect(timeConverter).toBeDefined();
|
expect(timeConverter).toBeDefined();
|
||||||
});
|
});
|
||||||
it('should convert a paris datetime to utc', () => {
|
|
||||||
const timeConverter: TimeConverter = new TimeConverter();
|
describe('localDateTimeToUtc', () => {
|
||||||
const parisDate = '2023-06-22';
|
it('should convert a paris datetime to utc', () => {
|
||||||
const parisTime = '08:00';
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
const utcDatetime = timeConverter.dateTimeToUtc(
|
const parisDate = '2023-06-22';
|
||||||
parisDate,
|
const parisTime = '08:00';
|
||||||
parisTime,
|
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||||
'Europe/Paris',
|
parisDate,
|
||||||
);
|
parisTime,
|
||||||
expect(utcDatetime.toISOString()).toEqual('2023-06-22T06:00:00.000Z');
|
'Europe/Paris',
|
||||||
|
);
|
||||||
|
expect(utcDatetime.toISOString()).toEqual('2023-06-22T06:00:00.000Z');
|
||||||
|
});
|
||||||
|
it('should return undefined if date is invalid', () => {
|
||||||
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
|
const parisDate = '2023-16-22';
|
||||||
|
const parisTime = '08:00';
|
||||||
|
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||||
|
parisDate,
|
||||||
|
parisTime,
|
||||||
|
'Europe/Paris',
|
||||||
|
);
|
||||||
|
expect(utcDatetime).toBeUndefined();
|
||||||
|
});
|
||||||
|
it('should return undefined if time is invalid', () => {
|
||||||
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
|
const parisDate = '2023-06-22';
|
||||||
|
const parisTime = '28:00';
|
||||||
|
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||||
|
parisDate,
|
||||||
|
parisTime,
|
||||||
|
'Europe/Paris',
|
||||||
|
);
|
||||||
|
expect(utcDatetime).toBeUndefined();
|
||||||
|
});
|
||||||
|
it('should return undefined if timezone is invalid', () => {
|
||||||
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
|
const parisDate = '2023-06-22';
|
||||||
|
const parisTime = '08:00';
|
||||||
|
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||||
|
parisDate,
|
||||||
|
parisTime,
|
||||||
|
'Foo/Bar',
|
||||||
|
);
|
||||||
|
expect(utcDatetime).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should return undefined if date is invalid', () => {
|
|
||||||
const timeConverter: TimeConverter = new TimeConverter();
|
describe('utcDatetimeToLocalTime', () => {
|
||||||
const parisDate = '2023-16-22';
|
it('should convert an utc datetime isostring to a paris local time', () => {
|
||||||
const parisTime = '08:00';
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
const utcDatetime = timeConverter.dateTimeToUtc(
|
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||||
parisDate,
|
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||||
parisTime,
|
utcDatetimeIsostring,
|
||||||
'Europe/Paris',
|
'Europe/Paris',
|
||||||
);
|
);
|
||||||
expect(utcDatetime).toBeUndefined();
|
expect(parisTime).toBe('08:25');
|
||||||
});
|
});
|
||||||
it('should return undefined if time is invalid', () => {
|
it('should return undefined if isostring input is invalid', () => {
|
||||||
const timeConverter: TimeConverter = new TimeConverter();
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
const parisDate = '2023-06-22';
|
const utcDatetimeIsostring = 'not_an_isostring';
|
||||||
const parisTime = '28:00';
|
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||||
const utcDatetime = timeConverter.dateTimeToUtc(
|
utcDatetimeIsostring,
|
||||||
parisDate,
|
'Europe/Paris',
|
||||||
parisTime,
|
);
|
||||||
'Europe/Paris',
|
expect(parisTime).toBeUndefined();
|
||||||
);
|
});
|
||||||
expect(utcDatetime).toBeUndefined();
|
it('should return undefined if timezone input is invalid', () => {
|
||||||
});
|
const timeConverter: TimeConverter = new TimeConverter();
|
||||||
it('should return undefined if timezone is invalid', () => {
|
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||||
const timeConverter: TimeConverter = new TimeConverter();
|
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||||
const parisDate = '2023-06-22';
|
utcDatetimeIsostring,
|
||||||
const parisTime = '08:00';
|
'Foo/Bar',
|
||||||
const utcDatetime = timeConverter.dateTimeToUtc(
|
);
|
||||||
parisDate,
|
expect(parisTime).toBeUndefined();
|
||||||
parisTime,
|
});
|
||||||
'Foo/Bar',
|
|
||||||
);
|
|
||||||
expect(utcDatetime).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { hasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/dtos/validators/waypoint-position';
|
import { hasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/dtos/validators/waypoint-position';
|
||||||
import { WaypointDTO } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
|
|
||||||
describe('addresses position validator', () => {
|
describe('addresses position validator', () => {
|
||||||
const mockAddress1: WaypointDTO = {
|
const mockAddress1: WaypointDto = {
|
||||||
lon: 48.68944505415954,
|
lon: 48.68944505415954,
|
||||||
lat: 6.176510296462267,
|
lat: 6.176510296462267,
|
||||||
houseNumber: '5',
|
houseNumber: '5',
|
||||||
|
@ -11,14 +11,14 @@ describe('addresses position validator', () => {
|
||||||
postalCode: '54000',
|
postalCode: '54000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const mockAddress2: WaypointDTO = {
|
const mockAddress2: WaypointDto = {
|
||||||
lon: 48.8566,
|
lon: 48.8566,
|
||||||
lat: 2.3522,
|
lat: 2.3522,
|
||||||
locality: 'Paris',
|
locality: 'Paris',
|
||||||
postalCode: '75000',
|
postalCode: '75000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const mockAddress3: WaypointDTO = {
|
const mockAddress3: WaypointDto = {
|
||||||
lon: 49.2628,
|
lon: 49.2628,
|
||||||
lat: 4.0347,
|
lat: 4.0347,
|
||||||
locality: 'Reims',
|
locality: 'Reims',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ArgumentMetadata } from '@nestjs/common';
|
import { ArgumentMetadata } from '@nestjs/common';
|
||||||
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
|
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
|
||||||
import { FindAdByIdRequestDTO } from '../../../modules/ad/interface/queries/find-ad-by-id/dtos/find-ad-by-id.request.dto';
|
import { FindAdByIdRequestDto } from '../../../modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto';
|
||||||
|
|
||||||
describe('RpcValidationPipe', () => {
|
describe('RpcValidationPipe', () => {
|
||||||
it('should not validate request', async () => {
|
it('should not validate request', async () => {
|
||||||
|
@ -10,10 +10,10 @@ describe('RpcValidationPipe', () => {
|
||||||
});
|
});
|
||||||
const metadata: ArgumentMetadata = {
|
const metadata: ArgumentMetadata = {
|
||||||
type: 'body',
|
type: 'body',
|
||||||
metatype: FindAdByIdRequestDTO,
|
metatype: FindAdByIdRequestDto,
|
||||||
data: '',
|
data: '',
|
||||||
};
|
};
|
||||||
await target.transform(<FindAdByIdRequestDTO>{}, metadata).catch((err) => {
|
await target.transform(<FindAdByIdRequestDto>{}, metadata).catch((err) => {
|
||||||
expect(err.message).toEqual('Rpc Exception');
|
expect(err.message).toEqual('Rpc Exception');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue