1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 15:29:34 +01:00

feat(production): Rezept archivieren (#30)

This commit is contained in:
Sebastian Frick 2026-02-19 21:43:56 +01:00
parent 7b1c114693
commit 408813a5b5
5 changed files with 99 additions and 1 deletions

View file

@ -0,0 +1,53 @@
package de.effigenix.application.production;
import de.effigenix.application.production.command.ArchiveRecipeCommand;
import de.effigenix.domain.production.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class ArchiveRecipe {
private final RecipeRepository recipeRepository;
private final AuthorizationPort authorizationPort;
public ArchiveRecipe(RecipeRepository recipeRepository, AuthorizationPort authorizationPort) {
this.recipeRepository = recipeRepository;
this.authorizationPort = authorizationPort;
}
public Result<RecipeError, Recipe> execute(ArchiveRecipeCommand cmd, ActorId performedBy) {
if (!authorizationPort.can(performedBy, ProductionAction.RECIPE_WRITE)) {
return Result.failure(new RecipeError.Unauthorized("Not authorized to modify recipes"));
}
var recipeId = RecipeId.of(cmd.recipeId());
Recipe recipe;
switch (recipeRepository.findById(recipeId)) {
case Result.Failure(var err) ->
{ return Result.failure(new RecipeError.RepositoryFailure(err.message())); }
case Result.Success(var opt) -> {
if (opt.isEmpty()) {
return Result.failure(new RecipeError.RecipeNotFound(recipeId));
}
recipe = opt.get();
}
}
switch (recipe.archive()) {
case Result.Failure(var err) -> { return Result.failure(err); }
case Result.Success(var ignored) -> { }
}
switch (recipeRepository.save(recipe)) {
case Result.Failure(var err) ->
{ return Result.failure(new RecipeError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(recipe);
}
}

View file

@ -0,0 +1,4 @@
package de.effigenix.application.production.command;
public record ArchiveRecipeCommand(String recipeId) {
}

View file

@ -29,6 +29,7 @@ import static de.effigenix.shared.common.Result.*;
* 10. Step numbers must be unique within a recipe * 10. Step numbers must be unique within a recipe
* 11. Recipe can only be activated when it has at least one ingredient * 11. Recipe can only be activated when it has at least one ingredient
* 12. Recipe can only be activated from DRAFT status * 12. Recipe can only be activated from DRAFT status
* 13. Recipe can only be archived from ACTIVE status
*/ */
public class Recipe { public class Recipe {
@ -232,6 +233,15 @@ public class Recipe {
return Result.success(null); return Result.success(null);
} }
public Result<RecipeError, Void> archive() {
if (status != RecipeStatus.ACTIVE) {
return Result.failure(new RecipeError.InvalidStatusTransition(status, RecipeStatus.ARCHIVED));
}
this.status = RecipeStatus.ARCHIVED;
touch();
return Result.success(null);
}
// ==================== Getters ==================== // ==================== Getters ====================
public RecipeId id() { return id; } public RecipeId id() { return id; }

View file

@ -1,6 +1,7 @@
package de.effigenix.infrastructure.config; package de.effigenix.infrastructure.config;
import de.effigenix.application.production.ActivateRecipe; import de.effigenix.application.production.ActivateRecipe;
import de.effigenix.application.production.ArchiveRecipe;
import de.effigenix.application.production.AddProductionStep; import de.effigenix.application.production.AddProductionStep;
import de.effigenix.application.production.AddRecipeIngredient; import de.effigenix.application.production.AddRecipeIngredient;
import de.effigenix.application.production.CreateRecipe; import de.effigenix.application.production.CreateRecipe;
@ -43,4 +44,9 @@ public class ProductionUseCaseConfiguration {
public ActivateRecipe activateRecipe(RecipeRepository recipeRepository, AuthorizationPort authorizationPort) { public ActivateRecipe activateRecipe(RecipeRepository recipeRepository, AuthorizationPort authorizationPort) {
return new ActivateRecipe(recipeRepository, authorizationPort); return new ActivateRecipe(recipeRepository, authorizationPort);
} }
@Bean
public ArchiveRecipe archiveRecipe(RecipeRepository recipeRepository, AuthorizationPort authorizationPort) {
return new ArchiveRecipe(recipeRepository, authorizationPort);
}
} }

View file

@ -1,12 +1,14 @@
package de.effigenix.infrastructure.production.web.controller; package de.effigenix.infrastructure.production.web.controller;
import de.effigenix.application.production.ActivateRecipe; import de.effigenix.application.production.ActivateRecipe;
import de.effigenix.application.production.ArchiveRecipe;
import de.effigenix.application.production.AddProductionStep; import de.effigenix.application.production.AddProductionStep;
import de.effigenix.application.production.AddRecipeIngredient; import de.effigenix.application.production.AddRecipeIngredient;
import de.effigenix.application.production.CreateRecipe; import de.effigenix.application.production.CreateRecipe;
import de.effigenix.application.production.RemoveProductionStep; import de.effigenix.application.production.RemoveProductionStep;
import de.effigenix.application.production.RemoveRecipeIngredient; import de.effigenix.application.production.RemoveRecipeIngredient;
import de.effigenix.application.production.command.ActivateRecipeCommand; import de.effigenix.application.production.command.ActivateRecipeCommand;
import de.effigenix.application.production.command.ArchiveRecipeCommand;
import de.effigenix.application.production.command.AddProductionStepCommand; import de.effigenix.application.production.command.AddProductionStepCommand;
import de.effigenix.application.production.command.AddRecipeIngredientCommand; import de.effigenix.application.production.command.AddRecipeIngredientCommand;
import de.effigenix.application.production.command.CreateRecipeCommand; import de.effigenix.application.production.command.CreateRecipeCommand;
@ -43,19 +45,22 @@ public class RecipeController {
private final AddProductionStep addProductionStep; private final AddProductionStep addProductionStep;
private final RemoveProductionStep removeProductionStep; private final RemoveProductionStep removeProductionStep;
private final ActivateRecipe activateRecipe; private final ActivateRecipe activateRecipe;
private final ArchiveRecipe archiveRecipe;
public RecipeController(CreateRecipe createRecipe, public RecipeController(CreateRecipe createRecipe,
AddRecipeIngredient addRecipeIngredient, AddRecipeIngredient addRecipeIngredient,
RemoveRecipeIngredient removeRecipeIngredient, RemoveRecipeIngredient removeRecipeIngredient,
AddProductionStep addProductionStep, AddProductionStep addProductionStep,
RemoveProductionStep removeProductionStep, RemoveProductionStep removeProductionStep,
ActivateRecipe activateRecipe) { ActivateRecipe activateRecipe,
ArchiveRecipe archiveRecipe) {
this.createRecipe = createRecipe; this.createRecipe = createRecipe;
this.addRecipeIngredient = addRecipeIngredient; this.addRecipeIngredient = addRecipeIngredient;
this.removeRecipeIngredient = removeRecipeIngredient; this.removeRecipeIngredient = removeRecipeIngredient;
this.addProductionStep = addProductionStep; this.addProductionStep = addProductionStep;
this.removeProductionStep = removeProductionStep; this.removeProductionStep = removeProductionStep;
this.activateRecipe = activateRecipe; this.activateRecipe = activateRecipe;
this.archiveRecipe = archiveRecipe;
} }
@PostMapping @PostMapping
@ -192,6 +197,26 @@ public class RecipeController {
return ResponseEntity.ok(RecipeResponse.from(result.unsafeGetValue())); return ResponseEntity.ok(RecipeResponse.from(result.unsafeGetValue()));
} }
@PostMapping("/{id}/archive")
@PreAuthorize("hasAuthority('RECIPE_WRITE')")
public ResponseEntity<RecipeResponse> archiveRecipe(
@PathVariable("id") String recipeId,
Authentication authentication
) {
var actorId = extractActorId(authentication);
logger.info("Archiving recipe: {} by actor: {}", recipeId, actorId.value());
var cmd = new ArchiveRecipeCommand(recipeId);
var result = archiveRecipe.execute(cmd, actorId);
if (result.isFailure()) {
throw new RecipeDomainErrorException(result.unsafeGetError());
}
logger.info("Recipe archived: {}", recipeId);
return ResponseEntity.ok(RecipeResponse.from(result.unsafeGetValue()));
}
private ActorId extractActorId(Authentication authentication) { private ActorId extractActorId(Authentication authentication) {
if (authentication == null || authentication.getName() == null) { if (authentication == null || authentication.getName() == null) {
throw new IllegalStateException("No authentication found in SecurityContext"); throw new IllegalStateException("No authentication found in SecurityContext");