diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index 2a65832..229a8a1 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -39,6 +39,7 @@ public class RecipeControllerTests { private Recipe createTestRecipe(User owner, boolean isPublic) { final RecipeCreateSpec spec = new RecipeCreateSpec(); + spec.setSlug("test-recipe"); spec.setTitle("Test Recipe"); spec.setRawText("# Hello, World!"); spec.setPublic(isPublic); @@ -50,12 +51,12 @@ public class RecipeControllerTests { public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception { final User owner = this.createTestUser("owner"); final Recipe recipe = this.createTestRecipe(owner, true); - this.mockMvc.perform(get("/recipes/{id}", recipe.getId())) + this.mockMvc.perform(get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.slug").value(recipe.getSlug())) .andExpect(jsonPath("$.title").value("Test Recipe")) .andExpect(jsonPath("$.text").value("

Hello, World!

")) - .andExpect(jsonPath("$.ownerId").value(owner.getId())) .andExpect(jsonPath("$.ownerUsername").value(owner.getUsername())) .andExpect(jsonPath("$.starCount").value(0)) .andExpect(jsonPath("$.viewerCount").value(0)); @@ -74,8 +75,8 @@ public class RecipeControllerTests { .andExpect(jsonPath("$.content", hasSize(1))) .andExpect(jsonPath("$.content[0].id").value(recipe.getId())) .andExpect(jsonPath("$.content[0].updated").exists()) + .andExpect(jsonPath("$.content[0].slug").value(recipe.getSlug())) .andExpect(jsonPath("$.content[0].title").value(recipe.getTitle())) - .andExpect(jsonPath("$.content[0].ownerId").value(owner.getId())) .andExpect(jsonPath("$.content[0].ownerUsername").value(owner.getUsername())) .andExpect(jsonPath("$.content[0].public").value(true)) .andExpect(jsonPath("$.content[0].starCount").value(0)); diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java index 0be0421..521841f 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeRepositoryTests.java @@ -42,6 +42,7 @@ public class RecipeRepositoryTests { @DirtiesContext public void findsAllPublicRecipes() { final RecipeEntity publicRecipe = new RecipeEntity(); + publicRecipe.setSlug("public-recipe"); publicRecipe.setPublic(true); publicRecipe.setOwner(this.getOwnerUser()); publicRecipe.setTitle("Public Recipe"); @@ -56,6 +57,7 @@ public class RecipeRepositoryTests { @DirtiesContext public void doesNotFindNonPublicRecipe() { final RecipeEntity nonPublicRecipe = new RecipeEntity(); + nonPublicRecipe.setSlug("non-public-recipe"); nonPublicRecipe.setOwner(this.getOwnerUser()); nonPublicRecipe.setTitle("Non-Public Recipe"); nonPublicRecipe.setRawText("Hello, World!"); @@ -69,6 +71,7 @@ public class RecipeRepositoryTests { @DirtiesContext public void findsAllForViewer() { final RecipeEntity recipe = new RecipeEntity(); + recipe.setSlug("test-recipe"); recipe.setOwner(this.getOwnerUser()); recipe.setTitle("Test Recipe"); recipe.setRawText("Hello, World!"); @@ -89,6 +92,7 @@ public class RecipeRepositoryTests { @DirtiesContext public void doesNotIncludeNonViewable() { final RecipeEntity recipe = new RecipeEntity(); + recipe.setSlug("test-recipe"); recipe.setOwner(this.getOwnerUser()); recipe.setTitle("Test Recipe"); recipe.setRawText("Hello, World!"); diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java index b188132..406981c 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java @@ -43,11 +43,16 @@ public class RecipeServiceTests { } private Recipe createTestRecipe(@Nullable User owner) { - return this.createTestRecipe(owner, false); + return this.createTestRecipe(owner, false, null); } private Recipe createTestRecipe(@Nullable User owner, boolean isPublic) { + return this.createTestRecipe(owner, isPublic, null); + } + + private Recipe createTestRecipe(@Nullable User owner, boolean isPublic, @Nullable String slug) { final RecipeCreateSpec spec = new RecipeCreateSpec(); + spec.setSlug(slug != null ? slug : "my-recipe"); spec.setTitle("My Recipe"); spec.setRawText("Hello!"); spec.setPublic(isPublic); @@ -88,6 +93,7 @@ public class RecipeServiceTests { final Recipe recipe = this.createTestRecipe(owner, true); final Recipe byId = this.recipeService.getById(recipe.getId(), null); assertThat(byId.getId(), is(recipe.getId())); + assertThat(byId.getSlug(), is(recipe.getSlug())); assertThat(byId.getTitle(), is("My Recipe")); assertThat(byId.getRawText(), is("Hello!")); assertThat(byId.isPublic(), is(true)); @@ -167,9 +173,9 @@ public class RecipeServiceTests { final User u0 = this.createTestUser("u0"); final User u1 = this.createTestUser("u1"); - final Recipe r0 = this.createTestRecipe(owner, true); - final Recipe r1 = this.createTestRecipe(owner, true); - final Recipe r2 = this.createTestRecipe(owner, true); + final Recipe r0 = this.createTestRecipe(owner, true, "r0"); + final Recipe r1 = this.createTestRecipe(owner, true, "r1"); + final Recipe r2 = this.createTestRecipe(owner, true, "r2"); // r0.stars = 0, r1.stars = 1, r2.stars = 2 this.recipeStarService.create(r1.getId(), u0.getUsername()); @@ -197,9 +203,9 @@ public class RecipeServiceTests { final User u1 = this.createTestUser("u1"); final User viewer = this.createTestUser("recipeViewer"); - Recipe r0 = this.createTestRecipe(owner); // not public - Recipe r1 = this.createTestRecipe(owner); - Recipe r2 = this.createTestRecipe(owner); + Recipe r0 = this.createTestRecipe(owner, false, "r0"); // not public + Recipe r1 = this.createTestRecipe(owner, false, "r1"); + Recipe r2 = this.createTestRecipe(owner, false, "r2"); for (final User starer : List.of(u0, u1)) { r0 = this.recipeService.addViewer(r0.getId(), owner, starer); @@ -243,8 +249,8 @@ public class RecipeServiceTests { public void getPublicRecipes() { final User owner = this.createTestUser("recipeOwner"); - Recipe r0 = this.createTestRecipe(owner, true); - Recipe r1 = this.createTestRecipe(owner, true); + Recipe r0 = this.createTestRecipe(owner, true, "r0"); + Recipe r1 = this.createTestRecipe(owner, true, "r1"); final List publicRecipes = this.recipeService.getPublicRecipes(); assertThat(publicRecipes.size(), is(2)); @@ -279,6 +285,7 @@ public class RecipeServiceTests { public void updateRawText() throws RecipeException { final User owner = this.createTestUser("recipeOwner"); final RecipeCreateSpec createSpec = new RecipeCreateSpec(); + createSpec.setSlug("my-recipe"); createSpec.setTitle("My Recipe"); createSpec.setRawText("# A Heading"); Recipe recipe = this.recipeService.create(owner, createSpec); diff --git a/src/integrationTest/resources/application.properties b/src/integrationTest/resources/application.properties index 224bc54..60d3fb7 100644 --- a/src/integrationTest/resources/application.properties +++ b/src/integrationTest/resources/application.properties @@ -2,6 +2,7 @@ spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password=sa +app.mealsmadeeasy.api.baseUrl=http://localhost:8080 app.mealsmadeeasy.api.security.access-token-lifetime=60 app.mealsmadeeasy.api.security.refresh-token-lifetime=120 app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000 diff --git a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java index 576c2bd..d605f94 100644 --- a/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java +++ b/src/main/java/app/mealsmadeeasy/api/DevConfiguration.java @@ -61,6 +61,7 @@ public class DevConfiguration { logger.info("Created {}", obazdaImage); final RecipeCreateSpec recipeCreateSpec = new RecipeCreateSpec(); + recipeCreateSpec.setSlug("test-recipe"); recipeCreateSpec.setTitle("Test Recipe"); recipeCreateSpec.setRawText("Hello, World!"); recipeCreateSpec.setPublic(true); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java index f171bd8..9dca31f 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/Recipe.java @@ -13,6 +13,7 @@ public interface Recipe { Long getId(); LocalDateTime getCreated(); @Nullable LocalDateTime getModified(); + String getSlug(); String getTitle(); String getRawText(); User getOwner(); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java index 2699548..1910d5c 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java @@ -31,10 +31,14 @@ public class RecipeController { )); } - @GetMapping("/{id}") - public ResponseEntity getById(@PathVariable long id, @AuthenticationPrincipal User user) + @GetMapping("/{username}/{slug}") + public ResponseEntity getById( + @PathVariable String username, + @PathVariable String slug, + @AuthenticationPrincipal User viewer + ) throws RecipeException { - return ResponseEntity.ok(this.recipeService.getFullViewById(id, user)); + return ResponseEntity.ok(this.recipeService.getFullViewByUsernameAndSlug(username, slug, viewer)); } @GetMapping diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java index 34651ee..490515b 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeEntity.java @@ -27,6 +27,9 @@ public final class RecipeEntity implements Recipe { private LocalDateTime modified; + @Column(nullable = false, unique = true) + private String slug; + @Column(nullable = false) private String title; @@ -86,6 +89,15 @@ public final class RecipeEntity implements Recipe { this.modified = modified; } + @Override + public String getSlug() { + return this.slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + @Override public String getTitle() { return this.title; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java index 5026bde..c3c5625 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeException.java @@ -3,7 +3,7 @@ package app.mealsmadeeasy.api.recipe; public class RecipeException extends Exception { public enum Type { - INVALID_OWNER_USERNAME, INVALID_STAR, NOT_VIEWABLE, INVALID_COMMENT_ID, INVALID_ID + INVALID_OWNER_USERNAME, INVALID_STAR, NOT_VIEWABLE, INVALID_COMMENT_ID, INVALID_USERNAME_OR_SLUG, INVALID_ID } private final Type type; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java index cd93267..35830a8 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeRepository.java @@ -18,6 +18,9 @@ public interface RecipeRepository extends JpaRepository { List findAllByOwner(UserEntity owner); + @Query("SELECT r from Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2") + Optional findByOwnerUsernameAndSlug(String ownerUsername, String slug); + @Query("SELECT r FROM Recipe r WHERE size(r.stars) >= ?1 AND (r.isPublic OR ?2 MEMBER OF r.viewers)") List findAllViewableByStarsGreaterThanEqual(long stars, UserEntity viewer); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java index bc7e3c6..5960f64 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java @@ -7,5 +7,6 @@ public interface RecipeSecurity { boolean isOwner(Recipe recipe, User user); boolean isOwner(long recipeId, User user) throws RecipeException; boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException; + boolean isViewableBy(String ownerUsername, String slug, @Nullable User user) throws RecipeException; boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException; } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java index 435a01c..de87b84 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java @@ -56,6 +56,16 @@ public class RecipeSecurityImpl implements RecipeSecurity { return false; } + @Override + public boolean isViewableBy(String ownerUsername, String slug, @Nullable User user) throws RecipeException { + final Recipe recipe = this.recipeRepository.findByOwnerUsernameAndSlug(ownerUsername, slug) + .orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_USERNAME_OR_SLUG, + "No such Recipe for username " + ownerUsername + " and slug: " + slug + )); + return this.isViewableBy(recipe, user); + } + @Override public boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException { final Recipe recipe = this.recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException( diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java index ac1c63e..19d87e5 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java @@ -18,6 +18,7 @@ public interface RecipeService { Recipe getById(long id, @Nullable User viewer) throws RecipeException; Recipe getByIdWithStars(long id, @Nullable User viewer) throws RecipeException; FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException; + FullRecipeView getFullViewByUsernameAndSlug(String username, String slug, @Nullable User viewer) throws RecipeException; Slice getInfoViewsViewableBy(Pageable pageable, @Nullable User viewer); List getByMinimumStars(long minimumStars, @Nullable User viewer); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 3fc00ad..5154718 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -53,6 +53,7 @@ public class RecipeServiceImpl implements RecipeService { final RecipeEntity draft = new RecipeEntity(); draft.setCreated(LocalDateTime.now()); draft.setOwner((UserEntity) owner); + draft.setSlug(spec.getSlug()); draft.setTitle(spec.getTitle()); draft.setRawText(spec.getRawText()); draft.setMainImage((S3ImageEntity) spec.getMainImage()); @@ -97,26 +98,44 @@ public class RecipeServiceImpl implements RecipeService { return this.recipeRepository.getViewerCount(recipeId); } - @Override - @PostAuthorize("@recipeSecurity.isViewableBy(#id, #viewer)") - public FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException { - final RecipeEntity recipe = this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException( - RecipeException.Type.INVALID_ID, "No such Recipe for id: " + id - )); + private FullRecipeView getFullView(RecipeEntity recipe) { final FullRecipeView view = new FullRecipeView(); view.setId(recipe.getId()); view.setCreated(recipe.getCreated()); view.setModified(recipe.getModified()); + view.setSlug(recipe.getSlug()); view.setTitle(recipe.getTitle()); view.setText(this.getRenderedMarkdown(recipe)); view.setOwnerId(recipe.getOwner().getId()); view.setOwnerUsername(recipe.getOwner().getUsername()); view.setStarCount(this.getStarCount(recipe)); view.setViewerCount(this.getViewerCount(recipe.getId())); - view.setMainImage(this.imageService.toImageView(recipe.getMainImage())); + if (recipe.getMainImage() != null) { + view.setMainImage(this.imageService.toImageView(recipe.getMainImage())); + } return view; } + @Override + @PreAuthorize("@recipeSecurity.isViewableBy(#id, #viewer)") + public FullRecipeView getFullViewById(long id, @Nullable User viewer) throws RecipeException { + final RecipeEntity recipe = this.recipeRepository.findById(id).orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_ID, "No such Recipe for id: " + id + )); + return this.getFullView(recipe); + } + + @Override + @PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)") + public FullRecipeView getFullViewByUsernameAndSlug(String username, String slug, @Nullable User viewer) throws RecipeException { + final RecipeEntity recipe = this.recipeRepository.findByOwnerUsernameAndSlug(username, slug) + .orElseThrow(() -> new RecipeException( + RecipeException.Type.INVALID_USERNAME_OR_SLUG, + "No such Recipe for username " + username + " and slug: " + slug + )); + return this.getFullView(recipe); + } + @Override public Slice getInfoViewsViewableBy(Pageable pageable, @Nullable User viewer) { return this.recipeRepository.findAllViewableBy((UserEntity) viewer, pageable).map(entity -> { @@ -127,12 +146,14 @@ public class RecipeServiceImpl implements RecipeService { } else { view.setUpdated(entity.getCreated()); } + view.setSlug(entity.getSlug()); view.setTitle(entity.getTitle()); - view.setOwnerId(entity.getOwner().getId()); view.setOwnerUsername(entity.getOwner().getUsername()); view.setPublic(entity.isPublic()); view.setStarCount(this.getStarCount(entity)); - view.setMainImage(this.imageService.toImageView(entity.getMainImage())); + if (entity.getMainImage() != null) { + view.setMainImage(this.imageService.toImageView(entity.getMainImage())); + } return view; }); } @@ -164,6 +185,10 @@ public class RecipeServiceImpl implements RecipeService { public Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException { final RecipeEntity entity = this.findRecipeEntity(id); boolean didModify = false; + if (spec.getSlug() != null) { + entity.setSlug(spec.getSlug()); + didModify = true; + } if (spec.getTitle() != null) { entity.setTitle(spec.getTitle()); didModify = true; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java index 149606a..f45b9f8 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeCreateSpec.java @@ -5,11 +5,20 @@ import org.jetbrains.annotations.Nullable; public class RecipeCreateSpec { + private String slug; private String title; private String rawText; private boolean isPublic; private @Nullable Image mainImage; + public String getSlug() { + return this.slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + public String getTitle() { return this.title; } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java index 5af41af..fcdb8e6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java @@ -5,11 +5,20 @@ import org.jetbrains.annotations.Nullable; public class RecipeUpdateSpec { + private @Nullable String slug; private @Nullable String title; private @Nullable String rawText; private @Nullable Boolean isPublic; private @Nullable Image mainImage; + public @Nullable String getSlug() { + return this.slug; + } + + public void setSlug(@Nullable String slug) { + this.slug = slug; + } + public @Nullable String getTitle() { return this.title; } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java index 2f1286b..491c2bc 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/view/FullRecipeView.java @@ -10,6 +10,7 @@ public class FullRecipeView { private long id; private LocalDateTime created; private LocalDateTime modified; + private String slug; private String title; private String text; private long ownerId; @@ -42,6 +43,14 @@ public class FullRecipeView { this.modified = modified; } + public String getSlug() { + return this.slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + public String getTitle() { return this.title; } diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipeInfoView.java b/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipeInfoView.java index b2ea158..e9d980e 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipeInfoView.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/view/RecipeInfoView.java @@ -1,6 +1,7 @@ package app.mealsmadeeasy.api.recipe.view; import app.mealsmadeeasy.api.image.view.ImageView; +import org.jetbrains.annotations.Nullable; import java.time.LocalDateTime; @@ -8,12 +9,12 @@ public final class RecipeInfoView { private long id; private LocalDateTime updated; + private String slug; private String title; - private long ownerId; private String ownerUsername; private boolean isPublic; private int starCount; - private ImageView mainImage; + private @Nullable ImageView mainImage; public long getId() { return this.id; @@ -31,6 +32,14 @@ public final class RecipeInfoView { this.updated = updated; } + public String getSlug() { + return this.slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + public String getTitle() { return this.title; } @@ -39,14 +48,6 @@ public final class RecipeInfoView { this.title = title; } - public long getOwnerId() { - return this.ownerId; - } - - public void setOwnerId(long ownerId) { - this.ownerId = ownerId; - } - public String getOwnerUsername() { return this.ownerUsername; } @@ -71,11 +72,11 @@ public final class RecipeInfoView { this.starCount = starCount; } - public ImageView getMainImage() { + public @Nullable ImageView getMainImage() { return this.mainImage; } - public void setMainImage(ImageView mainImage) { + public void setMainImage(@Nullable ImageView mainImage) { this.mainImage = mainImage; }