mirror of
https://gitlab.com/etc404/software-engineering-project.git
synced 2026-05-10 20:52:58 +00:00
Merge branch 'NapoleonsLeftBoot-main-patch-74b7' into 'main'
Update 12 files See merge request etc404/software-engineering-project!2
This commit is contained in:
@@ -9,6 +9,7 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@@ -17,8 +18,11 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/login", "/register", "/css/**", "/images/**").permitAll()
|
.requestMatchers("/login", "/register", "/css/**", "/images/**").permitAll()
|
||||||
|
.requestMatchers("/api/users").permitAll()
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin(form -> form
|
.formLogin(form -> form
|
||||||
@@ -27,6 +31,7 @@ public class SecurityConfig {
|
|||||||
.permitAll()
|
.permitAll()
|
||||||
)
|
)
|
||||||
.logout(logout -> logout.permitAll());
|
.logout(logout -> logout.permitAll());
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.example.demo.controller;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import com.example.demo.dto.UserDto;
|
||||||
|
import com.example.demo.service.UserService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin")
|
||||||
|
public class AdminController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public AdminController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{id}/ban")
|
||||||
|
public ResponseEntity<UserDto> banUser(@PathVariable Integer id) {
|
||||||
|
return new ResponseEntity<>(userService.banUser(id), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{id}/unban")
|
||||||
|
public ResponseEntity<UserDto> unbanUser(@PathVariable Integer id) {
|
||||||
|
return new ResponseEntity<>(userService.unbanUser(id), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{id}/make-admin")
|
||||||
|
public ResponseEntity<UserDto> makeAdmin(@PathVariable Integer id) {
|
||||||
|
return new ResponseEntity<>(userService.makeAdmin(id), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{id}/make-user")
|
||||||
|
public ResponseEntity<UserDto> makeUser(@PathVariable Integer id) {
|
||||||
|
return new ResponseEntity<>(userService.makeUser(id), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
import com.example.demo.dto.RecipeDto;
|
import com.example.demo.dto.RecipeDto;
|
||||||
import com.example.demo.dto.UserDto;
|
import com.example.demo.dto.UserDto;
|
||||||
@@ -35,9 +36,9 @@ public class RecipeController {
|
|||||||
|
|
||||||
// build create recipe REST API
|
// build create recipe REST API
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<RecipeDto> saveRecipe(@Valid @RequestBody Recipe recipe) {
|
public ResponseEntity<RecipeDto> saveRecipe(@RequestBody RecipeDto recipeDto, Authentication authentication) {
|
||||||
RecipeDto recipeDto = recipeService.convertToDto(recipe);
|
String currentUsername = authentication.getName();
|
||||||
return new ResponseEntity<RecipeDto>(recipeService.saveRecipe(recipeDto), HttpStatus.CREATED);
|
return new ResponseEntity<>(recipeService.saveRecipe(recipeDto, currentUsername), HttpStatus.CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build get all recipes REST API
|
// build get all recipes REST API
|
||||||
@@ -69,17 +70,21 @@ public class RecipeController {
|
|||||||
// build update recipe REST API
|
// build update recipe REST API
|
||||||
// http://localhost:8080/api/recipes/(id number goes here)
|
// http://localhost:8080/api/recipes/(id number goes here)
|
||||||
@PutMapping("{id}")
|
@PutMapping("{id}")
|
||||||
public ResponseEntity<RecipeDto> updateRecipe(@PathVariable("id") Integer recipeId, @RequestBody Recipe recipe) {
|
public ResponseEntity<RecipeDto> updateRecipe(
|
||||||
RecipeDto recipeDto = recipeService.convertToDto(recipe);
|
@PathVariable("id") Integer recipeId,
|
||||||
return new ResponseEntity<RecipeDto>(recipeService.updateRecipe(recipeDto, recipeId), HttpStatus.OK);
|
@RequestBody RecipeDto recipeDto,
|
||||||
|
Authentication authentication) {
|
||||||
|
String currentUsername = authentication.getName();
|
||||||
|
return new ResponseEntity<>(recipeService.updateRecipe(recipeDto, recipeId, currentUsername), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build delete recipe REST API
|
// build delete recipe REST API
|
||||||
// http://localhost:8080/api/recipes/(id number goes here)
|
// http://localhost:8080/api/recipes/(id number goes here)
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
public ResponseEntity<String> deleteRecipe(@PathVariable("id") Integer recipeId) {
|
public ResponseEntity<String> deleteRecipe(@PathVariable("id") Integer recipeId, Authentication authentication) {
|
||||||
recipeService.deleteRecipe(recipeId);
|
String currentUsername = authentication.getName();
|
||||||
return new ResponseEntity<String>("Recipe deleted succesfully!", HttpStatus.OK);
|
recipeService.deleteRecipe(recipeId, currentUsername);
|
||||||
|
return new ResponseEntity<>("Recipe deleted successfully!", HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.demo.controller;
|
package com.example.demo.controller;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -15,26 +13,20 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
|
|
||||||
import com.example.demo.dto.UserDto;
|
import com.example.demo.dto.UserDto;
|
||||||
import com.example.demo.entity.User;
|
import com.example.demo.entity.User;
|
||||||
import com.example.demo.service.UserService;
|
import com.example.demo.service.UserService;
|
||||||
import com.example.demo.repository.UserRepo;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/users")
|
@RequestMapping("/api/users")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
private UserRepo userRepo;
|
|
||||||
|
|
||||||
public UserController(UserService userService, UserRepo userRepo) {
|
public UserController(UserService userService) {
|
||||||
super();
|
super();
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.userRepo = userRepo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// build create user REST API
|
// build create user REST API
|
||||||
@@ -52,21 +44,11 @@ public class UserController {
|
|||||||
|
|
||||||
// build get user by name REST API
|
// build get user by name REST API
|
||||||
@GetMapping("/search")
|
@GetMapping("/search")
|
||||||
public ResponseEntity<List<UserDto>> getUsersByName(@RequestParam String string) {
|
public ResponseEntity<List<UserDto>> getUsersByName(@RequestParam String name) {
|
||||||
List<UserDto> users = userService.getUsersByName(string);
|
List<UserDto> users = userService.getUsersByName(name);
|
||||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build get current user REST API
|
|
||||||
@GetMapping("/me")
|
|
||||||
public UserDto getLoggedInUser(Principal principal) {
|
|
||||||
if (principal == null) return null;
|
|
||||||
String username = principal.getName();
|
|
||||||
User user = (userRepo.findByUsername(username))
|
|
||||||
.orElse(null);
|
|
||||||
return userService.convertToDto(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// build get user by id REST API
|
// build get user by id REST API
|
||||||
// http://localhost:8080/api/users/(id number goes here)
|
// http://localhost:8080/api/users/(id number goes here)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Positive;
|
import jakarta.validation.constraints.Positive;
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -21,20 +20,17 @@ public class Recipe {
|
|||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT")
|
|
||||||
@NotBlank(message = "Please provide a recipe title")
|
@NotBlank(message = "Please provide a recipe title")
|
||||||
@Size(max = 128, message = "Title cannot be longer than 128 characters")
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
@Size(max = 500, message = "Description cannot be longer than 500 characters")
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@NotNull(message = "Please Provide a prep time amount in minutes")
|
@NotNull(message = "Please Provide a prep time amount in mintutes")
|
||||||
@Positive(message = "This value cannot be negative")
|
@Positive(message = "This value cannot be negative")
|
||||||
private Integer prepTimeMinutes;
|
private Integer prepTimeMinutes;
|
||||||
|
|
||||||
@NotNull(message = "Please Provide a cook time amount in minutes")
|
@NotNull(message = "Please Provide a cook time amount in mintutes")
|
||||||
@Positive(message = "This value cannot be negative")
|
@Positive(message = "This value cannot be negative")
|
||||||
private Integer cookTimeMinutes;
|
private Integer cookTimeMinutes;
|
||||||
|
|
||||||
@@ -56,13 +52,10 @@ public class Recipe {
|
|||||||
// Recipe ingredients relationship
|
// Recipe ingredients relationship
|
||||||
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
@NotEmpty(message = "At least one ingredient is required")
|
@NotEmpty(message = "At least one ingredient is required")
|
||||||
@Size(max = 256, message = "Cannot have more than 256 ingredients")
|
|
||||||
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
|
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
|
||||||
|
|
||||||
// Recipe Steps relationship
|
// Recipe Steps relationship
|
||||||
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
@NotEmpty(message = "At least one step is required")
|
|
||||||
@Size(max = 50, message = "Cannot have more than 50 steps")
|
|
||||||
private Set<Step> steps = new HashSet<>();
|
private Set<Step> steps = new HashSet<>();
|
||||||
|
|
||||||
// Recipe Images relationship
|
// Recipe Images relationship
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
package com.example.demo.entity;
|
package com.example.demo.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinTable;
|
import jakarta.persistence.JoinTable;
|
||||||
import jakarta.persistence.ManyToMany;
|
import jakarta.persistence.ManyToMany;
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -31,17 +29,15 @@ public class User implements UserDetails {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@NotNull
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
@NotBlank(message = "Username is required")
|
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
@NotBlank(message = "Email is required")
|
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
private String hashedpassword;
|
private String hashedpassword;
|
||||||
@@ -49,20 +45,23 @@ public class User implements UserDetails {
|
|||||||
@Column(name = "created_at")
|
@Column(name = "created_at")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
// User Recipe relationship
|
@Column(nullable = false)
|
||||||
|
private boolean banned = false;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
private Set<Recipe> recipes = new HashSet<>();
|
private Set<Recipe> recipes = new HashSet<>();
|
||||||
|
|
||||||
// Favorite relationship and also junction table
|
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
@JsonIgnore
|
@JoinTable(
|
||||||
@JoinTable(name = "favorites", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns = {
|
name = "favorites",
|
||||||
@JoinColumn(name = "recipeId") })
|
joinColumns = { @JoinColumn(name = "userId") },
|
||||||
|
inverseJoinColumns = { @JoinColumn(name = "recipeId") }
|
||||||
|
)
|
||||||
private Set<Recipe> FavRecipes = new HashSet<>();
|
private Set<Recipe> FavRecipes = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return new ArrayList<>();
|
return List.of(new SimpleGrantedAuthority(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,6 +74,26 @@ public class User implements UserDetails {
|
|||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return !banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return !banned;
|
||||||
|
}
|
||||||
|
|
||||||
public User() {
|
public User() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +103,7 @@ public class User implements UserDetails {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
this.hashedpassword = hashedpassword;
|
this.hashedpassword = hashedpassword;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
|
this.banned = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
@@ -130,12 +150,27 @@ public class User implements UserDetails {
|
|||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBanned() {
|
||||||
|
return banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBanned(boolean banned) {
|
||||||
|
this.banned = banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Recipe> getRecipes() {
|
||||||
|
return recipes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecipes(Set<Recipe> recipes) {
|
||||||
|
this.recipes = recipes;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Recipe> getFavRecipes() {
|
public Set<Recipe> getFavRecipes() {
|
||||||
return FavRecipes;
|
return FavRecipes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFavRecipes(Set<Recipe> favRecipes) {
|
public void setFavRecipes(Set<Recipe> favRecipes) {
|
||||||
FavRecipes = favRecipes;
|
this.FavRecipes = favRecipes;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.demo.repository;
|
package com.example.demo.repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -10,4 +11,8 @@ public interface RecipeRepo extends JpaRepository<Recipe, Integer> {
|
|||||||
List<Recipe> findByTitleContainingIgnoreCase(String name);
|
List<Recipe> findByTitleContainingIgnoreCase(String name);
|
||||||
List<Recipe> findByTitleContainingIgnoreCaseAndTags_NameIn(String title, List<String> tags);
|
List<Recipe> findByTitleContainingIgnoreCaseAndTags_NameIn(String title, List<String> tags);
|
||||||
|
|
||||||
|
long countByUserIdAndCreatedAtAfter(Integer userId, LocalDateTime after);
|
||||||
|
|
||||||
|
List<Recipe> findByUserId(Integer userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,8 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
return userRepo.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
return userRepo.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.example.demo.service.Impl;
|
package com.example.demo.service.Impl;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.example.demo.dto.RecipeDto;
|
import com.example.demo.dto.RecipeDto;
|
||||||
@@ -55,6 +57,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
this.tagRepository = tagRepository;
|
this.tagRepository = tagRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public RecipeDto convertToDto(Recipe recipe) {
|
public RecipeDto convertToDto(Recipe recipe) {
|
||||||
List<RecipeIngredientDto> ingredientDtos = recipe.getRecipeIngredients().stream()
|
List<RecipeIngredientDto> ingredientDtos = recipe.getRecipeIngredients().stream()
|
||||||
.map(ri -> new RecipeIngredientDto(ri.getIngredient().getName(), ri.getQuantity(), ri.getUnit(),
|
.map(ri -> new RecipeIngredientDto(ri.getIngredient().getName(), ri.getQuantity(), ri.getUnit(),
|
||||||
@@ -71,21 +74,65 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
UserDto userDto = new UserDto(recipe.getUser().getId(), recipe.getUser().getUsername(),
|
UserDto userDto = new UserDto(recipe.getUser().getId(), recipe.getUser().getUsername(),
|
||||||
recipe.getUser().getEmail());
|
recipe.getUser().getEmail());
|
||||||
|
|
||||||
return new RecipeDto(recipe.getTitle(), recipe.getDescription(), recipe.getPrepTimeMinutes(),
|
RecipeDto dto = new RecipeDto(recipe.getTitle(), recipe.getDescription(), recipe.getPrepTimeMinutes(),
|
||||||
recipe.getCookTimeMinutes(), recipe.getServings(), userDto, recipe.getStatus(), ingredientDtos,
|
recipe.getCookTimeMinutes(), recipe.getServings(), userDto, recipe.getStatus(), ingredientDtos,
|
||||||
stepDtos, imageDtos, tagDtos);
|
stepDtos, imageDtos, tagDtos);
|
||||||
|
|
||||||
|
dto.setId(recipe.getId());
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private User getCurrentUser(String currentUsername) {
|
||||||
|
return userRepository.findByUsername(currentUsername)
|
||||||
|
.orElseThrow(() -> new NotFoundException("User", "username", currentUsername));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin(User user) {
|
||||||
|
return "ROLE_ADMIN".equals(user.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureUserNotBanned(User user) {
|
||||||
|
if (user.isBanned()) {
|
||||||
|
throw new AccessDeniedException("Banned users cannot perform this action.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceUploadLimit(User user) {
|
||||||
|
if (isAdmin(user)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime cutoff = LocalDateTime.now().minusHours(24);
|
||||||
|
long uploadsInLast24Hours = recipeRepository.countByUserIdAndCreatedAtAfter(user.getId(), cutoff);
|
||||||
|
|
||||||
|
if (uploadsInLast24Hours >= 10) {
|
||||||
|
throw new AccessDeniedException("Upload limit reached. Maximum is 10 recipes per 24 hours.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceOwnerOrAdmin(User currentUser, Recipe recipe) {
|
||||||
|
if (isAdmin(currentUser)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recipe.getUser().getId().equals(currentUser.getId())) {
|
||||||
|
throw new AccessDeniedException("You do not have permission to modify this recipe.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public RecipeDto saveRecipe(RecipeDto dto) {
|
public RecipeDto saveRecipe(RecipeDto dto, String currentUsername) {
|
||||||
|
|
||||||
User user = userRepository.findById(dto.getUserDto().getId())
|
User currentUser = getCurrentUser(currentUsername);
|
||||||
.orElseThrow(() -> new NotFoundException("User", "id", dto.getUserDto().getId()));
|
ensureUserNotBanned(currentUser);
|
||||||
|
enforceUploadLimit(currentUser);
|
||||||
|
|
||||||
Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(),
|
Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(),
|
||||||
dto.getCookTimeMinutes(), dto.getServings(), user, dto.getStatus());
|
dto.getCookTimeMinutes(), dto.getServings(), currentUser, dto.getStatus());
|
||||||
|
|
||||||
|
if (dto.getIngredients() != null) {
|
||||||
for (RecipeIngredientDto riDto : dto.getIngredients()) {
|
for (RecipeIngredientDto riDto : dto.getIngredients()) {
|
||||||
|
|
||||||
Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
|
Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
|
||||||
@@ -100,6 +147,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
|
|
||||||
recipe.getRecipeIngredients().add(ri);
|
recipe.getRecipeIngredients().add(ri);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dto.getSteps() != null) {
|
if (dto.getSteps() != null) {
|
||||||
for (StepDto stepDto : dto.getSteps()) {
|
for (StepDto stepDto : dto.getSteps()) {
|
||||||
@@ -115,6 +163,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dto.getTags() != null) {
|
||||||
for (TagDto tDto : dto.getTags()) {
|
for (TagDto tDto : dto.getTags()) {
|
||||||
|
|
||||||
Tag tag = tagRepository.findByName(tDto.getName()).orElseGet(() -> new Tag(tDto.getName()));
|
Tag tag = tagRepository.findByName(tDto.getName()).orElseGet(() -> new Tag(tDto.getName()));
|
||||||
@@ -124,11 +173,11 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
recipe.getTags().add(tag);
|
recipe.getTags().add(tag);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Recipe saved = recipeRepository.save(recipe);
|
Recipe saved = recipeRepository.save(recipe);
|
||||||
|
|
||||||
return getRecipeById(saved.getId());
|
return getRecipeById(saved.getId());
|
||||||
//return convertToDto(saved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -152,10 +201,15 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public RecipeDto updateRecipe(RecipeDto recipeDto, Integer id) {
|
public RecipeDto updateRecipe(RecipeDto recipeDto, Integer id, String currentUsername) {
|
||||||
|
User currentUser = getCurrentUser(currentUsername);
|
||||||
|
ensureUserNotBanned(currentUser);
|
||||||
|
|
||||||
Recipe existingRecipe = recipeRepository.findById(id)
|
Recipe existingRecipe = recipeRepository.findById(id)
|
||||||
.orElseThrow(() -> new NotFoundException("Recipe", "id", id));
|
.orElseThrow(() -> new NotFoundException("Recipe", "id", id));
|
||||||
|
|
||||||
|
enforceOwnerOrAdmin(currentUser, existingRecipe);
|
||||||
|
|
||||||
existingRecipe.setTitle(recipeDto.getTitle());
|
existingRecipe.setTitle(recipeDto.getTitle());
|
||||||
existingRecipe.setDescription(recipeDto.getDescription());
|
existingRecipe.setDescription(recipeDto.getDescription());
|
||||||
existingRecipe.setPrepTimeMinutes(recipeDto.getPrepTimeMinutes());
|
existingRecipe.setPrepTimeMinutes(recipeDto.getPrepTimeMinutes());
|
||||||
@@ -197,13 +251,10 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
|
|
||||||
for (RecipeIngredientDto riDto : updatedIngredients) {
|
for (RecipeIngredientDto riDto : updatedIngredients) {
|
||||||
|
|
||||||
// go through the old list of ingredients until we find a match with updated
|
|
||||||
// list
|
|
||||||
RecipeIngredient existingRI = existingRecipe.getRecipeIngredients().stream()
|
RecipeIngredient existingRI = existingRecipe.getRecipeIngredients().stream()
|
||||||
.filter(ri -> ri.getIngredient().getName().equals(riDto.getIngredientName())).findFirst()
|
.filter(ri -> ri.getIngredient().getName().equals(riDto.getIngredientName())).findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
// if old ingredient just update parameters
|
|
||||||
if (existingRI != null) {
|
if (existingRI != null) {
|
||||||
|
|
||||||
existingRI.setQuantity(riDto.getQuantity());
|
existingRI.setQuantity(riDto.getQuantity());
|
||||||
@@ -211,7 +262,6 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
existingRI.setNotes(riDto.getNotes());
|
existingRI.setNotes(riDto.getNotes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// if new ingredient, have to make a whole new thing
|
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
|
Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
|
||||||
@@ -229,7 +279,6 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updatedSteps != null) {
|
if (updatedSteps != null) {
|
||||||
// find steps that weren't included
|
|
||||||
for (Step step : existingRecipe.getSteps()) {
|
for (Step step : existingRecipe.getSteps()) {
|
||||||
boolean existsInUpdatedList = updatedSteps.stream()
|
boolean existsInUpdatedList = updatedSteps.stream()
|
||||||
.anyMatch(dto -> dto.getStepNumber().equals(step.getStepNumber()));
|
.anyMatch(dto -> dto.getStepNumber().equals(step.getStepNumber()));
|
||||||
@@ -237,22 +286,17 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
if (!existsInUpdatedList)
|
if (!existsInUpdatedList)
|
||||||
stepsToRemove.add(step);
|
stepsToRemove.add(step);
|
||||||
}
|
}
|
||||||
// delete those steps
|
|
||||||
existingRecipe.getSteps().removeAll(stepsToRemove);
|
existingRecipe.getSteps().removeAll(stepsToRemove);
|
||||||
|
|
||||||
// go through updated steps
|
|
||||||
for (StepDto stepDto : updatedSteps) {
|
for (StepDto stepDto : updatedSteps) {
|
||||||
|
|
||||||
// find matching step by step number
|
|
||||||
Step existingStep = existingRecipe.getSteps().stream()
|
Step existingStep = existingRecipe.getSteps().stream()
|
||||||
.filter(s -> s.getStepNumber().equals(stepDto.getStepNumber())).findFirst().orElse(null);
|
.filter(s -> s.getStepNumber().equals(stepDto.getStepNumber())).findFirst().orElse(null);
|
||||||
|
|
||||||
// if there's a match update the instruction string
|
|
||||||
if (existingStep != null) {
|
if (existingStep != null) {
|
||||||
existingStep.setInstruction(stepDto.getInstruction());
|
existingStep.setInstruction(stepDto.getInstruction());
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no match then make a whole new step
|
|
||||||
else {
|
else {
|
||||||
Step newStep = new Step(existingRecipe, stepDto.getStepNumber(), stepDto.getInstruction());
|
Step newStep = new Step(existingRecipe, stepDto.getStepNumber(), stepDto.getInstruction());
|
||||||
existingRecipe.getSteps().add(newStep);
|
existingRecipe.getSteps().add(newStep);
|
||||||
@@ -260,7 +304,6 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// same process as above just with images instead
|
|
||||||
if (updatedImages != null) {
|
if (updatedImages != null) {
|
||||||
for (Image image : existingRecipe.getImages()) {
|
for (Image image : existingRecipe.getImages()) {
|
||||||
boolean existsInUpdatedList = updatedImages.stream()
|
boolean existsInUpdatedList = updatedImages.stream()
|
||||||
@@ -284,8 +327,6 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// same process as above just with tags instead, except for saving the tag
|
|
||||||
// since the relationship for this one was slightly different
|
|
||||||
if (updatedTags != null) {
|
if (updatedTags != null) {
|
||||||
for (Tag tag : existingRecipe.getTags()) {
|
for (Tag tag : existingRecipe.getTags()) {
|
||||||
boolean existsInUpdatedList = updatedTags.stream().anyMatch(dto -> dto.getName().equals(tag.getName()));
|
boolean existsInUpdatedList = updatedTags.stream().anyMatch(dto -> dto.getName().equals(tag.getName()));
|
||||||
@@ -314,10 +355,16 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteRecipe(Integer Id) {
|
@Transactional
|
||||||
recipeRepository.findById(Id).orElseThrow(() -> new NotFoundException("Recipe", "id", Id));
|
public void deleteRecipe(Integer id, String currentUsername) {
|
||||||
recipeRepository.deleteById(Id);
|
User currentUser = getCurrentUser(currentUsername);
|
||||||
|
ensureUserNotBanned(currentUser);
|
||||||
|
|
||||||
|
Recipe recipe = recipeRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new NotFoundException("Recipe", "id", id));
|
||||||
|
|
||||||
|
enforceOwnerOrAdmin(currentUser, recipe);
|
||||||
|
recipeRepository.delete(recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -326,7 +373,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
|
|
||||||
List<Recipe> recipes;
|
List<Recipe> recipes;
|
||||||
|
|
||||||
if(!name.isBlank()) {
|
if (!name.isBlank()) {
|
||||||
recipes = recipeRepository.findByTitleContainingIgnoreCase(name);
|
recipes = recipeRepository.findByTitleContainingIgnoreCase(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +381,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
recipes = recipeRepository.findAll();
|
recipes = recipeRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tags.isEmpty() && !recipes.isEmpty()) {
|
if (!tags.isEmpty() && !recipes.isEmpty()) {
|
||||||
recipes = recipes.stream()
|
recipes = recipes.stream()
|
||||||
.filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tags.contains(tag.getName())))
|
.filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tags.contains(tag.getName())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.demo.service.Impl;
|
ppackage com.example.demo.service.Impl;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,6 +39,10 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User saveUser(User user) {
|
public User saveUser(User user) {
|
||||||
|
if (user.getRole() == null || user.getRole().isBlank()) {
|
||||||
|
user.setRole("ROLE_USER");
|
||||||
|
}
|
||||||
|
user.setBanned(false);
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,4 +129,39 @@ public class UserServiceImpl implements UserService {
|
|||||||
return userList;
|
return userList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDto banUser(Integer id) {
|
||||||
|
User user = userRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new NotFoundException("User", "id", id));
|
||||||
|
user.setBanned(true);
|
||||||
|
userRepository.save(user);
|
||||||
|
return convertToDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDto unbanUser(Integer id) {
|
||||||
|
User user = userRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new NotFoundException("User", "id", id));
|
||||||
|
user.setBanned(false);
|
||||||
|
userRepository.save(user);
|
||||||
|
return convertToDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDto makeAdmin(Integer id) {
|
||||||
|
User user = userRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new NotFoundException("User", "id", id));
|
||||||
|
user.setRole("ROLE_ADMIN");
|
||||||
|
userRepository.save(user);
|
||||||
|
return convertToDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDto makeUser(Integer id) {
|
||||||
|
User user = userRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new NotFoundException("User", "id", id));
|
||||||
|
user.setRole("ROLE_USER");
|
||||||
|
userRepository.save(user);
|
||||||
|
return convertToDto(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.example.demo.entity.User;
|
|||||||
public interface RecipeService {
|
public interface RecipeService {
|
||||||
RecipeDto convertToDto(Recipe recipe);
|
RecipeDto convertToDto(Recipe recipe);
|
||||||
|
|
||||||
RecipeDto saveRecipe(RecipeDto recipe);
|
RecipeDto saveRecipe(RecipeDto recipe, String currentUsername);
|
||||||
|
|
||||||
List<RecipeDto> getAllRecipes();
|
List<RecipeDto> getAllRecipes();
|
||||||
|
|
||||||
@@ -19,8 +19,9 @@ public interface RecipeService {
|
|||||||
|
|
||||||
List<RecipeDto> getRecipes(String name, List<String> tags);
|
List<RecipeDto> getRecipes(String name, List<String> tags);
|
||||||
|
|
||||||
RecipeDto updateRecipe(RecipeDto recipedto, Integer Id);
|
RecipeDto updateRecipe(RecipeDto recipedto, Integer Id, String currentUsername);
|
||||||
|
|
||||||
void deleteRecipe(Integer Id);
|
void deleteRecipe(Integer Id, String currentUsername);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,23 @@ public interface UserService {
|
|||||||
|
|
||||||
List<UserDto> getAllUsers();
|
List<UserDto> getAllUsers();
|
||||||
|
|
||||||
UserDto getUserById(Integer Id);
|
UserDto getUserById(Integer id);
|
||||||
|
|
||||||
List<UserDto> getUsersByName(String name);
|
List<UserDto> getUsersByName(String name);
|
||||||
|
|
||||||
UserDto saveFavorite(Integer userId, Integer recipeId);
|
UserDto saveFavorite(Integer userId, Integer recipeId);
|
||||||
|
|
||||||
UserDto updateUser(User user, Integer Id);
|
UserDto updateUser(User user, Integer id);
|
||||||
|
|
||||||
void deleteUser(Integer Id);
|
void deleteUser(Integer id);
|
||||||
|
|
||||||
void deleteFavorite(Integer userId, Integer recipeId);
|
void deleteFavorite(Integer userId, Integer recipeId);
|
||||||
|
|
||||||
|
UserDto banUser(Integer id);
|
||||||
|
|
||||||
|
UserDto unbanUser(Integer id);
|
||||||
|
|
||||||
|
UserDto makeAdmin(Integer id);
|
||||||
|
|
||||||
|
UserDto makeUser(Integer id);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user