meals-made-easy-api/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java

180 lines
7.5 KiB
Java

package app.mealsmadeeasy.api.recipe;
import app.mealsmadeeasy.api.image.ImageException;
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.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.RecipeExceptionView;
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
import app.mealsmadeeasy.api.sliceview.SliceViewService;
import app.mealsmadeeasy.api.user.User;
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.access.AccessDeniedException;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/recipes")
public class RecipeController {
private final RecipeService recipeService;
private final RecipeStarService recipeStarService;
private final RecipeCommentService recipeCommentService;
private final SliceViewService sliceViewService;
public RecipeController(
RecipeService recipeService,
RecipeStarService recipeStarService,
RecipeCommentService recipeCommentService,
SliceViewService sliceViewService
) {
this.recipeService = recipeService;
this.recipeStarService = recipeStarService;
this.recipeCommentService = recipeCommentService;
this.sliceViewService = sliceViewService;
}
@ExceptionHandler(RecipeException.class)
public ResponseEntity<RecipeExceptionView> onRecipeException(RecipeException recipeException) {
final HttpStatus status = switch (recipeException.getType()) {
case INVALID_ID, INVALID_USERNAME_OR_SLUG -> HttpStatus.NOT_FOUND;
case INVALID_COMMENT_ID -> HttpStatus.BAD_REQUEST;
};
return ResponseEntity.status(status.value()).body(new RecipeExceptionView(
recipeException.getType().toString(),
recipeException.getMessage()
));
}
private Map<String, Object> getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) {
Map<String, Object> 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<Map<String, Object>> getByUsernameAndSlug(
@PathVariable String username,
@PathVariable String slug,
@RequestParam(defaultValue = "false") boolean includeRawText,
@AuthenticationPrincipal User viewer
) throws RecipeException {
final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug(
username,
slug,
includeRawText,
viewer
);
return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, viewer));
}
@PostMapping("/{username}/{slug}")
public ResponseEntity<Map<String, Object>> updateByUsernameAndSlug(
@PathVariable String username,
@PathVariable String slug,
@RequestParam(defaultValue = "true") boolean includeRawText,
@RequestBody RecipeUpdateSpec updateSpec,
@AuthenticationPrincipal User principal
) throws ImageException, RecipeException {
final Recipe updated = this.recipeService.update(username, slug, updateSpec, principal);
final FullRecipeView view = this.recipeService.toFullRecipeView(updated, includeRawText, principal);
return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal));
}
@GetMapping
public ResponseEntity<Map<String, Object>> getRecipeInfoViews(
Pageable pageable,
@AuthenticationPrincipal User user
) {
final Slice<RecipeInfoView> slice = this.recipeService.getInfoViewsViewableBy(pageable, user);
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
}
@PostMapping("/{username}/{slug}/star")
public ResponseEntity<RecipeStar> addStar(
@PathVariable String username,
@PathVariable String slug,
@Nullable @AuthenticationPrincipal User principal
) throws RecipeException {
if (principal == null) {
throw new AccessDeniedException("Must be logged in to star a recipe.");
}
return ResponseEntity.status(HttpStatus.CREATED).body(this.recipeStarService.create(username, slug, principal));
}
@GetMapping("/{username}/{slug}/star")
public ResponseEntity<Map<String, Object>> getStar(
@PathVariable String username,
@PathVariable String slug,
@Nullable @AuthenticationPrincipal User principal
) throws RecipeException {
if (principal == null) {
throw new AccessDeniedException("Must be logged in to get a recipe star.");
}
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<Object> removeStar(
@PathVariable String username,
@PathVariable String slug,
@Nullable @AuthenticationPrincipal User principal
) throws RecipeException {
if (principal == null) {
throw new AccessDeniedException("Must be logged in to delete a recipe star.");
}
this.recipeStarService.delete(username, slug, principal);
return ResponseEntity.noContent().build();
}
@GetMapping("/{username}/{slug}/comments")
public ResponseEntity<Map<String, Object>> getComments(
@PathVariable String username,
@PathVariable String slug,
Pageable pageable,
@Nullable @AuthenticationPrincipal User principal
) throws RecipeException {
final Slice<RecipeCommentView> slice = this.recipeCommentService.getComments(
username,
slug,
pageable,
principal
);
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
}
@PostMapping("/{username}/{slug}/comments")
public ResponseEntity<RecipeCommentView> addComment(
@PathVariable String username,
@PathVariable String slug,
@RequestBody RecipeCommentCreateBody body,
@Nullable @AuthenticationPrincipal User principal
) throws RecipeException {
if (principal == null) {
throw new AccessDeniedException("Must be logged in to comment on a recipe.");
}
final RecipeComment comment = this.recipeCommentService.create(username, slug, principal, body);
return ResponseEntity.ok(RecipeCommentView.from(comment, false));
}
}