diff --git a/src/main/java/se/issuetrackingsystem/comment/controller/CommentController.java b/src/main/java/se/issuetrackingsystem/comment/controller/CommentController.java index c29cd2a..1ffc36b 100644 --- a/src/main/java/se/issuetrackingsystem/comment/controller/CommentController.java +++ b/src/main/java/se/issuetrackingsystem/comment/controller/CommentController.java @@ -1,5 +1,6 @@ package se.issuetrackingsystem.comment.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -21,7 +22,7 @@ public class CommentController { private final CommentService commentService; @PostMapping - public ResponseEntity> commentCreate(@RequestParam("issueId") Long issueId,@RequestBody CommentRequest commentRequest){ + public ResponseEntity> commentCreate(@RequestParam("issueId") Long issueId, @Valid @RequestBody CommentRequest commentRequest){ commentService.create(issueId,commentRequest.getMessage(),commentRequest.getAuthorId()); List comments = commentService.getList(issueId); List responses = new ArrayList<>(); @@ -37,7 +38,7 @@ public ResponseEntity commentDelete(@RequestParam("commentId") Long com } @PatchMapping - public ResponseEntity commentModify(@RequestParam("commentId") Long commentId, @RequestBody CommentRequest commentRequest){ + public ResponseEntity commentModify(@RequestParam("commentId") Long commentId, @Valid @RequestBody CommentRequest commentRequest){ return ResponseEntity.ok(commentService.modify(commentId, commentRequest.getMessage())); } diff --git a/src/main/java/se/issuetrackingsystem/comment/dto/CommentRequest.java b/src/main/java/se/issuetrackingsystem/comment/dto/CommentRequest.java index 1bcac7c..ddba1bc 100644 --- a/src/main/java/se/issuetrackingsystem/comment/dto/CommentRequest.java +++ b/src/main/java/se/issuetrackingsystem/comment/dto/CommentRequest.java @@ -8,9 +8,9 @@ @Getter @Setter public class CommentRequest { - @NotBlank + + @NotBlank(message = "Message는 공백이 될 수 없습니다.") private String message; - @NotBlank private Long authorId; -} +} \ No newline at end of file diff --git a/src/main/java/se/issuetrackingsystem/common/exception/ErrorCode.java b/src/main/java/se/issuetrackingsystem/common/exception/ErrorCode.java index 4d15962..c7e5a87 100644 --- a/src/main/java/se/issuetrackingsystem/common/exception/ErrorCode.java +++ b/src/main/java/se/issuetrackingsystem/common/exception/ErrorCode.java @@ -12,13 +12,13 @@ public enum ErrorCode { * 400 BAD_REQUEST: 잘못된 요청 */ BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), + ROLE_BAD_REQUEST(HttpStatus.BAD_REQUEST, "역할이 올바르지 않습니다."), + PASSWORD_BAD_REQUEST(HttpStatus.BAD_REQUEST, "비밀번호가 올바르지 않습니다."), /* * 403 Forbidden: 승인을 거부함 */ USERNAME_FORBIDDEN(HttpStatus.FORBIDDEN, "존재하는 회원 이름입니다."), - ROLE_FORBIDDEN(HttpStatus.FORBIDDEN, "역할이 올바르지 않습니다."), - PASSWORD_FORBIDDEN(HttpStatus.FORBIDDEN, "비밀번호가 올바르지 않습니다."), /* * 404 NOT_FOUND: 리소스를 찾을 수 없음 diff --git a/src/main/java/se/issuetrackingsystem/common/exception/GlobalExceptionHandler.java b/src/main/java/se/issuetrackingsystem/common/exception/GlobalExceptionHandler.java index 5e79125..e4ebef3 100644 --- a/src/main/java/se/issuetrackingsystem/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/se/issuetrackingsystem/common/exception/GlobalExceptionHandler.java @@ -1,11 +1,17 @@ package se.issuetrackingsystem.common.exception; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.HashMap; +import java.util.Map; + @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @@ -21,6 +27,20 @@ protected ResponseEntity handleCustomException(final CustomExcept .body(new ErrorResponse(e.getErrorCode())); } + /* + * 유효성 검사 오류 메시지 반환 + * */ + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException e) { + Map errors = new HashMap<>(); + e.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors); + } + /* * HTTP 405 Exception * */ diff --git a/src/main/java/se/issuetrackingsystem/issue/controller/IssueController.java b/src/main/java/se/issuetrackingsystem/issue/controller/IssueController.java index a841672..5747d5d 100644 --- a/src/main/java/se/issuetrackingsystem/issue/controller/IssueController.java +++ b/src/main/java/se/issuetrackingsystem/issue/controller/IssueController.java @@ -1,6 +1,7 @@ package se.issuetrackingsystem.issue.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -24,7 +25,7 @@ public class IssueController { private final IssueService issueService; @PostMapping - public ResponseEntity> issueCreate(@RequestBody IssueRequest issueRequest, @RequestParam("projectId") Long projectId){ + public ResponseEntity> issueCreate(@Valid @RequestBody IssueRequest issueRequest, @RequestParam("projectId") Long projectId){ issueService.create(projectId,issueRequest.getTitle(),issueRequest.getDescription(),issueRequest.getUserid(),issueRequest.getPriority()); List issues = issueService.getList(projectId); List responses = new ArrayList<>(); @@ -57,13 +58,13 @@ public ResponseEntity issueDelete(@RequestParam("issueId") Long i } @PatchMapping - public ResponseEntity issueModify(@RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ + public ResponseEntity issueModify(@Valid @RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ Issue issue = issueService.modify(issueId,issueRequest.getTitle(),issueRequest.getDescription(),issueRequest.getPriority(),issueRequest.getUserid()); return ResponseEntity.ok(new IssueResponse(issue)); } @PostMapping("/assignees") - public ResponseEntity issueSetAssignee(@RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ + public ResponseEntity issueSetAssignee(@Valid @RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ Issue issue = issueService.setAssignee(issueId,issueRequest.getUserid(),issueRequest.getAssigneeId()); return ResponseEntity.ok(new IssueResponse(issue)); } @@ -79,7 +80,7 @@ public ResponseEntity> issueCheckByStatus(@PathVariable("sta } @PatchMapping("/status") - public ResponseEntity issueChangeStatus(@RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ + public ResponseEntity issueChangeStatus(@Valid @RequestBody IssueRequest issueRequest,@RequestParam("issueId") Long issueId){ Issue issue = issueService.changeStatus(issueRequest.getUserid(),issueId); return ResponseEntity.ok(new IssueResponse(issue)); } diff --git a/src/main/java/se/issuetrackingsystem/issue/dto/IssueRequest.java b/src/main/java/se/issuetrackingsystem/issue/dto/IssueRequest.java index 3f2ad22..170b31b 100644 --- a/src/main/java/se/issuetrackingsystem/issue/dto/IssueRequest.java +++ b/src/main/java/se/issuetrackingsystem/issue/dto/IssueRequest.java @@ -1,5 +1,6 @@ package se.issuetrackingsystem.issue.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; import se.issuetrackingsystem.issue.domain.Issue; @@ -7,6 +8,8 @@ @Getter @Setter public class IssueRequest { + + @NotBlank(message = "Title은 공백이 될 수 없습니다.") private String title; private String description; @@ -15,7 +18,7 @@ public class IssueRequest { private Issue.Status status; - private Long userid; + private Long userId; private Long assigneeId; diff --git a/src/main/java/se/issuetrackingsystem/issue/service/IssueService.java b/src/main/java/se/issuetrackingsystem/issue/service/IssueService.java index d38e1f2..c794f43 100644 --- a/src/main/java/se/issuetrackingsystem/issue/service/IssueService.java +++ b/src/main/java/se/issuetrackingsystem/issue/service/IssueService.java @@ -99,7 +99,7 @@ public Issue setAssignee(Long issueId, Long userId, Long assigneeId){ User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); User assignee = userRepository.findById(assigneeId).orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); if (!user.canSetAssignee()||!assignee.canChangeIssueStateAssignedToFixed()) { - throw new CustomException(ErrorCode.ROLE_FORBIDDEN); + throw new CustomException(ErrorCode.ROLE_BAD_REQUEST); } issue.setAssignee(assignee); issue.setStatus(Issue.Status.ASSIGNED); diff --git a/src/main/java/se/issuetrackingsystem/project/controller/ProjectController.java b/src/main/java/se/issuetrackingsystem/project/controller/ProjectController.java index 4b4b3a9..eb643b6 100644 --- a/src/main/java/se/issuetrackingsystem/project/controller/ProjectController.java +++ b/src/main/java/se/issuetrackingsystem/project/controller/ProjectController.java @@ -1,5 +1,6 @@ package se.issuetrackingsystem.project.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -17,7 +18,7 @@ public class ProjectController { private final ProjectService projectService; @PostMapping - public ResponseEntity createProject(@RequestBody ProjectRequest request) { + public ResponseEntity createProject(@Valid @RequestBody ProjectRequest request) { return ResponseEntity.ok(projectService.createProject(request)); } diff --git a/src/main/java/se/issuetrackingsystem/project/dto/ProjectRequest.java b/src/main/java/se/issuetrackingsystem/project/dto/ProjectRequest.java index ce886f8..245cd60 100644 --- a/src/main/java/se/issuetrackingsystem/project/dto/ProjectRequest.java +++ b/src/main/java/se/issuetrackingsystem/project/dto/ProjectRequest.java @@ -10,12 +10,10 @@ @RequiredArgsConstructor public class ProjectRequest { - @NotBlank + @NotBlank(message = "Title은 공백이 될 수 없습니다.") private final String title; - @NotBlank private final Long adminId; private final List contributorIds; - } diff --git a/src/main/java/se/issuetrackingsystem/user/controller/UserController.java b/src/main/java/se/issuetrackingsystem/user/controller/UserController.java index 314861e..7ab24d1 100644 --- a/src/main/java/se/issuetrackingsystem/user/controller/UserController.java +++ b/src/main/java/se/issuetrackingsystem/user/controller/UserController.java @@ -1,5 +1,6 @@ package se.issuetrackingsystem.user.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -18,12 +19,12 @@ public class UserController { private final UserService userService; @PostMapping("/register") - public ResponseEntity register(@RequestBody RegisterRequest request) { + public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { return ResponseEntity.ok(userService.register(request)); } @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequest request) { + public ResponseEntity login(@Valid @RequestBody LoginRequest request) { return ResponseEntity.ok(userService.login(request)); } diff --git a/src/main/java/se/issuetrackingsystem/user/dto/LoginRequest.java b/src/main/java/se/issuetrackingsystem/user/dto/LoginRequest.java index 752b258..d06762f 100644 --- a/src/main/java/se/issuetrackingsystem/user/dto/LoginRequest.java +++ b/src/main/java/se/issuetrackingsystem/user/dto/LoginRequest.java @@ -8,9 +8,9 @@ @RequiredArgsConstructor public class LoginRequest { - @NotBlank + @NotBlank(message = "Username은 공백이 될 수 없습니다.") private final String username; - @NotBlank + @NotBlank(message = "Password는 공백이 될 수 없습니다.") private final String password; } diff --git a/src/main/java/se/issuetrackingsystem/user/dto/RegisterRequest.java b/src/main/java/se/issuetrackingsystem/user/dto/RegisterRequest.java index 9b62cec..b78f5af 100644 --- a/src/main/java/se/issuetrackingsystem/user/dto/RegisterRequest.java +++ b/src/main/java/se/issuetrackingsystem/user/dto/RegisterRequest.java @@ -8,12 +8,12 @@ @RequiredArgsConstructor public class RegisterRequest { - @NotBlank + @NotBlank(message = "Username은 공백이 될 수 없습니다.") private final String username; - @NotBlank + @NotBlank(message = "Password는 공백이 될 수 없습니다.") private final String password; - @NotBlank + @NotBlank(message = "Role은 공백이 될 수 없습니다.") private final String role; } \ No newline at end of file diff --git a/src/main/java/se/issuetrackingsystem/user/dto/UserResponse.java b/src/main/java/se/issuetrackingsystem/user/dto/UserResponse.java index 6242f4e..fddfa09 100644 --- a/src/main/java/se/issuetrackingsystem/user/dto/UserResponse.java +++ b/src/main/java/se/issuetrackingsystem/user/dto/UserResponse.java @@ -7,13 +7,8 @@ @Getter public class UserResponse { - @NotBlank private final Long userId; - - @NotBlank private final String username; - - @NotBlank private final String role; public UserResponse(User user) { diff --git a/src/main/java/se/issuetrackingsystem/user/repository/UserRepository.java b/src/main/java/se/issuetrackingsystem/user/repository/UserRepository.java index 758630d..22ba2e6 100644 --- a/src/main/java/se/issuetrackingsystem/user/repository/UserRepository.java +++ b/src/main/java/se/issuetrackingsystem/user/repository/UserRepository.java @@ -1,6 +1,7 @@ package se.issuetrackingsystem.user.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import se.issuetrackingsystem.user.domain.User; @@ -10,4 +11,7 @@ @Repository public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + + @Query("SELECT u FROM users u WHERE TYPE(u) IN (PL, Dev, Tester)") + Optional> findContributors(); } diff --git a/src/main/java/se/issuetrackingsystem/user/service/UserService.java b/src/main/java/se/issuetrackingsystem/user/service/UserService.java index 0be7f47..f7444ce 100644 --- a/src/main/java/se/issuetrackingsystem/user/service/UserService.java +++ b/src/main/java/se/issuetrackingsystem/user/service/UserService.java @@ -50,7 +50,7 @@ public UserResponse login(LoginRequest request) { .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new CustomException(ErrorCode.PASSWORD_FORBIDDEN); + throw new CustomException(ErrorCode.PASSWORD_BAD_REQUEST); } return new UserResponse(user); @@ -66,6 +66,18 @@ public List getUsers() { .toList(); } + @Transactional(readOnly = true) + public List getContributors() { + + List users = userRepository.findContributors() + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + return users + .stream() + .map(UserResponse::new) + .toList(); + } + private boolean isDuplicateUsername(String username) { Optional user = userRepository.findByUsername(username); return user.isPresent(); @@ -77,7 +89,7 @@ private User createUser(String role, String username, String password) { case "PL" -> new PL(username, password); case "Dev" -> new Dev(username, password); case "Tester" -> new Tester(username, password); - default -> throw new CustomException(ErrorCode.ROLE_FORBIDDEN); + default -> throw new CustomException(ErrorCode.ROLE_BAD_REQUEST); }; } } diff --git a/src/test/java/se/issuetrackingsystem/user/service/UserServiceTest.java b/src/test/java/se/issuetrackingsystem/user/service/UserServiceTest.java index 6c01f5a..4d925d2 100644 --- a/src/test/java/se/issuetrackingsystem/user/service/UserServiceTest.java +++ b/src/test/java/se/issuetrackingsystem/user/service/UserServiceTest.java @@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import se.issuetrackingsystem.common.exception.CustomException; import se.issuetrackingsystem.common.exception.ErrorCode; +import se.issuetrackingsystem.user.domain.Admin; import se.issuetrackingsystem.user.domain.Dev; import se.issuetrackingsystem.user.domain.Tester; import se.issuetrackingsystem.user.dto.LoginRequest; @@ -99,4 +100,17 @@ void getUsers() { assertTrue(response.stream().anyMatch(u -> "TestDev".equals(u.getUsername()))); assertTrue(response.stream().anyMatch(u -> "TestTester".equals(u.getUsername()))); } + + @Test + void getContributors() { + Admin user1 = new Admin("TestAdmin", passwordEncoder.encode("0000")); + Tester user2 = new Tester("TestTester", passwordEncoder.encode("0000")); + userRepository.save(user1); + userRepository.save(user2); + + List response = userService.getContributors(); + + assertEquals(1, response.size()); + assertTrue(response.stream().anyMatch(u -> "TestTester".equals(u.getUsername()))); + } } \ No newline at end of file