wip
This commit is contained in:
parent
81cc4c019e
commit
c530bc55f5
|
@ -16,8 +16,8 @@ REDIS_HOST=v3-redis
|
|||
REDIS_PASSWORD=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# DEFAULT CARPOOL DEPARTURE MARGIN (in seconds)
|
||||
DEPARTURE_MARGIN=900
|
||||
# DEFAULT CARPOOL DEPARTURE TIME MARGIN (in seconds)
|
||||
DEPARTURE_TIME_MARGIN=900
|
||||
|
||||
# DEFAULT ROLE
|
||||
ROLE=passenger
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
|
||||
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"migrate": "docker exec v3-auth-api sh -c 'npx prisma migrate dev'",
|
||||
"migrate": "docker exec v3-ad-api sh -c 'npx prisma migrate dev'",
|
||||
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
|
||||
"migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
|
||||
"migrate:deploy": "npx prisma migrate deploy"
|
||||
|
@ -97,7 +97,7 @@
|
|||
"main.ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"testRegex": ".converter.*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
|
|
|
@ -10,20 +10,6 @@ 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,
|
||||
"monMargin" INTEGER NOT NULL,
|
||||
"tueMargin" INTEGER NOT NULL,
|
||||
"wedMargin" INTEGER NOT NULL,
|
||||
"thuMargin" INTEGER NOT NULL,
|
||||
"friMargin" INTEGER NOT NULL,
|
||||
"satMargin" INTEGER NOT NULL,
|
||||
"sunMargin" INTEGER NOT NULL,
|
||||
"seatsProposed" SMALLINT NOT NULL,
|
||||
"seatsRequested" SMALLINT NOT NULL,
|
||||
"strict" BOOLEAN NOT NULL,
|
||||
|
@ -33,6 +19,19 @@ CREATE TABLE "ad" (
|
|||
CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "schedule_item" (
|
||||
"uuid" UUID NOT NULL,
|
||||
"adUuid" UUID NOT NULL,
|
||||
"day" INTEGER NOT NULL,
|
||||
"time" TIME(4) NOT NULL,
|
||||
"margin" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "schedule_item_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "waypoint" (
|
||||
"uuid" UUID NOT NULL,
|
||||
|
@ -52,5 +51,8 @@ CREATE TABLE "waypoint" (
|
|||
CONSTRAINT "waypoint_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_item" ADD CONSTRAINT "schedule_item_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "waypoint" ADD CONSTRAINT "waypoint_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -12,37 +12,37 @@ datasource db {
|
|||
}
|
||||
|
||||
model Ad {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
userUuid String @db.Uuid
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
userUuid String @db.Uuid
|
||||
driver Boolean
|
||||
passenger Boolean
|
||||
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()
|
||||
monMargin Int
|
||||
tueMargin Int
|
||||
wedMargin Int
|
||||
thuMargin Int
|
||||
friMargin Int
|
||||
satMargin Int
|
||||
sunMargin Int
|
||||
seatsProposed Int @db.SmallInt
|
||||
seatsRequested Int @db.SmallInt
|
||||
fromDate DateTime @db.Date
|
||||
toDate DateTime @db.Date
|
||||
schedule ScheduleItem[]
|
||||
seatsProposed Int @db.SmallInt
|
||||
seatsRequested Int @db.SmallInt
|
||||
strict Boolean
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
waypoints Waypoint[]
|
||||
|
||||
@@map("ad")
|
||||
}
|
||||
|
||||
model ScheduleItem {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
adUuid String @db.Uuid
|
||||
day Int
|
||||
time DateTime @db.Time(4)
|
||||
margin Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
Ad Ad @relation(fields: [adUuid], references: [uuid], onDelete: Cascade)
|
||||
|
||||
@@map("schedule_item")
|
||||
}
|
||||
|
||||
model Waypoint {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
adUuid String @db.Uuid
|
||||
|
|
|
@ -17,7 +17,7 @@ async function bootstrap() {
|
|||
join(__dirname, 'health.proto'),
|
||||
],
|
||||
url: `${process.env.SERVICE_URL}:${process.env.SERVICE_PORT}`,
|
||||
loader: { keepCase: true },
|
||||
loader: { keepCase: true, enums: String },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import { Mapper } from '@mobicoop/ddd-library';
|
||||
import { AdResponseDto } from './interface/dtos/ad.response.dto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AdEntity } from './core/domain/ad.entity';
|
||||
import {
|
||||
AdWriteModel,
|
||||
AdReadModel,
|
||||
WaypointModel,
|
||||
ScheduleItemModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
import { Frequency } from './core/domain/ad.types';
|
||||
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from './ad.di-tokens';
|
||||
import { TimezoneFinderPort } from './core/application/ports/timezone-finder.port';
|
||||
import { DefaultParamsProviderPort } from './core/application/ports/default-params-provider.port';
|
||||
import { DefaultParams } from './core/application/ports/default-params.type';
|
||||
import { TimeConverterPort } from './core/application/ports/time-converter.port';
|
||||
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
|
||||
|
||||
/**
|
||||
* Mapper constructs objects that are used in different layers:
|
||||
|
@ -31,27 +24,8 @@ import { TimeConverterPort } from './core/application/ports/time-converter.port'
|
|||
export class AdMapper
|
||||
implements Mapper<AdEntity, AdReadModel, AdWriteModel, AdResponseDto>
|
||||
{
|
||||
private readonly _defaultParams: DefaultParams;
|
||||
|
||||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER)
|
||||
private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
|
||||
toPersistence = (entity: AdEntity): AdWriteModel => {
|
||||
const copy = entity.getProps();
|
||||
const { lon, lat } = copy.waypoints[0].address.coordinates;
|
||||
const timezone = this.timezoneFinder.timezones(
|
||||
lon,
|
||||
lat,
|
||||
this._defaultParams.DEFAULT_TIMEZONE,
|
||||
)[0];
|
||||
const now = new Date();
|
||||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
|
@ -61,62 +35,22 @@ export class AdMapper
|
|||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
monTime: copy.schedule.mon
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.mon,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
tueTime: copy.schedule.tue
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.tue,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
wedTime: copy.schedule.wed
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.wed,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
thuTime: copy.schedule.thu
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.thu,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
friTime: copy.schedule.fri
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.fri,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
satTime: copy.schedule.sat
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.sat,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
sunTime: copy.schedule.sun
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.sun,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
monMargin: copy.marginDurations.mon,
|
||||
tueMargin: copy.marginDurations.tue,
|
||||
wedMargin: copy.marginDurations.wed,
|
||||
thuMargin: copy.marginDurations.thu,
|
||||
friMargin: copy.marginDurations.fri,
|
||||
satMargin: copy.marginDurations.sat,
|
||||
sunMargin: copy.marginDurations.sun,
|
||||
schedule: {
|
||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
1,
|
||||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
},
|
||||
seatsProposed: copy.seatsProposed,
|
||||
seatsRequested: copy.seatsRequested,
|
||||
strict: copy.strict,
|
||||
|
@ -143,11 +77,6 @@ export class AdMapper
|
|||
};
|
||||
|
||||
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({
|
||||
id: record.uuid,
|
||||
createdAt: new Date(record.createdAt),
|
||||
|
@ -159,34 +88,17 @@ export class AdMapper
|
|||
frequency: Frequency[record.frequency],
|
||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||
toDate: record.toDate.toISOString().split('T')[0],
|
||||
schedule: {
|
||||
mon: record.monTime?.toISOString(),
|
||||
tue: record.tueTime?.toISOString(),
|
||||
wed: record.wedTime
|
||||
? this.timeConverter.utcDatetimeToLocalTime(
|
||||
record.wedTime.toISOString(),
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
thu: record.thuTime
|
||||
? this.timeConverter.utcDatetimeToLocalTime(
|
||||
record.thuTime.toISOString(),
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
fri: record.friTime?.toISOString(),
|
||||
sat: record.satTime?.toISOString(),
|
||||
sun: record.sunTime?.toISOString(),
|
||||
},
|
||||
marginDurations: {
|
||||
mon: record.monMargin,
|
||||
tue: record.tueMargin,
|
||||
wed: record.wedMargin,
|
||||
thu: record.thuMargin,
|
||||
fri: record.friMargin,
|
||||
sat: record.satMargin,
|
||||
sun: record.sunMargin,
|
||||
},
|
||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
||||
day: scheduleItem.day,
|
||||
time: `${scheduleItem.time
|
||||
.getUTCHours()
|
||||
.toString()
|
||||
.padStart(2, '0')}:${scheduleItem.time
|
||||
.getUTCMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}`,
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: record.seatsProposed,
|
||||
seatsRequested: record.seatsRequested,
|
||||
strict: record.strict,
|
||||
|
@ -219,8 +131,13 @@ export class AdMapper
|
|||
response.frequency = props.frequency;
|
||||
response.fromDate = props.fromDate;
|
||||
response.toDate = props.toDate;
|
||||
response.schedule = { ...props.schedule };
|
||||
response.marginDurations = { ...props.marginDurations };
|
||||
response.schedule = props.schedule.map(
|
||||
(scheduleItem: ScheduleItemProps) => ({
|
||||
day: scheduleItem.day,
|
||||
time: scheduleItem.time,
|
||||
margin: scheduleItem.margin,
|
||||
}),
|
||||
);
|
||||
response.seatsProposed = props.seatsProposed;
|
||||
response.seatsRequested = props.seatsRequested;
|
||||
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
|
@ -236,12 +153,4 @@ export class AdMapper
|
|||
}));
|
||||
return response;
|
||||
};
|
||||
|
||||
/* ^ Data returned to the user is whitelisted to avoid leaks.
|
||||
If a new property is added, like password or a
|
||||
credit card number, it won't be returned
|
||||
unless you specifically allow this.
|
||||
(avoid blacklisting, which will return everything
|
||||
but blacklisted items, which can lead to a data leak).
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Schedule } from '../../types/schedule';
|
||||
import { MarginDurations } from '../../types/margin-durations';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
|
@ -11,8 +10,7 @@ export class CreateAdCommand extends Command {
|
|||
readonly frequency?: Frequency;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly schedule: Schedule;
|
||||
readonly marginDurations?: MarginDurations;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly seatsProposed?: number;
|
||||
readonly seatsRequested?: number;
|
||||
readonly strict?: boolean;
|
||||
|
@ -27,7 +25,6 @@ export class CreateAdCommand extends Command {
|
|||
this.fromDate = props.fromDate;
|
||||
this.toDate = props.toDate;
|
||||
this.schedule = props.schedule;
|
||||
this.marginDurations = props.marginDurations;
|
||||
this.seatsProposed = props.seatsProposed;
|
||||
this.seatsRequested = props.seatsRequested;
|
||||
this.strict = props.strict;
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { CreateAdCommand } from './create-ad.command';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||
import {
|
||||
AD_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { DefaultParams } from '../../ports/default-params.type';
|
||||
|
@ -9,6 +14,10 @@ import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
|||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
import { TimeConverterPort } from '../../ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '../../ports/timezone-finder.port';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
|
||||
@CommandHandler(CreateAdCommand)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
|
@ -19,21 +28,55 @@ export class CreateAdService implements ICommandHandler {
|
|||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER)
|
||||
private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||
const timezone = this.timezoneFinder.timezones(
|
||||
command.waypoints[0].lon,
|
||||
command.waypoints[0].lat,
|
||||
this._defaultParams.DEFAULT_TIMEZONE,
|
||||
)[0];
|
||||
const ad = AdEntity.create(
|
||||
{
|
||||
userId: command.userId,
|
||||
driver: command.driver,
|
||||
passenger: command.passenger,
|
||||
frequency: command.frequency,
|
||||
fromDate: command.fromDate,
|
||||
toDate: command.toDate,
|
||||
schedule: command.schedule,
|
||||
marginDurations: command.marginDurations,
|
||||
fromDate: this.getFromDate(
|
||||
command.fromDate,
|
||||
command.frequency,
|
||||
command.schedule[0].time,
|
||||
timezone,
|
||||
),
|
||||
toDate: this.getToDate(
|
||||
command.fromDate,
|
||||
command.toDate,
|
||||
command.frequency,
|
||||
command.schedule[0].time,
|
||||
timezone,
|
||||
),
|
||||
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||
day: this.getDay(
|
||||
scheduleItem.day,
|
||||
command.fromDate,
|
||||
command.frequency,
|
||||
scheduleItem.time,
|
||||
timezone,
|
||||
),
|
||||
time: this.getTime(
|
||||
command.fromDate,
|
||||
command.frequency,
|
||||
scheduleItem.time,
|
||||
timezone,
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: command.seatsProposed,
|
||||
seatsRequested: command.seatsRequested,
|
||||
strict: command.strict,
|
||||
|
@ -56,15 +99,7 @@ export class CreateAdService implements ICommandHandler {
|
|||
{
|
||||
driver: this._defaultParams.DRIVER,
|
||||
passenger: this._defaultParams.PASSENGER,
|
||||
marginDurations: {
|
||||
mon: this._defaultParams.MON_MARGIN,
|
||||
tue: this._defaultParams.TUE_MARGIN,
|
||||
wed: this._defaultParams.WED_MARGIN,
|
||||
thu: this._defaultParams.THU_MARGIN,
|
||||
fri: this._defaultParams.FRI_MARGIN,
|
||||
sat: this._defaultParams.SAT_MARGIN,
|
||||
sun: this._defaultParams.SUN_MARGIN,
|
||||
},
|
||||
marginDuration: this._defaultParams.DEPARTURE_TIME_MARGIN,
|
||||
strict: this._defaultParams.STRICT,
|
||||
seatsProposed: this._defaultParams.SEATS_PROPOSED,
|
||||
seatsRequested: this._defaultParams.SEATS_REQUESTED,
|
||||
|
@ -81,4 +116,71 @@ export class CreateAdService implements ICommandHandler {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getFromDate = (
|
||||
fromDate: string,
|
||||
frequency: Frequency,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): string => {
|
||||
if (frequency === Frequency.RECURRENT) return fromDate;
|
||||
return this.timeConverter
|
||||
.localStringDateTimeToUtcDate(fromDate, time, timezone)
|
||||
.toISOString();
|
||||
};
|
||||
|
||||
private getToDate = (
|
||||
fromDate: string,
|
||||
toDate: string,
|
||||
frequency: Frequency,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): string => {
|
||||
if (frequency === Frequency.RECURRENT) return toDate;
|
||||
return this.getFromDate(fromDate, frequency, time, timezone);
|
||||
};
|
||||
|
||||
private getDay = (
|
||||
day: number,
|
||||
fromDate: string,
|
||||
frequency: Frequency,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): number => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.getRecurrentDay(day, time, timezone);
|
||||
return new Date(
|
||||
this.getFromDate(fromDate, frequency, time, timezone),
|
||||
).getDay();
|
||||
};
|
||||
|
||||
private getTime = (
|
||||
fromDate: string,
|
||||
frequency: Frequency,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): string => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.timeConverter.localStringTimeToUtcStringTime(time, timezone);
|
||||
return new Date(
|
||||
this.getFromDate(fromDate, frequency, time, timezone),
|
||||
).toTimeString();
|
||||
};
|
||||
|
||||
private getRecurrentDay = (
|
||||
day: number,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): number => {
|
||||
// continuer ici
|
||||
const baseDate = new Date('1970-01-01T00:00:00Z');
|
||||
const hour = parseInt(time.split(':')[0]);
|
||||
const utcHour = parseInt(
|
||||
this.timeConverter
|
||||
.localStringTimeToUtcStringTime(time, timezone)
|
||||
.split(':')[0],
|
||||
);
|
||||
if (utcHour >= 11 && hour < 13) return day > 0 ? day - 1 : 6;
|
||||
return day;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
export type DefaultParams = {
|
||||
MON_MARGIN: number;
|
||||
TUE_MARGIN: number;
|
||||
WED_MARGIN: number;
|
||||
THU_MARGIN: number;
|
||||
FRI_MARGIN: number;
|
||||
SAT_MARGIN: number;
|
||||
SUN_MARGIN: number;
|
||||
DRIVER: boolean;
|
||||
SEATS_PROPOSED: number;
|
||||
PASSENGER: boolean;
|
||||
SEATS_PROPOSED: number;
|
||||
SEATS_REQUESTED: number;
|
||||
DEPARTURE_TIME_MARGIN: number;
|
||||
STRICT: boolean;
|
||||
DEFAULT_TIMEZONE: string;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface TimeConverterPort {
|
||||
localDateTimeToUtc(
|
||||
localStringTimeToUtcStringTime(time: string, timezone: string): string;
|
||||
localStringDateTimeToUtcDate(
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export interface TimezoneFinderPort {
|
||||
timezones(lon: number, lat: number, defaultTimezone?: string): string[];
|
||||
offset(timezone: string): number;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export type ScheduleItem = {
|
||||
day?: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
export type Schedule = {
|
||||
mon?: string;
|
||||
tue?: string;
|
||||
wed?: string;
|
||||
thu?: string;
|
||||
fri?: string;
|
||||
sat?: string;
|
||||
sun?: string;
|
||||
};
|
|
@ -2,8 +2,8 @@ import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
|||
import { v4 } from 'uuid';
|
||||
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
||||
import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types';
|
||||
import { Waypoint } from './value-objects/waypoint.value-object';
|
||||
import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
export class AdEntity extends AggregateRoot<AdProps> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
@ -15,7 +15,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
const id = v4();
|
||||
const props: AdProps = { ...create };
|
||||
const ad = new AdEntity({ id, props })
|
||||
.setMissingMarginDurations(defaultAdProps.marginDurations)
|
||||
.setMissingMarginDurations(defaultAdProps.marginDuration)
|
||||
.setMissingStrict(defaultAdProps.strict)
|
||||
.setDefaultDriverAndPassengerParameters({
|
||||
driver: defaultAdProps.driver,
|
||||
|
@ -33,24 +33,15 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
frequency: props.frequency,
|
||||
fromDate: props.fromDate,
|
||||
toDate: props.toDate,
|
||||
monTime: props.schedule.mon,
|
||||
tueTime: props.schedule.tue,
|
||||
wedTime: props.schedule.wed,
|
||||
thuTime: props.schedule.thu,
|
||||
friTime: props.schedule.fri,
|
||||
satTime: props.schedule.sat,
|
||||
sunTime: props.schedule.sun,
|
||||
monMarginDuration: props.marginDurations.mon,
|
||||
tueMarginDuration: props.marginDurations.tue,
|
||||
wedMarginDuration: props.marginDurations.wed,
|
||||
thuMarginDuration: props.marginDurations.thu,
|
||||
friMarginDuration: props.marginDurations.fri,
|
||||
satMarginDuration: props.marginDurations.sat,
|
||||
sunMarginDuration: props.marginDurations.sun,
|
||||
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
||||
day: day.day,
|
||||
time: day.time,
|
||||
margin: day.margin,
|
||||
})),
|
||||
seatsProposed: props.seatsProposed,
|
||||
seatsRequested: props.seatsRequested,
|
||||
strict: props.strict,
|
||||
waypoints: props.waypoints.map((waypoint: Waypoint) => ({
|
||||
waypoints: props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
position: waypoint.position,
|
||||
name: waypoint.address.name,
|
||||
houseNumber: waypoint.address.houseNumber,
|
||||
|
@ -67,23 +58,11 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
};
|
||||
|
||||
private setMissingMarginDurations = (
|
||||
defaultMarginDurations: MarginDurationsProps,
|
||||
defaultMarginDuration: number,
|
||||
): AdEntity => {
|
||||
if (!this.props.marginDurations) this.props.marginDurations = {};
|
||||
if (!this.props.marginDurations.mon)
|
||||
this.props.marginDurations.mon = defaultMarginDurations.mon;
|
||||
if (!this.props.marginDurations.tue)
|
||||
this.props.marginDurations.tue = defaultMarginDurations.tue;
|
||||
if (!this.props.marginDurations.wed)
|
||||
this.props.marginDurations.wed = defaultMarginDurations.wed;
|
||||
if (!this.props.marginDurations.thu)
|
||||
this.props.marginDurations.thu = defaultMarginDurations.thu;
|
||||
if (!this.props.marginDurations.fri)
|
||||
this.props.marginDurations.fri = defaultMarginDurations.fri;
|
||||
if (!this.props.marginDurations.sat)
|
||||
this.props.marginDurations.sat = defaultMarginDurations.sat;
|
||||
if (!this.props.marginDurations.sun)
|
||||
this.props.marginDurations.sun = defaultMarginDurations.sun;
|
||||
this.props.schedule.forEach((day: ScheduleItemProps) => {
|
||||
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
|
||||
import { ScheduleProps } from './value-objects/schedule.value-object';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
// All properties that an Ad has
|
||||
|
@ -10,8 +9,7 @@ export interface AdProps {
|
|||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: ScheduleProps;
|
||||
marginDurations: MarginDurationsProps;
|
||||
schedule: ScheduleItemProps[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -26,8 +24,7 @@ export interface CreateAdProps {
|
|||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: ScheduleProps;
|
||||
marginDurations: MarginDurationsProps;
|
||||
schedule: ScheduleItemProps[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -37,7 +34,7 @@ export interface CreateAdProps {
|
|||
export interface DefaultAdProps {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
marginDurations: MarginDurationsProps;
|
||||
marginDuration: number;
|
||||
strict: boolean;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
|
|
|
@ -7,20 +7,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
readonly frequency: string;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly monTime: string;
|
||||
readonly tueTime: string;
|
||||
readonly wedTime: string;
|
||||
readonly thuTime: string;
|
||||
readonly friTime: string;
|
||||
readonly satTime: string;
|
||||
readonly sunTime: string;
|
||||
readonly monMarginDuration: number;
|
||||
readonly tueMarginDuration: number;
|
||||
readonly wedMarginDuration: number;
|
||||
readonly thuMarginDuration: number;
|
||||
readonly friMarginDuration: number;
|
||||
readonly satMarginDuration: number;
|
||||
readonly sunMarginDuration: number;
|
||||
readonly schedule: ScheduleDay[];
|
||||
readonly seatsProposed: number;
|
||||
readonly seatsRequested: number;
|
||||
readonly strict: boolean;
|
||||
|
@ -34,20 +21,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
this.frequency = props.frequency;
|
||||
this.fromDate = props.fromDate;
|
||||
this.toDate = props.toDate;
|
||||
this.monTime = props.monTime;
|
||||
this.tueTime = props.tueTime;
|
||||
this.wedTime = props.wedTime;
|
||||
this.thuTime = props.thuTime;
|
||||
this.friTime = props.friTime;
|
||||
this.satTime = props.satTime;
|
||||
this.sunTime = props.sunTime;
|
||||
this.monMarginDuration = props.monMarginDuration;
|
||||
this.tueMarginDuration = props.tueMarginDuration;
|
||||
this.wedMarginDuration = props.wedMarginDuration;
|
||||
this.thuMarginDuration = props.thuMarginDuration;
|
||||
this.friMarginDuration = props.friMarginDuration;
|
||||
this.satMarginDuration = props.satMarginDuration;
|
||||
this.sunMarginDuration = props.sunMarginDuration;
|
||||
this.schedule = props.schedule;
|
||||
this.seatsProposed = props.seatsProposed;
|
||||
this.seatsRequested = props.seatsRequested;
|
||||
this.strict = props.strict;
|
||||
|
@ -55,6 +29,12 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
}
|
||||
}
|
||||
|
||||
export class ScheduleDay {
|
||||
day: number;
|
||||
time: string;
|
||||
margin: number;
|
||||
}
|
||||
|
||||
export class Waypoint {
|
||||
position: number;
|
||||
name?: string;
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface MarginDurationsProps {
|
||||
mon?: number;
|
||||
tue?: number;
|
||||
wed?: number;
|
||||
thu?: number;
|
||||
fri?: number;
|
||||
sat?: number;
|
||||
sun?: number;
|
||||
}
|
||||
|
||||
export class MarginDurations extends ValueObject<MarginDurationsProps> {
|
||||
get mon(): number {
|
||||
return this.props.mon;
|
||||
}
|
||||
|
||||
set mon(margin: number) {
|
||||
this.props.mon = margin;
|
||||
}
|
||||
|
||||
get tue(): number {
|
||||
return this.props.tue;
|
||||
}
|
||||
|
||||
set tue(margin: number) {
|
||||
this.props.tue = margin;
|
||||
}
|
||||
|
||||
get wed(): number {
|
||||
return this.props.wed;
|
||||
}
|
||||
|
||||
set wed(margin: number) {
|
||||
this.props.wed = margin;
|
||||
}
|
||||
|
||||
get thu(): number {
|
||||
return this.props.thu;
|
||||
}
|
||||
|
||||
set thu(margin: number) {
|
||||
this.props.thu = margin;
|
||||
}
|
||||
|
||||
get fri(): number {
|
||||
return this.props.fri;
|
||||
}
|
||||
|
||||
set fri(margin: number) {
|
||||
this.props.fri = margin;
|
||||
}
|
||||
|
||||
get sat(): number {
|
||||
return this.props.sat;
|
||||
}
|
||||
|
||||
set sat(margin: number) {
|
||||
this.props.sat = margin;
|
||||
}
|
||||
|
||||
get sun(): number {
|
||||
return this.props.sun;
|
||||
}
|
||||
|
||||
set sun(margin: number) {
|
||||
this.props.sun = margin;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: MarginDurationsProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface ScheduleItemProps {
|
||||
day: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
export class ScheduleItem extends ValueObject<ScheduleItemProps> {
|
||||
get day(): number {
|
||||
return this.props.day;
|
||||
}
|
||||
|
||||
get time(): string {
|
||||
return this.props.time;
|
||||
}
|
||||
|
||||
get margin(): number | undefined {
|
||||
return this.props.margin;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: ScheduleItemProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface ScheduleProps {
|
||||
mon?: string;
|
||||
tue?: string;
|
||||
wed?: string;
|
||||
thu?: string;
|
||||
fri?: string;
|
||||
sat?: string;
|
||||
sun?: string;
|
||||
}
|
||||
|
||||
export class Schedule extends ValueObject<ScheduleProps> {
|
||||
get mon(): string | undefined {
|
||||
return this.props.mon;
|
||||
}
|
||||
|
||||
get tue(): string | undefined {
|
||||
return this.props.tue;
|
||||
}
|
||||
|
||||
get wed(): string | undefined {
|
||||
return this.props.wed;
|
||||
}
|
||||
|
||||
get thu(): string | undefined {
|
||||
return this.props.thu;
|
||||
}
|
||||
|
||||
get fri(): string | undefined {
|
||||
return this.props.fri;
|
||||
}
|
||||
|
||||
get sat(): string | undefined {
|
||||
return this.props.sat;
|
||||
}
|
||||
|
||||
get sun(): string | undefined {
|
||||
return this.props.sun;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: ScheduleProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -19,20 +19,6 @@ export type AdBaseModel = {
|
|||
frequency: string;
|
||||
fromDate: Date;
|
||||
toDate: Date;
|
||||
monTime: Date;
|
||||
tueTime: Date;
|
||||
wedTime: Date;
|
||||
thuTime: Date;
|
||||
friTime: Date;
|
||||
satTime: Date;
|
||||
sunTime: Date;
|
||||
monMargin: number;
|
||||
tueMargin: number;
|
||||
wedMargin: number;
|
||||
thuMargin: number;
|
||||
friMargin: number;
|
||||
satMargin: number;
|
||||
sunMargin: number;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -42,12 +28,25 @@ export type AdBaseModel = {
|
|||
|
||||
export type AdReadModel = AdBaseModel & {
|
||||
waypoints: WaypointModel[];
|
||||
schedule: ScheduleItemModel[];
|
||||
};
|
||||
|
||||
export type AdWriteModel = AdBaseModel & {
|
||||
waypoints: {
|
||||
create: WaypointModel[];
|
||||
};
|
||||
schedule: {
|
||||
create: ScheduleItemModel[];
|
||||
};
|
||||
};
|
||||
|
||||
export type ScheduleItemModel = {
|
||||
uuid: string;
|
||||
day: number;
|
||||
time: Date;
|
||||
margin: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type WaypointModel = {
|
||||
|
|
|
@ -7,17 +7,13 @@ import { DefaultParams } from '../core/application/ports/default-params.type';
|
|||
export class DefaultParamsProvider implements DefaultParamsProviderPort {
|
||||
constructor(private readonly _configService: ConfigService) {}
|
||||
getParams = (): DefaultParams => ({
|
||||
MON_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
TUE_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
WED_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
THU_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
FRI_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
SAT_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
SUN_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
DRIVER: this._configService.get('ROLE') == 'driver',
|
||||
SEATS_PROPOSED: parseInt(this._configService.get('SEATS_PROPOSED')),
|
||||
PASSENGER: this._configService.get('ROLE') == 'passenger',
|
||||
SEATS_REQUESTED: parseInt(this._configService.get('SEATS_REQUESTED')),
|
||||
DEPARTURE_TIME_MARGIN: parseInt(
|
||||
this._configService.get('DEPARTURE_TIME_MARGIN'),
|
||||
),
|
||||
STRICT: this._configService.get('STRICT_FREQUENCY') == 'true',
|
||||
DEFAULT_TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'),
|
||||
});
|
||||
|
|
|
@ -4,19 +4,30 @@ import { TimeConverterPort } from '../core/application/ports/time-converter.port
|
|||
|
||||
@Injectable()
|
||||
export class TimeConverter implements TimeConverterPort {
|
||||
localDateTimeToUtc = (
|
||||
private readonly BASE_DATE = '1970-01-01';
|
||||
|
||||
localStringTimeToUtcStringTime = (time: string, timezone: string): string => {
|
||||
try {
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new DateTime(`${this.BASE_DATE}T${time}`, TimeZone.zone(timezone))
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.format('HH:mm');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
localStringDateTimeToUtcDate = (
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
dst = true,
|
||||
): Date => {
|
||||
try {
|
||||
if (!date || !time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst))
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toIsoString(),
|
||||
);
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst))
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toDate();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { find } from 'geo-tz';
|
||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||
import { zone } from 'timezonecomplete';
|
||||
|
||||
@Injectable()
|
||||
export class TimezoneFinder implements TimezoneFinderPort {
|
||||
|
@ -13,4 +14,7 @@ export class TimezoneFinder implements TimezoneFinderPort {
|
|||
if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
|
||||
return foundTimezones;
|
||||
};
|
||||
|
||||
offset = (timezone: string): number =>
|
||||
zone(timezone).offsetForUtc(1970, 1, 1, 0, 0, 0);
|
||||
}
|
||||
|
|
|
@ -9,23 +9,10 @@ export class AdResponseDto extends ResponseBase {
|
|||
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;
|
||||
};
|
||||
day: number;
|
||||
time: string;
|
||||
margin: number;
|
||||
}[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package ad;
|
||||
|
||||
service AdsService {
|
||||
service AdService {
|
||||
rpc FindOneById(AdById) returns (Ad);
|
||||
rpc FindAll(AdFilter) returns (Ads);
|
||||
rpc Create(Ad) returns (AdById);
|
||||
|
@ -22,32 +22,17 @@ message Ad {
|
|||
Frequency frequency = 5;
|
||||
string fromDate = 6;
|
||||
string toDate = 7;
|
||||
Schedule schedule = 8;
|
||||
MarginDurations marginDurations = 9;
|
||||
int32 seatsProposed = 10;
|
||||
int32 seatsRequested = 11;
|
||||
bool strict = 12;
|
||||
repeated Waypoint waypoints = 13;
|
||||
repeated ScheduleItem schedule = 8;
|
||||
int32 seatsProposed = 9;
|
||||
int32 seatsRequested = 10;
|
||||
bool strict = 11;
|
||||
repeated Waypoint waypoints = 12;
|
||||
}
|
||||
|
||||
message Schedule {
|
||||
string mon = 1;
|
||||
string tue = 2;
|
||||
string wed = 3;
|
||||
string thu = 4;
|
||||
string fri = 5;
|
||||
string sat = 6;
|
||||
string sun = 7;
|
||||
}
|
||||
|
||||
message MarginDurations {
|
||||
int32 mon = 1;
|
||||
int32 tue = 2;
|
||||
int32 wed = 3;
|
||||
int32 thu = 4;
|
||||
int32 fri = 5;
|
||||
int32 sat = 6;
|
||||
int32 sun = 7;
|
||||
message ScheduleItem {
|
||||
int32 day = 1;
|
||||
string time = 2;
|
||||
int32 margin = 3;
|
||||
}
|
||||
|
||||
message Waypoint {
|
||||
|
|
|
@ -19,7 +19,7 @@ import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
|||
export class CreateAdGrpcController {
|
||||
constructor(private readonly commandBus: CommandBus) {}
|
||||
|
||||
@GrpcMethod('AdsService', 'Create')
|
||||
@GrpcMethod('AdService', 'Create')
|
||||
async create(data: CreateAdRequestDto): Promise<IdResponse> {
|
||||
try {
|
||||
const aggregateID: AggregateID = await this.commandBus.execute(
|
||||
|
|
|
@ -9,14 +9,13 @@ import {
|
|||
IsArray,
|
||||
IsISO8601,
|
||||
} from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { ScheduleDto } from './schedule.dto';
|
||||
import { MarginDurationsDto } from './margin-durations.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ScheduleItemDto } from './schedule-item.dto';
|
||||
import { WaypointDto } from './waypoint.dto';
|
||||
import { intToFrequency } from './transformers/int-to-frequency';
|
||||
import { IsSchedule } from './validators/decorators/is-schedule.decorator';
|
||||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
||||
import { HasDay } from './validators/decorators/has-day.decorator';
|
||||
|
||||
export class CreateAdRequestDto {
|
||||
@IsUUID(4)
|
||||
|
@ -30,10 +29,10 @@ export class CreateAdRequestDto {
|
|||
@IsBoolean()
|
||||
passenger?: boolean;
|
||||
|
||||
@Transform(({ value }) => intToFrequency(value), {
|
||||
toClassOnly: true,
|
||||
})
|
||||
@IsEnum(Frequency)
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
@IsISO8601({
|
||||
|
@ -46,17 +45,16 @@ export class CreateAdRequestDto {
|
|||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
@IsAfterOrEqual('fromDate', {
|
||||
message: 'toDate must be after or equal to fromDate',
|
||||
})
|
||||
toDate: string;
|
||||
|
||||
@Type(() => ScheduleDto)
|
||||
@IsSchedule()
|
||||
@Type(() => ScheduleItemDto)
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
schedule: ScheduleDto;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => MarginDurationsDto)
|
||||
@ValidateNested({ each: true })
|
||||
marginDurations?: MarginDurationsDto;
|
||||
schedule: ScheduleItemDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import { IsInt, IsOptional } from 'class-validator';
|
||||
|
||||
export class MarginDurationsDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
mon?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
tue?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
wed?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
thu?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
fri?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sat?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sun?: number;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { IsOptional, IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
||||
|
||||
export class ScheduleItemDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
day?: number;
|
||||
|
||||
@IsMilitaryTime()
|
||||
time: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
margin?: number;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { IsOptional, IsMilitaryTime } from 'class-validator';
|
||||
|
||||
export class ScheduleDto {
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
mon?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
tue?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
wed?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
thu?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
fri?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
sat?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
sun?: string;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasDay(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasDay',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
value == Frequency.PUNCTUAL ||
|
||||
(Array.isArray(relatedValue) &&
|
||||
relatedValue.some((scheduleItem) =>
|
||||
scheduleItem.hasOwnProperty('day'),
|
||||
))
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function IsAfterOrEqual(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isAfterOrEqual',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
typeof relatedValue === 'string' &&
|
||||
value >= relatedValue
|
||||
); // you can return a Promise<boolean> here as well, if you want to make async validation
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import {
|
||||
ValidateBy,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
buildMessage,
|
||||
} from 'class-validator';
|
||||
|
||||
export const IsSchedule = (
|
||||
validationOptions?: ValidationOptions,
|
||||
): PropertyDecorator =>
|
||||
ValidateBy(
|
||||
{
|
||||
name: '',
|
||||
constraints: [],
|
||||
validator: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate: (value, args: ValidationArguments): boolean =>
|
||||
Object.keys(value).length > 0,
|
||||
defaultMessage: buildMessage(
|
||||
() => `schedule is invalid`,
|
||||
validationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
|
@ -1,14 +1,6 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import {
|
||||
AdReadModel,
|
||||
AdWriteModel,
|
||||
|
@ -26,15 +18,13 @@ const adEntity: AdEntity = new AdEntity({
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: {
|
||||
mon: '07:15',
|
||||
tue: '07:15',
|
||||
wed: '07:15',
|
||||
thu: '07:15',
|
||||
fri: '07:15',
|
||||
sat: '07:15',
|
||||
sun: '07:15',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '07:15',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
waypoints: [
|
||||
{
|
||||
position: 0,
|
||||
|
@ -63,15 +53,6 @@ const adEntity: AdEntity = new AdEntity({
|
|||
},
|
||||
},
|
||||
],
|
||||
marginDurations: {
|
||||
mon: 600,
|
||||
tue: 600,
|
||||
wed: 600,
|
||||
thu: 600,
|
||||
fri: 600,
|
||||
sat: 600,
|
||||
sun: 600,
|
||||
},
|
||||
strict: false,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
|
@ -87,13 +68,16 @@ const adReadModel: AdReadModel = {
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: new Date('2023-06-21'),
|
||||
toDate: new Date('2023-06-21'),
|
||||
monTime: undefined,
|
||||
tueTime: undefined,
|
||||
wedTime: new Date('2023-06-21T07:15:00Z'),
|
||||
thuTime: undefined,
|
||||
friTime: undefined,
|
||||
satTime: undefined,
|
||||
sunTime: undefined,
|
||||
schedule: [
|
||||
{
|
||||
uuid: '3978f3d6-560f-4a8f-83ba-9bf5aa9a2d27',
|
||||
day: 3,
|
||||
time: new Date('2023-06-21T07:05:00Z'),
|
||||
margin: 900,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
],
|
||||
waypoints: [
|
||||
{
|
||||
uuid: '6f53f55e-2bdb-4c23-b6a9-6d7b498e47b9',
|
||||
|
@ -120,13 +104,6 @@ const adReadModel: AdReadModel = {
|
|||
updatedAt: now,
|
||||
},
|
||||
],
|
||||
monMargin: 600,
|
||||
tueMargin: 600,
|
||||
wedMargin: 600,
|
||||
thuMargin: 600,
|
||||
friMargin: 600,
|
||||
satMargin: 600,
|
||||
sunMargin: 600,
|
||||
strict: false,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
|
@ -134,64 +111,12 @@ const adReadModel: AdReadModel = {
|
|||
updatedAt: now,
|
||||
};
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
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,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
timezones: jest.fn().mockImplementation((lon: number, lat: number) => {
|
||||
if (lon < 60) return 'Europe/Paris';
|
||||
return 'America/New_York';
|
||||
}),
|
||||
};
|
||||
|
||||
const mockTimeConverter: TimeConverterPort = {
|
||||
localDateTimeToUtc: jest
|
||||
.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
|
||||
return datetime;
|
||||
}),
|
||||
utcDatetimeToLocalTime: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Ad Mapper', () => {
|
||||
let adMapper: AdMapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
AdMapper,
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
},
|
||||
{
|
||||
provide: TIME_CONVERTER,
|
||||
useValue: mockTimeConverter,
|
||||
},
|
||||
],
|
||||
providers: [AdMapper],
|
||||
}).compile();
|
||||
adMapper = module.get<AdMapper>(AdMapper);
|
||||
});
|
||||
|
@ -204,6 +129,7 @@ describe('Ad Mapper', () => {
|
|||
const mapped: AdWriteModel = adMapper.toPersistence(adEntity);
|
||||
expect(mapped.waypoints.create[0].uuid.length).toBe(36);
|
||||
expect(mapped.waypoints.create[1].uuid.length).toBe(36);
|
||||
expect(mapped.schedule.create.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should map persisted data to domain entity', async () => {
|
||||
|
@ -212,6 +138,8 @@ describe('Ad Mapper', () => {
|
|||
48.689445,
|
||||
);
|
||||
expect(mapped.getProps().waypoints[1].address.coordinates.lon).toBe(2.3522);
|
||||
expect(mapped.getProps().schedule.length).toBe(1);
|
||||
expect(mapped.getProps().schedule[0].time).toBe('07:05');
|
||||
});
|
||||
|
||||
it('should map domain entity to response', async () => {
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { MarginDurationsProps } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
|
||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||
|
||||
const originWaypointProps: WaypointProps = {
|
||||
|
@ -33,15 +32,7 @@ const destinationWaypointProps: WaypointProps = {
|
|||
},
|
||||
},
|
||||
};
|
||||
const marginDurationsProps: MarginDurationsProps = {
|
||||
mon: 600,
|
||||
tue: 600,
|
||||
wed: 600,
|
||||
thu: 600,
|
||||
fri: 600,
|
||||
sat: 600,
|
||||
sun: 600,
|
||||
};
|
||||
|
||||
const baseCreateAdProps = {
|
||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
seatsProposed: 3,
|
||||
|
@ -52,77 +43,86 @@ const baseCreateAdProps = {
|
|||
const punctualCreateAdProps = {
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: {
|
||||
wed: '08:30',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
};
|
||||
const recurrentCreateAdProps = {
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2024-06-20',
|
||||
schedule: {
|
||||
mon: '08:30',
|
||||
tue: '08:30',
|
||||
wed: '08:00',
|
||||
thu: '08:30',
|
||||
fri: '08:30',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 1,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 2,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 3,
|
||||
time: '08:00',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 4,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 5,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.RECURRENT,
|
||||
};
|
||||
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const recurrentPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const punctualDriverCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const recurrentDriverCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualDriverPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const recurrentDriverPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDurations: {
|
||||
mon: 900,
|
||||
tue: 900,
|
||||
wed: 900,
|
||||
thu: 900,
|
||||
fri: 900,
|
||||
sat: 900,
|
||||
sun: 900,
|
||||
},
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
|
@ -136,8 +136,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualPassengerAd.id.length).toBe(36);
|
||||
expect(punctualPassengerAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualPassengerAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualPassengerAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualPassengerAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualPassengerAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -147,8 +148,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverAd.id.length).toBe(36);
|
||||
expect(punctualDriverAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualDriverAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualDriverAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualDriverAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualDriverAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualDriverAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualDriverAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
|
@ -158,8 +160,11 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualDriverPassengerAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -169,8 +174,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentPassengerAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(recurrentPassengerAd.getProps().schedule.sat).toBeUndefined();
|
||||
expect(recurrentPassengerAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentPassengerAd.getProps().schedule[0].day).toBe(1);
|
||||
expect(recurrentPassengerAd.getProps().schedule[2].time).toBe('08:00');
|
||||
expect(recurrentPassengerAd.getProps().driver).toBeFalsy();
|
||||
expect(recurrentPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -180,8 +186,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverAd.id.length).toBe(36);
|
||||
expect(recurrentDriverAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(recurrentDriverAd.getProps().schedule.sat).toBeUndefined();
|
||||
expect(recurrentDriverAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentDriverAd.getProps().schedule[1].day).toBe(2);
|
||||
expect(recurrentDriverAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(recurrentDriverAd.getProps().driver).toBeTruthy();
|
||||
expect(recurrentDriverAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
|
@ -191,10 +198,11 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(
|
||||
recurrentDriverPassengerAd.getProps().schedule.sat,
|
||||
).toBeUndefined();
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule[3].day).toBe(4);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule[4].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.getProps().driver).toBeTruthy();
|
||||
expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -205,7 +213,6 @@ describe('Ad entity create', () => {
|
|||
const punctualWithoutRoleCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -214,8 +221,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutRoleAd.id.length).toBe(36);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutRoleAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutRoleAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -227,7 +234,6 @@ describe('Ad entity create', () => {
|
|||
strict: undefined,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
|
@ -236,8 +242,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutStrictAd.id.length).toBe(36);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutStrictAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutStrictAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutStrictAd.getProps().strict).toBeFalsy();
|
||||
|
@ -250,7 +256,6 @@ describe('Ad entity create', () => {
|
|||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
|
@ -259,10 +264,10 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutSeatsRequestedAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().driver).toBeFalsy();
|
||||
|
@ -277,7 +282,6 @@ describe('Ad entity create', () => {
|
|||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -286,10 +290,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutSeatsProposedAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().driver).toBeTruthy();
|
||||
|
@ -297,56 +299,29 @@ describe('Ad entity create', () => {
|
|||
expect(punctualWithoutSeatsProposedAd.getProps().seatsProposed).toBe(3);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with margin durations if margin durations are empty', async () => {
|
||||
const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = {
|
||||
const punctualWithoutMarginDurationCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: {},
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationsCreateAdProps,
|
||||
const punctualWithoutMarginDurationAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutMarginDurationAd.id.length).toBe(36);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy();
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().marginDurations.mon,
|
||||
).toBe(900);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with margin durations if margin durations are undefined', async () => {
|
||||
const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: undefined,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationsCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy();
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().marginDurations.mon,
|
||||
punctualWithoutMarginDurationAd.getProps().schedule[0].margin,
|
||||
).toBe(900);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
it('should create a new punctual passenger ad entity with valid positions if positions are missing', async () => {
|
||||
const punctualWithoutPositionsCreateAdProps: CreateAdProps = {
|
||||
|
@ -383,7 +358,6 @@ describe('Ad entity create', () => {
|
|||
},
|
||||
],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -392,10 +366,10 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutPositionsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutPositionsAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutPositionsAd.getProps().waypoints[0].position).toBe(
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { ScheduleItem } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
|
||||
|
||||
describe('Schedule item value object', () => {
|
||||
it('should create a schedule item value object', () => {
|
||||
const scheduleItemVO = new ScheduleItem({
|
||||
day: 0,
|
||||
time: '07:00',
|
||||
margin: 900,
|
||||
});
|
||||
expect(scheduleItemVO.day).toBe(0);
|
||||
expect(scheduleItemVO.time).toBe('07:00');
|
||||
expect(scheduleItemVO.margin).toBe(900);
|
||||
});
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
import { Schedule } from '@modules/ad/core/domain/value-objects/schedule.value-object';
|
||||
|
||||
describe('Schedule value object', () => {
|
||||
it('should create a schedule value object', () => {
|
||||
const scheduleVO = new Schedule({
|
||||
mon: '07:00',
|
||||
tue: '07:05',
|
||||
wed: '07:10',
|
||||
thu: '07:15',
|
||||
fri: '07:20',
|
||||
sat: '07:25',
|
||||
sun: '07:30',
|
||||
});
|
||||
expect(scheduleVO.mon).toBe('07:00');
|
||||
expect(scheduleVO.tue).toBe('07:05');
|
||||
expect(scheduleVO.wed).toBe('07:10');
|
||||
expect(scheduleVO.thu).toBe('07:15');
|
||||
expect(scheduleVO.fri).toBe('07:20');
|
||||
expect(scheduleVO.sat).toBe('07:25');
|
||||
expect(scheduleVO.sun).toBe('07:30');
|
||||
});
|
||||
});
|
|
@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||
const mockConfigService = {
|
||||
get: jest.fn().mockImplementation((value: string) => {
|
||||
switch (value) {
|
||||
case 'DEPARTURE_MARGIN':
|
||||
case 'DEPARTURE_TIME_MARGIN':
|
||||
return 900;
|
||||
case 'ROLE':
|
||||
return 'passenger';
|
||||
|
@ -50,7 +50,7 @@ describe('DefaultParamsProvider', () => {
|
|||
|
||||
it('should provide default params', async () => {
|
||||
const params: DefaultParams = defaultParamsProvider.getParams();
|
||||
expect(params.SUN_MARGIN).toBe(900);
|
||||
expect(params.DEPARTURE_TIME_MARGIN).toBe(900);
|
||||
expect(params.PASSENGER).toBeTruthy();
|
||||
expect(params.DRIVER).toBeFalsy();
|
||||
expect(params.DEFAULT_TIMEZONE).toBe('Europe/Paris');
|
||||
|
|
|
@ -6,35 +6,29 @@ describe('Time Converter', () => {
|
|||
expect(timeConverter).toBeDefined();
|
||||
});
|
||||
|
||||
describe('localDateTimeToUtc', () => {
|
||||
it('should convert a paris datetime to utc', () => {
|
||||
describe('localStringTimeToUtcStringTime', () => {
|
||||
it('should convert a paris time to utc time with dst', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDatetime.toISOString()).toEqual('2023-06-22T06:00:00.000Z');
|
||||
expect(utcDatetime).toBe('07:00');
|
||||
});
|
||||
it('should return undefined if date is invalid', () => {
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-16-22';
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const parisTime = '28:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '28:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const parisTime = undefined;
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
|
@ -42,55 +36,51 @@ describe('Time Converter', () => {
|
|||
});
|
||||
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,
|
||||
const fooBarTime = '08:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
fooBarTime,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if date is undefined', () => {
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = undefined;
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
const fooBarTime = '08:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
fooBarTime,
|
||||
undefined,
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('utcDatetimeToLocalTime', () => {
|
||||
it('should convert an utc datetime isostring to a paris local time', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBe('08:25');
|
||||
});
|
||||
it('should return undefined if isostring input is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = 'not_an_isostring';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone input is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
// describe('utcDatetimeToLocalTime', () => {
|
||||
// it('should convert an utc datetime isostring to a paris local time', () => {
|
||||
// const timeConverter: TimeConverter = new TimeConverter();
|
||||
// const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
// const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
// utcDatetimeIsostring,
|
||||
// 'Europe/Paris',
|
||||
// );
|
||||
// expect(parisTime).toBe('08:25');
|
||||
// });
|
||||
// it('should return undefined if isostring input is invalid', () => {
|
||||
// const timeConverter: TimeConverter = new TimeConverter();
|
||||
// const utcDatetimeIsostring = 'not_an_isostring';
|
||||
// const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
// utcDatetimeIsostring,
|
||||
// 'Europe/Paris',
|
||||
// );
|
||||
// expect(parisTime).toBeUndefined();
|
||||
// });
|
||||
// it('should return undefined if timezone input is invalid', () => {
|
||||
// const timeConverter: TimeConverter = new TimeConverter();
|
||||
// const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
// const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
// utcDatetimeIsostring,
|
||||
// 'Foo/Bar',
|
||||
// );
|
||||
// expect(parisTime).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ScheduleDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule.dto';
|
||||
import { ScheduleItemDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule.dto';
|
||||
import { IsSchedule } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('schedule decorator', () => {
|
||||
class MyClass {
|
||||
@IsSchedule()
|
||||
schedule: ScheduleDto;
|
||||
schedule: ScheduleItemDto;
|
||||
}
|
||||
it('should return a property decorator has a function', () => {
|
||||
const isSchedule = IsSchedule();
|
||||
|
|
Loading…
Reference in New Issue