From db0da7cd6e44074dd4b65847f4839423e28e2b39 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 14:14:39 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(#197/recruit):=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EA=B0=80=EC=9E=85=20=EC=8B=9C=20=EC=9E=85?= =?UTF-8?q?=EA=B8=88=20=EC=97=AC=EB=B6=80=20=EB=AC=B4=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?false=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해당 필드가 HR 팀에서 입금 확인 여부 체크 용도로 사용될 거라 false로 저장되도록 변경합니다. --- .../gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java b/src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java index be1151f..3baa07a 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java @@ -40,7 +40,7 @@ public RecruitMember toEntity() { .birth(birth) .major(major) .doubleMajor(doubleMajor) - .isPayed(isPayed) + .isPayed(false) .build(); } } From 5f052cb7ea1a4fd7499f79a4d63342bcfa3ffe17 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 14:38:06 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor(#197/recruit):=20=ED=95=99?= =?UTF-8?q?=EB=B2=88=20=EC=A4=91=EB=B3=B5=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D=20=EB=B0=8F=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=ED=98=95=EC=8B=9D=20=EA=B5=AC=ED=98=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RecruitMemberController.java | 26 +++++++++++++------ .../dto/request/CheckStudentIdRequest.java | 14 ---------- .../dto/response/CheckStudentIdResponse.java | 5 ++++ .../recruit/service/RecruitMemberService.java | 6 +++-- .../global/error/GlobalExceptionHandler.java | 19 ++++++++++++++ 5 files changed, 46 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/inha/gdgoc/domain/recruit/dto/request/CheckStudentIdRequest.java create mode 100644 src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java diff --git a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java index ddbe949..f64fdc8 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java @@ -7,11 +7,13 @@ import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest; import inha.gdgoc.domain.recruit.dto.request.CheckPhoneNumberRequest; -import inha.gdgoc.domain.recruit.dto.request.CheckStudentIdRequest; +import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse; import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.service.RecruitMemberService; import inha.gdgoc.global.dto.response.ApiResponse; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -38,17 +40,19 @@ public ResponseEntity> recruitMemberAdd( return ResponseEntity.ok(ApiResponse.ok(MEMBER_SAVE_SUCCESS)); } - // TODO valid 핸들러 추가 - // TODO DTO로 응답 리팩토링, requestparam으로 변경하기 - @GetMapping("/check/studentId") - public ResponseEntity> duplicatedStudentIdDetails( - @Valid @ModelAttribute CheckStudentIdRequest studentIdRequest + @GetMapping("/studentId") + public ResponseEntity> duplicatedStudentIdDetails( + @RequestParam + @NotBlank(message = "학번은 필수 입력 값입니다.") + @Pattern(regexp = "^12[0-9]{6}$", message = "유효하지 않은 학번 값입니다.") + String studentId ) { - boolean exists = recruitMemberService.isRegisteredStudentId(studentIdRequest.getStudentId()); + CheckStudentIdResponse response = recruitMemberService.isRegisteredStudentId(studentId); - return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, exists)); + return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, response)); } + // TODO DTO로 응답 리팩토링 @GetMapping("/check/phoneNumber") public ResponseEntity> duplicatedPhoneNumberDetails( @@ -68,4 +72,10 @@ public ResponseEntity> getSpecifiedMe return ResponseEntity.ok(ApiResponse.ok(MEMBER_RETRIEVED_SUCCESS, response)); } + + // TODO 전체 응답 조회 및 검색 + + // TODO 입금 완료 + + // TODO 입금 미완료 } diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/request/CheckStudentIdRequest.java b/src/main/java/inha/gdgoc/domain/recruit/dto/request/CheckStudentIdRequest.java deleted file mode 100644 index 6f7fb80..0000000 --- a/src/main/java/inha/gdgoc/domain/recruit/dto/request/CheckStudentIdRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package inha.gdgoc.domain.recruit.dto.request; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class CheckStudentIdRequest { - @NotBlank(message = "학번은 필수 입력 값입니다.") - @Pattern(regexp = "^12[0-9]{6}$", message = "유효하지 않은 학번 값입니다.") - private String studentId; -} diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java new file mode 100644 index 0000000..008e7a0 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java @@ -0,0 +1,5 @@ +package inha.gdgoc.domain.recruit.dto.response; + +public record CheckStudentIdResponse(boolean exists) { + +} diff --git a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java index ee9910e..1d36caa 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java +++ b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest; +import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse; import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.entity.Answer; import inha.gdgoc.domain.recruit.entity.RecruitMember; @@ -45,8 +46,9 @@ public void addRecruitMember(ApplicationRequest applicationRequest) { answerRepository.saveAll(answers); } - public boolean isRegisteredStudentId(String studentId) { - return recruitMemberRepository.existsByStudentId(studentId); + public CheckStudentIdResponse isRegisteredStudentId(String studentId) { + boolean exists = recruitMemberRepository.existsByStudentId(studentId); + return new CheckStudentIdResponse(exists); } public boolean isRegisteredPhoneNumber(String phoneNumber) { diff --git a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java b/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java index b8399b7..460d155 100644 --- a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java +++ b/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import inha.gdgoc.global.dto.response.ApiResponse; import inha.gdgoc.global.dto.response.ErrorMeta; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolation; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -76,6 +77,24 @@ public ResponseEntity> handleTypeMismatch( .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message, meta)); } + @ExceptionHandler(jakarta.validation.ConstraintViolationException.class) + public ResponseEntity> handleConstraintViolation( + jakarta.validation.ConstraintViolationException ex, + HttpServletRequest request + ) { + String message = ex.getConstraintViolations() + .stream() + .findFirst() + .map(ConstraintViolation::getMessage) + .orElse("유효하지 않은 요청입니다."); + + log.error("ConstraintViolationException 발생: {}", message); + + ErrorMeta meta = createMeta(request); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message, meta)); + } + @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity> handleNotFound( NoHandlerFoundException ex, From 1bba2a188fa319c7e2b708231fe59b5a60d367e6 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 14:39:27 +0900 Subject: [PATCH 3/8] =?UTF-8?q?chore(#197/*):=20global=20error=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=AA=85=EC=9D=84=20exception=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java | 2 +- .../java/inha/gdgoc/domain/auth/exception/AuthException.java | 4 ++-- .../domain/recruit/exception/RecruitMemberErrorCode.java | 2 +- .../domain/recruit/exception/RecruitMemberException.java | 4 ++-- .../gdgoc/domain/resource/exception/ResourceErrorCode.java | 2 +- .../gdgoc/domain/resource/exception/ResourceException.java | 4 ++-- .../gdgoc/domain/study/exception/StudyAttendeeErrorCode.java | 2 +- .../gdgoc/domain/study/exception/StudyAttendeeException.java | 4 ++-- .../inha/gdgoc/domain/study/exception/StudyErrorCode.java | 2 +- .../inha/gdgoc/domain/study/exception/StudyException.java | 4 ++-- .../java/inha/gdgoc/domain/user/exception/UserErrorCode.java | 2 +- .../java/inha/gdgoc/domain/user/exception/UserException.java | 4 ++-- src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java | 2 +- .../java/inha/gdgoc/global/dto/response/ErrorResponse.java | 2 +- .../gdgoc/global/{error => exception}/BusinessException.java | 2 +- .../inha/gdgoc/global/{error => exception}/ErrorCode.java | 2 +- .../gdgoc/global/{error => exception}/GlobalErrorCode.java | 2 +- .../global/{error => exception}/GlobalExceptionHandler.java | 2 +- src/main/java/inha/gdgoc/global/security/SecurityConfig.java | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) rename src/main/java/inha/gdgoc/global/{error => exception}/BusinessException.java (87%) rename src/main/java/inha/gdgoc/global/{error => exception}/ErrorCode.java (78%) rename src/main/java/inha/gdgoc/global/{error => exception}/GlobalErrorCode.java (97%) rename src/main/java/inha/gdgoc/global/{error => exception}/GlobalExceptionHandler.java (99%) diff --git a/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java b/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java index 1e81c0f..f054597 100644 --- a/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.auth.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import org.springframework.http.HttpStatus; public enum AuthErrorCode implements ErrorCode { diff --git a/src/main/java/inha/gdgoc/domain/auth/exception/AuthException.java b/src/main/java/inha/gdgoc/domain/auth/exception/AuthException.java index 236985e..33debae 100644 --- a/src/main/java/inha/gdgoc/domain/auth/exception/AuthException.java +++ b/src/main/java/inha/gdgoc/domain/auth/exception/AuthException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.auth.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class AuthException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberErrorCode.java b/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberErrorCode.java index 20c1d6b..e78520a 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.recruit.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberException.java b/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberException.java index 591d656..8d07d43 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberException.java +++ b/src/main/java/inha/gdgoc/domain/recruit/exception/RecruitMemberException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.recruit.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class RecruitMemberException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/domain/resource/exception/ResourceErrorCode.java b/src/main/java/inha/gdgoc/domain/resource/exception/ResourceErrorCode.java index 7ab7469..00a93d6 100644 --- a/src/main/java/inha/gdgoc/domain/resource/exception/ResourceErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/resource/exception/ResourceErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.resource.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import org.springframework.http.HttpStatus; public enum ResourceErrorCode implements ErrorCode { diff --git a/src/main/java/inha/gdgoc/domain/resource/exception/ResourceException.java b/src/main/java/inha/gdgoc/domain/resource/exception/ResourceException.java index d87c713..fc39a49 100644 --- a/src/main/java/inha/gdgoc/domain/resource/exception/ResourceException.java +++ b/src/main/java/inha/gdgoc/domain/resource/exception/ResourceException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.resource.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class ResourceException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeErrorCode.java b/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeErrorCode.java index 4f30573..4c02f76 100644 --- a/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.study.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import org.springframework.http.HttpStatus; public enum StudyAttendeeErrorCode implements ErrorCode { diff --git a/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeException.java b/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeException.java index 9085522..bf8f506 100644 --- a/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeException.java +++ b/src/main/java/inha/gdgoc/domain/study/exception/StudyAttendeeException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.study.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class StudyAttendeeException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/domain/study/exception/StudyErrorCode.java b/src/main/java/inha/gdgoc/domain/study/exception/StudyErrorCode.java index 5c3f36d..cd05434 100644 --- a/src/main/java/inha/gdgoc/domain/study/exception/StudyErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/study/exception/StudyErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.study.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import org.springframework.http.HttpStatus; public enum StudyErrorCode implements ErrorCode { diff --git a/src/main/java/inha/gdgoc/domain/study/exception/StudyException.java b/src/main/java/inha/gdgoc/domain/study/exception/StudyException.java index 501b734..0d6e0de 100644 --- a/src/main/java/inha/gdgoc/domain/study/exception/StudyException.java +++ b/src/main/java/inha/gdgoc/domain/study/exception/StudyException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.study.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class StudyException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/domain/user/exception/UserErrorCode.java b/src/main/java/inha/gdgoc/domain/user/exception/UserErrorCode.java index a73ac6a..6a2db9b 100644 --- a/src/main/java/inha/gdgoc/domain/user/exception/UserErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/user/exception/UserErrorCode.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.user.exception; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/inha/gdgoc/domain/user/exception/UserException.java b/src/main/java/inha/gdgoc/domain/user/exception/UserException.java index 1da69d3..1feff66 100644 --- a/src/main/java/inha/gdgoc/domain/user/exception/UserException.java +++ b/src/main/java/inha/gdgoc/domain/user/exception/UserException.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.user.exception; -import inha.gdgoc.global.error.BusinessException; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.BusinessException; +import inha.gdgoc.global.exception.ErrorCode; public class UserException extends BusinessException { diff --git a/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java b/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java index 6c38760..ebab670 100644 --- a/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java +++ b/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java @@ -1,7 +1,7 @@ package inha.gdgoc.global.dto.response; import com.fasterxml.jackson.annotation.JsonInclude; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; @JsonInclude(JsonInclude.Include.NON_NULL) public record ApiResponse(int code, String message, T data, M meta) { diff --git a/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java b/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java index dd0d1e7..52469a5 100644 --- a/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java +++ b/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java @@ -1,6 +1,6 @@ package inha.gdgoc.global.dto.response; -import inha.gdgoc.global.error.ErrorCode; +import inha.gdgoc.global.exception.ErrorCode; import lombok.Getter; import org.springframework.http.HttpStatus; diff --git a/src/main/java/inha/gdgoc/global/error/BusinessException.java b/src/main/java/inha/gdgoc/global/exception/BusinessException.java similarity index 87% rename from src/main/java/inha/gdgoc/global/error/BusinessException.java rename to src/main/java/inha/gdgoc/global/exception/BusinessException.java index ec0cf9f..e45c264 100644 --- a/src/main/java/inha/gdgoc/global/error/BusinessException.java +++ b/src/main/java/inha/gdgoc/global/exception/BusinessException.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.error; +package inha.gdgoc.global.exception; import lombok.Getter; diff --git a/src/main/java/inha/gdgoc/global/error/ErrorCode.java b/src/main/java/inha/gdgoc/global/exception/ErrorCode.java similarity index 78% rename from src/main/java/inha/gdgoc/global/error/ErrorCode.java rename to src/main/java/inha/gdgoc/global/exception/ErrorCode.java index a3d879a..74de75a 100644 --- a/src/main/java/inha/gdgoc/global/error/ErrorCode.java +++ b/src/main/java/inha/gdgoc/global/exception/ErrorCode.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.error; +package inha.gdgoc.global.exception; import org.springframework.http.HttpStatus; diff --git a/src/main/java/inha/gdgoc/global/error/GlobalErrorCode.java b/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java similarity index 97% rename from src/main/java/inha/gdgoc/global/error/GlobalErrorCode.java rename to src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java index bb4951a..6dd5e48 100644 --- a/src/main/java/inha/gdgoc/global/error/GlobalErrorCode.java +++ b/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.error; +package inha.gdgoc.global.exception; import org.springframework.http.HttpStatus; diff --git a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java b/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java similarity index 99% rename from src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java rename to src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java index 460d155..028aa93 100644 --- a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java +++ b/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.error; +package inha.gdgoc.global.exception; import inha.gdgoc.global.dto.response.ApiResponse; import inha.gdgoc.global.dto.response.ErrorMeta; diff --git a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java index 3a7c6c9..cb4429e 100644 --- a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java +++ b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inha.gdgoc.global.dto.response.ErrorResponse; -import inha.gdgoc.global.error.GlobalErrorCode; +import inha.gdgoc.global.exception.GlobalErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; From b0ee8eb349c59c33f94f7bd1ed4e358c576b0301 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 14:54:24 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor(#197/recruit):=20=EC=A0=84?= =?UTF-8?q?=ED=99=94=EB=B2=88=ED=98=B8=20=EC=A4=91=EB=B3=B5=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9A=94=EC=B2=AD=20=ED=98=95=EC=8B=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 4 ++-- .../controller/RecruitMemberController.java | 20 +++++++++---------- .../response/CheckPhoneNumberResponse.java | 5 +++++ .../dto/response/CheckStudentIdResponse.java | 2 +- .../recruit/service/RecruitMemberService.java | 8 ++++++-- 5 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckPhoneNumberResponse.java diff --git a/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java b/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java index 4b3f18e..048cff2 100644 --- a/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java +++ b/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java @@ -91,7 +91,7 @@ public Map processOAuthLogin(String code, HttpServletResponse re Optional foundUser = userRepository.findByEmail(email); if (foundUser.isEmpty()) { return Map.of( - "exists", false, + "isExists", false, "email", email, "name", name ); @@ -117,7 +117,7 @@ public Map processOAuthLogin(String code, HttpServletResponse re response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); return Map.of( - "exists", true, + "isExists", true, "access_token", jwtAccessToken ); } diff --git a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java index f64fdc8..6de9714 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java @@ -6,18 +6,16 @@ import static inha.gdgoc.domain.recruit.controller.message.RecruitMemberMessage.STUDENT_ID_DUPLICATION_CHECK_SUCCESS; import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest; -import inha.gdgoc.domain.recruit.dto.request.CheckPhoneNumberRequest; +import inha.gdgoc.domain.recruit.dto.response.CheckPhoneNumberResponse; import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse; import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.service.RecruitMemberService; import inha.gdgoc.global.dto.response.ApiResponse; -import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -52,20 +50,22 @@ public ResponseEntity> duplicatedStude return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, response)); } - - // TODO DTO로 응답 리팩토링 @GetMapping("/check/phoneNumber") - public ResponseEntity> duplicatedPhoneNumberDetails( - @Valid @ModelAttribute CheckPhoneNumberRequest phoneNumberRequest + public ResponseEntity> duplicatedPhoneNumberDetails( + @RequestParam + @NotBlank(message = "전화번호는 필수 입력 값입니다.") + @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식은 010-XXXX-XXXX 이어야 합니다.") + String phoneNumber ) { - boolean exists = recruitMemberService.isRegisteredPhoneNumber(phoneNumberRequest.getPhoneNumber()); + CheckPhoneNumberResponse response = recruitMemberService + .isRegisteredPhoneNumber(phoneNumber); - return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, exists)); + return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, response)); } // TODO 코어 멤버 인증 리팩토링 (Authentication), requestparam으로 변경하기 @GetMapping("/recruit/member") - public ResponseEntity> getSpecifiedMember ( + public ResponseEntity> getSpecifiedMember( @RequestParam Long userId ) { SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(userId); diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckPhoneNumberResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckPhoneNumberResponse.java new file mode 100644 index 0000000..759f42f --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckPhoneNumberResponse.java @@ -0,0 +1,5 @@ +package inha.gdgoc.domain.recruit.dto.response; + +public record CheckPhoneNumberResponse(boolean isExists) { + +} diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java index 008e7a0..8537486 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/CheckStudentIdResponse.java @@ -1,5 +1,5 @@ package inha.gdgoc.domain.recruit.dto.response; -public record CheckStudentIdResponse(boolean exists) { +public record CheckStudentIdResponse(boolean isExists) { } diff --git a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java index 1d36caa..8a83ac8 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java +++ b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest; +import inha.gdgoc.domain.recruit.dto.response.CheckPhoneNumberResponse; import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse; import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.entity.Answer; @@ -48,11 +49,14 @@ public void addRecruitMember(ApplicationRequest applicationRequest) { public CheckStudentIdResponse isRegisteredStudentId(String studentId) { boolean exists = recruitMemberRepository.existsByStudentId(studentId); + return new CheckStudentIdResponse(exists); } - public boolean isRegisteredPhoneNumber(String phoneNumber) { - return recruitMemberRepository.existsByPhoneNumber(phoneNumber); + public CheckPhoneNumberResponse isRegisteredPhoneNumber(String phoneNumber) { + boolean exists = recruitMemberRepository.existsByPhoneNumber(phoneNumber); + + return new CheckPhoneNumberResponse(exists); } public SpecifiedMemberResponse findSpecifiedMember(Long id) { From 687b0e184b873b63cd851abe4873e4cccffb7db3 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 16:32:53 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat(#197/global):=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B2=BD=EC=9A=B0,=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/GlobalErrorCode.java | 4 ++ .../exception/GlobalExceptionHandler.java | 40 ++++++++++++++++--- .../gdgoc/global/security/SecurityConfig.java | 22 +++++++--- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java b/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java index 6dd5e48..5cf5675 100644 --- a/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java +++ b/src/main/java/inha/gdgoc/global/exception/GlobalErrorCode.java @@ -4,6 +4,9 @@ public enum GlobalErrorCode implements ErrorCode { + // 401 Unauthorized + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), + // 400 Bad Request BAD_REQUEST(HttpStatus.BAD_REQUEST, "요청 경로의 파라미터는 올바른 형식이 아닙니다."), INVALID_JSON_REQUEST(HttpStatus.BAD_REQUEST, "JSON 형식이 올바르지 않습니다."), @@ -11,6 +14,7 @@ public enum GlobalErrorCode implements ErrorCode { // 403 FORBIDDEN INVALID_JWT_REQUEST(HttpStatus.FORBIDDEN, "잘못된 JWT 토큰입니다."), + FORBIDDEN_USER(HttpStatus.NOT_FOUND, "권한이 부족합니다."), // 404 Not Found RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 리소스입니다."), diff --git a/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java b/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java index 028aa93..a139c84 100644 --- a/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/inha/gdgoc/global/exception/GlobalExceptionHandler.java @@ -1,5 +1,8 @@ package inha.gdgoc.global.exception; +import static inha.gdgoc.global.exception.GlobalErrorCode.*; +import static inha.gdgoc.global.exception.GlobalErrorCode.FORBIDDEN_USER; + import inha.gdgoc.global.dto.response.ApiResponse; import inha.gdgoc.global.dto.response.ErrorMeta; import jakarta.servlet.http.HttpServletRequest; @@ -7,6 +10,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.core.AuthenticationException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -39,7 +45,7 @@ public ResponseEntity> handleMissingRequestExceptio ) { log.error("요청 헤더 {}가 누락되었습니다.", ex.getHeaderName()); - String message = GlobalErrorCode.MISSING_HEADER.format(ex.getHeaderName()); + String message = MISSING_HEADER.format(ex.getHeaderName()); ErrorMeta meta = createMeta(request); return ResponseEntity.status(HttpStatus.BAD_REQUEST) @@ -69,11 +75,11 @@ public ResponseEntity> handleTypeMismatch( HttpServletRequest request ) { log.error("MethodArgumentTypeMismatchException 발생: {}", ex.getMessage()); - String message = GlobalErrorCode.BAD_REQUEST.format(ex.getName()); + String message = BAD_REQUEST.format(ex.getName()); ErrorMeta meta = createMeta(request); - return ResponseEntity.status(GlobalErrorCode.BAD_REQUEST.getStatus()) + return ResponseEntity.status(BAD_REQUEST.getStatus()) .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message, meta)); } @@ -95,6 +101,30 @@ public ResponseEntity> handleConstraintViolation( .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message, meta)); } + @ExceptionHandler({ AuthenticationCredentialsNotFoundException.class, AuthenticationException.class }) + public ResponseEntity> handleAuthentication( + AuthenticationException ex, + HttpServletRequest request + ) { + log.warn("AuthenticationException: {}", ex.getMessage()); + ErrorMeta meta = createMeta(request); + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.error(UNAUTHORIZED_USER, meta)); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity> handleAccessDenied( + AccessDeniedException ex, + HttpServletRequest request + ) { + log.warn("AccessDeniedException: {}", ex.getMessage()); + ErrorMeta meta = createMeta(request); + + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(ApiResponse.error(FORBIDDEN_USER, meta)); + } + @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity> handleNotFound( NoHandlerFoundException ex, @@ -105,7 +135,7 @@ public ResponseEntity> handleNotFound( ErrorMeta meta = createMeta(request); return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ApiResponse.error(GlobalErrorCode.RESOURCE_NOT_FOUND, meta)); + .body(ApiResponse.error(RESOURCE_NOT_FOUND, meta)); } @ExceptionHandler(Exception.class) @@ -118,7 +148,7 @@ public ResponseEntity> handleUnhandledException( ErrorMeta meta = createMeta(request); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ApiResponse.error(GlobalErrorCode.INTERNAL_SERVER_ERROR, meta)); + .body(ApiResponse.error(INTERNAL_SERVER_ERROR, meta)); } private ErrorMeta createMeta(HttpServletRequest request) { diff --git a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java index cb4429e..961fd2b 100644 --- a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java +++ b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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; @@ -20,6 +21,7 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +@EnableMethodSecurity @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -53,17 +55,27 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class) .exceptionHandling(ex -> ex .authenticationEntryPoint((request, response, authException) -> { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType("application/json; charset=UTF-8"); + + ErrorResponse errorResponse = new ErrorResponse( + GlobalErrorCode.UNAUTHORIZED_USER + ); + + ObjectMapper objectMapper = new ObjectMapper(); + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + response.getWriter().flush(); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { response.setStatus(HttpStatus.FORBIDDEN.value()); response.setContentType("application/json; charset=UTF-8"); - // ErrorResponse 생성 ErrorResponse errorResponse = new ErrorResponse( - GlobalErrorCode.INVALID_JWT_REQUEST); + GlobalErrorCode.FORBIDDEN_USER + ); - // JSON 직렬화 후 응답에 쓰기 ObjectMapper objectMapper = new ObjectMapper(); - response.getWriter() - .write(objectMapper.writeValueAsString(errorResponse)); + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); response.getWriter().flush(); }) ); From 7b3859c57d3da125294ed9de2163fa739ab1a58e Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 17:01:50 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor(#197/recruit):=20=ED=8A=B9?= =?UTF-8?q?=EC=A0=95=20=EB=A9=A4=EB=B2=84=20=EA=B0=80=EC=9E=85=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=8B=9C=20=EA=B6=8C=ED=95=9C=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RecruitMemberController.java | 10 ++-- .../recruit/dto/response/AnswerResponse.java | 49 +++++++++++++++++++ .../recruit/dto/response/AnswersResponse.java | 17 +++++++ .../dto/response/SpecifiedMemberResponse.java | 15 ++++-- .../recruit/repository/AnswerRepository.java | 8 +++ .../recruit/service/RecruitMemberService.java | 4 +- .../global/config/jwt/TokenProvider.java | 30 +++++++----- 7 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswerResponse.java create mode 100644 src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswersResponse.java diff --git a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java index 6de9714..78e5367 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java @@ -15,7 +15,9 @@ import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -63,12 +65,12 @@ public ResponseEntity> duplicatedPho return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, response)); } - // TODO 코어 멤버 인증 리팩토링 (Authentication), requestparam으로 변경하기 - @GetMapping("/recruit/member") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/recruit/members/{memberId}") public ResponseEntity> getSpecifiedMember( - @RequestParam Long userId + @PathVariable Long memberId ) { - SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(userId); + SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(memberId); return ResponseEntity.ok(ApiResponse.ok(MEMBER_RETRIEVED_SUCCESS, response)); } diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswerResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswerResponse.java new file mode 100644 index 0000000..3ac3d17 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswerResponse.java @@ -0,0 +1,49 @@ +package inha.gdgoc.domain.recruit.dto.response; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import inha.gdgoc.domain.recruit.entity.Answer; +import inha.gdgoc.domain.recruit.enums.InputType; +import java.util.List; +import java.util.Map; + +public record AnswerResponse( + Long id, + InputType inputType, + Object responseValue +) { + public static AnswerResponse from(Answer answer, ObjectMapper om) { + return new AnswerResponse( + answer.getId(), + answer.getInputType(), + toFriendlyValue(answer.getResponseValue(), om) + ); + } + + private static Object toFriendlyValue(String json, ObjectMapper om) { + if (json == null || json.isBlank()) return null; + try { + JsonNode node = om.readTree(json); + + if (node.isTextual()) { + return node.asText(); + } + if (node.isArray()) { + return om.convertValue(node, new TypeReference>() {}); + } + if (node.isObject()) { + return om.convertValue(node, new TypeReference>() {}); + } + if (node.isNumber()) { + return node.numberValue(); + } + if (node.isBoolean()) { + return node.booleanValue(); + } + return null; + } catch (Exception e) { + return json; + } + } +} \ No newline at end of file diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswersResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswersResponse.java new file mode 100644 index 0000000..896c8cc --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/AnswersResponse.java @@ -0,0 +1,17 @@ +package inha.gdgoc.domain.recruit.dto.response; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inha.gdgoc.domain.recruit.entity.Answer; +import java.util.List; + +public record AnswersResponse( + List answers +) { + public static AnswersResponse from(List entities, ObjectMapper objectMapper) { + return new AnswersResponse( + entities.stream() + .map(a -> AnswerResponse.from(a, objectMapper)) + .toList() + ); + } +} \ No newline at end of file diff --git a/src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java b/src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java index 669793e..838bd30 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java +++ b/src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java @@ -1,20 +1,29 @@ package inha.gdgoc.domain.recruit.dto.response; +import com.fasterxml.jackson.databind.ObjectMapper; +import inha.gdgoc.domain.recruit.entity.Answer; import inha.gdgoc.domain.recruit.entity.RecruitMember; +import java.util.List; public record SpecifiedMemberResponse( String name, String major, String studentId, - boolean isPayed + boolean isPayed, + AnswersResponse answers ) { - public static SpecifiedMemberResponse from(RecruitMember member) { + public static SpecifiedMemberResponse from( + RecruitMember member, + List answers, + ObjectMapper objectMapper + ) { return new SpecifiedMemberResponse( member.getName(), member.getMajor(), member.getStudentId(), - Boolean.TRUE.equals(member.getIsPayed()) + Boolean.TRUE.equals(member.getIsPayed()), + AnswersResponse.from(answers, objectMapper) ); } } diff --git a/src/main/java/inha/gdgoc/domain/recruit/repository/AnswerRepository.java b/src/main/java/inha/gdgoc/domain/recruit/repository/AnswerRepository.java index e53831d..3ae036d 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/repository/AnswerRepository.java +++ b/src/main/java/inha/gdgoc/domain/recruit/repository/AnswerRepository.java @@ -1,7 +1,15 @@ package inha.gdgoc.domain.recruit.repository; import inha.gdgoc.domain.recruit.entity.Answer; +import inha.gdgoc.domain.recruit.entity.RecruitMember; +import inha.gdgoc.domain.recruit.enums.SurveyType; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface AnswerRepository extends JpaRepository { + + List findByRecruitMemberAndSurveyType( + RecruitMember recruitMember, + SurveyType surveyType + ); } diff --git a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java index 8a83ac8..f6f2255 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java +++ b/src/main/java/inha/gdgoc/domain/recruit/service/RecruitMemberService.java @@ -62,7 +62,9 @@ public CheckPhoneNumberResponse isRegisteredPhoneNumber(String phoneNumber) { public SpecifiedMemberResponse findSpecifiedMember(Long id) { RecruitMember member = recruitMemberRepository.findById(id) .orElseThrow(() -> new RecruitMemberException(RECRUIT_MEMBER_NOT_FOUND)); + List answers = answerRepository + .findByRecruitMemberAndSurveyType(member, SurveyType.RECRUIT); - return SpecifiedMemberResponse.from(member); + return SpecifiedMemberResponse.from(member, answers, objectMapper); } } diff --git a/src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java b/src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java index e41fd91..ae7cb26 100644 --- a/src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java +++ b/src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java @@ -1,8 +1,11 @@ package inha.gdgoc.global.config.jwt; +import static inha.gdgoc.global.exception.GlobalErrorCode.INVALID_JWT_REQUEST; + import inha.gdgoc.domain.auth.enums.LoginType; import inha.gdgoc.domain.user.entity.User; import inha.gdgoc.domain.user.enums.UserRole; +import inha.gdgoc.global.exception.BusinessException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; @@ -11,6 +14,12 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; +import java.time.Duration; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Set; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -19,13 +28,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; -import java.time.Duration; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Set; - @RequiredArgsConstructor @Service public class TokenProvider { @@ -67,19 +69,23 @@ public Claims validToken(String token) throws ExpiredJwtException, UnsupportedJw public Authentication getAuthentication(String token) { Claims claims = getClaims(token); - UserRole userRole = UserRole.valueOf(claims.get("role", String.class)); - Long userId = claims.get("id", Integer.class).longValue(); - String username = claims.getSubject(); + Number idNum = claims.get("id", Number.class); + if (idNum == null) throw new BusinessException(INVALID_JWT_REQUEST); + Long userId = idNum.longValue(); + + String username = claims.getSubject(); + UserRole userRole = UserRole.valueOf(claims.get("role", String.class)); + String roleName = "ROLE_" + userRole.name(); Set authorities = Collections.singleton( - new SimpleGrantedAuthority(userRole.getRole()) + new SimpleGrantedAuthority(roleName) ); CustomUserDetails userDetails = new CustomUserDetails(userId, username, "", authorities); return new UsernamePasswordAuthenticationToken( userDetails, - token, + null, authorities ); } From ac037413b8787dafc3ea5a67079c4fc6e6bb1c7e Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 17:16:57 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor(#197/recruit):=20swagger=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RecruitMemberController.java | 3 ++ .../global/config/openapi/OpenApiConfig.java | 53 ++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java index 78e5367..9637e21 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java @@ -11,6 +11,8 @@ import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.service.RecruitMemberService; import inha.gdgoc.global.dto.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; @@ -65,6 +67,7 @@ public ResponseEntity> duplicatedPho return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, response)); } + @Operation(summary = "특정 멤버 가입 신청서 조회", security = { @SecurityRequirement(name = "BearerAuth") }) @PreAuthorize("hasRole('ADMIN')") @GetMapping("/recruit/members/{memberId}") public ResponseEntity> getSpecifiedMember( diff --git a/src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java b/src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java index 14ac53b..85d88d1 100644 --- a/src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java +++ b/src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java @@ -1,6 +1,10 @@ package inha.gdgoc.global.config.openapi; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import java.util.List; import org.springdoc.core.customizers.OpenApiCustomizer; @@ -11,12 +15,28 @@ @Configuration public class OpenApiConfig { + @Bean + public OpenAPI openAPI() { + String schemeName = "BearerAuth"; + return new OpenAPI() + .info(new Info().title("GDGoC API").version("v1")) + .components(new Components().addSecuritySchemes( + schemeName, + new SecurityScheme() + .name(schemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + @Bean public GroupedOpenApi all() { return GroupedOpenApi.builder() - .group("all") - .pathsToMatch("/**") - .build(); + .group("all") + .pathsToMatch("/**") + .build(); } @Bean @@ -24,29 +44,34 @@ public GroupedOpenApi v1Api() { return groupedApi("v1", "/api/v1"); } - @Bean - public GroupedOpenApi v2Api() { - return groupedApi("v2", "/api/v2"); - } + @Bean + public GroupedOpenApi v2Api() { + return groupedApi("v2", "/api/v2"); + } private GroupedOpenApi groupedApi(String group, String fullPrefix) { return GroupedOpenApi.builder() - .group(group) - .pathsToMatch(fullPrefix + "/**") - .addOpenApiCustomizer(stripPrefixAndSetServer(fullPrefix)) - .build(); + .group(group) + .pathsToMatch(fullPrefix + "/**") + .addOpenApiCustomizer(stripPrefixAndSetServer(fullPrefix)) + .build(); } private OpenApiCustomizer stripPrefixAndSetServer(String fullPrefix) { return openApi -> { Paths src = openApi.getPaths(); - if (src == null || src.isEmpty()) return; + if (src == null || src.isEmpty()) { + return; + } Paths dst = new Paths(); src.forEach((path, item) -> { String p = path; - if (p.equals(fullPrefix)) p = "/"; - else if (p.startsWith(fullPrefix + "/")) p = p.substring(fullPrefix.length()); + if (p.equals(fullPrefix)) { + p = "/"; + } else if (p.startsWith(fullPrefix + "/")) { + p = p.substring(fullPrefix.length()); + } dst.addPathItem(p, item); }); From f31472106ce64b1bbcb85476204fae0233967992 Mon Sep 17 00:00:00 2001 From: kaswhy Date: Tue, 26 Aug 2025 18:01:17 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat(#197/security):=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9D=B8=EC=A6=9D=20=EC=A0=9C=EC=99=B8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/inha/gdgoc/global/security/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java index 961fd2b..e38bac4 100644 --- a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java +++ b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java @@ -44,7 +44,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/api/v1/auth/**", "/api/v1/game/**", "/api/v1/apply/**", - "/api/v1/check/**") + "/api/v1/check/**", + "/api/v1/password-reset/**") .permitAll() .anyRequest() .authenticated()