diff --git a/backend/src/main/java/com/group1/cuisines/config/JwtAuthenticationFilter.java b/backend/src/main/java/com/group1/cuisines/config/JwtAuthenticationFilter.java index 5a05e20e..2468cd7f 100644 --- a/backend/src/main/java/com/group1/cuisines/config/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/group1/cuisines/config/JwtAuthenticationFilter.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.group1.cuisines.dao.response.ErrorResponse; +import com.group1.cuisines.entities.User; +import com.group1.cuisines.repositories.UserRepository; import com.group1.cuisines.services.JwtService; import com.group1.cuisines.services.UserService; import io.jsonwebtoken.ExpiredJwtException; @@ -17,18 +19,25 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final UserRepository userRepository; private final JwtService jwtService; - private final UserService userService; private final ObjectMapper objectMapper; + private static final Logger logger = LoggerFactory.getLogger(UserService.class); + @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @@ -57,9 +66,7 @@ protected void doFilterInternal( StringUtils.isNotEmpty(userEmail) && SecurityContextHolder.getContext().getAuthentication() == null ) { - UserDetails userDetails = userService - .userDetailsService() - .loadUserByUsername(userEmail); + UserDetails userDetails = userDetailsService().loadUserByUsername(userEmail); if (jwtService.isTokenValid(jwt, userDetails)) { SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -88,4 +95,20 @@ protected void doFilterInternal( } filterChain.doFilter(request, response); } + + public UserDetailsService userDetailsService() { + return new UserDetailsService() { + + @Override + public UserDetails loadUserByUsername(String usernameOrEmail) { + logger.debug("Attempting to find user by email or username: {}", usernameOrEmail); + + User user = userRepository.findByEmailOrUsername(usernameOrEmail, usernameOrEmail) + .orElseThrow(() -> new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)); + + return user; + } + + }; + } } diff --git a/backend/src/main/java/com/group1/cuisines/config/SecurityConfiguration.java b/backend/src/main/java/com/group1/cuisines/config/SecurityConfiguration.java index 7ee00e81..3d51c093 100644 --- a/backend/src/main/java/com/group1/cuisines/config/SecurityConfiguration.java +++ b/backend/src/main/java/com/group1/cuisines/config/SecurityConfiguration.java @@ -24,11 +24,8 @@ @RequiredArgsConstructor public class SecurityConfiguration { - private final UserService userService; - private final JwtAuthenticationFilter jwtAuthenticationFilter; - @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -69,7 +66,7 @@ public PasswordEncoder passwordEncoder() { public AuthenticationProvider authenticationProvider() { // Authentication provider DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userService.userDetailsService()); + authProvider.setUserDetailsService(jwtAuthenticationFilter.userDetailsService()); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } diff --git a/backend/src/main/java/com/group1/cuisines/controllers/RecipeController.java b/backend/src/main/java/com/group1/cuisines/controllers/RecipeController.java index 814ec4d0..a4cdf703 100644 --- a/backend/src/main/java/com/group1/cuisines/controllers/RecipeController.java +++ b/backend/src/main/java/com/group1/cuisines/controllers/RecipeController.java @@ -3,6 +3,7 @@ import com.group1.cuisines.dto.*; import com.group1.cuisines.entities.Comment; import com.group1.cuisines.entities.User; +import com.group1.cuisines.services.AuthenticationService; import com.group1.cuisines.services.RecipeService; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; @@ -20,6 +21,8 @@ public class RecipeController { @Autowired private RecipeService recipeService; + @Autowired + private AuthenticationService authenticationService; @GetMapping("/recipes/{recipeId}") @@ -45,14 +48,13 @@ public ResponseEntity> getRecipes(@RequestParam(required = false @PostMapping("/recipes") public ResponseEntity createRecipe(@RequestBody NewRecipeDto newRecipe) throws Exception{ - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authenticationService.getUser().map(User::getUsername).orElse(null); // Check if the user is authenticated - if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + if (username == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required."); } - String username = authentication.getName(); RecipeDetailDto recipeDetails = recipeService.createRecipe(newRecipe, username); if (recipeDetails != null) { return ResponseEntity.status(HttpStatus.CREATED).body(recipeDetails); @@ -62,14 +64,12 @@ public ResponseEntity createRecipe(@RequestBody NewRecipeDto newRecipe) throw } @DeleteMapping("/recipes/{id}") public ResponseEntity deleteRecipe(@PathVariable Integer id) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authenticationService.getUser().map(User::getUsername).orElse(null); - // Check if the user is authenticated - if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + if (username == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required."); } - String username = authentication.getName(); if (recipeService.deleteRecipe(id, username)) { return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Recipe deleted successfully."); } else { @@ -78,12 +78,11 @@ public ResponseEntity deleteRecipe(@PathVariable Integer id) { } @PostMapping("/recipes/{recipeId}/rating") public ResponseEntity rateRecipe(@PathVariable Integer recipeId, @RequestBody RatingDto ratingDto) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + String username = authenticationService.getUser().map(User::getUsername).orElse(null); + if (username == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required."); } - String username = authentication.getName(); // Assuming the username can be obtained like this boolean success = recipeService.rateRecipe(recipeId, username, ratingDto.getRating()); if (success) { @@ -95,12 +94,11 @@ public ResponseEntity rateRecipe(@PathVariable Integer recipeId, @RequestBody @PostMapping("/recipes/{recipeId}/bookmarks") public ResponseEntity bookmarkRecipe(@PathVariable Integer recipeId) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + String username = authenticationService.getUser().map(User::getUsername).orElse(null); + if (username == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required."); } - String username = authentication.getName(); // Assuming the username can be obtained like this boolean success = recipeService.bookmarkRecipe(recipeId, username); if (success) { @@ -112,8 +110,8 @@ public ResponseEntity bookmarkRecipe(@PathVariable Integer recipeId) { @GetMapping("/recipes/{recipeId}/bookmarks") public ResponseEntity getBookmarks(@PathVariable Integer recipeId) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + String username = authenticationService.getUser().map(User::getUsername).orElse(null); + if (username == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required."); } diff --git a/backend/src/main/java/com/group1/cuisines/dto/RecipeDetailsDto.java b/backend/src/main/java/com/group1/cuisines/dto/RecipeDetailsDto.java index 5f5ec185..623dc8ab 100644 --- a/backend/src/main/java/com/group1/cuisines/dto/RecipeDetailsDto.java +++ b/backend/src/main/java/com/group1/cuisines/dto/RecipeDetailsDto.java @@ -6,6 +6,7 @@ import lombok.*; @Data +@Builder @NoArgsConstructor @AllArgsConstructor public class RecipeDetailsDto { @@ -21,6 +22,7 @@ public class RecipeDetailsDto { private CuisineDto cuisine; private DishDto dish; private Double avgRating; + private Integer selfRating; private AuthorDto author; diff --git a/backend/src/main/java/com/group1/cuisines/repositories/RatingRepository.java b/backend/src/main/java/com/group1/cuisines/repositories/RatingRepository.java index c9c4ff02..8ee5c779 100644 --- a/backend/src/main/java/com/group1/cuisines/repositories/RatingRepository.java +++ b/backend/src/main/java/com/group1/cuisines/repositories/RatingRepository.java @@ -5,8 +5,10 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface RatingRepository extends JpaRepository { - Rating findByRecipeIdAndUserId(Integer recipeId, Integer userId); + Optional findByRecipeIdAndUserId(Integer recipeId, Integer userId); } diff --git a/backend/src/main/java/com/group1/cuisines/services/AuthenticationService.java b/backend/src/main/java/com/group1/cuisines/services/AuthenticationService.java index 1221f008..bd948232 100644 --- a/backend/src/main/java/com/group1/cuisines/services/AuthenticationService.java +++ b/backend/src/main/java/com/group1/cuisines/services/AuthenticationService.java @@ -9,9 +9,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service @RequiredArgsConstructor public class AuthenticationService { @@ -93,4 +97,14 @@ public ApiResponse signin( ); } } + + public Optional getUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication.getPrincipal().equals("anonymousUser")) { + return Optional.empty(); + } + String username = authentication.getName(); + return userRepository.findByUsername(username); + } + } diff --git a/backend/src/main/java/com/group1/cuisines/services/RecipeService.java b/backend/src/main/java/com/group1/cuisines/services/RecipeService.java index 51efe39f..c39fb45d 100644 --- a/backend/src/main/java/com/group1/cuisines/services/RecipeService.java +++ b/backend/src/main/java/com/group1/cuisines/services/RecipeService.java @@ -18,6 +18,8 @@ @Service public class RecipeService { + @Autowired + private AuthenticationService authenticationService; @Autowired private IngredientsRepository ingredientRepository; @@ -126,7 +128,7 @@ public boolean rateRecipe(Integer recipeId, String username, Integer ratingValue Recipe recipe = recipeRepository.findById(recipeId).orElse(null); if (user.isPresent() && recipe != null && ratingValue >= 1 && ratingValue <= 5) { - Rating existingRating = ratingRepository.findByRecipeIdAndUserId(recipeId, user.get().getId()); + Rating existingRating = ratingRepository.findByRecipeIdAndUserId(recipeId, user.get().getId()).orElse(null); if (existingRating != null) { return false; // Indicates that the user has already rated } @@ -185,59 +187,26 @@ public List getCommentsByRecipeId(Integer recipeId) { public RecipeDetailsDto getRecipeById(Integer recipeId) { Optional recipe = recipeRepository.findById(recipeId); - - - if (recipe.isPresent()) { - CuisineDto cuisineDto = new CuisineDto(); - Recipe r = recipe.get(); - if (r.getDish() != null && !r.getDish().getCuisines().isEmpty()) { - - cuisineDto.setId(r.getDish().getCuisines().get(0).getId()); - cuisineDto.setName(r.getDish().getCuisines().get(0).getName()); - - } - else if(r.getDish() != null && r.getDish().getCuisines().isEmpty()){ - cuisineDto.setId("No cuisine Id from wikidata"); - cuisineDto.setName("No cuisine name from wikidata"); - } - // Conversion from Recipe entity to RecipeDetailsDto - return new RecipeDetailsDto( - r.getId(), - r.getTitle(), - - r.getInstructions(), - r.getIngredients().stream().map(ingredient -> new IngredientsDto( ingredient.getName())).collect(Collectors.toList()), - - r.getCookingTime(), - r.getServingSize(), - cuisineDto, - - new DishDto(r.getDish().getId(), r.getDish().getName(), r.getDish().getImage()), - r.getAverageRating(), - new AuthorDto(r.getUser().getId(), r.getUser().getFirstName() , r.getUser().getUsername(), r.getUser().getFollowing().size(),r.getUser().getFollowers().size(), r.getUser().getRecipeCount()) - - ); - } - return null; + return recipe.map(this::convertToRecipeDetailsDto).orElse(null); } public List getRecipesByType(String type, @Nullable String username) { if ("explore".equals(type)) { return recipeRepository.findAll().stream() - .map(this::convertToRecipeDto) + .map(this::convertToRecipeDetailsDto) .collect(Collectors.toList()); } else if ("following".equals(type) && username != null) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("User not found")); return user.getFollowing().stream() .flatMap(followingUser -> followingUser.getRecipes().stream()) - .map(this::convertToRecipeDto) + .map(this::convertToRecipeDetailsDto) .collect(Collectors.toList()); } return new ArrayList<>(); // Return an empty list if username is null or other conditions are not met } - private RecipeDetailsDto convertToRecipeDto(Recipe r) { + public RecipeDetailsDto convertToRecipeDetailsDto(Recipe r) { CuisineDto cuisineDto = new CuisineDto(); if (r.getDish() != null && !r.getDish().getCuisines().isEmpty()) { @@ -249,22 +218,25 @@ else if(r.getDish() != null && r.getDish().getCuisines().isEmpty()){ cuisineDto.setId("No cuisine Id from wikidata"); cuisineDto.setName("No cuisine name from wikidata"); } - // Conversion logic here - return new RecipeDetailsDto( - r.getId(), - r.getTitle(), - - r.getInstructions(), - r.getIngredients().stream().map(ingredient -> new IngredientsDto( ingredient.getName())).collect(Collectors.toList()), - r.getCookingTime(), - r.getServingSize(), - cuisineDto, + Integer userRating = authenticationService.getUser().map(User::getId) + .flatMap(userId -> ratingRepository.findByRecipeIdAndUserId(r.getId(), userId)) + .map(Rating::getRatingValue) + .orElse(null); - new DishDto(r.getDish().getId(), r.getDish().getName(), r.getDish().getImage()), - r.getAverageRating(), - new AuthorDto(r.getUser().getId(), r.getUser().getFirstName() , r.getUser().getUsername(),r.getUser().getFollowing().size(), r.getUser().getFollowers().size(), r.getUser().getRecipeCount()) - - ); + // Conversion logic here + return RecipeDetailsDto.builder() + .id(r.getId()) + .name(r.getTitle()) + .instructions(r.getInstructions()) + .ingredients(r.getIngredients().stream().map(ingredient -> new IngredientsDto( ingredient.getName())).collect(Collectors.toList())) + .cookTime(r.getCookingTime()) + .servingSize(r.getServingSize()) + .cuisine(cuisineDto) + .dish(new DishDto(r.getDish().getId(), r.getDish().getName(), r.getDish().getImage())) + .avgRating(r.getAverageRating()) + .selfRating(userRating) + .author(new AuthorDto(r.getUser().getId(), r.getUser().getFirstName() , r.getUser().getUsername(), r.getUser().getFollowing().size(), r.getUser().getFollowers().size(), r.getUser().getRecipeCount())) + .build(); } } diff --git a/backend/src/main/java/com/group1/cuisines/services/UserService.java b/backend/src/main/java/com/group1/cuisines/services/UserService.java index 835b789c..ec7b80b3 100644 --- a/backend/src/main/java/com/group1/cuisines/services/UserService.java +++ b/backend/src/main/java/com/group1/cuisines/services/UserService.java @@ -7,6 +7,7 @@ import com.group1.cuisines.repositories.UserRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -24,24 +25,10 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + @Autowired + private final RecipeService recipeService; private static final Logger logger = LoggerFactory.getLogger(UserService.class); - public UserDetailsService userDetailsService() { - return new UserDetailsService() { - - @Override - public UserDetails loadUserByUsername(String usernameOrEmail) { - logger.debug("Attempting to find user by email or username: {}", usernameOrEmail); - - User user = userRepository.findByEmailOrUsername(usernameOrEmail, usernameOrEmail) - .orElseThrow(() -> new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)); - - return user; - } - - }; - } - public List searchUsers(String searchTerm) { if (searchTerm == null || searchTerm.trim().isEmpty()) { @@ -142,7 +129,7 @@ public UserProfileDto getUserProfileById(Integer userId, String currentUsername) profile.setFollowingCount(user.getFollowing().size()); profile.setRecipeCount(user.getRecipes().size()); profile.setRecipes(user.getRecipes().stream() - .map(this::convertToRecipeDetailsDto) + .map(recipeService::convertToRecipeDetailsDto) .collect(Collectors.toList())); if (isSelf) { @@ -156,35 +143,6 @@ public UserProfileDto getUserProfileById(Integer userId, String currentUsername) return profile; } - private RecipeDetailsDto convertToRecipeDetailsDto(Recipe recipe) { - CuisineDto cuisineDto = new CuisineDto(); - if (recipe.getDish() != null && !recipe.getDish().getCuisines().isEmpty()) { - - cuisineDto.setId(recipe.getDish().getCuisines().get(0).getId()); - cuisineDto.setName(recipe.getDish().getCuisines().get(0).getName()); - - } - else if(recipe.getDish() != null && recipe.getDish().getCuisines().isEmpty()){ - cuisineDto.setId("No cuisine Id from wikidata"); - cuisineDto.setName("No cuisine name from wikidata"); - } - return new RecipeDetailsDto( - recipe.getId(), - recipe.getTitle(), - recipe.getInstructions(), - recipe.getIngredients().stream() - .map(ingredient -> new IngredientsDto(ingredient.getName())) - .collect(Collectors.toList()), - recipe.getCookingTime(), - recipe.getServingSize(), - cuisineDto, - new DishDto(recipe.getDish().getId(), recipe.getDish().getName(), recipe.getDish().getImage()), - recipe.getAverageRating(), - new AuthorDto(recipe.getUser().getId(), recipe.getUser().getUsername(), recipe.getUser().getFirstName()+ " " + recipe.getUser().getLastName() , - recipe.getUser().getFollowerCount(), recipe.getUser().getFollowingCount(),recipe.getUser().getRecipeCount()) - ); - } - private BookmarkDto convertToBookmarkDto(Bookmark bookmark) { Recipe recipe = bookmark.getRecipe(); @@ -233,7 +191,7 @@ public UserProfileDto getUserProfileDtoById(Integer userId) { profile.setFollowingCount(user.getFollowing().size()); profile.setRecipeCount(user.getRecipes().size()); profile.setRecipes(user.getRecipes().stream() - .map(this::convertToRecipeDetailsDto) + .map(recipeService::convertToRecipeDetailsDto) .collect(Collectors.toList())); return profile; }