Compare commits
10 Commits
189245ef42
...
c3dd337c7d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3dd337c7d | ||
![]() |
d14ae93f95 | ||
![]() |
b39307f7d9 | ||
![]() |
acd85c93e0 | ||
![]() |
38b0f46f7f | ||
![]() |
fa920bd840 | ||
![]() |
80a04c4614 | ||
300d3b7a32 | |||
64569c4f81 | |||
d769bee503 |
30
pom.xml
30
pom.xml
@ -30,10 +30,6 @@
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
@ -58,6 +54,32 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.guams.review;
|
||||
|
||||
import com.guams.review.modele.Post;
|
||||
import com.guams.review.modele.User;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
|
34
src/main/java/com/guams/review/configuration/Advice.java
Normal file
34
src/main/java/com/guams/review/configuration/Advice.java
Normal file
@ -0,0 +1,34 @@
|
||||
package com.guams.review.configuration;
|
||||
|
||||
import com.guams.review.exception.AlreadyExistsException;
|
||||
import com.guams.review.exception.InvalidNameOrPasswordException;
|
||||
import com.guams.review.exception.NotFoundException;
|
||||
import com.guams.review.exception.ForbiddenExecption;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@ControllerAdvice
|
||||
public class Advice {
|
||||
|
||||
@ExceptionHandler(value = NotFoundException.class)
|
||||
public ResponseEntity<Object> handleNotFound(NotFoundException exception) {
|
||||
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = AlreadyExistsException.class)
|
||||
public ResponseEntity<Object> handleAlreadyExists(AlreadyExistsException exception) {
|
||||
return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = InvalidNameOrPasswordException.class)
|
||||
public ResponseEntity<Object> handleInvalidNameOrPassword(InvalidNameOrPasswordException exception) {
|
||||
return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = ForbiddenExecption.class)
|
||||
public ResponseEntity<Object> handleUnauthorizedExecption(ForbiddenExecption exception) {
|
||||
return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.guams.review.configuration;
|
||||
import com.guams.review.service.AuthorService;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
private final AuthorService authorService;
|
||||
|
||||
public JwtAuthenticationFilter(JwtTokenUtil jwtTokenUtil, AuthorService authorService) {
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
this.authorService = authorService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
String token = authHeader.substring(7); // Retire le préfixe "Bearer "
|
||||
try {
|
||||
String username = jwtTokenUtil.extractUsername(token); // Récupère l'utilisateur du token
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = authorService.loadUserByUsername(username);
|
||||
|
||||
if (jwtTokenUtil.validateToken(token, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication); // Met à jour le contexte
|
||||
}
|
||||
}
|
||||
} catch (JwtException e) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response); // Continue le traitement
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.guams.review.configuration;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class JwtTokenUtil {
|
||||
|
||||
private final String SECRET_KEY = "9f87d7a0eb7dea860b98adf6bb94feefe4a33698022733bb012d662d92db8081";
|
||||
private final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 heures
|
||||
|
||||
public String generateToken(String username) {
|
||||
return Jwts.builder()
|
||||
.setSubject(username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
|
||||
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(SECRET_KEY)
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
String username = extractUsername(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(SECRET_KEY)
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getExpiration();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.guams.review.configuration;
|
||||
import com.guams.review.service.AuthorService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SpringSecurityConfig {
|
||||
|
||||
private final AuthorService authorService;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
|
||||
return http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // Ajout de la configuration CORS
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(HttpMethod.GET,
|
||||
"/api/authors",
|
||||
"/api/authors/{id}",
|
||||
"/api/authors/{id}/posts",
|
||||
"/api/posts",
|
||||
"/api/posts/{id}").permitAll() // Autorise les GET sur ces routes
|
||||
.requestMatchers("/api/authors/login", "/api/authors/register").permitAll() // Autorise sans authentification
|
||||
.requestMatchers("/api/authors/me").authenticated() // Requiert authentification
|
||||
.anyRequest().authenticated() // Toutes les autres routes nécessitent une authentification
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Ajoute le filtre JWT
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(List.of("http://localhost:4200")); // N'autorise que localhost:4200
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); // Spécifie les méthodes autorisées
|
||||
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); // Limite les en-têtes autorisés
|
||||
configuration.setAllowCredentials(true); // Autorise l'utilisation des cookies ou des tokens
|
||||
configuration.setMaxAge(3600L); // Cache la configuration CORS pendant 1 heure
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration); // Applique les règles à toutes les routes
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder) throws Exception {
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||
authenticationManagerBuilder.userDetailsService(authorService).passwordEncoder(passwordEncoder);
|
||||
return authenticationManagerBuilder.build();
|
||||
}
|
||||
|
||||
}
|
124
src/main/java/com/guams/review/controller/AuthorController.java
Normal file
124
src/main/java/com/guams/review/controller/AuthorController.java
Normal file
@ -0,0 +1,124 @@
|
||||
package com.guams.review.controller;
|
||||
|
||||
import com.guams.review.configuration.JwtTokenUtil;
|
||||
import com.guams.review.exception.AlreadyExistsException;
|
||||
import com.guams.review.exception.InvalidNameOrPasswordException;
|
||||
import com.guams.review.exception.NotFoundException;
|
||||
import com.guams.review.exception.ForbiddenExecption;
|
||||
import com.guams.review.model.AuthorRepository;
|
||||
import com.guams.review.model.dao.Author;
|
||||
import com.guams.review.model.dao.AuthorToken;
|
||||
import com.guams.review.model.dao.Post;
|
||||
import com.guams.review.service.AuthorService;
|
||||
import com.guams.review.service.mapper.Mapper;
|
||||
import com.guams.review.service.mapper.ReturnableAuthor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "api/authors")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthorController {
|
||||
|
||||
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final AuthorService authorService;
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
private final AuthorRepository authorRepository;
|
||||
private final Mapper mapper;
|
||||
|
||||
@GetMapping
|
||||
public List<ReturnableAuthor> getUsers() {
|
||||
return authorService.list();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ReturnableAuthor findUser(@PathVariable UUID id) {
|
||||
Author author = authorService.findById(id).orElseThrow(() -> new NotFoundException("Author not found"));
|
||||
return mapper.mapAuthor(author);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public void updateUser(@PathVariable UUID id, @RequestBody Author updatedAuthor, Authentication authentication) {
|
||||
Author authorToUpdate = authorService.verifyIfUserIsAuthorized(authentication, id);
|
||||
authorService.insert(updatedAuthor.setId(authorToUpdate.getId()));
|
||||
}
|
||||
|
||||
@PutMapping(value = "{id}/avatar", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
|
||||
public void updateUserAvatar(@PathVariable UUID id, @RequestPart MultipartFile avatar, Authentication authentication) throws IOException {
|
||||
Author authorToUpdate = authorService.verifyIfUserIsAuthorized(authentication, id);
|
||||
authorService.insert(authorToUpdate.setProfilePicture(avatar.getBytes()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void deleteUser(@PathVariable UUID id, Authentication authentication) {
|
||||
Author authorToDelete = authorService.verifyIfUserIsAuthorized(authentication, id);
|
||||
authorService.delete(authorToDelete);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/posts")
|
||||
public void updateUserPosts(@PathVariable("id") UUID authorId, @RequestBody List<Long> postIds, Authentication authentication) {
|
||||
Author author = authorService.verifyIfUserIsAuthorized(authentication, authorId);
|
||||
authorService.insertPublications(author.getId(), postIds);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/posts")
|
||||
public List<Post> getUserPosts(@PathVariable UUID id) {
|
||||
Author author = authorService.findById(id).orElseThrow(() -> new NotFoundException("Author not found"));
|
||||
return authorService.listPublicationOfAuthor(author.getId());
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public AuthorToken authorLogin(@RequestBody Author author) {
|
||||
try {
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(author.getName(), author.getPassword())
|
||||
);
|
||||
|
||||
String token = jwtTokenUtil.generateToken(author.getName());
|
||||
return new AuthorToken().setToken(token);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidNameOrPasswordException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<Author> authorRegister(@RequestBody Author author) {
|
||||
Assert.isNull(author.getId(), "Author id must be null");
|
||||
if (authorRepository.findByName(author.getName()) != null) {
|
||||
throw new AlreadyExistsException("Author already exists");
|
||||
}
|
||||
author.setPassword(passwordEncoder.encode(author.getPassword()));
|
||||
return new ResponseEntity<>(authorRepository.save(author.setRole("USER")).setPassword(""), HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/me", produces = "application/json")
|
||||
public Author getAuthenticatedUser(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new ForbiddenExecption("You are not authorized to access this resource");
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
Author author = authorRepository.findByName(username);
|
||||
|
||||
if (author == null) {
|
||||
throw new NotFoundException("Author not found");
|
||||
}
|
||||
|
||||
return author.setPassword("");
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.guams.review.controller;
|
||||
|
||||
import com.guams.review.exception.NotFoundException;
|
||||
import com.guams.review.exception.ForbiddenExecption;
|
||||
import com.guams.review.model.AuthorRepository;
|
||||
import com.guams.review.model.dao.Author;
|
||||
import com.guams.review.model.dao.Post;
|
||||
import com.guams.review.service.AuthorService;
|
||||
import com.guams.review.service.PostService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/posts")
|
||||
public class PostController {
|
||||
|
||||
private final PostService postService;
|
||||
private final AuthorService authorService;
|
||||
private final AuthorRepository authorRepository;
|
||||
|
||||
@GetMapping
|
||||
public List<Post> listPosts() {
|
||||
return postService.list();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Post findPost(@PathVariable Long id) {
|
||||
return postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public void updatePost(@PathVariable Long id, @RequestBody Post updatedPost, Authentication authentication) {
|
||||
if (authentication == null) {
|
||||
throw new ForbiddenExecption("You have to login to do that");
|
||||
}
|
||||
Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
|
||||
//Si l'user authent possède ce post
|
||||
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
|
||||
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
|
||||
postService.insert(updatedPost
|
||||
.setId(postToUpdate.getId())
|
||||
.setIllustration(postToUpdate.getIllustration())
|
||||
.setPublicationDate(postToUpdate.getPublicationDate()));
|
||||
} else {
|
||||
throw new ForbiddenExecption("You do not have permission to update this post");
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping(value = "{id}/illustration", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
|
||||
public void updateIllustration(@PathVariable Long id, @RequestPart MultipartFile illustration, Authentication authentication) throws IOException {
|
||||
if (authentication == null) {
|
||||
throw new ForbiddenExecption("You have to login to do that");
|
||||
}
|
||||
Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
|
||||
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
|
||||
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
|
||||
postService.insert(postToUpdate.setIllustration(illustration.getBytes()));
|
||||
} else {
|
||||
throw new ForbiddenExecption("You do not have permission to update this post");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Post> addPost(@RequestBody Post postToCreate, Authentication authentication) {
|
||||
Assert.isNull(postToCreate.getId(), "Post id must be null");
|
||||
if (authentication == null) {
|
||||
throw new ForbiddenExecption("You have to login to do that");
|
||||
}
|
||||
return new ResponseEntity<>(postService.insert(postToCreate.setPublicationDate(LocalDate.now())), HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
public void deletePost(@PathVariable Long id, Authentication authentication) {
|
||||
if (authentication == null) {
|
||||
throw new ForbiddenExecption("You have to login to do that");
|
||||
}
|
||||
Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
|
||||
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
|
||||
Post postToDelete = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
|
||||
postService.delete(postToDelete.getId());
|
||||
} else {
|
||||
throw new ForbiddenExecption("You do not have permission to delete this post");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package com.guams.review.controller;
|
||||
|
||||
import com.guams.review.modele.User;
|
||||
import com.guams.review.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "api/users")
|
||||
public class UserController
|
||||
{
|
||||
private final UserService userService;
|
||||
|
||||
@Autowired
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<User>> getUsers()
|
||||
{
|
||||
List<User> users = userService.getUsers();
|
||||
return new ResponseEntity<>(users, new HttpHeaders(),HttpStatus.OK);
|
||||
};
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<String> saveUser(@RequestParam User user)
|
||||
{
|
||||
userService.saveUser(user);
|
||||
Optional<User> exists = userService.findById(user.getId());
|
||||
if (exists.isPresent())
|
||||
{
|
||||
return new ResponseEntity<>("Created", new HttpHeaders(), HttpStatus.CREATED);
|
||||
}
|
||||
return new ResponseEntity<>("Error", new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.guams.review.exception;
|
||||
|
||||
public class AlreadyExistsException extends RuntimeException {
|
||||
public AlreadyExistsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.guams.review.exception;
|
||||
|
||||
public class ForbiddenExecption extends RuntimeException {
|
||||
public ForbiddenExecption(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.guams.review.exception;
|
||||
|
||||
public class InvalidNameOrPasswordException extends RuntimeException {
|
||||
public InvalidNameOrPasswordException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.guams.review.exception;
|
||||
|
||||
public class NotFoundException extends RuntimeException {
|
||||
public NotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
26
src/main/java/com/guams/review/model/AuthorRepository.java
Normal file
26
src/main/java/com/guams/review/model/AuthorRepository.java
Normal file
@ -0,0 +1,26 @@
|
||||
package com.guams.review.model;
|
||||
|
||||
import com.guams.review.model.dao.Author;
|
||||
import org.springframework.data.jdbc.repository.query.Modifying;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface AuthorRepository extends CrudRepository<Author, UUID> {
|
||||
|
||||
@Modifying
|
||||
@Query("insert into publication (author_fk, post_fk) values (:authorId, :postId)")
|
||||
void insertPublication(UUID authorId, Long postId);
|
||||
|
||||
@Modifying
|
||||
@Query("delete from publication where author_fk=:authorId and post_fk=:postId")
|
||||
void deletePublication(UUID authorId, Long postId);
|
||||
|
||||
List<Author> findAll();
|
||||
|
||||
Author findByName(String name);
|
||||
}
|
19
src/main/java/com/guams/review/model/PostRepository.java
Normal file
19
src/main/java/com/guams/review/model/PostRepository.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.guams.review.model;
|
||||
|
||||
import com.guams.review.model.dao.Post;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PostRepository extends CrudRepository<Post, Long> {
|
||||
|
||||
@Query("select id, description, illustration, title, body, category, publication_date " +
|
||||
"from post join publication on post.id=publication.post_fk where publication.author_fk=:authorId")
|
||||
List<Post> findPostsOfAuthor(UUID authorId);
|
||||
|
||||
List<Post> findAll();
|
||||
}
|
39
src/main/java/com/guams/review/model/dao/Author.java
Normal file
39
src/main/java/com/guams/review/model/dao/Author.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.guams.review.model.dao;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@Table(name = "author")
|
||||
public class Author {
|
||||
|
||||
@Id
|
||||
UUID id;
|
||||
|
||||
@Column("name")
|
||||
String name;
|
||||
|
||||
@Column("password")
|
||||
String password;
|
||||
|
||||
@Column("profile_picture")
|
||||
byte[] profilePicture;
|
||||
|
||||
@Column("role")
|
||||
String role;
|
||||
|
||||
}
|
12
src/main/java/com/guams/review/model/dao/AuthorToken.java
Normal file
12
src/main/java/com/guams/review/model/dao/AuthorToken.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.guams.review.model.dao;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class AuthorToken {
|
||||
String token;
|
||||
}
|
32
src/main/java/com/guams/review/model/dao/Post.java
Normal file
32
src/main/java/com/guams/review/model/dao/Post.java
Normal file
@ -0,0 +1,32 @@
|
||||
package com.guams.review.model.dao;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@Table(name = "post")
|
||||
public class Post {
|
||||
@Id
|
||||
Long id;
|
||||
@Column("description")
|
||||
String description;
|
||||
@Column("illustration")
|
||||
byte[] illustration;
|
||||
@Column("title")
|
||||
String title;
|
||||
@Column("body")
|
||||
String body;
|
||||
@Column("category")
|
||||
String category;
|
||||
@Column("publication_date")
|
||||
LocalDate publicationDate;
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package com.guams.review.modele;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@Table(name = "POST")
|
||||
public class Post
|
||||
{
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@Column(name = "TITLE", nullable = false)
|
||||
private String title;
|
||||
@Column(name = "BODY", nullable = false)
|
||||
private String body;
|
||||
@Column(name = "IMAGE")
|
||||
private String image;
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "USER_ID", nullable = false)
|
||||
private User user;
|
||||
|
||||
public Post()
|
||||
{}
|
||||
|
||||
|
||||
public Post setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Post setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Post setBody(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Post setImage(String image) {
|
||||
this.image = image;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Post setUser(User user) {
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package com.guams.review.modele;
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@Table(name = "USERS")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Column(name = "LOGIN", nullable = false, unique = true)
|
||||
private String login;
|
||||
|
||||
@Column(name = "PASSWORD", nullable = false)
|
||||
private String password;
|
||||
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<Post> posts;
|
||||
|
||||
|
||||
public User setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public User setLogin(String login) {
|
||||
this.login = login;
|
||||
return this;
|
||||
}
|
||||
|
||||
public User setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public User setPosts(List<Post> posts) {
|
||||
this.posts = posts;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.guams.review.repository;
|
||||
|
||||
import com.guams.review.modele.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long>
|
||||
{
|
||||
}
|
84
src/main/java/com/guams/review/service/AuthorService.java
Normal file
84
src/main/java/com/guams/review/service/AuthorService.java
Normal file
@ -0,0 +1,84 @@
|
||||
package com.guams.review.service;
|
||||
|
||||
import com.guams.review.exception.NotFoundException;
|
||||
import com.guams.review.exception.ForbiddenExecption;
|
||||
import com.guams.review.model.AuthorRepository;
|
||||
import com.guams.review.model.PostRepository;
|
||||
import com.guams.review.model.dao.Author;
|
||||
import com.guams.review.model.dao.Post;
|
||||
import com.guams.review.service.mapper.Mapper;
|
||||
import com.guams.review.service.mapper.ReturnableAuthor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
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 java.util.*;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthorService implements UserDetailsService
|
||||
{
|
||||
private final Mapper mapper;
|
||||
private final AuthorRepository authorRepository;
|
||||
private final PostRepository postRepository;
|
||||
|
||||
public List<ReturnableAuthor> list() {
|
||||
return authorRepository.findAll().stream()
|
||||
.map(mapper::mapAuthor)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<Post> listPublicationOfAuthor(UUID authorId) {
|
||||
return postRepository.findPostsOfAuthor(authorId);
|
||||
}
|
||||
|
||||
public Optional<Author> findById(UUID id) {
|
||||
return authorRepository.findById(id);
|
||||
}
|
||||
|
||||
public ReturnableAuthor insert(Author author) {
|
||||
return mapper.mapAuthor(authorRepository.save(author));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void insertPublications(UUID authorId, List<Long> postIds) {
|
||||
for (Long postId : postIds) {
|
||||
authorRepository.deletePublication(authorId, postId);
|
||||
if (postRepository.findById(postId).isPresent()) {
|
||||
authorRepository.insertPublication(authorId, postId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Author verifyIfUserIsAuthorized(Authentication authentication, UUID id) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new ForbiddenExecption("You have to login first");
|
||||
}
|
||||
Author author = findById(id).orElseThrow(() -> new NotFoundException("Author not found"));
|
||||
String username = authentication.getName();
|
||||
Author authorAuthenticated = authorRepository.findByName(username);
|
||||
if (authorAuthenticated.getId().compareTo(author.getId()) != 0 && !authorAuthenticated.getRole().equals("ADMIN")) {
|
||||
throw new ForbiddenExecption("Specified Author is not authorized to do that");
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
public void delete(Author author) {
|
||||
authorRepository.delete(author);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
Author author = authorRepository.findByName(username);
|
||||
if (author == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
return new User(author.getName(), author.getPassword(), Collections.singletonList(new SimpleGrantedAuthority(author.getRole())));
|
||||
}
|
||||
}
|
34
src/main/java/com/guams/review/service/PostService.java
Normal file
34
src/main/java/com/guams/review/service/PostService.java
Normal file
@ -0,0 +1,34 @@
|
||||
package com.guams.review.service;
|
||||
|
||||
|
||||
import com.guams.review.model.dao.Post;
|
||||
import com.guams.review.model.PostRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PostService
|
||||
{
|
||||
private final PostRepository postRepository;
|
||||
|
||||
public List<Post> list() {
|
||||
return postRepository.findAll();
|
||||
}
|
||||
|
||||
public Optional<Post> findById(Long id) {
|
||||
return postRepository.findById(id);
|
||||
}
|
||||
|
||||
public Post insert(Post post) {
|
||||
return postRepository.save(post);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
postRepository.deleteById(id);
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.guams.review.service;
|
||||
|
||||
import com.guams.review.modele.Post;
|
||||
import com.guams.review.modele.User;
|
||||
import com.guams.review.repository.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserService
|
||||
{
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public UserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public List<User> getUsers()
|
||||
{
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
public void saveUser(User user)
|
||||
{
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public Optional<User> findById(Long id)
|
||||
{
|
||||
return userRepository.findById(id);
|
||||
}
|
||||
}
|
18
src/main/java/com/guams/review/service/mapper/Mapper.java
Normal file
18
src/main/java/com/guams/review/service/mapper/Mapper.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.guams.review.service.mapper;
|
||||
|
||||
import com.guams.review.model.dao.Author;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Mapper {
|
||||
|
||||
public Mapper() {}
|
||||
|
||||
public ReturnableAuthor mapAuthor(Author author) {
|
||||
return new ReturnableAuthor()
|
||||
.setId(author.getId())
|
||||
.setName(author.getName())
|
||||
.setRole(author.getRole())
|
||||
.setProfilePicture(author.getProfilePicture());
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.guams.review.service.mapper;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class ReturnableAuthor {
|
||||
private UUID id;
|
||||
private String name;
|
||||
private byte[] profilePicture;
|
||||
private String role;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
spring.security.oauth2.client.registration.github.client-id=Ov23ligoCzKHHyyIzIbS
|
||||
spring.security.oauth2.client.registration.github.client-secret=c660f476763404f41da43e7a3f7e9648f94b107d
|
||||
spring.application.name=reView
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/review
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/reviewDB
|
||||
spring.datasource.username=postgres
|
||||
spring.datasource.password=Azerty1234
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
spring.jpa.show-sql=true
|
31
src/main/resources/script.sql
Normal file
31
src/main/resources/script.sql
Normal file
@ -0,0 +1,31 @@
|
||||
drop table if exists publication;
|
||||
drop table if exists post;
|
||||
drop table if exists author;
|
||||
|
||||
create table author
|
||||
(
|
||||
id uuid default gen_random_uuid() primary key,
|
||||
name varchar(255) unique not null,
|
||||
password text not null,
|
||||
profile_picture bytea,
|
||||
role varchar(50) default 'USER' not null
|
||||
);
|
||||
|
||||
create table post
|
||||
(
|
||||
id serial primary key,
|
||||
description varchar(512) not null,
|
||||
illustration bytea,
|
||||
title varchar(50) not null,
|
||||
body text not null,
|
||||
category varchar(50) not null,
|
||||
publication_date date not null
|
||||
);
|
||||
|
||||
create table publication
|
||||
(
|
||||
author_fk uuid,
|
||||
post_fk serial,
|
||||
foreign key (author_fk) references author (id),
|
||||
foreign key (post_fk) references post (id)
|
||||
);
|
Loading…
Reference in New Issue
Block a user