correct cascade delete options

This commit is contained in:
Anika Raemer 2025-10-04 18:01:46 +02:00
parent e33dfdb845
commit b1b714f44e
19 changed files with 207 additions and 52 deletions

View file

@ -28,4 +28,25 @@ export abstract class AbstractDtoEntityMapper<
// Abstract methods to be implemented by subclasses
abstract toDto(entity: E): D;
abstract toEntity(dto: D): E;
abstract mergeDtoIntoEntity(dto: D, entity: E): E;
abstract createNewEntity() : E;
mergeDtoListIntoEntityList(dtos: D[], entities: E[]) : E[]{
const updatedEntities: E[] = [];
const existingMap = new Map(entities?.map(e => [e.id, e]) ?? []);
for (const dto of dtos) {
if (dto.id && existingMap.has(dto.id)) {
// update existing
const entity = existingMap.get(dto.id)!;
updatedEntities.push(this.mergeDtoIntoEntity(dto, entity));
} else {
// create new
const newEntity = this.createNewEntity();
updatedEntities.push(this.mergeDtoIntoEntity(dto, newEntity));
}
}
return updatedEntities;
}
}

View file

@ -17,4 +17,16 @@ export class CompactRecipeDtoEntityMapper extends AbstractDtoEntityMapper<Recipe
throw new Error("Mapping CompactRecipeDto to RecipeEntity is not allowed!");
}
mergeDtoIntoEntity(dto: CompactRecipeDto, entity: RecipeEntity): RecipeEntity {
throw new Error("Mapping CompactRecipeDto to RecipeEntity is not allowed!");
}
createNewEntity() : RecipeEntity {
throw new Error("Mapping CompactRecipeDto to RecipeEntity is not allowed!");
}
mergeDtoListIntoEntityList(dtos: CompactRecipeDto[], entities: RecipeEntity[]) : RecipeEntity[]{
throw new Error("Mapping CompactRecipeDto to RecipeEntity is not allowed!");
}
}

View file

@ -21,14 +21,12 @@ export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity,
dto.amountDescription = entity.amountDescription;
// map instructions
const instructionStepEntities = entity.instructionSteps;
const instructionStepDtos = instructionStepEntities.map((stepEntity) => this.instructionStepMapper.toDto(stepEntity));
dto.instructions = instructionStepDtos;
dto.instructions = entity.instructionSteps.map((stepEntity) => this.instructionStepMapper.toDto(stepEntity));
// @todo map ids dto.instructions.forEach(step => step.recipeId = entity.id); // set recipe relation explicitly!
// map ingredient groups
const ingredientGroupEntities = entity.ingredientGroups;
const ingredientGroupDtos = ingredientGroupEntities.map((groupEntity) => this.ingredientGroupMapper.toDto(groupEntity));
dto.ingredientGroups = ingredientGroupDtos;
dto.ingredientGroups = entity.ingredientGroups.map((groupEntity) => this.ingredientGroupMapper.toDto(groupEntity));
return dto;
}
@ -42,16 +40,47 @@ export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity,
entity.amountDescription = dto.amountDescription;
// map instructions
const instructionStepDtos = dto.instructions;
const instructionStepEntities = instructionStepDtos.map((stepDto) => this.instructionStepMapper.toEntity(stepDto));
entity.instructionSteps = instructionStepEntities;
entity.instructionSteps = dto.instructions.map((stepDto) => {
const stepEntity = this.instructionStepMapper.toEntity(stepDto);
// Always set the relation
stepEntity.recipe = entity;
// If it's a new step (no id from client), let DB generate a new UUID
if (!stepDto.id) {
delete (stepEntity as any).id;
}
return stepEntity;
});
// map ingredient groups
const ingredientGroupDtos = dto.ingredientGroups;
const ingredientGroupEntities = ingredientGroupDtos.map((ingredientGroupDto) => this.ingredientGroupMapper.toEntity(ingredientGroupDto));
entity.ingredientGroups = ingredientGroupEntities;
entity.ingredientGroups = dto.ingredientGroups.map((groupDto) => {
const groupEntity = this.ingredientGroupMapper.toEntity(groupDto);
groupEntity.recipe = entity;
if (!groupDto.id) {
delete (groupEntity as any).id;
}
return groupEntity;
});
return entity;
return entity;
}
mergeDtoIntoEntity(dto: RecipeDto, entity: RecipeEntity): RecipeEntity {
entity.title = dto.title;
entity.amount = dto.amount;
entity.amountDescription = dto.amountDescription;
// --- Instruction Steps ---
entity.instructionSteps = this.instructionStepMapper.mergeDtoListIntoEntityList(dto.instructions, entity.instructionSteps);
// --- Ingredient Groups ---
entity.ingredientGroups = this.ingredientGroupMapper.mergeDtoListIntoEntityList(dto.ingredientGroups, entity.ingredientGroups);
return entity
}
createNewEntity(): RecipeEntity {
return new RecipeEntity();
}
}

View file

@ -29,4 +29,17 @@ export class RecipeIngredientDtoEntityMapper extends AbstractDtoEntityMapper<Rec
return entity;
}
createNewEntity(): RecipeIngredientEntity {
return new RecipeIngredientEntity();
}
mergeDtoIntoEntity(dto: RecipeIngredientDto, entity: RecipeIngredientEntity): RecipeIngredientEntity {
entity.name = dto.name;
entity.amount = dto.amount;
entity.unit = dto.unit;
entity.subtext = dto.subtext;
entity.sortOrder = dto.sortOrder;
return entity;
}
}

View file

@ -3,27 +3,25 @@ import { RecipeIngredientGroupEntity } from "../entities/RecipeIngredientGroupEn
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js";
import { RecipeIngredientDtoEntityMapper } from "./RecipeIngredientDtoEntityMapper.js";
export class RecipeIngredientGroupDtoEntityMapper extends AbstractDtoEntityMapper<RecipeIngredientGroupEntity,RecipeIngredientGroupDto>{
constructor(
private ingredientMapper : RecipeIngredientDtoEntityMapper
){
super();
}
export class RecipeIngredientGroupDtoEntityMapper extends AbstractDtoEntityMapper<RecipeIngredientGroupEntity, RecipeIngredientGroupDto> {
constructor(
private ingredientMapper: RecipeIngredientDtoEntityMapper
) {
super();
}
toDto(entity: RecipeIngredientGroupEntity): RecipeIngredientGroupDto {
const dto = new RecipeIngredientGroupDto();
this.mapBaseEntityToDto(entity, dto);
toDto(entity: RecipeIngredientGroupEntity): RecipeIngredientGroupDto {
const dto = new RecipeIngredientGroupDto();
this.mapBaseEntityToDto(entity, dto);
dto.title = entity.title;
dto.sortOrder = entity.sortOrder
dto.title = entity.title;
dto.sortOrder = entity.sortOrder
// map ingredients
const ingredientEntities = entity.ingredients;
const ingredientDtos = ingredientEntities?.map((ingredientEntity) => this.ingredientMapper.toDto(ingredientEntity));
dto.ingredients = ingredientDtos;
// map ingredients
dto.ingredients = entity.ingredients?.map((ingredientEntity) => this.ingredientMapper.toDto(ingredientEntity));
return dto;
}
return dto;
}
toEntity(dto: RecipeIngredientGroupDto): RecipeIngredientGroupEntity {
const entity = new RecipeIngredientGroupEntity();
@ -33,11 +31,34 @@ export class RecipeIngredientGroupDtoEntityMapper extends AbstractDtoEntityMappe
entity.sortOrder = dto.sortOrder
// map ingredients
const ingredientDtos = dto.ingredients;
const ingredientEntities = ingredientDtos?.map((ingredientDto) => this.ingredientMapper.toEntity(ingredientDto));
entity.ingredients = ingredientEntities;
entity.ingredients = dto.ingredients.map((ingredientDto) => {
const ingredientEntity = this.ingredientMapper.toEntity(ingredientDto);
ingredientEntity.ingredientGroup = entity;
// remove id from new entity completely and allow ORM to generate a new one
if (!ingredientDto.id) {
delete (ingredientEntity as any).id;
}
return ingredientEntity;
});
return entity;
}
createNewEntity(): RecipeIngredientGroupEntity {
return new RecipeIngredientGroupEntity();
}
mergeDtoIntoEntity(dto: RecipeIngredientGroupDto, entity: RecipeIngredientGroupEntity): RecipeIngredientGroupEntity {
entity.title = dto.title;
entity.sortOrder = dto.sortOrder;
// sync ingredients inside each group
entity.ingredients = this.ingredientMapper.mergeDtoListIntoEntityList(
dto.ingredients,
entity.ingredients,
);
return entity;
}
}

View file

@ -24,4 +24,14 @@ export class RecipeInstructionStepDtoEntityMapper extends AbstractDtoEntityMappe
return entity;
}
createNewEntity(): RecipeInstructionStepEntity {
return new RecipeInstructionStepEntity();
}
mergeDtoIntoEntity(dto: RecipeInstructionStepDto, entity: RecipeInstructionStepEntity): RecipeInstructionStepEntity {
entity.text = dto.text;
entity.sortOrder = dto.sortOrder;
return entity;
}
}

View file

@ -20,6 +20,16 @@ export class UserDtoEntityMapper extends AbstractDtoEntityMapper<UserEntity, Use
const entity = new UserEntity();
this.mapBaseDtoToEntity(dto, entity);
this.mergeDtoIntoEntity(dto, entity)
return entity;
}
createNewEntity(): UserEntity {
return new UserEntity;
}
mergeDtoIntoEntity(dto: UserDto, entity: UserEntity): UserEntity {
entity.userName = dto.userName;
entity.email = dto.email;
entity.firstName = dto.firstName;