Skip to content

Commit

Permalink
feature: Add unified global exception handling using AlertDto
Browse files Browse the repository at this point in the history
Task: 8696zwhj1
  • Loading branch information
KinTrae committed Dec 22, 2024
1 parent f0159f3 commit 68f9de3
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -16,7 +15,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.webjars.NotFoundException;

@RestController
@RequestMapping("api/posts")
Expand All @@ -33,51 +31,26 @@ public ResponseEntity<Page<PostDto>> getPosts(@AuthenticationPrincipal UserDetai

@GetMapping("/{login}")
public ResponseEntity<Page<PostDto>> getPostsForUser(@PathVariable("login") String login, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int pageNo, @RequestParam(defaultValue = "10") int pageSize) {
try {
Page<PostDto> posts = postService.getPostsForUser(login, userDetails.getUsername(), pageNo, pageSize);
return ResponseEntity.ok(posts);
} catch (UsernameNotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested user not found").build();
}
Page<PostDto> posts = postService.getPostsForUser(login, userDetails.getUsername(), pageNo, pageSize);
return ResponseEntity.ok(posts);

}

@PostMapping("")
public ResponseEntity<PostDto> createPost(@RequestParam String content, @AuthenticationPrincipal UserDetails userDetails) {
try {
PostDto postDto = postService.createPost(userDetails.getUsername(), content);
return ResponseEntity.ok(postDto);
} catch (UsernameNotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested user not found").build();
}
PostDto postDto = postService.createPost(userDetails.getUsername(), content);
return ResponseEntity.ok(postDto);
}

@DeleteMapping("/{postId}")
public ResponseEntity<Void> deletePost(@PathVariable("postId") String postId, @AuthenticationPrincipal UserDetails userDetails) {
try {
postService.deletePost(userDetails.getUsername(), postId);
return ResponseEntity.ok().build();
} catch (UsernameNotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested user not found").build();
} catch (NotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested post not found").build();
}
postService.deletePost(userDetails.getUsername(), postId);
return ResponseEntity.ok().build();
}

@PutMapping("/{postId}")
public ResponseEntity<PostDto> updatePost(@PathVariable("postId") String postId, @RequestParam String content, @AuthenticationPrincipal UserDetails userDetails) {
try {
PostDto postDto = postService.updatePost(userDetails.getUsername(), postId, content);
return ResponseEntity.ok(postDto);
} catch (UsernameNotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested user not found").build();
} catch (NotFoundException exception) {
exception.printStackTrace();
return ResponseEntity.notFound().eTag("Requested post not found").build();
}
PostDto postDto = postService.updatePost(userDetails.getUsername(), postId, content);
return ResponseEntity.ok(postDto);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import meowhub.backend.posts.dtos.PostDto;
import meowhub.backend.posts.models.Post;
import meowhub.backend.posts.services.PostService;
import meowhub.backend.shared.constants.AlertConstants;
import meowhub.backend.users.dtos.BasicUserInfoDto;
import meowhub.backend.users.models.User;
import meowhub.backend.posts.repositories.PostRepository;
Expand All @@ -15,6 +16,8 @@
import org.springframework.stereotype.Service;
import org.webjars.NotFoundException;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
public class PostServiceImpl implements PostService {
Expand All @@ -31,7 +34,7 @@ public Page<PostDto> getPosts(String requestedBy, int pageNo, int pageSize) {
public Page<PostDto> getPostsForUser(String login, String requestedBy, int pageNo, int pageSize) {
Pageable pageable = PageRequest.of(pageNo, pageSize);
userRepository.findByLogin(login)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login)));
.orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login)));

if (login.equals(requestedBy)) {
return postRepository.findOwn(login, pageable);
Expand All @@ -43,12 +46,14 @@ public Page<PostDto> getPostsForUser(String login, String requestedBy, int pageN
@Override
public PostDto createPost(String login, String content) {
User postOwner = userRepository.findByLogin(login)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login)));
.orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login)));
Post post = new Post();
post.setContentHtml(content);
post.setUser(postOwner);
post.setCreatedAt(LocalDateTime.now());
post = postRepository.save(post);

return convertToPostDto(postRepository.save(post));
return convertToPostDto(post);
}

@Override
Expand All @@ -64,17 +69,22 @@ public void deletePost(String login, String postId) {
}

private Post findUserPost(String login, String postId) {
userRepository.findByLogin(login).orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login)));
userRepository.findByLogin(login)
.orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login)));

return postRepository.findByUserLoginAndId(login, postId)
.orElseThrow(() -> new NotFoundException(String.format("Post with id: '%s' not found for user '%s'", postId, login)));
.orElseThrow(() -> new NotFoundException(String.format(AlertConstants.RESOURCE_NOT_FOUND, "post", "id", postId)));
}

private PostDto convertToPostDto(Post post) {
if (post == null) {
return null;
}

BasicUserInfoDto author = userRepository.findBasicUserInfoByLogin(post.getUser().getLogin()).orElseThrow();
String login = post.getUser().getLogin();
BasicUserInfoDto author = userRepository.findBasicUserInfoByLogin(login)
.orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login)));

return PostDto.builder()
.id(post.getId())
.content(post.getContentHtml())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@
import meowhub.backend.security.responses.LoginResponse;
import meowhub.backend.security.requests.SignUpRequest;
import meowhub.backend.security.services.AuthService;
import org.hibernate.NonUniqueObjectException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("api/auth")
@RequiredArgsConstructor
Expand All @@ -24,24 +19,13 @@ public class AuthController {

@PostMapping("/public/sign-in")
public ResponseEntity<Object> authenticateUser(@RequestBody LoginRequest request) {
try {
LoginResponse response = authService.authenticateUser(request);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("message", "Bad credentials");
map.put("status", Boolean.FALSE);
return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST);
}
LoginResponse response = authService.authenticateUser(request);
return ResponseEntity.ok(response);
}

@PostMapping("/public/sign-up")
public ResponseEntity<Object> signUpUser(@RequestBody SignUpRequest request) {
try {
authService.signUpUser(request);
return ResponseEntity.ok("User registration complete");
} catch (NonUniqueObjectException e) {
return new ResponseEntity<>(e.getIdentifier(), HttpStatus.NOT_ACCEPTABLE);
}
authService.signUpUser(request);
return ResponseEntity.ok("User registration complete");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.RequiredArgsConstructor;
import meowhub.backend.constants.PrivacySettings;
import meowhub.backend.constants.Roles;
import meowhub.backend.shared.constants.AlertConstants;
import meowhub.backend.shared.exceptions.NotUniqueObjectException;
import meowhub.backend.users.models.Gender;
import meowhub.backend.users.models.PrivacySetting;
import meowhub.backend.users.models.Role;
Expand All @@ -16,7 +18,6 @@
import meowhub.backend.security.requests.SignUpRequest;
import meowhub.backend.security.responses.LoginResponse;
import meowhub.backend.security.services.AuthService;
import org.hibernate.NonUniqueObjectException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
Expand Down Expand Up @@ -68,7 +69,7 @@ public void signUpUser(SignUpRequest request) {
.orElseGet(() -> roleRepository.save(new Role(Roles.ROLE_USER)));

Gender gender = genderRepository.findByCode(request.getGender().name())
.orElseThrow(IllegalArgumentException::new);
.orElseThrow(() -> new IllegalArgumentException(String.format(AlertConstants.RESOURCE_NOT_FOUND, "gender", "gender.code", request.getGender())));

PrivacySetting publicSettings = privacySettingRepository.findByCode(PrivacySettings.PUBLIC.name())
.orElseGet(() -> privacySettingRepository.save(new PrivacySetting(PrivacySettings.PUBLIC)));
Expand All @@ -95,14 +96,12 @@ public void signUpUser(SignUpRequest request) {
private void validateSignUpRequest(SignUpRequest request) {
boolean isLoginNotUnique = userRepository.existsByLogin(request.getLogin());
if (isLoginNotUnique) {
throw new NonUniqueObjectException("The login is already in use.", "login");
throw new NotUniqueObjectException(String.format(AlertConstants.NOT_UNIQUE_OBJECT, "login", request.getLogin()));
}

boolean isEmailNotUnique = userRepository.existsByEmail(request.getEmail());
if (isEmailNotUnique) {
throw new NonUniqueObjectException("The email is already in use.", "email");
throw new NotUniqueObjectException(String.format(AlertConstants.NOT_UNIQUE_OBJECT, "email", request.getEmail()));
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import meowhub.backend.shared.constants.AlertConstants;
import meowhub.backend.users.models.User;
import meowhub.backend.users.repositories.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
Expand All @@ -18,7 +19,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByLogin(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with login: " + username));
.orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, username)));

return UserDetailsImpl.build(user);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package meowhub.backend.shared.constants;

public class AlertConstants {
//title
public static final String USER_WITH_LOGIN_NOT_FOUND_TITLE = "User not found";
public static final String RESOURCE_NOT_FOUND_TITLE = "Resource not found";
public static final String NOT_UNIQUE_OBJECT_TITLE = "Not unique object";
public static final String ILLEGAL_ARGUMENT_TITLE = "Illegal argument";

//message
public static final String USER_WITH_LOGIN_NOT_FOUND = "User with login '%s' not found";
public static final String RESOURCE_NOT_FOUND = "%s not found for %s = '%s'";
public static final String UNKNOWN_ERROR = "Unknown error";
public static final String BAD_CREDENTIALS = "Bad credentials";
public static final String NOT_UNIQUE_OBJECT = "%s:'%s' is not unique";
public static final String ILLEGAL_ARGUMENT = "%s cannot be equal %s";

private AlertConstants() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package meowhub.backend.shared.constants;

public enum AlertLevel {
WARNING, INFO, ERROR
}
20 changes: 20 additions & 0 deletions backend/src/main/java/meowhub/backend/shared/dtos/AlertDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package meowhub.backend.shared.dtos;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import meowhub.backend.shared.constants.AlertLevel;

import java.time.LocalDateTime;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class AlertDto {
private String title;
private String message;
private AlertLevel level;
private LocalDateTime timestamp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package meowhub.backend.shared.exceptions;

public class NotUniqueObjectException extends RuntimeException {
public NotUniqueObjectException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package meowhub.backend.shared.handlers;

import meowhub.backend.shared.dtos.AlertDto;
import meowhub.backend.shared.exceptions.NotUniqueObjectException;
import meowhub.backend.shared.utils.AlertUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.webjars.NotFoundException;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<AlertDto> handleUsernameNotFoundException(UsernameNotFoundException ex) {
return new ResponseEntity<>(AlertUtils.userNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(NotUniqueObjectException.class)
public ResponseEntity<Object> handleNotUniqueObjectException(NotUniqueObjectException ex) {
return new ResponseEntity<>(AlertUtils.notUniqueObjectException(ex.getMessage()), HttpStatus.NOT_ACCEPTABLE);
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<AlertDto> handleNotFoundException(NotFoundException ex) {
return new ResponseEntity<>(AlertUtils.resourceNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<AlertDto> handleUnknownException(Exception ex) {
ex.printStackTrace();
return new ResponseEntity<>(AlertUtils.unknownException(), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<AlertDto> handleBadCredentialsException() {
return new ResponseEntity<>(AlertUtils.badCredentialsException(), HttpStatus.UNAUTHORIZED);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<AlertDto> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(AlertUtils.illegalArgumentException(ex.getMessage()), HttpStatus.NOT_ACCEPTABLE);
}

}
Loading

0 comments on commit 68f9de3

Please sign in to comment.