Added data about principal's relation to FullRecipeView and related implementation.

This commit is contained in:
Jesse Brault 2024-08-14 18:48:32 -05:00
parent 862d30fae9
commit 84596865dd
8 changed files with 69 additions and 78 deletions

View File

@ -79,21 +79,23 @@ public class RecipeControllerTests {
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug()) get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
) )
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.recipe.id").value(1))
.andExpect(jsonPath("$.created").exists()) // TODO: better matching of exact LocalDateTime .andExpect(jsonPath("$.recipe.created").exists()) // TODO: better matching of exact LocalDateTime
.andExpect(jsonPath("$.modified").doesNotExist()) .andExpect(jsonPath("$.recipe.modified").doesNotExist())
.andExpect(jsonPath("$.slug").value(recipe.getSlug())) .andExpect(jsonPath("$.recipe.slug").value(recipe.getSlug()))
.andExpect(jsonPath("$.title").value("Test Recipe")) .andExpect(jsonPath("$.recipe.title").value("Test Recipe"))
.andExpect(jsonPath("$.preparationTime").value(recipe.getPreparationTime())) .andExpect(jsonPath("$.recipe.preparationTime").value(recipe.getPreparationTime()))
.andExpect(jsonPath("$.cookingTime").value(recipe.getCookingTime())) .andExpect(jsonPath("$.recipe.cookingTime").value(recipe.getCookingTime()))
.andExpect(jsonPath("$.totalTime").value(recipe.getTotalTime())) .andExpect(jsonPath("$.recipe.totalTime").value(recipe.getTotalTime()))
.andExpect(jsonPath("$.text").value("<h1>Hello, World!</h1>")) .andExpect(jsonPath("$.recipe.text").value("<h1>Hello, World!</h1>"))
.andExpect(jsonPath("$.owner.id").value(owner.getId())) .andExpect(jsonPath("$.recipe.owner.id").value(owner.getId()))
.andExpect(jsonPath("$.owner.username").value(owner.getUsername())) .andExpect(jsonPath("$.recipe.owner.username").value(owner.getUsername()))
.andExpect(jsonPath("$.starCount").value(0)) .andExpect(jsonPath("$.recipe.starCount").value(0))
.andExpect(jsonPath("$.recipe.viewerCount").value(0))
.andExpect(jsonPath("$.recipe.isPublic").value(true))
.andExpect(jsonPath("$.isStarred").value(nullValue())) .andExpect(jsonPath("$.isStarred").value(nullValue()))
.andExpect(jsonPath("$.viewerCount").value(0)) .andExpect(jsonPath("$.isOwner").value(nullValue()));
.andExpect(jsonPath("$.isPublic").value(true));
} }
@Test @Test

View File

@ -4,7 +4,6 @@ import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.recipe.star.RecipeStar;
import app.mealsmadeeasy.api.recipe.star.RecipeStarService; 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.recipe.view.RecipeInfoView;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserEntity; import app.mealsmadeeasy.api.user.UserEntity;
@ -24,7 +23,6 @@ import static app.mealsmadeeasy.api.recipe.ContainsRecipeInfoViewsForRecipesMatc
import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes; import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -352,44 +350,4 @@ public class RecipeServiceTests {
assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner)); assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner));
} }
@Test
@DirtiesContext
public void getFullViewByUsernameAndSlugIncludesStarredFalseWhenViewerNotNullButNotStarer() throws RecipeException {
final User owner = this.createTestUser("recipeOwner");
final Recipe recipe = this.createTestRecipe(owner);
final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug(
owner.getUsername(),
recipe.getSlug(),
owner
);
assertThat(view.getIsStarred(), is(false));
}
@Test
@DirtiesContext
public void getFullViewByUsernameAndSlugIncludesStarredTrueWhenViewerNotNullAndStarer() throws RecipeException {
final User owner = this.createTestUser("recipeOwner");
final Recipe recipe = this.createTestRecipe(owner);
this.recipeStarService.create(recipe.getId(), owner.getUsername());
final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug(
owner.getUsername(),
recipe.getSlug(),
owner
);
assertThat(view.getIsStarred(), is(true));
}
@Test
@DirtiesContext
public void getFullViewByUsernameIncludesStarredNullWhenViewerNull() throws RecipeException {
final User owner = this.createTestUser("recipeOwner");
final Recipe recipe = this.createTestRecipe(owner, true);
final FullRecipeView view = this.recipeService.getFullViewByUsernameAndSlug(
owner.getUsername(),
recipe.getSlug(),
null
);
assertThat(view.getIsStarred(), is(nullValue()));
}
} }

View File

@ -57,7 +57,14 @@ public class RecipeStarRepositoryTests {
starDraft.setId(starId); starDraft.setId(starId);
this.recipeStarRepository.save(starDraft); this.recipeStarRepository.save(starDraft);
assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(true)); assertThat(
this.recipeStarRepository.isStarer(
recipe.getOwner().getUsername(),
recipe.getSlug(),
owner.getUsername()
),
is(true)
);
} }
@Test @Test
@ -65,7 +72,14 @@ public class RecipeStarRepositoryTests {
public void returnsFalseIfNotStarer() { public void returnsFalseIfNotStarer() {
final UserEntity owner = this.getOwnerUser(); final UserEntity owner = this.getOwnerUser();
final RecipeEntity recipe = this.getTestRecipe(owner); final RecipeEntity recipe = this.getTestRecipe(owner);
assertThat(this.recipeStarRepository.isStarer(recipe.getId(), owner.getUsername()), is(false)); assertThat(
this.recipeStarRepository.isStarer(
recipe.getOwner().getUsername(),
recipe.getSlug(),
owner.getUsername()
),
is(false)
);
} }
} }

View File

@ -43,13 +43,17 @@ public class RecipeController {
} }
@GetMapping("/{username}/{slug}") @GetMapping("/{username}/{slug}")
public ResponseEntity<FullRecipeView> getById( public ResponseEntity<Map<String, Object>> getByUsernameAndSlug(
@PathVariable String username, @PathVariable String username,
@PathVariable String slug, @PathVariable String slug,
@AuthenticationPrincipal User viewer @AuthenticationPrincipal User viewer
) ) throws RecipeException {
throws RecipeException { final FullRecipeView recipe = this.recipeService.getFullViewByUsernameAndSlug(username, slug, viewer);
return ResponseEntity.ok(this.recipeService.getFullViewByUsernameAndSlug(username, slug, viewer)); final Map<String, Object> body = new HashMap<>();
body.put("recipe", recipe);
body.put("isStarred", this.recipeService.isStarer(username, slug, viewer));
body.put("isOwner", this.recipeService.isOwner(username, slug, viewer));
return ResponseEntity.ok(body);
} }
@GetMapping @GetMapping

View File

@ -5,6 +5,7 @@ import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
import app.mealsmadeeasy.api.recipe.view.FullRecipeView; import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView; import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
@ -36,4 +37,10 @@ public interface RecipeService {
void deleteRecipe(long id, User modifier); void deleteRecipe(long id, User modifier);
@Contract("_, _, null -> null")
@Nullable Boolean isStarer(String username, String slug, @Nullable User viewer);
@Contract("_, _, null -> null")
@Nullable Boolean isOwner(String username, String slug, @Nullable User viewer);
} }

View File

@ -130,9 +130,6 @@ public class RecipeServiceImpl implements RecipeService {
recipe, recipe,
this.getRenderedMarkdown(recipe), this.getRenderedMarkdown(recipe),
this.getStarCount(recipe), this.getStarCount(recipe),
viewer != null
? this.recipeStarRepository.isStarer(recipe.getId(), viewer.getUsername())
: null,
this.getViewerCount(recipe.getId()), this.getViewerCount(recipe.getId()),
this.getImageView(recipe.getMainImage(), viewer) this.getImageView(recipe.getMainImage(), viewer)
); );
@ -274,4 +271,24 @@ public class RecipeServiceImpl implements RecipeService {
this.recipeRepository.deleteById(id); this.recipeRepository.deleteById(id);
} }
@Override
@PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)")
@Contract("_, _, null -> null")
public @Nullable Boolean isStarer(String username, String slug, @Nullable User viewer) {
if (viewer == null) {
return null;
}
return this.recipeStarRepository.isStarer(username, slug, viewer.getUsername());
}
@Override
@PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)")
@Contract("_, _, null -> null")
public @Nullable Boolean isOwner(String username, String slug, @Nullable User viewer) {
if (viewer == null) {
return null;
}
return viewer.getUsername().equals(username);
}
} }

View File

@ -12,8 +12,8 @@ public interface RecipeStarRepository extends JpaRepository<RecipeStarEntity, Lo
@Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2") @Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2")
Optional<RecipeStarEntity> findByRecipeIdAndOwnerUsername(Long recipeId, String username); Optional<RecipeStarEntity> findByRecipeIdAndOwnerUsername(Long recipeId, String username);
@Query("SELECT count(rs) > 0 FROM RecipeStar rs WHERE rs.id.recipeId = ?1 AND rs.id.ownerUsername = ?2") @Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.id.recipeId AND rs.id.ownerUsername = ?3")
boolean isStarer(long recipeId, String username); boolean isStarer(String ownerUsername, String slug, String viewerUsername);
@Modifying @Modifying
@Transactional @Transactional

View File

@ -13,7 +13,6 @@ public class FullRecipeView {
Recipe recipe, Recipe recipe,
String renderedText, String renderedText,
int starCount, int starCount,
Boolean starred,
int viewerCount, int viewerCount,
ImageView mainImage ImageView mainImage
) { ) {
@ -29,7 +28,6 @@ public class FullRecipeView {
view.setText(renderedText); view.setText(renderedText);
view.setOwner(UserInfoView.from(recipe.getOwner())); view.setOwner(UserInfoView.from(recipe.getOwner()));
view.setStarCount(starCount); view.setStarCount(starCount);
view.setIsStarred(starred);
view.setViewerCount(viewerCount); view.setViewerCount(viewerCount);
view.setMainImage(mainImage); view.setMainImage(mainImage);
view.setIsPublic(recipe.isPublic()); view.setIsPublic(recipe.isPublic());
@ -47,7 +45,6 @@ public class FullRecipeView {
private String text; private String text;
private UserInfoView owner; private UserInfoView owner;
private int starCount; private int starCount;
private @Nullable Boolean starred;
private int viewerCount; private int viewerCount;
private ImageView mainImage; private ImageView mainImage;
private boolean isPublic; private boolean isPublic;
@ -140,14 +137,6 @@ public class FullRecipeView {
this.starCount = starCount; this.starCount = starCount;
} }
public @Nullable Boolean getIsStarred() {
return this.starred;
}
public void setIsStarred(@Nullable Boolean starred) {
this.starred = starred;
}
public int getViewerCount() { public int getViewerCount() {
return this.viewerCount; return this.viewerCount;
} }