From b26af70ef5756b4aa628ffb21b2e377d2777e550 Mon Sep 17 00:00:00 2001 From: durn Date: Fri, 17 Apr 2026 10:46:38 -0600 Subject: [PATCH 1/4] create account and recipe working. Explore kind of works --- .../demo/controller/RecipeController.java | 3 ++- .../demo/controller/SiteController.java | 22 +++++++++++++--- .../demo/controller/UserController.java | 5 +++- .../demo/service/Impl/RecipeServiceImpl.java | 8 +++--- .../demo/service/Impl/UserServiceImpl.java | 10 +++++++- src/main/resources/static/css/explore.css | 9 ++++--- .../resources/templates/create-account.html | 14 ++++++++--- src/main/resources/templates/explore.html | 25 +++++++++++++++++-- 8 files changed, 78 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/example/demo/controller/RecipeController.java b/src/main/java/com/example/demo/controller/RecipeController.java index 360314c..2b36792 100644 --- a/src/main/java/com/example/demo/controller/RecipeController.java +++ b/src/main/java/com/example/demo/controller/RecipeController.java @@ -36,7 +36,8 @@ public class RecipeController { // build create recipe REST API @PostMapping - public ResponseEntity saveRecipe(@RequestBody RecipeDto recipeDto, Authentication authentication) { + public ResponseEntity saveRecipe(@Valid @RequestBody Recipe recipe, Authentication authentication) { + RecipeDto recipeDto = recipeService.convertToDto(recipe); String currentUsername = authentication.getName(); return new ResponseEntity<>(recipeService.saveRecipe(recipeDto, currentUsername), HttpStatus.CREATED); } diff --git a/src/main/java/com/example/demo/controller/SiteController.java b/src/main/java/com/example/demo/controller/SiteController.java index 8c4d0f8..2199588 100644 --- a/src/main/java/com/example/demo/controller/SiteController.java +++ b/src/main/java/com/example/demo/controller/SiteController.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; @Controller public class SiteController { @@ -49,12 +50,25 @@ public class SiteController { return "view-recipe"; } +// @GetMapping("/explore") +// public String viewExplorePage(Model model) { +// //model.addAttribute("allemplist", employeeServiceImpl.getAllEmployee()); +// List recipes = recipeService.getAllRecipes(); +// model.addAttribute("recipes", recipes); +// return "explore"; +// } + @GetMapping("/explore") - public String viewExplorePage(Model model) { - //model.addAttribute("allemplist", employeeServiceImpl.getAllEmployee()); - List recipes = recipeService.getAllRecipes(); - model.addAttribute("recipes", recipes); + public String explore( + @RequestParam(required = false) String q, + @RequestParam(required = false) List tags, + Model model + ) { + List recipes = recipeService.getRecipes(q, tags); + model.addAttribute("recipes", recipes); + model.addAttribute("q", q); return "explore"; } + } \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/UserController.java b/src/main/java/com/example/demo/controller/UserController.java index 059a688..0250e33 100644 --- a/src/main/java/com/example/demo/controller/UserController.java +++ b/src/main/java/com/example/demo/controller/UserController.java @@ -4,8 +4,11 @@ import java.security.Principal; import java.util.List; import java.util.Optional; +import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -33,11 +36,11 @@ public class UserController { this.userService = userService; this.userRepo = userRepo; } + // build create user REST API @PostMapping public ResponseEntity saveUser(@RequestBody User user) { - return new ResponseEntity(userService.saveUser(user), HttpStatus.CREATED); } diff --git a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java index da898cb..1ad1852 100644 --- a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java @@ -129,6 +129,7 @@ public class RecipeServiceImpl implements RecipeService { ensureUserNotBanned(currentUser); enforceUploadLimit(currentUser); + Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(), dto.getCookTimeMinutes(), dto.getServings(), currentUser, dto.getStatus()); @@ -146,6 +147,7 @@ public class RecipeServiceImpl implements RecipeService { riDto.getNotes()); recipe.getRecipeIngredients().add(ri); + } } @@ -372,8 +374,8 @@ public class RecipeServiceImpl implements RecipeService { public List getRecipes(String name, List tags) { List recipes; - - if (!name.isBlank()) { + + if ((name != null) && (!name.isBlank())) { recipes = recipeRepository.findByTitleContainingIgnoreCase(name); } @@ -381,7 +383,7 @@ public class RecipeServiceImpl implements RecipeService { recipes = recipeRepository.findAll(); } - if (!tags.isEmpty() && !recipes.isEmpty()) { + if ((tags != null) && (!tags.isEmpty()) && !recipes.isEmpty()) { recipes = recipes.stream() .filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tags.contains(tag.getName()))) .collect(Collectors.toList()); diff --git a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java index dd29369..d00b343 100644 --- a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java @@ -4,6 +4,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import com.example.demo.dto.ImageDto; @@ -26,11 +29,13 @@ public class UserServiceImpl implements UserService { private UserRepo userRepository; private RecipeRepo recipeRepository; + private PasswordEncoder passwordEncoder; - public UserServiceImpl(UserRepo userRepository, RecipeRepo recipeRepository) { + public UserServiceImpl(UserRepo userRepository, RecipeRepo recipeRepository, PasswordEncoder passwordEncoder) { super(); this.userRepository = userRepository; this.recipeRepository = recipeRepository; + this.passwordEncoder = passwordEncoder; } public UserDto convertToDto(User user) { @@ -43,6 +48,9 @@ public class UserServiceImpl implements UserService { user.setRole("ROLE_USER"); } user.setBanned(false); + user.setHashedpassword(passwordEncoder.encode(user.getPassword())); + + return userRepository.save(user); } diff --git a/src/main/resources/static/css/explore.css b/src/main/resources/static/css/explore.css index 8503a41..24d6d03 100644 --- a/src/main/resources/static/css/explore.css +++ b/src/main/resources/static/css/explore.css @@ -191,15 +191,18 @@ body, html { .main-content { width: 100%; flex-grow: 1; - display: flex; + /*display: flex; this line was breaking the searched results. They returned recipes would not load at the top of the page*/ flex-direction: column; justify-content: flex-start; align-items: flex-start; - overflow: scroll; + overflow: auto; scrollbar-color: var(--dusty-red) var(--pale-yellow); - height: 100%; + height: auto; } + + + /* safari and old browsers*/ ::-webkit-scrollbar-track { background: var(--pale-yellow); diff --git a/src/main/resources/templates/create-account.html b/src/main/resources/templates/create-account.html index 4a37b9c..720540f 100644 --- a/src/main/resources/templates/create-account.html +++ b/src/main/resources/templates/create-account.html @@ -2,6 +2,9 @@ + + + Create Thyme Crunch Account @@ -85,12 +88,17 @@ hashedpassword: password, role: "USER" }; - + const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content'); + + console.log("JSON to submit:", JSON.stringify(userData, null, 2)); + fetch("http://localhost:8080/api/users", { method: "POST", headers: { - "Content-Type": "application/json" - }, + [csrfHeader]: csrfToken, + 'Content-Type': 'application/json' + }, body: JSON.stringify(userData) }); diff --git a/src/main/resources/templates/explore.html b/src/main/resources/templates/explore.html index 27cb15c..ff36ba6 100644 --- a/src/main/resources/templates/explore.html +++ b/src/main/resources/templates/explore.html @@ -40,10 +40,10 @@
+ From c2e672282912322c287b29cbd3ce5e6c56aa992b Mon Sep 17 00:00:00 2001 From: durn Date: Fri, 17 Apr 2026 19:30:25 -0600 Subject: [PATCH 2/4] explore page working i believe --- .../demo/controller/RecipeController.java | 7 +- .../demo/controller/SiteController.java | 9 +- .../java/com/example/demo/dto/RecipeDto.java | 14 +- .../java/com/example/demo/entity/Recipe.java | 15 +- .../demo/service/Impl/RecipeServiceImpl.java | 46 +++++- .../example/demo/service/RecipeService.java | 2 +- src/main/resources/static/css/explore.css | 81 ++++++++-- .../resources/templates/create-recipe.html | 2 + src/main/resources/templates/explore.html | 147 +++++++++++++++++- 9 files changed, 300 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/example/demo/controller/RecipeController.java b/src/main/java/com/example/demo/controller/RecipeController.java index 2b36792..5560a87 100644 --- a/src/main/java/com/example/demo/controller/RecipeController.java +++ b/src/main/java/com/example/demo/controller/RecipeController.java @@ -59,12 +59,15 @@ public class RecipeController { @GetMapping("/search") public ResponseEntity> searchRecipes( @RequestParam(required = false) String name, // by not adding a name all recipes will appear basically - @RequestParam(required = false) List tags // since users can choose no tags this isnt required + @RequestParam(required = false) List tags, // since users can choose no tags this isnt required + @RequestParam(required = false) List prices, + @RequestParam(required = false) List cookTime, + @RequestParam(required = false) List prepTime ) { - List recipes = recipeService.getRecipes(name, tags); + List recipes = recipeService.getRecipes(name, tags, prices, cookTime, prepTime); return new ResponseEntity<>(recipes, HttpStatus.OK); } diff --git a/src/main/java/com/example/demo/controller/SiteController.java b/src/main/java/com/example/demo/controller/SiteController.java index 2199588..880d25d 100644 --- a/src/main/java/com/example/demo/controller/SiteController.java +++ b/src/main/java/com/example/demo/controller/SiteController.java @@ -62,11 +62,18 @@ public class SiteController { public String explore( @RequestParam(required = false) String q, @RequestParam(required = false) List tags, + @RequestParam(required = false) List prices, + @RequestParam(required = false) List cookTime, + @RequestParam(required = false) List prepTime, Model model ) { - List recipes = recipeService.getRecipes(q, tags); + List recipes = recipeService.getRecipes(q, tags, prices, cookTime, prepTime); model.addAttribute("recipes", recipes); + String displayQuery = q; + + model.addAttribute("q", q); + model.addAttribute("tags", tags); return "explore"; } diff --git a/src/main/java/com/example/demo/dto/RecipeDto.java b/src/main/java/com/example/demo/dto/RecipeDto.java index ff5cb80..7a2531b 100644 --- a/src/main/java/com/example/demo/dto/RecipeDto.java +++ b/src/main/java/com/example/demo/dto/RecipeDto.java @@ -17,6 +17,7 @@ public class RecipeDto { private List steps; private List images; private List tags; + private Integer cost; public RecipeDto() { super(); @@ -24,7 +25,7 @@ public class RecipeDto { public RecipeDto(String title, String description, Integer prepTimeMinutes, Integer cookTimeMinutes, Integer servings, UserDto userDto, String status, List ingredients, - List steps, List images, List tags) { + List steps, List images, List tags, Integer cost) { super(); this.title = title; this.description = description; @@ -37,6 +38,7 @@ public class RecipeDto { this.steps = steps; this.images = images; this.tags = tags; + this.cost = cost; } // getters and setters @@ -136,4 +138,14 @@ public class RecipeDto { public void setId(Integer id) { this.id = id; } + + public Integer getCost() { + return cost; + } + + public void setCost(Integer cost) { + this.cost = cost; + } + + } diff --git a/src/main/java/com/example/demo/entity/Recipe.java b/src/main/java/com/example/demo/entity/Recipe.java index 616ed38..3acf02d 100644 --- a/src/main/java/com/example/demo/entity/Recipe.java +++ b/src/main/java/com/example/demo/entity/Recipe.java @@ -40,6 +40,10 @@ public class Recipe { private String status; + @NotNull(message = "Please Provide a cost") + @Positive(message = "This value cannot be negative") + private Integer cost; + private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -76,7 +80,7 @@ public class Recipe { } public Recipe(String title, String description, Integer prepTimeMinutes, Integer cookTimeMinutes, Integer servings, - User user, String status) { + User user, String status, Integer cost) { this.title = title; this.description = description; this.prepTimeMinutes = prepTimeMinutes; @@ -86,6 +90,7 @@ public class Recipe { this.status = status; this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); + this.cost = cost; } // Getters and setters @@ -208,4 +213,12 @@ public class Recipe { public void setUsers(Set users) { this.users = users; } + + public Integer getCost() { + return cost; + } + + public void setCost(Integer cost) { + this.cost = cost; + } } \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java index 1ad1852..8a7165e 100644 --- a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java @@ -76,7 +76,7 @@ public class RecipeServiceImpl implements RecipeService { RecipeDto dto = new RecipeDto(recipe.getTitle(), recipe.getDescription(), recipe.getPrepTimeMinutes(), recipe.getCookTimeMinutes(), recipe.getServings(), userDto, recipe.getStatus(), ingredientDtos, - stepDtos, imageDtos, tagDtos); + stepDtos, imageDtos, tagDtos, recipe.getCost()); dto.setId(recipe.getId()); @@ -131,7 +131,7 @@ public class RecipeServiceImpl implements RecipeService { Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(), - dto.getCookTimeMinutes(), dto.getServings(), currentUser, dto.getStatus()); + dto.getCookTimeMinutes(), dto.getServings(), currentUser, dto.getStatus(), dto.getCost()); if (dto.getIngredients() != null) { for (RecipeIngredientDto riDto : dto.getIngredients()) { @@ -218,6 +218,7 @@ public class RecipeServiceImpl implements RecipeService { existingRecipe.setCookTimeMinutes(recipeDto.getCookTimeMinutes()); existingRecipe.setServings(recipeDto.getServings()); existingRecipe.setStatus(recipeDto.getStatus()); + existingRecipe.setCost(recipeDto.getCost()); List updatedIngredients = recipeDto.getIngredients(); List ingredientsToRemove = new ArrayList<>(); @@ -371,7 +372,7 @@ public class RecipeServiceImpl implements RecipeService { @Override @Transactional - public List getRecipes(String name, List tags) { + public List getRecipes(String name, List tags, List prices, List cookTime, List prepTime) { List recipes; @@ -388,6 +389,45 @@ public class RecipeServiceImpl implements RecipeService { .filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tags.contains(tag.getName()))) .collect(Collectors.toList()); } + + if (prices != null && !prices.isEmpty() && !recipes.isEmpty()) { + recipes = recipes.stream() + .filter(recipe -> prices.contains(recipe.getCost())) + .collect(Collectors.toList()); + } + + if (cookTime != null && !cookTime.isEmpty() && !recipes.isEmpty()) { + recipes = recipes.stream() + .filter(recipe -> { + int minutes = recipe.getCookTimeMinutes(); + for (Integer ct : cookTime) { + if (ct == 15 && minutes <= 15) return true; + if (ct == 30 && minutes > 15 && minutes <= 30) return true; + if (ct == 60 && minutes > 30 && minutes <= 60) return true; + if (ct == 120 && minutes > 60 && minutes <= 120) return true; + if (ct == 121 && minutes > 120) return true; + } + return false; + }) + .collect(Collectors.toList()); + } + + if (prepTime != null && !prepTime.isEmpty() && !recipes.isEmpty()) { + recipes = recipes.stream() + .filter(recipe -> { + int minutes = recipe.getPrepTimeMinutes(); + for (Integer ct : prepTime) { + if (ct == 15 && minutes <= 15) return true; + if (ct == 30 && minutes > 15 && minutes <= 30) return true; + if (ct == 60 && minutes > 30 && minutes <= 60) return true; + if (ct == 240 && minutes > 60 && minutes <= 240) return true; + if (ct == 241 && minutes > 240) return true; + } + return false; + }) + .collect(Collectors.toList()); + } + List recipeList = new ArrayList<>(); diff --git a/src/main/java/com/example/demo/service/RecipeService.java b/src/main/java/com/example/demo/service/RecipeService.java index 623a07c..0882b35 100644 --- a/src/main/java/com/example/demo/service/RecipeService.java +++ b/src/main/java/com/example/demo/service/RecipeService.java @@ -17,7 +17,7 @@ public interface RecipeService { RecipeDto getRecipeById(Integer recipeId); - List getRecipes(String name, List tags); + List getRecipes(String name, List tags, List prices, List cookTime, List prepTime); RecipeDto updateRecipe(RecipeDto recipedto, Integer Id, String currentUsername); diff --git a/src/main/resources/static/css/explore.css b/src/main/resources/static/css/explore.css index 24d6d03..042e0c0 100644 --- a/src/main/resources/static/css/explore.css +++ b/src/main/resources/static/css/explore.css @@ -202,20 +202,11 @@ body, html { - -/* safari and old browsers*/ -::-webkit-scrollbar-track { - background: var(--pale-yellow); -} -::-webkit-scrollbar-thumb { - background: var(--dusty-red); -} - /* ========================= Search Bar ========================= */ .search-bar, input[type="search"] { - width: 90%; +/* width: 90%; */ margin: 10px; flex-grow: 1; display: flex; @@ -269,10 +260,76 @@ input[type="search"]::-webkit-search-cancel-button { } +#tag-input-wrapper { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 5px; + background: var(--dusty-red); + border-radius: 10px; + padding: 6px 10px; + min-height: 50px; + flex: 1; + cursor: text; +} + +#tag-input-wrapper input[type="text"] { + flex: 1; + min-width: 140px; + background: transparent; + border: none; + outline: none; + color: var(--dark-yellow); + font-size: 20px; + font-family: 'Mali', cursive; + font-weight: 600; + height: auto; + padding: 0; + margin: 0; + width: auto; +} + +#chips-container { + display: flex; + flex-wrap: wrap; + gap: 15px; +} + +.tag-chip { + display: inline-flex; + align-items: center; + gap: 5px; + background: var(--dark); + min-height: 32px; + color: var(--dark-yellow); + border-radius: 20px; + padding: 2px 8px 3px 12px; + font-size: 0.75em; + font-weight: 700; + white-space: nowrap; +} + +.tag-chip button { + background: none; + border: none; + color: var(--dark-yellow); + cursor: pointer; + padding: 0; + font-size: 1.4em; + line-height: 1; + opacity: 0.8; + font-family: 'Mali', cursive; + font-weight: 800; +} + +.tag-chip button:hover { opacity: 1; } + + /* ========================= Recipe Cards Layout ========================= */ .recipe-card { + padding-top: 20px; margin-top: 35px; width: 99.5%; display: flex; @@ -282,8 +339,12 @@ input[type="search"]::-webkit-search-cancel-button { flex-direction: row; height: fit-content; padding-right: 10px; + overflow-y: auto; + flex: 1; + scrollbar-color: var(--dusty-red) var(--pale-yellow); } + a { text-decoration: none; color: var(--dark); diff --git a/src/main/resources/templates/create-recipe.html b/src/main/resources/templates/create-recipe.html index 583dc74..598d09c 100644 --- a/src/main/resources/templates/create-recipe.html +++ b/src/main/resources/templates/create-recipe.html @@ -249,6 +249,7 @@ function buildRecipeJSON(user) { const cookTimeMinutes = Number(document.getElementById('cooking').value); const servings = Number(document.getElementById('servings').value); const status = "DRAFT"; + const cost = Number(document.getElementById('cost').value); // Ingredients const recipeIngredients = [...document.querySelectorAll('#ingredients-container .dynamic-row')] @@ -282,6 +283,7 @@ function buildRecipeJSON(user) { cookTimeMinutes, servings, status, + cost, user, recipeIngredients, steps, diff --git a/src/main/resources/templates/explore.html b/src/main/resources/templates/explore.html index ff36ba6..811fb26 100644 --- a/src/main/resources/templates/explore.html +++ b/src/main/resources/templates/explore.html @@ -40,14 +40,17 @@
+ From 659ab79497f69f2e1b5aa9d10ee16c56ab89243a Mon Sep 17 00:00:00 2001 From: Madeleine Stamp Date: Sat, 18 Apr 2026 14:08:08 -0600 Subject: [PATCH 3/4] Update 16 files - /src/main/resources/templates/create-account.html - /src/main/resources/templates/my-profile.html - /src/main/resources/templates/home.html - /src/main/resources/templates/update-recipe.html - /src/main/resources/templates/explore.html - /src/main/resources/templates/public-profile.html - /src/main/java/com/example/demo/controller/SiteController.java - /src/main/java/com/example/demo/controller/ProfileController.java - /src/main/java/com/example/demo/dto/UserDto.java - /src/main/java/com/example/demo/dto/UpdateProfileDto.java - /src/main/java/com/example/demo/dto/ProfileDto.java - /src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java - /src/main/java/com/example/demo/service/Impl/UserServiceImpl.java - /src/main/java/com/example/demo/service/UserService.java - /src/main/java/com/example/demo/config/SecurityConfig.java - /src/main/java/com/example/demo/entity/User.java --- .../example/demo/config/SecurityConfig.java | 4 +- .../demo/controller/ProfileController.java | 54 +++ .../demo/controller/SiteController.java | 26 +- .../java/com/example/demo/dto/ProfileDto.java | 61 +++ .../example/demo/dto/UpdateProfileDto.java | 25 ++ .../java/com/example/demo/dto/UserDto.java | 34 +- .../java/com/example/demo/entity/User.java | 29 ++ .../demo/service/Impl/RecipeServiceImpl.java | 19 +- .../demo/service/Impl/UserServiceImpl.java | 118 ++++-- .../com/example/demo/service/UserService.java | 8 + .../resources/templates/create-account.html | 57 ++- src/main/resources/templates/explore.html | 380 ++++++++---------- src/main/resources/templates/home.html | 2 +- src/main/resources/templates/my-profile.html | 61 +++ .../resources/templates/public-profile.html | 195 +++++++++ .../resources/templates/update-recipe.html | 0 16 files changed, 781 insertions(+), 292 deletions(-) create mode 100644 src/main/java/com/example/demo/controller/ProfileController.java create mode 100644 src/main/java/com/example/demo/dto/ProfileDto.java create mode 100644 src/main/java/com/example/demo/dto/UpdateProfileDto.java create mode 100644 src/main/resources/templates/my-profile.html create mode 100644 src/main/resources/templates/public-profile.html create mode 100644 src/main/resources/templates/update-recipe.html diff --git a/src/main/java/com/example/demo/config/SecurityConfig.java b/src/main/java/com/example/demo/config/SecurityConfig.java index ddd1cc9..6dd4a50 100644 --- a/src/main/java/com/example/demo/config/SecurityConfig.java +++ b/src/main/java/com/example/demo/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.example.demo.config; +ppackage com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -6,7 +6,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.csrf.CsrfAuthenticationStrategy; @Configuration public class SecurityConfig { @@ -22,6 +21,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers("/login", "/register", "/css/**", "/images/**").permitAll() .requestMatchers("/api/users").permitAll() + .requestMatchers("/users/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) diff --git a/src/main/java/com/example/demo/controller/ProfileController.java b/src/main/java/com/example/demo/controller/ProfileController.java new file mode 100644 index 0000000..546d3bb --- /dev/null +++ b/src/main/java/com/example/demo/controller/ProfileController.java @@ -0,0 +1,54 @@ +package com.example.demo.controller; + +import java.security.Principal; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ModelAttribute; + +import com.example.demo.dto.ProfileDto; +import com.example.demo.dto.UpdateProfileDto; +import com.example.demo.service.UserService; + +@Controller +public class ProfileController { + + private final UserService userService; + + public ProfileController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/users/{id}") + public String viewPublicProfile(@PathVariable Integer id, Model model) { + ProfileDto profile = userService.getProfileByUserId(id); + model.addAttribute("profile", profile); + return "public-profile"; + } + + @GetMapping("/my-profile") + public String viewMyProfile(Principal principal, Model model) { + String username = principal.getName(); + + ProfileDto profile = userService.getCurrentUserProfile(username); + + UpdateProfileDto updateProfileDto = new UpdateProfileDto(); + updateProfileDto.setDisplayName(profile.getDisplayName()); + updateProfileDto.setBio(profile.getBio()); + + model.addAttribute("profile", profile); + model.addAttribute("updateProfileDto", updateProfileDto); + + return "my-profile"; + } + + @PostMapping("/my-profile/update") + public String updateMyProfile(@ModelAttribute UpdateProfileDto dto, Principal principal) { + String username = principal.getName(); + userService.updateProfile(username, dto); + return "redirect:/my-profile"; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/SiteController.java b/src/main/java/com/example/demo/controller/SiteController.java index 880d25d..db3b44c 100644 --- a/src/main/java/com/example/demo/controller/SiteController.java +++ b/src/main/java/com/example/demo/controller/SiteController.java @@ -2,10 +2,8 @@ package com.example.demo.controller; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import com.example.demo.service.RecipeService; import com.example.demo.dto.RecipeDto; -import com.example.demo.entity.Recipe; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -20,10 +18,10 @@ public class SiteController { public SiteController(RecipeService recipeService) { this.recipeService = recipeService; } + @GetMapping("/") public String viewHomePage(Model model) { - //model.addAttribute("allemplist", employeeServiceImpl.getAllEmployee()); - List recipes = recipeService.getAllRecipes(); + List recipes = recipeService.getAllRecipes(); model.addAttribute("recipes", recipes); return "home"; } @@ -49,14 +47,13 @@ public class SiteController { model.addAttribute("recipe", recipe); return "view-recipe"; } - -// @GetMapping("/explore") -// public String viewExplorePage(Model model) { -// //model.addAttribute("allemplist", employeeServiceImpl.getAllEmployee()); -// List recipes = recipeService.getAllRecipes(); -// model.addAttribute("recipes", recipes); -// return "explore"; -// } + + @GetMapping("/recipes/{id}/edit") + public String viewEditRecipePage(@PathVariable Integer id, Model model) { + RecipeDto recipe = recipeService.getRecipeById(id); + model.addAttribute("recipe", recipe); + return "update-recipe"; + } @GetMapping("/explore") public String explore( @@ -69,13 +66,8 @@ public class SiteController { ) { List recipes = recipeService.getRecipes(q, tags, prices, cookTime, prepTime); model.addAttribute("recipes", recipes); - String displayQuery = q; - - model.addAttribute("q", q); model.addAttribute("tags", tags); return "explore"; } - - } \ No newline at end of file diff --git a/src/main/java/com/example/demo/dto/ProfileDto.java b/src/main/java/com/example/demo/dto/ProfileDto.java new file mode 100644 index 0000000..ca7e057 --- /dev/null +++ b/src/main/java/com/example/demo/dto/ProfileDto.java @@ -0,0 +1,61 @@ +package com.example.demo.dto; + +import java.util.List; + +public class ProfileDto { + private Integer id; + private String username; + private String displayName; + private String bio; + private List recipes; + + public ProfileDto() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } + + public List getRecipes() { + return recipes; + } + + public void setRecipes(List recipes) { + this.recipes = recipes; + } + + public String getEffectiveDisplayName() { + if (displayName != null && !displayName.isBlank()) { + return displayName; + } + return username; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/dto/UpdateProfileDto.java b/src/main/java/com/example/demo/dto/UpdateProfileDto.java new file mode 100644 index 0000000..fce6e13 --- /dev/null +++ b/src/main/java/com/example/demo/dto/UpdateProfileDto.java @@ -0,0 +1,25 @@ +package com.example.demo.dto; + +public class UpdateProfileDto { + private String displayName; + private String bio; + + public UpdateProfileDto() { + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/dto/UserDto.java b/src/main/java/com/example/demo/dto/UserDto.java index 7b8a011..2cc8c70 100644 --- a/src/main/java/com/example/demo/dto/UserDto.java +++ b/src/main/java/com/example/demo/dto/UserDto.java @@ -4,6 +4,8 @@ public class UserDto { private Integer id; private String username; private String email; + private String displayName; + private String bio; public UserDto() { } @@ -14,6 +16,14 @@ public class UserDto { this.email = email; } + public UserDto(Integer id, String username, String email, String displayName, String bio) { + this.id = id; + this.username = username; + this.email = email; + this.displayName = displayName; + this.bio = bio; + } + public Integer getId() { return id; } @@ -38,4 +48,26 @@ public class UserDto { this.email = email; } -} + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } + + public String getEffectiveDisplayName() { + if (displayName != null && !displayName.isBlank()) { + return displayName; + } + return username; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/entity/User.java b/src/main/java/com/example/demo/entity/User.java index f7b0a1d..1597aeb 100644 --- a/src/main/java/com/example/demo/entity/User.java +++ b/src/main/java/com/example/demo/entity/User.java @@ -34,6 +34,12 @@ public class User implements UserDetails { @Column(nullable = false, unique = true) private String username; + @Column(length = 100) + private String displayName; + + @Column(length = 1000) + private String bio; + @Column(nullable = false) private String role; @@ -118,6 +124,22 @@ public class User implements UserDetails { this.username = username; } + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } + public String getRole() { return role; } @@ -173,4 +195,11 @@ public class User implements UserDetails { public void setFavRecipes(Set favRecipes) { this.FavRecipes = favRecipes; } + + public String getEffectiveDisplayName() { + if (displayName != null && !displayName.isBlank()) { + return displayName; + } + return username; + } } \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java index 8a7165e..d729629 100644 --- a/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/RecipeServiceImpl.java @@ -71,8 +71,13 @@ public class RecipeServiceImpl implements RecipeService { List tagDtos = recipe.getTags().stream().map(ri -> new TagDto(ri.getName())).toList(); - UserDto userDto = new UserDto(recipe.getUser().getId(), recipe.getUser().getUsername(), - recipe.getUser().getEmail()); + UserDto userDto = new UserDto( + recipe.getUser().getId(), + recipe.getUser().getUsername(), + recipe.getUser().getEmail(), + recipe.getUser().getDisplayName(), + recipe.getUser().getBio() + ); RecipeDto dto = new RecipeDto(recipe.getTitle(), recipe.getDescription(), recipe.getPrepTimeMinutes(), recipe.getCookTimeMinutes(), recipe.getServings(), userDto, recipe.getStatus(), ingredientDtos, @@ -129,7 +134,6 @@ public class RecipeServiceImpl implements RecipeService { ensureUserNotBanned(currentUser); enforceUploadLimit(currentUser); - Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(), dto.getCookTimeMinutes(), dto.getServings(), currentUser, dto.getStatus(), dto.getCost()); @@ -375,7 +379,7 @@ public class RecipeServiceImpl implements RecipeService { public List getRecipes(String name, List tags, List prices, List cookTime, List prepTime) { List recipes; - + if ((name != null) && (!name.isBlank())) { recipes = recipeRepository.findByTitleContainingIgnoreCase(name); } @@ -389,13 +393,13 @@ public class RecipeServiceImpl implements RecipeService { .filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tags.contains(tag.getName()))) .collect(Collectors.toList()); } - + if (prices != null && !prices.isEmpty() && !recipes.isEmpty()) { recipes = recipes.stream() .filter(recipe -> prices.contains(recipe.getCost())) .collect(Collectors.toList()); } - + if (cookTime != null && !cookTime.isEmpty() && !recipes.isEmpty()) { recipes = recipes.stream() .filter(recipe -> { @@ -411,7 +415,7 @@ public class RecipeServiceImpl implements RecipeService { }) .collect(Collectors.toList()); } - + if (prepTime != null && !prepTime.isEmpty() && !recipes.isEmpty()) { recipes = recipes.stream() .filter(recipe -> { @@ -428,7 +432,6 @@ public class RecipeServiceImpl implements RecipeService { .collect(Collectors.toList()); } - List recipeList = new ArrayList<>(); for (Recipe recipe : recipes) { diff --git a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java index d00b343..3283988 100644 --- a/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java +++ b/src/main/java/com/example/demo/service/Impl/UserServiceImpl.java @@ -2,24 +2,20 @@ package com.example.demo.service.Impl; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import org.springframework.context.annotation.Bean; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import com.example.demo.dto.ImageDto; -import com.example.demo.dto.RecipeDto; -import com.example.demo.dto.RecipeIngredientDto; -import com.example.demo.dto.StepDto; -import com.example.demo.dto.TagDto; +import com.example.demo.dto.ProfileDto; +import com.example.demo.dto.UpdateProfileDto; import com.example.demo.dto.UserDto; +import com.example.demo.dto.RecipeDto; import com.example.demo.entity.Recipe; import com.example.demo.entity.User; import com.example.demo.exception.NotFoundException; import com.example.demo.repository.RecipeRepo; import com.example.demo.repository.UserRepo; +import com.example.demo.service.RecipeService; import com.example.demo.service.UserService; import jakarta.transaction.Transactional; @@ -30,16 +26,26 @@ public class UserServiceImpl implements UserService { private UserRepo userRepository; private RecipeRepo recipeRepository; private PasswordEncoder passwordEncoder; + private RecipeService recipeService; - public UserServiceImpl(UserRepo userRepository, RecipeRepo recipeRepository, PasswordEncoder passwordEncoder) { + public UserServiceImpl(UserRepo userRepository, RecipeRepo recipeRepository, + PasswordEncoder passwordEncoder, RecipeService recipeService) { super(); this.userRepository = userRepository; this.recipeRepository = recipeRepository; this.passwordEncoder = passwordEncoder; + this.recipeService = recipeService; } + @Override public UserDto convertToDto(User user) { - return new UserDto(user.getId(), user.getUsername(), user.getEmail()); + return new UserDto( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getDisplayName(), + user.getBio() + ); } @Override @@ -49,27 +55,23 @@ public class UserServiceImpl implements UserService { } user.setBanned(false); user.setHashedpassword(passwordEncoder.encode(user.getPassword())); - - return userRepository.save(user); } @Override public List getAllUsers() { - List list = new ArrayList<>(); for (User user : userRepository.findAll()) { UserDto userDto = convertToDto(user); list.add(userDto); } - return list; } @Override - public UserDto getUserById(Integer Id) { - - return convertToDto(userRepository.findById(Id).orElseThrow(() -> new NotFoundException("User", "id", Id))); + public UserDto getUserById(Integer id) { + return convertToDto(userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("User", "id", id))); } @Override @@ -88,9 +90,9 @@ public class UserServiceImpl implements UserService { } @Override - public UserDto updateUser(User user, Integer Id) { - - User existingUser = userRepository.findById(Id).orElseThrow(() -> new NotFoundException("User", "id", Id)); + public UserDto updateUser(User user, Integer id) { + User existingUser = userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("User", "id", id)); existingUser.setUsername(user.getUsername()); existingUser.setEmail(user.getEmail()); @@ -101,9 +103,10 @@ public class UserServiceImpl implements UserService { } @Override - public void deleteUser(Integer Id) { - userRepository.findById(Id).orElseThrow(() -> new NotFoundException("User", "id", Id)); - userRepository.deleteById(Id); + public void deleteUser(Integer id) { + userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("User", "id", id)); + userRepository.deleteById(id); } @Override @@ -114,27 +117,26 @@ public class UserServiceImpl implements UserService { Recipe existingRecipe = recipeRepository.findById(recipeId) .orElseThrow(() -> new NotFoundException("Recipe", "id", recipeId)); - userRepository.save(existingUser); existingUser.getFavRecipes().remove(existingRecipe); - + userRepository.save(existingUser); } @Override public List getUsersByName(String name) { List users = userRepository.findByUsernameContainingIgnoreCase(name); - if (users.isEmpty()) { - throw new NotFoundException("User", "username containing", name); - } + if (users.isEmpty()) { + throw new NotFoundException("User", "username containing", name); + } - List userList = new ArrayList<>(); + List userList = new ArrayList<>(); - for (User user : users) { - UserDto dto = convertToDto(user); - userList.add(dto); - } - return userList; + for (User user : users) { + UserDto dto = convertToDto(user); + userList.add(dto); + } + return userList; } @Override @@ -172,4 +174,50 @@ public class UserServiceImpl implements UserService { userRepository.save(user); return convertToDto(user); } -} + + @Override + @Transactional + public ProfileDto getProfileByUserId(Integer userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("User", "id", userId)); + + List recipes = recipeRepository.findByUserId(userId); + List recipeDtos = new ArrayList<>(); + + for (Recipe recipe : recipes) { + recipeDtos.add(recipeService.convertToDto(recipe)); + } + + ProfileDto profile = new ProfileDto(); + profile.setId(user.getId()); + profile.setUsername(user.getUsername()); + profile.setDisplayName(user.getDisplayName()); + profile.setBio(user.getBio()); + profile.setRecipes(recipeDtos); + + return profile; + } + + @Override + @Transactional + public ProfileDto getCurrentUserProfile(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new NotFoundException("User", "username", username)); + + return getProfileByUserId(user.getId()); + } + + @Override + @Transactional + public ProfileDto updateProfile(String username, UpdateProfileDto dto) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new NotFoundException("User", "username", username)); + + user.setDisplayName(dto.getDisplayName()); + user.setBio(dto.getBio()); + + userRepository.save(user); + + return getProfileByUserId(user.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/UserService.java b/src/main/java/com/example/demo/service/UserService.java index cf315f5..ec96be1 100644 --- a/src/main/java/com/example/demo/service/UserService.java +++ b/src/main/java/com/example/demo/service/UserService.java @@ -2,6 +2,8 @@ package com.example.demo.service; import java.util.List; +import com.example.demo.dto.ProfileDto; +import com.example.demo.dto.UpdateProfileDto; import com.example.demo.dto.UserDto; import com.example.demo.entity.User; @@ -31,4 +33,10 @@ public interface UserService { UserDto makeAdmin(Integer id); UserDto makeUser(Integer id); + + ProfileDto getProfileByUserId(Integer userId); + + ProfileDto getCurrentUserProfile(String username); + + ProfileDto updateProfile(String username, UpdateProfileDto dto); } diff --git a/src/main/resources/templates/create-account.html b/src/main/resources/templates/create-account.html index 720540f..4fc709d 100644 --- a/src/main/resources/templates/create-account.html +++ b/src/main/resources/templates/create-account.html @@ -51,34 +51,39 @@ + document.querySelectorAll('input[name="cookTime"]').forEach(cb => { + cb.addEventListener('change', submitSearch); + }); + + document.querySelectorAll('input[name="prepTime"]').forEach(cb => { + cb.addEventListener('change', submitSearch); + }); + + document.getElementById('search-form').addEventListener('submit', (e) => { + e.preventDefault(); + submitSearch(); + }); + + function addChip(tag) { + if (tags.includes(tag)) return; + tags.push(tag); + + const chip = document.createElement('span'); + chip.className = 'tag-chip'; + chip.innerHTML = `#${tag} `; + + chip.querySelector('button').addEventListener('click', () => { + chip.remove(); + tags.splice(tags.indexOf(tag), 1); + submitSearch(); + }); + + input.insertAdjacentElement('afterend', chip); + } + + function submitSearch() { + const cleanedQuery = input.value.replace(/#\w+/g, '').trim(); + const out = new URLSearchParams(); + + if (cleanedQuery) out.set('q', cleanedQuery); + tags.forEach(t => out.append('tags', t)); + + document.querySelectorAll('input[name="price"]:checked') + .forEach(cb => out.append('prices', cb.value)); + + document.querySelectorAll('input[name="cookTime"]:checked') + .forEach(cb => out.append('cookTime', cb.value)); + + document.querySelectorAll('input[name="prepTime"]:checked') + .forEach(cb => out.append('prepTime', cb.value)); + + window.location.href = '/explore?' + out.toString(); + } + })(); + - + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 619f36c..bd28522 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -21,7 +21,7 @@
  • Home
  • Explore
  • -
  • Profile
  • +
  • Profile
  • Saved
  • diff --git a/src/main/resources/templates/my-profile.html b/src/main/resources/templates/my-profile.html new file mode 100644 index 0000000..3784d47 --- /dev/null +++ b/src/main/resources/templates/my-profile.html @@ -0,0 +1,61 @@ + + + + + My Profile + + +

    My Profile

    + +

    Username: username

    +

    + Public Profile: + View my public profile +

    + +

    Edit Profile

    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + +

    My Recipes

    + +
    +

    You have not created any recipes yet.

    +
    + +
    +
    +

    Recipe Title

    + +

    + Author: + + Author Name + +

    + +

    Recipe description

    + + View Recipe + | + Edit Recipe +
    +
    + + \ No newline at end of file diff --git a/src/main/resources/templates/public-profile.html b/src/main/resources/templates/public-profile.html new file mode 100644 index 0000000..6bc91c8 --- /dev/null +++ b/src/main/resources/templates/public-profile.html @@ -0,0 +1,195 @@ + + + + + Explore Recipes + + +

    Explore Recipes

    + + + +
    + + + +

    Prep Time

    +
    +
    +
    +
    + + +

    Cook Time

    +
    +
    +
    +
    + + +

    Price

    +
    +
    +
    + +
    + +
    +

    No recipes found.

    +
    + +
    +
    +

    + Recipe Title +

    + +

    + Author: + + Author Name + +

    + +

    Recipe description

    + +

    + Prep: 0 min | + Cook: 0 min | + Servings: 0 | + Cost: 0 +

    + +
    + Recipe Image +
    + +

    View Recipe

    +
    +
    + + + + \ No newline at end of file diff --git a/src/main/resources/templates/update-recipe.html b/src/main/resources/templates/update-recipe.html new file mode 100644 index 0000000..e69de29 From 2a14f1264ac73c5ad035fee4953cec4c76441b18 Mon Sep 17 00:00:00 2001 From: Madeleine Stamp Date: Sat, 18 Apr 2026 14:09:38 -0600 Subject: [PATCH 4/4] Edit SecurityConfig.java --- src/main/java/com/example/demo/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/demo/config/SecurityConfig.java b/src/main/java/com/example/demo/config/SecurityConfig.java index 6dd4a50..0831b95 100644 --- a/src/main/java/com/example/demo/config/SecurityConfig.java +++ b/src/main/java/com/example/demo/config/SecurityConfig.java @@ -1,4 +1,4 @@ -ppackage com.example.demo.config; +package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;