package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.recipe.body.RecipeAiSearchBody; import app.mealsmadeeasy.api.recipe.body.RecipeSearchBody; import app.mealsmadeeasy.api.recipe.body.RecipeUpdateBody; import app.mealsmadeeasy.api.recipe.comment.RecipeComment; import app.mealsmadeeasy.api.recipe.comment.RecipeCommentCreateBody; import app.mealsmadeeasy.api.recipe.comment.RecipeCommentService; import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView; import app.mealsmadeeasy.api.recipe.converter.RecipeToFullViewConverter; import app.mealsmadeeasy.api.recipe.converter.RecipeToInfoViewConverter; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.recipe.star.RecipeStarService; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; import app.mealsmadeeasy.api.sliceview.SliceView; import app.mealsmadeeasy.api.sliceview.SliceViewService; import app.mealsmadeeasy.api.user.User; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/recipes") @RequiredArgsConstructor public class RecipesController { private final RecipeService recipeService; private final RecipeStarService recipeStarService; private final RecipeCommentService recipeCommentService; private final SliceViewService sliceViewService; private final ObjectMapper objectMapper; private final RecipeToFullViewConverter recipeToFullViewConverter; private final RecipeToInfoViewConverter recipeToInfoViewConverter; private Map getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) { Map wrapper = new HashMap<>(); wrapper.put("recipe", view); wrapper.put("isStarred", this.recipeService.isStarer(username, slug, viewer)); wrapper.put("isOwner", this.recipeService.isOwner(username, slug, viewer)); return wrapper; } @GetMapping("/{username}/{slug}") public ResponseEntity> getByUsernameAndSlug( @PathVariable String username, @PathVariable String slug, @RequestParam(defaultValue = "false") boolean includeRawText, @AuthenticationPrincipal User viewer ) { final Recipe recipe = this.recipeService.getByUsernameAndSlug(username, slug, viewer); final FullRecipeView view = this.recipeToFullViewConverter.convert(recipe, includeRawText, viewer); return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, viewer)); } @PostMapping("/{username}/{slug}") public ResponseEntity> updateByUsernameAndSlug( @PathVariable String username, @PathVariable String slug, @RequestParam(defaultValue = "true") boolean includeRawText, @RequestBody RecipeUpdateBody updateBody, @AuthenticationPrincipal User principal ) { final RecipeUpdateSpec spec = RecipeUpdateSpec.from(updateBody); final Recipe updated = this.recipeService.update(username, slug, spec, principal); final FullRecipeView view = this.recipeToFullViewConverter.convert(updated, includeRawText, principal); return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal)); } @GetMapping public ResponseEntity> getRecipeInfoViews( Pageable pageable, @AuthenticationPrincipal User user ) { if (user == null) { final Slice publicRecipes = this.recipeService.getPublicRecipes(pageable); final Slice publicRecipeInfoViews = publicRecipes.map( recipe -> this.recipeToInfoViewConverter.convert(recipe, null) ); final int count = this.recipeService.countPublicRecipes(); return ResponseEntity.ok(this.sliceViewService.getSliceView(publicRecipeInfoViews, count)); } else { final Slice recipes = this.recipeService.getViewableBy(pageable, user); final Slice recipeInfoViews = recipes.map( recipe -> this.recipeToInfoViewConverter.convert(recipe, user) ); final int count = this.recipeService.countViewableBy(user); return ResponseEntity.ok(this.sliceViewService.getSliceView(recipeInfoViews, count)); } } @PostMapping public ResponseEntity> searchRecipes( @RequestBody(required = false) RecipeSearchBody recipeSearchBody, @AuthenticationPrincipal User user ) { if (recipeSearchBody.getType() == RecipeSearchBody.Type.AI_PROMPT) { final RecipeAiSearchBody spec = this.objectMapper.convertValue( recipeSearchBody.getData(), RecipeAiSearchBody.class ); final List results = this.recipeService.aiSearch(spec, user) .stream() .map(recipe -> this.recipeToInfoViewConverter.convert(recipe, user)) .toList(); return ResponseEntity.ok(Map.of("results", results)); } else { throw new IllegalArgumentException("Invalid recipeSearchBody type: " + recipeSearchBody.getType()); } } @PostMapping("/{username}/{slug}/star") public ResponseEntity addStar( @PathVariable String username, @PathVariable String slug, @Nullable @AuthenticationPrincipal User principal ) { return ResponseEntity.status(HttpStatus.CREATED).body(this.recipeStarService.create(username, slug, principal)); } @GetMapping("/{username}/{slug}/star") public ResponseEntity> getStar( @PathVariable String username, @PathVariable String slug, @Nullable @AuthenticationPrincipal User principal ) { final @Nullable RecipeStar star = this.recipeStarService.find(username, slug, principal).orElse(null); if (star != null) { return ResponseEntity.ok(Map.of("isStarred", true, "star", star)); } else { return ResponseEntity.ok(Map.of("isStarred", false)); } } @DeleteMapping("/{username}/{slug}/star") public ResponseEntity removeStar( @PathVariable String username, @PathVariable String slug, @Nullable @AuthenticationPrincipal User principal ) { this.recipeStarService.delete(username, slug, principal); return ResponseEntity.noContent().build(); } @GetMapping("/{username}/{slug}/comments") public ResponseEntity> getComments( @PathVariable String username, @PathVariable String slug, Pageable pageable, @Nullable @AuthenticationPrincipal User principal ) { final Slice slice = this.recipeCommentService.getComments( username, slug, pageable, principal ); final int count = this.recipeCommentService.countComments(username, slug); return ResponseEntity.ok(this.sliceViewService.getSliceView(slice, count)); } @PostMapping("/{username}/{slug}/comments") public ResponseEntity addComment( @PathVariable String username, @PathVariable String slug, @RequestBody RecipeCommentCreateBody body, @Nullable @AuthenticationPrincipal User principal ) { final RecipeComment comment = this.recipeCommentService.create(username, slug, principal, body); return ResponseEntity.ok(RecipeCommentView.from(comment, false)); } }