Implement update ad command
This commit is contained in:
parent
3d4ff00066
commit
7a84bff260
|
@ -11,7 +11,9 @@ import {
|
||||||
AdReadModel,
|
AdReadModel,
|
||||||
AdWriteModel,
|
AdWriteModel,
|
||||||
ScheduleItemModel,
|
ScheduleItemModel,
|
||||||
|
ScheduleWriteModel,
|
||||||
WaypointModel,
|
WaypointModel,
|
||||||
|
WaypointWriteModel,
|
||||||
} from './infrastructure/ad.repository';
|
} from './infrastructure/ad.repository';
|
||||||
import { AdResponseDto } from './interface/dtos/ad.response.dto';
|
import { AdResponseDto } from './interface/dtos/ad.response.dto';
|
||||||
|
|
||||||
|
@ -31,9 +33,8 @@ export class AdMapper
|
||||||
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
|
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
toPersistence = (entity: AdEntity): AdWriteModel => {
|
toPersistence = (entity: AdEntity, update?: boolean): AdWriteModel => {
|
||||||
const copy = entity.getProps();
|
const copy = entity.getProps();
|
||||||
const now = new Date();
|
|
||||||
const record: AdWriteModel = {
|
const record: AdWriteModel = {
|
||||||
uuid: copy.id,
|
uuid: copy.id,
|
||||||
userUuid: copy.userId,
|
userUuid: copy.userId,
|
||||||
|
@ -43,50 +44,80 @@ export class AdMapper
|
||||||
frequency: copy.frequency,
|
frequency: copy.frequency,
|
||||||
fromDate: new Date(copy.fromDate),
|
fromDate: new Date(copy.fromDate),
|
||||||
toDate: new Date(copy.toDate),
|
toDate: new Date(copy.toDate),
|
||||||
schedule: copy.schedule
|
schedule: this.toScheduleItemWriteModel(copy.schedule, update),
|
||||||
? {
|
|
||||||
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,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
seatsProposed: copy.seatsProposed as number,
|
seatsProposed: copy.seatsProposed as number,
|
||||||
seatsRequested: copy.seatsRequested as number,
|
seatsRequested: copy.seatsRequested as number,
|
||||||
strict: copy.strict as boolean,
|
strict: copy.strict as boolean,
|
||||||
waypoints: copy.waypoints
|
waypoints: this.toWaypointWriteModel(copy.waypoints, update),
|
||||||
? {
|
|
||||||
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
|
||||||
uuid: v4(),
|
|
||||||
position: waypoint.position,
|
|
||||||
name: waypoint.address.name,
|
|
||||||
houseNumber: waypoint.address.houseNumber,
|
|
||||||
street: waypoint.address.street,
|
|
||||||
locality: waypoint.address.locality,
|
|
||||||
postalCode: waypoint.address.postalCode,
|
|
||||||
country: waypoint.address.country,
|
|
||||||
lon: waypoint.address.coordinates.lon,
|
|
||||||
lat: waypoint.address.coordinates.lat,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
comment: copy.comment,
|
comment: copy.comment,
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toScheduleItemWriteModel = (
|
||||||
|
schedule: ScheduleItemProps[],
|
||||||
|
update?: boolean,
|
||||||
|
): ScheduleWriteModel | undefined => {
|
||||||
|
if (!schedule) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
const record: ScheduleWriteModel = {
|
||||||
|
create: 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,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
if (update) {
|
||||||
|
record.deleteMany = {
|
||||||
|
createdAt: { lt: now },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
toWaypointWriteModel = (
|
||||||
|
waypoints: WaypointProps[],
|
||||||
|
update?: boolean,
|
||||||
|
): WaypointWriteModel | undefined => {
|
||||||
|
if (!waypoints) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
const record: WaypointWriteModel = {
|
||||||
|
create: waypoints.map((waypoint: WaypointProps) => ({
|
||||||
|
uuid: v4(),
|
||||||
|
position: waypoint.position,
|
||||||
|
name: waypoint.address.name,
|
||||||
|
houseNumber: waypoint.address.houseNumber,
|
||||||
|
street: waypoint.address.street,
|
||||||
|
locality: waypoint.address.locality,
|
||||||
|
postalCode: waypoint.address.postalCode,
|
||||||
|
country: waypoint.address.country,
|
||||||
|
lon: waypoint.address.coordinates.lon,
|
||||||
|
lat: waypoint.address.coordinates.lat,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
if (update) {
|
||||||
|
record.deleteMany = {
|
||||||
|
createdAt: { lt: now },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
toDomain = (record: AdReadModel): AdEntity => {
|
toDomain = (record: AdReadModel): AdEntity => {
|
||||||
const entity = new AdEntity({
|
const entity = new AdEntity({
|
||||||
id: record.uuid,
|
id: record.uuid,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { CreateAdService } from './core/application/commands/create-ad/create-ad
|
||||||
import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
|
import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
|
||||||
import { DeleteUserAdsService } from './core/application/commands/delete-user-ads/delete-user-ads.service';
|
import { DeleteUserAdsService } from './core/application/commands/delete-user-ads/delete-user-ads.service';
|
||||||
import { InvalidateAdService } from './core/application/commands/invalidate-ad/invalidate-ad.service';
|
import { InvalidateAdService } from './core/application/commands/invalidate-ad/invalidate-ad.service';
|
||||||
|
import { UpdateAdService } from './core/application/commands/update-ad/update-ad.service';
|
||||||
import { ValidateAdService } from './core/application/commands/validate-ad/validate-ad.service';
|
import { ValidateAdService } from './core/application/commands/validate-ad/validate-ad.service';
|
||||||
import { PublishMessageWhenAdIsDeletedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-deleted.domain-event-handler';
|
import { PublishMessageWhenAdIsDeletedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-deleted.domain-event-handler';
|
||||||
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
|
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
|
||||||
|
@ -58,6 +59,7 @@ const eventHandlers: Provider[] = [
|
||||||
|
|
||||||
const commandHandlers: Provider[] = [
|
const commandHandlers: Provider[] = [
|
||||||
CreateAdService,
|
CreateAdService,
|
||||||
|
UpdateAdService,
|
||||||
DeleteAdService,
|
DeleteAdService,
|
||||||
DeleteUserAdsService,
|
DeleteUserAdsService,
|
||||||
ValidateAdService,
|
ValidateAdService,
|
||||||
|
|
|
@ -13,6 +13,87 @@ import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||||
import { Waypoint } from '../../types/waypoint';
|
import { Waypoint } from '../../types/waypoint';
|
||||||
import { CreateAdCommand } from './create-ad.command';
|
import { CreateAdCommand } from './create-ad.command';
|
||||||
|
|
||||||
|
export function createPropsFromCommand(
|
||||||
|
command: CreateAdCommand,
|
||||||
|
datetimeTransformer: DateTimeTransformerPort,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
userId: command.userId,
|
||||||
|
driver: command.driver,
|
||||||
|
passenger: command.passenger,
|
||||||
|
frequency: command.frequency,
|
||||||
|
//TODO Shouldn't that kind of logic be in the domain layer?
|
||||||
|
fromDate: datetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: command.schedule[0].time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
toDate: datetimeTransformer.toDate(
|
||||||
|
command.toDate,
|
||||||
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: command.schedule[0].time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
schedule: command.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||||
|
day: datetimeTransformer.day(
|
||||||
|
scheduleItem.day,
|
||||||
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: scheduleItem.time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
time: datetimeTransformer.time(
|
||||||
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: scheduleItem.time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
margin: scheduleItem.margin,
|
||||||
|
})),
|
||||||
|
seatsProposed: command.seatsProposed ?? 0,
|
||||||
|
seatsRequested: command.seatsRequested ?? 0,
|
||||||
|
strict: command.strict,
|
||||||
|
waypoints: command.waypoints.map((waypoint: Waypoint) => ({
|
||||||
|
position: waypoint.position,
|
||||||
|
address: {
|
||||||
|
name: waypoint.name,
|
||||||
|
houseNumber: waypoint.houseNumber,
|
||||||
|
street: waypoint.street,
|
||||||
|
postalCode: waypoint.postalCode,
|
||||||
|
locality: waypoint.locality,
|
||||||
|
country: waypoint.country,
|
||||||
|
coordinates: {
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
comment: command.comment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@CommandHandler(CreateAdCommand)
|
@CommandHandler(CreateAdCommand)
|
||||||
export class CreateAdService implements ICommandHandler {
|
export class CreateAdService implements ICommandHandler {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -23,80 +104,9 @@ export class CreateAdService implements ICommandHandler {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||||
const ad = AdEntity.create({
|
const ad = AdEntity.create(
|
||||||
userId: command.userId,
|
createPropsFromCommand(command, this.datetimeTransformer),
|
||||||
driver: command.driver,
|
);
|
||||||
passenger: command.passenger,
|
|
||||||
frequency: command.frequency,
|
|
||||||
fromDate: this.datetimeTransformer.fromDate(
|
|
||||||
{
|
|
||||||
date: command.fromDate,
|
|
||||||
time: command.schedule[0].time,
|
|
||||||
coordinates: {
|
|
||||||
lon: command.waypoints[0].lon,
|
|
||||||
lat: command.waypoints[0].lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command.frequency,
|
|
||||||
),
|
|
||||||
toDate: this.datetimeTransformer.toDate(
|
|
||||||
command.toDate,
|
|
||||||
{
|
|
||||||
date: command.fromDate,
|
|
||||||
time: command.schedule[0].time,
|
|
||||||
coordinates: {
|
|
||||||
lon: command.waypoints[0].lon,
|
|
||||||
lat: command.waypoints[0].lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command.frequency,
|
|
||||||
),
|
|
||||||
schedule: command.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
|
||||||
day: this.datetimeTransformer.day(
|
|
||||||
scheduleItem.day,
|
|
||||||
{
|
|
||||||
date: command.fromDate,
|
|
||||||
time: scheduleItem.time,
|
|
||||||
coordinates: {
|
|
||||||
lon: command.waypoints[0].lon,
|
|
||||||
lat: command.waypoints[0].lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command.frequency,
|
|
||||||
),
|
|
||||||
time: this.datetimeTransformer.time(
|
|
||||||
{
|
|
||||||
date: command.fromDate,
|
|
||||||
time: scheduleItem.time,
|
|
||||||
coordinates: {
|
|
||||||
lon: command.waypoints[0].lon,
|
|
||||||
lat: command.waypoints[0].lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command.frequency,
|
|
||||||
),
|
|
||||||
margin: scheduleItem.margin,
|
|
||||||
})),
|
|
||||||
seatsProposed: command.seatsProposed ?? 0,
|
|
||||||
seatsRequested: command.seatsRequested ?? 0,
|
|
||||||
strict: command.strict,
|
|
||||||
waypoints: command.waypoints.map((waypoint: Waypoint) => ({
|
|
||||||
position: waypoint.position,
|
|
||||||
address: {
|
|
||||||
name: waypoint.name,
|
|
||||||
houseNumber: waypoint.houseNumber,
|
|
||||||
street: waypoint.street,
|
|
||||||
postalCode: waypoint.postalCode,
|
|
||||||
locality: waypoint.locality,
|
|
||||||
country: waypoint.country,
|
|
||||||
coordinates: {
|
|
||||||
lon: waypoint.lon,
|
|
||||||
lat: waypoint.lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
comment: command.comment,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.repository.insert(ad);
|
await this.repository.insert(ad);
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
import { CreateAdCommand } from '../create-ad/create-ad.command';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ad updates follow the PUT semantics: they replace the entire object.
|
||||||
|
* Therefore the update command extends the create command to inherit the same properties
|
||||||
|
* and re-use the data transformation logic.
|
||||||
|
*/
|
||||||
|
export class UpdateAdCommand extends CreateAdCommand {
|
||||||
|
public adId: string;
|
||||||
|
|
||||||
|
constructor(props: CommandProps<UpdateAdCommand>) {
|
||||||
|
super(props);
|
||||||
|
this.adId = props.adId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
AD_REPOSITORY,
|
||||||
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
|
} from '@modules/ad/ad.di-tokens';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||||
|
import { createPropsFromCommand } from '../create-ad/create-ad.service';
|
||||||
|
import { UpdateAdCommand } from './update-ad.command';
|
||||||
|
|
||||||
|
@CommandHandler(UpdateAdCommand)
|
||||||
|
export class UpdateAdService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
@Inject(INPUT_DATETIME_TRANSFORMER)
|
||||||
|
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: UpdateAdCommand): Promise<void> {
|
||||||
|
const ad = await this.repository.findOneById(command.adId, {
|
||||||
|
waypoints: true,
|
||||||
|
schedule: true,
|
||||||
|
});
|
||||||
|
ad.update(createPropsFromCommand(command, this.datetimeTransformer));
|
||||||
|
await this.repository.update(ad.id, ad);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { Address } from './address';
|
import { Address } from './address';
|
||||||
|
|
||||||
|
//TODO Why not use the Waypoint value-object from the domain?
|
||||||
export type Waypoint = {
|
export type Waypoint = {
|
||||||
position: number;
|
position: number;
|
||||||
} & Address;
|
} & Address;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { AdEntity } from '../core/domain/ad.entity';
|
|
||||||
import { AdMapper } from '../ad.mapper';
|
|
||||||
import { AdRepositoryPort } from '../core/application/ports/ad.repository.port';
|
|
||||||
import {
|
import {
|
||||||
LoggerBase,
|
LoggerBase,
|
||||||
MessagePublisherPort,
|
MessagePublisherPort,
|
||||||
PrismaRepositoryBase,
|
PrismaRepositoryBase,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { PrismaService } from './prisma.service';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { SERVICE_NAME } from '@src/app.constants';
|
import { SERVICE_NAME } from '@src/app.constants';
|
||||||
|
import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
||||||
|
import { AdMapper } from '../ad.mapper';
|
||||||
|
import { AdRepositoryPort } from '../core/application/ports/ad.repository.port';
|
||||||
|
import { AdEntity } from '../core/domain/ad.entity';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
export type AdBaseModel = {
|
export type AdBaseModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
@ -40,13 +40,21 @@ export type AdWriteModel = AdBaseModel & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScheduleWriteModel = {
|
export type ScheduleWriteModel = {
|
||||||
|
deleteMany?: PastCreatedFilter;
|
||||||
create: ScheduleItemModel[];
|
create: ScheduleItemModel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WaypointWriteModel = {
|
export type WaypointWriteModel = {
|
||||||
|
deleteMany?: PastCreatedFilter;
|
||||||
create: WaypointModel[];
|
create: WaypointModel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// used to delete records created in the past,
|
||||||
|
// because the order of `create` and `deleteMany` is not guaranteed
|
||||||
|
export type PastCreatedFilter = {
|
||||||
|
createdAt: { lt: Date };
|
||||||
|
};
|
||||||
|
|
||||||
export type ScheduleItemModel = {
|
export type ScheduleItemModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
day: number;
|
day: number;
|
||||||
|
|
Loading…
Reference in New Issue