Implement update ad command
This commit is contained in:
parent
3d4ff00066
commit
7a84bff260
|
@ -11,7 +11,9 @@ import {
|
|||
AdReadModel,
|
||||
AdWriteModel,
|
||||
ScheduleItemModel,
|
||||
ScheduleWriteModel,
|
||||
WaypointModel,
|
||||
WaypointWriteModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
import { AdResponseDto } from './interface/dtos/ad.response.dto';
|
||||
|
||||
|
@ -31,9 +33,8 @@ export class AdMapper
|
|||
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
|
||||
) {}
|
||||
|
||||
toPersistence = (entity: AdEntity): AdWriteModel => {
|
||||
toPersistence = (entity: AdEntity, update?: boolean): AdWriteModel => {
|
||||
const copy = entity.getProps();
|
||||
const now = new Date();
|
||||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
userUuid: copy.userId,
|
||||
|
@ -43,50 +44,80 @@ export class AdMapper
|
|||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
schedule: copy.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,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
schedule: this.toScheduleItemWriteModel(copy.schedule, update),
|
||||
seatsProposed: copy.seatsProposed as number,
|
||||
seatsRequested: copy.seatsRequested as number,
|
||||
strict: copy.strict as boolean,
|
||||
waypoints: copy.waypoints
|
||||
? {
|
||||
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,
|
||||
waypoints: this.toWaypointWriteModel(copy.waypoints, update),
|
||||
comment: copy.comment,
|
||||
};
|
||||
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 => {
|
||||
const entity = new AdEntity({
|
||||
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 { DeleteUserAdsService } from './core/application/commands/delete-user-ads/delete-user-ads.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 { 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';
|
||||
|
@ -58,6 +59,7 @@ const eventHandlers: Provider[] = [
|
|||
|
||||
const commandHandlers: Provider[] = [
|
||||
CreateAdService,
|
||||
UpdateAdService,
|
||||
DeleteAdService,
|
||||
DeleteUserAdsService,
|
||||
ValidateAdService,
|
||||
|
|
|
@ -13,6 +13,87 @@ import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
|||
import { Waypoint } from '../../types/waypoint';
|
||||
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)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
constructor(
|
||||
|
@ -23,80 +104,9 @@ export class CreateAdService implements ICommandHandler {
|
|||
) {}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||
const ad = AdEntity.create({
|
||||
userId: command.userId,
|
||||
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,
|
||||
});
|
||||
const ad = AdEntity.create(
|
||||
createPropsFromCommand(command, this.datetimeTransformer),
|
||||
);
|
||||
|
||||
try {
|
||||
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';
|
||||
|
||||
//TODO Why not use the Waypoint value-object from the domain?
|
||||
export type Waypoint = {
|
||||
position: number;
|
||||
} & 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 {
|
||||
LoggerBase,
|
||||
MessagePublisherPort,
|
||||
PrismaRepositoryBase,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { PrismaService } from './prisma.service';
|
||||
import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
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 = {
|
||||
uuid: string;
|
||||
|
@ -40,13 +40,21 @@ export type AdWriteModel = AdBaseModel & {
|
|||
};
|
||||
|
||||
export type ScheduleWriteModel = {
|
||||
deleteMany?: PastCreatedFilter;
|
||||
create: ScheduleItemModel[];
|
||||
};
|
||||
|
||||
export type WaypointWriteModel = {
|
||||
deleteMany?: PastCreatedFilter;
|
||||
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 = {
|
||||
uuid: string;
|
||||
day: number;
|
||||
|
|
Loading…
Reference in New Issue