diff --git a/src/main/java/com/devsuperior/movieflix/MovieflixApplication.java b/src/main/java/com/devsuperior/movieflix/MovieflixApplication.java index d4bedff1..896e1ce9 100644 --- a/src/main/java/com/devsuperior/movieflix/MovieflixApplication.java +++ b/src/main/java/com/devsuperior/movieflix/MovieflixApplication.java @@ -9,5 +9,4 @@ public class MovieflixApplication { public static void main(String[] args) { SpringApplication.run(MovieflixApplication.class, args); } - } diff --git a/src/main/java/com/devsuperior/movieflix/components/JwtTokenEnhancer.java b/src/main/java/com/devsuperior/movieflix/components/JwtTokenEnhancer.java new file mode 100644 index 00000000..582c54ca --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/components/JwtTokenEnhancer.java @@ -0,0 +1,37 @@ +package com.devsuperior.movieflix.components; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.stereotype.Component; + +import com.devsuperior.movieflix.entities.User; +import com.devsuperior.movieflix.repositories.UserRepository; + +@Component +public class JwtTokenEnhancer implements TokenEnhancer{ + + @Autowired + private UserRepository userRepository; + + @Override + public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + + User user = userRepository.findByEmail(authentication.getName()); + + Map map = new HashMap<>(); + map.put("userName", user.getName()); + map.put("userId", user.getId()); + + DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken; + token.setAdditionalInformation(map); + + return token; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/config/AppConfig.java b/src/main/java/com/devsuperior/movieflix/config/AppConfig.java new file mode 100644 index 00000000..2170e4c2 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/config/AppConfig.java @@ -0,0 +1,35 @@ +package com.devsuperior.movieflix.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +@Configuration +public class AppConfig { + + @Value("${jwt.secret}") + private String jwtSecret; + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public JwtAccessTokenConverter accessTokenConverter() { + JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter(); + tokenConverter.setSigningKey(jwtSecret); + return tokenConverter; + } + + @Bean + public JwtTokenStore tokenStore() { + return new JwtTokenStore(accessTokenConverter()); + } + + + +} diff --git a/src/main/java/com/devsuperior/movieflix/config/AuthorizationServerConfig.java b/src/main/java/com/devsuperior/movieflix/config/AuthorizationServerConfig.java new file mode 100644 index 00000000..f4ff3615 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/config/AuthorizationServerConfig.java @@ -0,0 +1,83 @@ +package com.devsuperior.movieflix.config; + +import java.util.Arrays; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import com.devsuperior.movieflix.components.JwtTokenEnhancer; + +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Value("${security.oauth2.client.client-id}") + private String clientId; + + @Value("${security.oauth2.client.client-secret}") + private String clientSecret; + + @Value("${jwt.duration}") + private Integer jwtDuration; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Autowired + private JwtAccessTokenConverter accessTokenConverter; + + @Autowired + private TokenStore tokenStore; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private JwtTokenEnhancer tokenEnhancer; + + @Autowired + private UserDetailsService userDetailsService; + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory() + .withClient(clientId) + .secret(passwordEncoder.encode(clientSecret)) + .scopes("read", "write") + .authorizedGrantTypes("password") + .accessTokenValiditySeconds(jwtDuration); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + + TokenEnhancerChain chain = new TokenEnhancerChain(); + chain.setTokenEnhancers(Arrays.asList(accessTokenConverter, tokenEnhancer)); + + endpoints.authenticationManager(authenticationManager) + .tokenStore(tokenStore) + .accessTokenConverter(accessTokenConverter) + .tokenEnhancer(chain) + .userDetailsService(userDetailsService); + } + + + +} diff --git a/src/main/java/com/devsuperior/movieflix/config/ResourceServerConfig.java b/src/main/java/com/devsuperior/movieflix/config/ResourceServerConfig.java new file mode 100644 index 00000000..15c861b0 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/config/ResourceServerConfig.java @@ -0,0 +1,45 @@ +package com.devsuperior.movieflix.config; + +import java.util.Arrays; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +@Configuration +@EnableResourceServer +public class ResourceServerConfig extends ResourceServerConfigurerAdapter{ + + @Autowired + private Environment env; + + @Autowired + private JwtTokenStore tokenStore; + + private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" }; + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.tokenStore(tokenStore); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + + // H2 + if (Arrays.asList(env.getActiveProfiles()).contains("test")) { + http.headers().frameOptions().disable(); + } + + http.authorizeRequests() + .antMatchers(PUBLIC).permitAll() + .anyRequest().authenticated(); + + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/config/WebSecurityConfig.java b/src/main/java/com/devsuperior/movieflix/config/WebSecurityConfig.java new file mode 100644 index 00000000..1155dd0a --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/config/WebSecurityConfig.java @@ -0,0 +1,44 @@ +package com.devsuperior.movieflix.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Autowired + private UserDetailsService userDetailService; + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder); + super.configure(auth); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/actuator/**"); + } + + @Override + @Bean + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/devsuperior/movieflix/controllers/UserController.java b/src/main/java/com/devsuperior/movieflix/controllers/UserController.java new file mode 100644 index 00000000..2cc77ec9 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/UserController.java @@ -0,0 +1,24 @@ +package com.devsuperior.movieflix.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.devsuperior.movieflix.dto.UserDTO; +import com.devsuperior.movieflix.services.UserService; + +@RestController +@RequestMapping(value = "/users") +public class UserController { + + @Autowired + private UserService service; + + @GetMapping(value = "/profile") + public ResponseEntity findCurrentUser(){ + UserDTO dto = service.findCurrentUser(); + return ResponseEntity.ok().body(dto); + } +} \ No newline at end of file diff --git a/src/main/java/com/devsuperior/movieflix/controllers/exceptions/FieldMessage.java b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/FieldMessage.java new file mode 100644 index 00000000..507526d0 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/FieldMessage.java @@ -0,0 +1,34 @@ +package com.devsuperior.movieflix.controllers.exceptions; + +import java.io.Serializable; + +public class FieldMessage implements Serializable { + private static final long serialVersionUID = 1L; + + private String fieldName; + private String message; + + public FieldMessage() {} + + public FieldMessage(String fieldName, String message) { + this.fieldName = fieldName; + this.message = message; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/controllers/exceptions/OAuthCustomError.java b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/OAuthCustomError.java new file mode 100644 index 00000000..d02762f3 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/OAuthCustomError.java @@ -0,0 +1,43 @@ +package com.devsuperior.movieflix.controllers.exceptions; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class OAuthCustomError implements Serializable { + private static final long serialVersionUID = 1L; + + private String error; + + @JsonProperty("error_description") + private String errorDescription; + + public OAuthCustomError() { + } + + public OAuthCustomError(String error, String errorDescription) { + super(); + this.error = error; + this.errorDescription = errorDescription; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getErrorDescription() { + return errorDescription; + } + + public void setErrorDescription(String errorDescription) { + this.errorDescription = errorDescription; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ResourceExceptioHandler.java b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ResourceExceptioHandler.java new file mode 100644 index 00000000..1102e88c --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ResourceExceptioHandler.java @@ -0,0 +1,74 @@ +package com.devsuperior.movieflix.controllers.exceptions; + +import java.time.Instant; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import com.devsuperior.movieflix.services.exceptions.DatabaseException; +import com.devsuperior.movieflix.services.exceptions.ForbiddenException; +import com.devsuperior.movieflix.services.exceptions.ResourceNotFoundException; +import com.devsuperior.movieflix.services.exceptions.UnauthorizedException; + +@ControllerAdvice +public class ResourceExceptioHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity entityNotFound(ResourceNotFoundException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.NOT_FOUND; + StandardError err = new StandardError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Resource not found"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + return ResponseEntity.status(status).body(err); + } + + @ExceptionHandler(DatabaseException.class) + public ResponseEntity database(DatabaseException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.BAD_REQUEST; + StandardError err = new StandardError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Database Exception"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + return ResponseEntity.status(status).body(err); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity validation(MethodArgumentNotValidException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY; + ValidationError err = new ValidationError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Validation Exception"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + + for (FieldError f : e.getBindingResult().getFieldErrors()) { + err.addError(f.getField(), f.getDefaultMessage()); + } + + return ResponseEntity.status(status).body(err); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity forbidden(ForbiddenException e, HttpServletRequest request) { + OAuthCustomError err = new OAuthCustomError("Forbidden", e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(err); + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity unauthorized(UnauthorizedException e, HttpServletRequest request) { + OAuthCustomError err = new OAuthCustomError("Unauthorized", e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(err); + } +} diff --git a/src/main/java/com/devsuperior/movieflix/controllers/exceptions/StandardError.java b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/StandardError.java new file mode 100644 index 00000000..5c86a8ff --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/StandardError.java @@ -0,0 +1,60 @@ +package com.devsuperior.movieflix.controllers.exceptions; + +import java.io.Serializable; +import java.time.Instant; + +public class StandardError implements Serializable { + private static final long serialVersionUID = 1L; + + private Instant timestamp; + private Integer status; + private String error; + private String message; + private String path; + + public StandardError() {} + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + + + +} diff --git a/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ValidationError.java b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ValidationError.java new file mode 100644 index 00000000..6e19f440 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/controllers/exceptions/ValidationError.java @@ -0,0 +1,20 @@ +package com.devsuperior.movieflix.controllers.exceptions; + +import java.util.ArrayList; +import java.util.List; + +public class ValidationError extends StandardError { + + private static final long serialVersionUID = 1L; + + private List errors = new ArrayList<>(); + + public List getErrors() { + return errors; + } + + public void addError(String fieldName, String message) { + errors.add(new FieldMessage(fieldName, message)); + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/dto/UserDTO.java b/src/main/java/com/devsuperior/movieflix/dto/UserDTO.java new file mode 100644 index 00000000..226f6716 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/dto/UserDTO.java @@ -0,0 +1,53 @@ +package com.devsuperior.movieflix.dto; + +import java.io.Serializable; + +import com.devsuperior.movieflix.entities.User; + +public class UserDTO implements Serializable{ + private static final long serialVersionUID = 1L; + + private Long id; + private String name; + private String email; + + public UserDTO() { + } + + public UserDTO(Long id, String name, String email) { + this.id = id; + this.name = name; + this.email = email; + } + + public UserDTO(User entity) { + id = entity.getId(); + name = entity.getName(); + email = entity.getEmail(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/entities/Genre.java b/src/main/java/com/devsuperior/movieflix/entities/Genre.java new file mode 100644 index 00000000..d11ffb29 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/entities/Genre.java @@ -0,0 +1,82 @@ +package com.devsuperior.movieflix.entities; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "tb_genre") +public class Genre implements Serializable{ + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + @OneToMany(mappedBy = "genre") + private List movies = new ArrayList<>(); + + public Genre() { + } + + public Genre(Long id, String name) { + super(); + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMovies() { + return movies; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Genre other = (Genre) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/entities/Movie.java b/src/main/java/com/devsuperior/movieflix/entities/Movie.java new file mode 100644 index 00000000..75708f13 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/entities/Movie.java @@ -0,0 +1,140 @@ +package com.devsuperior.movieflix.entities; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "tb_movie") +public class Movie implements Serializable{ + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; + private String subTitle; + private Integer year; + private String imgUrl; + + @Column(columnDefinition = "TEXT") + private String synopsis; + + @ManyToOne + @JoinColumn(name = "genre_id") + private Genre genre; + + @OneToMany(mappedBy = "movie") + private List reviews = new ArrayList<>(); + + public Movie() { + } + + public Movie(Long id, String title, String subTitle, Integer year, String imgUrl, String synopsis, Genre genre) { + super(); + this.id = id; + this.title = title; + this.subTitle = subTitle; + this.year = year; + this.imgUrl = imgUrl; + this.synopsis = synopsis; + this.genre = genre; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubTitle() { + return subTitle; + } + + public void setSubTitle(String subTitle) { + this.subTitle = subTitle; + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public String getSynopsis() { + return synopsis; + } + + public void setSynopsis(String synopsis) { + this.synopsis = synopsis; + } + + public Genre getGenre() { + return genre; + } + + public void setGenre(Genre genre) { + this.genre = genre; + } + + public List getReviews() { + return reviews; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Movie other = (Movie) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/entities/Review.java b/src/main/java/com/devsuperior/movieflix/entities/Review.java new file mode 100644 index 00000000..8bff235b --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/entities/Review.java @@ -0,0 +1,100 @@ +package com.devsuperior.movieflix.entities; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "tb_review") +public class Review implements Serializable{ + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String text; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "movie_id") + private Movie movie; + + public Review() { + } + + public Review(Long id, String text, User user, Movie movie) { + super(); + this.id = id; + this.text = text; + this.user = user; + this.movie = movie; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Movie getMovie() { + return movie; + } + + public void setMovie(Movie movie) { + this.movie = movie; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Review other = (Review) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/entities/Role.java b/src/main/java/com/devsuperior/movieflix/entities/Role.java new file mode 100644 index 00000000..f106bd81 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/entities/Role.java @@ -0,0 +1,71 @@ +package com.devsuperior.movieflix.entities; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "tb_role") +public class Role implements Serializable{ + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String authority; + + public Role() { + } + + public Role(Long id, String authority) { + this.id = id; + this.authority = authority; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Role other = (Role) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/entities/User.java b/src/main/java/com/devsuperior/movieflix/entities/User.java new file mode 100644 index 00000000..35a2c9aa --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/entities/User.java @@ -0,0 +1,164 @@ +package com.devsuperior.movieflix.entities; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + + +@Entity +@Table(name = "tb_user") +public class User implements UserDetails, Serializable{ + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + private String email; + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "tb_user_role", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles = new HashSet<>(); + + @OneToMany(mappedBy = "user") + private List reviews = new ArrayList<>(); + + public User() { + } + + public User(Long id, String name, String email, String password) { + super(); + this.id = id; + this.name = name; + this.email = email; + this.password = password; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRoles() { + return roles; + } + + public List getReviews() { + return reviews; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + @Override + public Collection getAuthorities() { + return roles.stream().map(role -> new SimpleGrantedAuthority(role.getAuthority())).collect(Collectors.toList()); + } + + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public boolean hasRole(String roleName) { + for(Role role: roles) { + if(role.getAuthority().equals(roleName)) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/repositories/GenreRepository.java b/src/main/java/com/devsuperior/movieflix/repositories/GenreRepository.java new file mode 100644 index 00000000..de3e6de9 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/repositories/GenreRepository.java @@ -0,0 +1,9 @@ +package com.devsuperior.movieflix.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.devsuperior.movieflix.entities.Genre; + +public interface GenreRepository extends JpaRepository { + +} diff --git a/src/main/java/com/devsuperior/movieflix/repositories/MovieRepository.java b/src/main/java/com/devsuperior/movieflix/repositories/MovieRepository.java new file mode 100644 index 00000000..f64f3bb2 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/repositories/MovieRepository.java @@ -0,0 +1,9 @@ +package com.devsuperior.movieflix.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.devsuperior.movieflix.entities.Movie; + +public interface MovieRepository extends JpaRepository { + +} diff --git a/src/main/java/com/devsuperior/movieflix/repositories/ReviewRepository.java b/src/main/java/com/devsuperior/movieflix/repositories/ReviewRepository.java new file mode 100644 index 00000000..73bfccef --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/repositories/ReviewRepository.java @@ -0,0 +1,9 @@ +package com.devsuperior.movieflix.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.devsuperior.movieflix.entities.Review; + +public interface ReviewRepository extends JpaRepository { + +} diff --git a/src/main/java/com/devsuperior/movieflix/repositories/RoleRepository.java b/src/main/java/com/devsuperior/movieflix/repositories/RoleRepository.java new file mode 100644 index 00000000..4c460621 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/repositories/RoleRepository.java @@ -0,0 +1,9 @@ +package com.devsuperior.movieflix.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.devsuperior.movieflix.entities.Role; + +public interface RoleRepository extends JpaRepository { + +} diff --git a/src/main/java/com/devsuperior/movieflix/repositories/UserRepository.java b/src/main/java/com/devsuperior/movieflix/repositories/UserRepository.java new file mode 100644 index 00000000..af1d2dc1 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/repositories/UserRepository.java @@ -0,0 +1,11 @@ +package com.devsuperior.movieflix.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.devsuperior.movieflix.entities.User; + +public interface UserRepository extends JpaRepository { + + User findByEmail(String email); + +} diff --git a/src/main/java/com/devsuperior/movieflix/services/UserService.java b/src/main/java/com/devsuperior/movieflix/services/UserService.java new file mode 100644 index 00000000..553f6635 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/services/UserService.java @@ -0,0 +1,51 @@ +package com.devsuperior.movieflix.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.devsuperior.movieflix.dto.UserDTO; +import com.devsuperior.movieflix.entities.User; +import com.devsuperior.movieflix.repositories.UserRepository; +import com.devsuperior.movieflix.services.exceptions.UnauthorizedException; + + +@Service +public class UserService implements UserDetailsService{ + + private static Logger logger = LoggerFactory.getLogger(UserService.class); + + @Autowired + private UserRepository repository; + + + @Transactional(readOnly = true) + public UserDTO findCurrentUser() { + try { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + User entity = repository.findByEmail(username); + return new UserDTO(entity); + } catch (Exception e) { + throw new UnauthorizedException("Invalid user"); + } + } + + + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = repository.findByEmail(username); + if (user == null) { + logger.error("User not found " + username); + throw new UsernameNotFoundException("Email não encontrado"); + } + logger.info("User found " + username); + return user; + } +} diff --git a/src/main/java/com/devsuperior/movieflix/services/exceptions/DatabaseException.java b/src/main/java/com/devsuperior/movieflix/services/exceptions/DatabaseException.java new file mode 100644 index 00000000..3e748d27 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/services/exceptions/DatabaseException.java @@ -0,0 +1,10 @@ +package com.devsuperior.movieflix.services.exceptions; + +public class DatabaseException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public DatabaseException(String msg) { + super(msg); + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/services/exceptions/ForbiddenException.java b/src/main/java/com/devsuperior/movieflix/services/exceptions/ForbiddenException.java new file mode 100644 index 00000000..922b2d2b --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/services/exceptions/ForbiddenException.java @@ -0,0 +1,12 @@ +package com.devsuperior.movieflix.services.exceptions; + +public class ForbiddenException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ForbiddenException(String message) { + super(message); + } + + + +} diff --git a/src/main/java/com/devsuperior/movieflix/services/exceptions/ResourceNotFoundException.java b/src/main/java/com/devsuperior/movieflix/services/exceptions/ResourceNotFoundException.java new file mode 100644 index 00000000..dfe7dcb1 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/services/exceptions/ResourceNotFoundException.java @@ -0,0 +1,10 @@ +package com.devsuperior.movieflix.services.exceptions; + +public class ResourceNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ResourceNotFoundException(String msg) { + super(msg); + } + +} diff --git a/src/main/java/com/devsuperior/movieflix/services/exceptions/UnauthorizedException.java b/src/main/java/com/devsuperior/movieflix/services/exceptions/UnauthorizedException.java new file mode 100644 index 00000000..98314539 --- /dev/null +++ b/src/main/java/com/devsuperior/movieflix/services/exceptions/UnauthorizedException.java @@ -0,0 +1,10 @@ +package com.devsuperior.movieflix.services.exceptions; + +public class UnauthorizedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public UnauthorizedException(String msg) { + super(msg); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a7dcd8af..ca3c13ab 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,9 @@ -spring.profiles.active=test +spring.profiles.active=${APP_PROFILE:test} spring.jpa.open-in-view=false + +security.oauth2.client.client-id=${CLIENT_ID:myclientid} +security.oauth2.client.client-secret=${CLIENT_SECRET:myclientsecret} + +jwt.secret=${JWT_SECRET:MY-JWT-SECRET} +jwt.duration=${JWT_DURATION:240} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 00000000..82cf0908 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,34 @@ +INSERT INTO tb_user (name, email, password) VALUES ('Bob Smith', 'bob@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); +INSERT INTO tb_user (name, email, password) VALUES ('Ana Green', 'ana@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); + +INSERT INTO tb_role (authority) VALUES ('ROLE_VISITOR'); +INSERT INTO tb_role (authority) VALUES ('ROLE_MEMBER'); + +INSERT INTO tb_user_role (user_id, role_id) VALUES (1, 1); +INSERT INTO tb_user_role (user_id, role_id) VALUES (2, 2); + +INSERT INTO tb_genre (name) VALUES ('DRAMA'); +INSERT INTO tb_genre (name) VALUES ('COMEDIA'); +INSERT INTO tb_genre (name) VALUES ('AVENTURA'); +INSERT INTO tb_genre (name) VALUES ('TERROR'); +INSERT INTO tb_genre (name) VALUES ('SUSPENSE'); +INSERT INTO tb_genre (name) VALUES ('FICCAO CIENTIFICA'); + +INSERT INTO tb_movie (title, sub_title, year, img_url, synopsis, genre_id) VALUES ('MARIGHELLA', null, '2021', 'https://m.media-amazon.com/images/M/MV5BMmZkNjg3MTgtY2E3OC00YTBmLWI3YTktZWVlZDIxMjAwOTcwXkEyXkFqcGdeQXVyMTkzODUwNzk@._V1_FMjpg_UX1000_.jpg', 'Neste filme biográfico, acompanhamos a história de Carlos Marighella, em 1969, um homem que não teve tempo pra ter medo.', 1); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('HOMEM-ARANHA: SEM VOLTA PARA CASA', null, 2021, 'https://s2.glbimg.com/TxQm7TbPjtdv99GcVGFgroItgxU=/e.glbimg.com/og/ed/f/original/2021/11/16/hasvc_cartaz.posted_1080x1350px_data.jpg', 'Em Homem-Aranha: Sem Volta para Casa, Peter Parker (Tom Holland) precisará lidar com as consequências da sua identidade como o herói mais querido do mundo. após ter sido revelada pela reportagem do Clarim Diário, com uma gravação feita por Mysterio (Jake Gyllenhaal) no filme anterior', 3); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('DEBI LOIDE 2', null, 2014, 'https://br.web.img2.acsta.net/pictures/14/10/23/13/15/276424.jpg', 'Mais nova aventura dos inseparáveis Lloyd Christmas (Jim Carrey) e Harry Dunne (Jeff Daniels). Desta vez, Harry descobre que teve uma filha ilegítima, que hoje precisa dele para um transplante de rim. Ele leva o amigo Lloyd para conhecer a garota, e os dois percebem que não têm a responsabilidade necessária para serem pais.', 2); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('AVATAR', null, 2010, 'https://br.web.img3.acsta.net/medias/nmedia/18/87/30/40/20028676.jpg', 'Jake Sully (Sam Worthington) ficou paraplégico após um combate na Terra. Ele é selecionado para participar do programa Avatar em substituição ao seu irmão gêmeo, falecido. Jake viaja a Pandora, uma lua extraterrestre, onde encontra diversas e estranhas formas de vida. O local é também o lar dos NaVi, seres humanóides que, apesar de primitivos, possuem maior capacidade física que os humanos.', 6); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('OS FAROFEIROS', null, 2018, 'https://br.web.img3.acsta.net/pictures/18/01/03/19/24/3938254.jpg', 'Quatro colegas de trabalho se programam para curtir o feriado prolongado em uma casa de praia e, chegando lá, descobrem que se meteram em uma tremenda roubada. Para começar o destino não é Búzios, mas Maringuaba; a residência alugada é encontrada caindo aos pedaços, bem diferente do prometido; a praia está sempre cheia; e as confusões são inúmeras.', 2); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('SOBRENATURAL', null, 2011, 'https://maniacosporfilme.files.wordpress.com/2011/08/insidious.jpg', 'O professor Josh Lambert e sua esposa Renai se mudam com seus três filhos – os garotos Dalton e Foster e o bebê Cali – para uma casa muito grande. Quando Dalton está explorando o sótão, ele cai da escada e bate com a cabeça no chão. Na manhã seguinte, Dalton não acorda e fica em coma, mas os médicos não conseguem diagnosticar o seu problema.', 4); +INSERT INTO tb_movie (title, sub_Title, year, img_Url, synopsis, genre_id) VALUES ('A ILHA DO MEDO', null, 2010, 'http://2.bp.blogspot.com/-M0R0KFo-kOc/U778Byzi-bI/AAAAAAAANMI/POj94JZQPTc/s1600/review-a-ilha-do-medo1.jpg', '1954. Teddy Daniels (Leonardo DiCaprio) investiga o desaparecimento de um paciente no Shutter Island Ashecliffe Hospital, em Boston. No local, ele descobre que os médicos realizam experiências radicais com os pacientes, envolvendo métodos ilegais e anti-éticos. Teddy tenta buscar mais informações, mas enfrenta a resistência dos médicos em lhe fornecer os arquivos que possam permitir que o caso seja aberto.', 5); + + + +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Muito bom!', 2, 1); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Emocionante', 2, 1); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Super Heroir favorito. Filmes Sensacional', 2, 2); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Altas risadas', 2, 3); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Lindo Filme. Cores vibrantes.', 2, 4); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Para rir muito', 2, 5); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Terror SObrenatural', 2, 6); +INSERT INTO tb_review (text, user_id, movie_id) VALUES ('Frio na espinha', 2, 7);