diff --git a/src/main/java/inha/gdgoc/GdgocApplication.java b/src/main/java/inha/gdgoc/GdgocApplication.java index 8ddf942..cda46ef 100644 --- a/src/main/java/inha/gdgoc/GdgocApplication.java +++ b/src/main/java/inha/gdgoc/GdgocApplication.java @@ -1,23 +1,13 @@ package inha.gdgoc; -import jakarta.annotation.PostConstruct; -import java.util.TimeZone; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -@EnableJpaAuditing @SpringBootApplication -// @EnableConfigurationProperties(JwtProperties.class) public class GdgocApplication { public static void main(String[] args) { SpringApplication.run(GdgocApplication.class, args); } - @PostConstruct - public void init() { - TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); - } - } diff --git a/src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java b/src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java index 8b11870..de8ec19 100644 --- a/src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java +++ b/src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java @@ -1,5 +1,15 @@ package inha.gdgoc.domain.auth.controller; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.ACCESS_TOKEN_REFRESH_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.CODE_CREATION_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.LOGIN_WITH_PASSWORD_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.LOGOUT_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.OAUTH_LOGIN_SIGNUP_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.PASSWORD_CHANGE_SUCCESS; +import static inha.gdgoc.domain.auth.controller.message.AuthMessage.PASSWORD_RESET_VERIFICATION_SUCCESS; +import static inha.gdgoc.domain.auth.exception.AuthErrorCode.UNAUTHORIZED_USER; +import static inha.gdgoc.domain.auth.exception.AuthErrorCode.USER_NOT_FOUND; + import inha.gdgoc.domain.auth.dto.request.CodeVerificationRequest; import inha.gdgoc.domain.auth.dto.request.PasswordResetRequest; import inha.gdgoc.domain.auth.dto.request.SendingCodeRequest; @@ -14,11 +24,8 @@ import inha.gdgoc.domain.auth.service.RefreshTokenService; import inha.gdgoc.domain.user.entity.User; import inha.gdgoc.domain.user.repository.UserRepository; -import inha.gdgoc.global.common.ApiResponse; -import inha.gdgoc.global.common.ErrorResponse; - +import inha.gdgoc.global.dto.response.ApiResponse; import inha.gdgoc.global.error.BusinessException; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -26,7 +33,6 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -39,7 +45,7 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j -@RequestMapping("/auth") +@RequestMapping("/api/v1/auth") @RestController @RequiredArgsConstructor public class AuthController { @@ -51,18 +57,18 @@ public class AuthController { private final AuthCodeService authCodeService; @GetMapping("/oauth2/google/callback") - public ResponseEntity>> handleGoogleCallback( + public ResponseEntity, Void>> handleGoogleCallback( @RequestParam String code, HttpServletResponse response ) { Map data = authService.processOAuthLogin(code, response); - return ResponseEntity.ok(ApiResponse.of(data, null)); + return ResponseEntity.ok(ApiResponse.ok(OAUTH_LOGIN_SIGNUP_SUCCESS, data)); } @PostMapping("/refresh") public ResponseEntity refreshAccessToken( - @CookieValue(value = "refresh_token", required = false) String refreshToken) { - + @CookieValue(value = "refresh_token", required = false) String refreshToken + ) { log.info("리프레시 토큰 요청 받음. 토큰 존재 여부: {}", refreshToken != null); if (refreshToken == null) { @@ -74,7 +80,9 @@ public ResponseEntity refreshAccessToken( try { String newAccessToken = refreshTokenService.refreshAccessToken(refreshToken); AccessTokenResponse accessTokenResponse = new AccessTokenResponse(newAccessToken); - return ResponseEntity.ok(ApiResponse.of(accessTokenResponse, null)); + + return ResponseEntity.ok( + ApiResponse.ok(ACCESS_TOKEN_REFRESH_SUCCESS, accessTokenResponse, null)); } catch (Exception e) { log.error("리프레시 토큰 처리 중 오류: {}", e.getMessage(), e); throw new BusinessException(AuthErrorCode.INVALID_REFRESH_TOKEN); @@ -82,28 +90,27 @@ public ResponseEntity refreshAccessToken( } @PostMapping("/login") - public ResponseEntity> login(@RequestBody UserLoginRequest userLoginRequest, - HttpServletResponse response) - throws NoSuchAlgorithmException, InvalidKeyException { + public ResponseEntity> login( + @RequestBody UserLoginRequest userLoginRequest, + HttpServletResponse response + ) throws NoSuchAlgorithmException, InvalidKeyException { LoginResponse loginResponse = authService.loginWithPassword(userLoginRequest, response); - return ResponseEntity.ok(ApiResponse.of(loginResponse, null)); + + return ResponseEntity.ok(ApiResponse.ok(LOGIN_WITH_PASSWORD_SUCCESS, loginResponse)); } @PostMapping("/logout") - public ResponseEntity> logout( - HttpServletRequest request, - HttpServletResponse response - ) { + public ResponseEntity> logout() { + // TODO 서비스로 넘기기 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponse.of(null, "인증되지 않은 사용자")); + throw new BusinessException(UNAUTHORIZED_USER); } String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다")); + .orElseThrow(() -> new BusinessException(USER_NOT_FOUND)); Long userId = user.getId(); log.info("로그아웃 시도: 사용자 ID: {}, 이메일: {}", userId, email); @@ -117,47 +124,52 @@ public ResponseEntity> logout( log.info("사용자 ID: {}의 리프레시 토큰이 성공적으로 삭제되었습니다.", userId); } } else { - log.warn("사용자 ID: {}의 쿠키에서 리프레시 토큰을 찾을 수 없습니다", userId); + log.warn("사용자를 찾을 수 없습니다."); } - return ResponseEntity.ok(ApiResponse.of(null, null)); + return ResponseEntity.ok(ApiResponse.ok(LOGOUT_SUCCESS)); } @PostMapping("/password-reset/request") - public ResponseEntity> responseResponseEntity( + public ResponseEntity> responseResponseEntity( @RequestBody SendingCodeRequest sendingCodeRequest ) { - if (userRepository.existsByNameAndEmail(sendingCodeRequest.name(), sendingCodeRequest.email())) { + // TODO 서비스로 넘기기 + if (userRepository.existsByNameAndEmail(sendingCodeRequest.name(), + sendingCodeRequest.email())) { String code = mailService.sendAuthCode(sendingCodeRequest.email()); authCodeService.saveAuthCode(sendingCodeRequest.email(), code); - return ResponseEntity.ok(ApiResponse.of(null)); + + return ResponseEntity.ok(ApiResponse.ok(CODE_CREATION_SUCCESS)); } - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.of(null, null)); + throw new BusinessException(USER_NOT_FOUND); } @PostMapping("/password-reset/verify") - public ResponseEntity> verifyCode( - @RequestBody CodeVerificationRequest codeVerificationRequest + public ResponseEntity> verifyCode( + @RequestBody CodeVerificationRequest request ) { - return ResponseEntity.ok(ApiResponse.of(new CodeVerificationResponse(authCodeService.verify( - codeVerificationRequest.email(), codeVerificationRequest.code())))); + // TODO 서비스 단 DTO 추가 + boolean verified = authCodeService.verify(request.email(), request.code()); + CodeVerificationResponse response = new CodeVerificationResponse(verified); + + return ResponseEntity.ok(ApiResponse.ok(PASSWORD_RESET_VERIFICATION_SUCCESS, response)); } @PostMapping("/password-reset/confirm") - public ResponseEntity> resetPassword(@RequestBody PasswordResetRequest passwordResetRequest) - throws NoSuchAlgorithmException, InvalidKeyException { + public ResponseEntity> resetPassword( + @RequestBody PasswordResetRequest passwordResetRequest + ) throws NoSuchAlgorithmException, InvalidKeyException { + // TODO 서비스 단으로 Optional user = userRepository.findByEmail(passwordResetRequest.email()); if (user.isEmpty()) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.of(null, null)); + throw new BusinessException(USER_NOT_FOUND); } User foundUser = user.get(); foundUser.updatePassword(passwordResetRequest.password()); userRepository.save(foundUser); - return ResponseEntity.ok(ApiResponse.of(null, null)); + return ResponseEntity.ok(ApiResponse.ok(PASSWORD_CHANGE_SUCCESS)); } } diff --git a/src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java b/src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java new file mode 100644 index 0000000..6d90026 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java @@ -0,0 +1,11 @@ +package inha.gdgoc.domain.auth.controller.message; + +public class AuthMessage { + public static final String OAUTH_LOGIN_SIGNUP_SUCCESS = "로그인/회원가입 요청이 성공적으로 실행됐습니다."; + public static final String ACCESS_TOKEN_REFRESH_SUCCESS = "액세스 토큰이 성공적으로 재발급되었습니다."; + public static final String LOGIN_WITH_PASSWORD_SUCCESS = "성공적으로 비밀번호를 사용하여 로그인했습니다."; + public static final String LOGOUT_SUCCESS = "성공적으로 로그아웃했습니다."; + public static final String CODE_CREATION_SUCCESS = "성공적으로 인증 코드를 발급했습니다."; + public static final String PASSWORD_RESET_VERIFICATION_SUCCESS = "성공적으로 비밀번호 변경을 위한 인증 코드 검증이 완료되었습니다."; + public static final String PASSWORD_CHANGE_SUCCESS = "성공적으로 비밀번호를 변경했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java b/src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java index b0b8193..2be62f5 100644 --- a/src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java +++ b/src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.auth.dto.request; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/inha/gdgoc/domain/auth/dto/response/AccessTokenResponse.java b/src/main/java/inha/gdgoc/domain/auth/dto/response/AccessTokenResponse.java index e129cf7..f8cc949 100644 --- a/src/main/java/inha/gdgoc/domain/auth/dto/response/AccessTokenResponse.java +++ b/src/main/java/inha/gdgoc/domain/auth/dto/response/AccessTokenResponse.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.auth.dto.response; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import lombok.Getter; @Getter diff --git a/src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java b/src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java index 5e40480..7d72657 100644 --- a/src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java +++ b/src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java @@ -1,7 +1,7 @@ package inha.gdgoc.domain.auth.entity; import inha.gdgoc.domain.user.entity.User; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; 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 744148c..1e81c0f 100644 --- a/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java +++ b/src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java @@ -5,9 +5,15 @@ public enum AuthErrorCode implements ErrorCode { + // 401 Unauthorized + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "인증되지 않은 사용자입니다."), + // 403 Forbidden INVALID_COOKIE(HttpStatus.FORBIDDEN, "Refresh Token 이 비어있습니다."), - INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "잘못된 Refresh Token 값입니다."); + INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "잘못된 Refresh Token 값입니다."), + + // 404 Not Found + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다"); private final HttpStatus status; private final String message; diff --git a/src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java b/src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java index 153c17b..ff102d4 100644 --- a/src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java +++ b/src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java @@ -1,34 +1,42 @@ package inha.gdgoc.domain.game.controller; +import static inha.gdgoc.domain.game.controller.message.GameQuestionMessage.GAME_QUESTION_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.game.controller.message.GameQuestionMessage.GAME_QUESTION_SAVE_SUCCESS; + import inha.gdgoc.domain.game.dto.request.GameQuestionRequest; import inha.gdgoc.domain.game.dto.response.GameQuestionResponse; -import inha.gdgoc.domain.game.entity.GameQuestion; import inha.gdgoc.domain.game.service.GameQuestionService; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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; +@RequestMapping("/api/v1/game/questions") @RequiredArgsConstructor @RestController public class GameQuestionController { private final GameQuestionService gameQuestionService; - @PostMapping("/game/question") - public ResponseEntity> saveQuestion( - @RequestBody GameQuestionRequest gameQuestionRequest) { + // 얘 api 엔드포인트 바뀜! + @PostMapping + public ResponseEntity> saveQuestion( + @RequestBody GameQuestionRequest gameQuestionRequest + ) { gameQuestionService.saveQuestion(gameQuestionRequest); - return ResponseEntity.ok(ApiResponse.of(null)); + return ResponseEntity.ok(ApiResponse.ok(GAME_QUESTION_SAVE_SUCCESS)); } - @GetMapping("/game/questions") - public ResponseEntity>> getRandomGameQuestions() { - return ResponseEntity.ok(ApiResponse.of(gameQuestionService.getRandomQuestionsByLanguage())); + @GetMapping + public ResponseEntity, Void>> getRandomGameQuestions() { + List response = gameQuestionService.getRandomQuestionsByLanguage(); + + return ResponseEntity.ok(ApiResponse.ok(GAME_QUESTION_RETRIEVED_SUCCESS, response)); } } diff --git a/src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java b/src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java index 0a09b79..53fd547 100644 --- a/src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java +++ b/src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java @@ -1,30 +1,41 @@ package inha.gdgoc.domain.game.controller; +import static inha.gdgoc.domain.game.controller.message.GameUserMessage.GAME_RANK_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.game.controller.message.GameUserMessage.GAME_RANK_SAVE_SUCCESS; + import inha.gdgoc.domain.game.dto.request.GameUserRequest; import inha.gdgoc.domain.game.dto.response.GameUserResponse; import inha.gdgoc.domain.game.service.GameUserService; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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; +@RequestMapping("/api/v1/game") @RequiredArgsConstructor @RestController public class GameUserController { private final GameUserService gameUserService; - @PostMapping("/game/result") - public ResponseEntity>> saveGameResult(@RequestBody GameUserRequest request) { - return ResponseEntity.ok(ApiResponse.of(gameUserService.saveGameResultAndGetRanking(request))); + @PostMapping("/result") + public ResponseEntity, Void>> saveGameResult( + @RequestBody GameUserRequest request + ) { + List response = gameUserService.saveGameResultAndGetRanking(request); + + return ResponseEntity.ok(ApiResponse.ok(GAME_RANK_SAVE_SUCCESS, response)); } - @GetMapping("/game/results") - public ResponseEntity>> getUserRankings() { - return ResponseEntity.ok(ApiResponse.of(gameUserService.findUserRankings())); + @GetMapping("/results") + public ResponseEntity, Void>> getUserRankings() { + List response = gameUserService.findUserRankings(); + + return ResponseEntity.ok(ApiResponse.ok(GAME_RANK_RETRIEVED_SUCCESS, response)); } } diff --git a/src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java b/src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java new file mode 100644 index 0000000..e48df6d --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java @@ -0,0 +1,6 @@ +package inha.gdgoc.domain.game.controller.message; + +public class GameQuestionMessage { + public static final String GAME_QUESTION_SAVE_SUCCESS = "성공적으로 게임 질문을 저장했습니다."; + public static final String GAME_QUESTION_RETRIEVED_SUCCESS = "성공적으로 게임 질문 리스트를 조회했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java b/src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java new file mode 100644 index 0000000..ec30ab0 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java @@ -0,0 +1,6 @@ +package inha.gdgoc.domain.game.controller.message; + +public class GameUserMessage { + public static final String GAME_RANK_SAVE_SUCCESS = "성공적으로 유저 랭킹 정보를 저장했습니다."; + public static final String GAME_RANK_RETRIEVED_SUCCESS = "성공적으로 랭킹 정보를 반환했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/game/entity/GameUser.java b/src/main/java/inha/gdgoc/domain/game/entity/GameUser.java index 09ea53f..cd754d6 100644 --- a/src/main/java/inha/gdgoc/domain/game/entity/GameUser.java +++ b/src/main/java/inha/gdgoc/domain/game/entity/GameUser.java @@ -1,6 +1,6 @@ package inha.gdgoc.domain.game.entity; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; 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 404e85e..07ed3db 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java @@ -1,23 +1,28 @@ package inha.gdgoc.domain.recruit.controller; +import static inha.gdgoc.domain.recruit.controller.message.RecruitMemberMessage.MEMBER_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.recruit.controller.message.RecruitMemberMessage.MEMBER_SAVE_SUCCESS; +import static inha.gdgoc.domain.recruit.controller.message.RecruitMemberMessage.PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS; +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.request.CheckStudentIdRequest; import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse; import inha.gdgoc.domain.recruit.service.RecruitMemberService; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.validation.BindingResult; 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; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@RequestMapping("/api/v1") @RequiredArgsConstructor @RestController public class RecruitMemberController { @@ -25,40 +30,41 @@ public class RecruitMemberController { private final RecruitMemberService recruitMemberService; @PostMapping("/apply") - public ResponseEntity> recruitMemberAdd( - @RequestBody ApplicationRequest applicationRequest) { + public ResponseEntity> recruitMemberAdd( + @RequestBody ApplicationRequest applicationRequest + ) { recruitMemberService.addRecruitMember(applicationRequest); - return ResponseEntity.ok(ApiResponse.of(null)); + + return ResponseEntity.ok(ApiResponse.ok(MEMBER_SAVE_SUCCESS)); } + // TODO valid 핸들러 추가 + // TODO DTO로 응답 리팩토링, requestparam으로 변경하기 @GetMapping("/check/studentId") - public ResponseEntity> duplicatedStudentIdDetails( - @Valid @ModelAttribute CheckStudentIdRequest studentIdRequest, BindingResult bindingResult) { - if (bindingResult.hasErrors()) { - return ResponseEntity.ok(ApiResponse.of(true)); - } - + public ResponseEntity> duplicatedStudentIdDetails( + @Valid @ModelAttribute CheckStudentIdRequest studentIdRequest + ) { boolean exists = recruitMemberService.isRegisteredStudentId(studentIdRequest.getStudentId()); - return ResponseEntity.ok(ApiResponse.of(exists)); + + return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, exists)); } + // TODO DTO로 응답 리팩토링 @GetMapping("/check/phoneNumber") - public ResponseEntity> duplicatedPhoneNumberDetails( - @Valid @ModelAttribute CheckPhoneNumberRequest phoneNumberRequest, BindingResult bindingResult) { - if (bindingResult.hasErrors()) { - return ResponseEntity.ok(ApiResponse.of(true)); - } - + public ResponseEntity> duplicatedPhoneNumberDetails( + @Valid @ModelAttribute CheckPhoneNumberRequest phoneNumberRequest + ) { boolean exists = recruitMemberService.isRegisteredPhoneNumber(phoneNumberRequest.getPhoneNumber()); - return ResponseEntity.ok(ApiResponse.of(exists)); + + return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, exists)); } + // TODO 코어 멤버 인증 리팩토링 (Authentication), requestparam으로 변경하기 @GetMapping("/recruit/member") - public ResponseEntity> getSpecifiedMember ( - Authentication authentication, + public ResponseEntity> getSpecifiedMember ( @RequestParam Long userId ) { - SpecifiedMemberResponse data = recruitMemberService.findSpecifiedMember(userId); - return ResponseEntity.ok(ApiResponse.of(data)); + SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(userId); + return ResponseEntity.ok(ApiResponse.ok(MEMBER_RETRIEVED_SUCCESS, response)); } } diff --git a/src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java b/src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java new file mode 100644 index 0000000..5142688 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java @@ -0,0 +1,8 @@ +package inha.gdgoc.domain.recruit.controller.message; + +public class RecruitMemberMessage { + public static final String MEMBER_SAVE_SUCCESS = "성공적으로 해당 학기 멤버 가입을 완료했습니다."; + public static final String STUDENT_ID_DUPLICATION_CHECK_SUCCESS = "성공적으로 학번 중복 조회를 완료했습니다."; + public static final String PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS = "성공적으로 전화번호 중복 조회를 완료했습니다."; + public static final String MEMBER_RETRIEVED_SUCCESS = "성공적으로 특정 멤버의 지원서를 조회했습니다."; +} 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 f14dc2e..2093bda 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,6 +1,6 @@ package inha.gdgoc.domain.recruit.dto.response; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; public class SpecifiedMemberResponse extends BaseEntity { private String name; diff --git a/src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java b/src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java index 4d12aa1..924089c 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java +++ b/src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java @@ -2,7 +2,7 @@ import inha.gdgoc.domain.recruit.enums.InputType; import inha.gdgoc.domain.recruit.enums.SurveyType; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java b/src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java index 8391326..d51801a 100644 --- a/src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java +++ b/src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inha.gdgoc.domain.recruit.enums.EnrolledClassification; import inha.gdgoc.domain.recruit.enums.Gender; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java b/src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java index e686fd3..99af090 100644 --- a/src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java +++ b/src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java @@ -1,11 +1,13 @@ package inha.gdgoc.domain.resource.controller; +import static inha.gdgoc.domain.resource.controller.message.ResourceMessage.IMAGE_SAVE_SUCCESS; + import inha.gdgoc.domain.auth.service.AuthService; import inha.gdgoc.domain.resource.dto.response.S3ResultResponse; import inha.gdgoc.domain.resource.enums.S3KeyType; import inha.gdgoc.domain.resource.exception.ResourceException; import inha.gdgoc.domain.resource.service.S3Service; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import inha.gdgoc.global.error.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -19,21 +21,22 @@ import java.io.IOException; @RestController -@RequestMapping("/resource") +@RequestMapping("/api/v1/resource") @RequiredArgsConstructor public class ResourceController { + private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; + private final S3Service s3Service; private final AuthService authService; + // TODO 책임 분리 @PostMapping("/image") - public ResponseEntity> uploadImage( + public ResponseEntity> uploadImage( Authentication authentication, @RequestParam("file") MultipartFile file, @RequestParam("s3key") S3KeyType s3key ) { - final long MAX_FILE_SIZE = 10 * 1024 * 1024; - if (file.getSize() > MAX_FILE_SIZE) { throw new BusinessException(ResourceException.INVALID_BIG_FILE); } @@ -41,7 +44,9 @@ public ResponseEntity> uploadImage( Long userId = authService.getAuthenticationUserId(authentication); try { String result_s3Key = s3Service.upload(userId, s3key, file); - return ResponseEntity.ok(ApiResponse.of(new S3ResultResponse(result_s3Key))); + S3ResultResponse response = new S3ResultResponse(result_s3Key); + + return ResponseEntity.ok(ApiResponse.ok(IMAGE_SAVE_SUCCESS, response)); } catch (IOException e) { throw new RuntimeException("s3 upload fail" + e); } diff --git a/src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java b/src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java new file mode 100644 index 0000000..d8e2719 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java @@ -0,0 +1,5 @@ +package inha.gdgoc.domain.resource.controller.message; + +public class ResourceMessage { + public static final String IMAGE_SAVE_SUCCESS = "성공적으로 이미지를 S3에 저장했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java b/src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java index ef4363f..b841d79 100644 --- a/src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java +++ b/src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java @@ -1,5 +1,10 @@ package inha.gdgoc.domain.study.controller; +import static inha.gdgoc.domain.study.controller.message.StudyAttendeeMessage.STUDY_ATTENDEES_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyAttendeeMessage.STUDY_ATTENDEE_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyAttendeeMessage.STUDY_ATTENDEE_SAVE_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyAttendeeMessage.STUDY_ATTENDEE_UPDATE_SUCCESS; + import inha.gdgoc.domain.auth.service.AuthService; import inha.gdgoc.domain.study.dto.StudyAttendeeListWithMetaDto; import inha.gdgoc.domain.study.dto.request.AttendeeCreateRequest; @@ -8,7 +13,7 @@ import inha.gdgoc.domain.study.dto.response.GetStudyAttendeeResponse; import inha.gdgoc.domain.study.dto.response.PageResponse; import inha.gdgoc.domain.study.service.StudyAttendeeService; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -23,59 +28,75 @@ import java.util.Optional; +@RequestMapping("/api/v1/study/{studyId}/attendee") @RestController -@RequestMapping("/study/{studyId}/attendee") @RequiredArgsConstructor public class StudyAttendeeController { + private final StudyAttendeeService studyAttendeeService; private final AuthService authService; @GetMapping - public ResponseEntity> getAttendeeList( + public ResponseEntity> getAttendeeList( @PathVariable("studyId") Long studyId, @RequestParam("page") Optional page ) { - StudyAttendeeListWithMetaDto result = studyAttendeeService.getStudyAttendeeList(studyId, page); + StudyAttendeeListWithMetaDto result = studyAttendeeService.getStudyAttendeeList( + studyId, + page + ); PageResponse meta = new PageResponse( result.getPage().intValue(), result.getPageCount().intValue() ); - return ResponseEntity.ok(ApiResponse.of(new GetStudyAttendeeListResponse(result.getAttendees()), meta)); + GetStudyAttendeeListResponse response = new GetStudyAttendeeListResponse( + result.getAttendees() + ); + + return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEES_RETRIEVED_SUCCESS, response, meta)); } @GetMapping("/{attendeeId}") - public ResponseEntity> getStudyAttendee( + public ResponseEntity> getStudyAttendee( Authentication authentication, @PathVariable Long studyId, @PathVariable Long attendeeId ) { - return ResponseEntity.ok(ApiResponse.of(studyAttendeeService.getStudyAttendee( - authService.getAuthenticationUserId(authentication), studyId, attendeeId)) + GetStudyAttendeeResponse response = studyAttendeeService.getStudyAttendee( + authService.getAuthenticationUserId(authentication), + studyId, + attendeeId ); - } + return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEE_RETRIEVED_SUCCESS, response)); + } + // TODO 이거 언제 쓰이는 거지 @PostMapping - public ResponseEntity> createAttendee( + public ResponseEntity> createAttendee( Authentication authentication, @PathVariable Long studyId, @RequestBody AttendeeCreateRequest attendeeCreateRequest ) { Long userId = authService.getAuthenticationUserId(authentication); - return ResponseEntity.ok( - ApiResponse.of(studyAttendeeService.createAttendee(userId, studyId, attendeeCreateRequest)) + GetStudyAttendeeResponse response = studyAttendeeService.createAttendee( + userId, + studyId, + attendeeCreateRequest ); + + return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEE_SAVE_SUCCESS, response)); } @PatchMapping - public ResponseEntity> updateAttendee( + public ResponseEntity> updateAttendee( Authentication authentication, @PathVariable("studyId") Long studyId, @RequestBody AttendeeUpdateRequest request ) { Long userId = authService.getAuthenticationUserId(authentication); studyAttendeeService.updateAttendee(userId, studyId, request); - return ResponseEntity.ok(ApiResponse.of(true)); - } + return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEE_UPDATE_SUCCESS, true)); + } } diff --git a/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java b/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java index f8480b0..d1693a9 100644 --- a/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java +++ b/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java @@ -1,5 +1,11 @@ package inha.gdgoc.domain.study.controller; +import static inha.gdgoc.domain.study.controller.message.StudyMessage.APPLIED_STUDY_LIST_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyMessage.MY_STUDY_LIST_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyMessage.STUDY_CREATE_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyMessage.STUDY_LIST_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.study.controller.message.StudyMessage.STUDY_RETRIEVED_SUCCESS; + import inha.gdgoc.domain.auth.service.AuthService; import inha.gdgoc.domain.study.dto.StudyAttendeeResultDto; import inha.gdgoc.domain.study.dto.StudyDto; @@ -14,7 +20,7 @@ import inha.gdgoc.domain.study.enums.StudyStatus; import inha.gdgoc.domain.study.service.StudyAttendeeService; import inha.gdgoc.domain.study.service.StudyService; -import inha.gdgoc.global.common.ApiResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -29,8 +35,8 @@ import java.util.List; import java.util.Optional; +@RequestMapping("/api/v1/study") @RestController -@RequestMapping("/study") @RequiredArgsConstructor public class StudyController { @@ -39,7 +45,7 @@ public class StudyController { private final AuthService authService; @GetMapping - public ResponseEntity> getStudyList( + public ResponseEntity> getStudyList( @RequestParam("page") Optional page, @RequestParam("status") Optional status, @RequestParam("creatorType") Optional creatorType @@ -49,40 +55,54 @@ public ResponseEntity> getStudyList( result.getPage().intValue(), result.getPageCount().intValue() ); - return ResponseEntity.ok(ApiResponse.of(new StudyListRequest(result.getStudyList()), meta)); + StudyListRequest response = new StudyListRequest(result.getStudyList()); + + return ResponseEntity.ok(ApiResponse.ok(STUDY_LIST_RETRIEVED_SUCCESS, response, meta)); } @GetMapping("/attendee/result") - public ResponseEntity> getStudyAttendeeResultList( + public ResponseEntity> getStudyAttendeeResultList( Authentication authentication ) { Long userId = authService.getAuthenticationUserId(authentication); - List attendeeDtoList = studyAttendeeService.getStudyAttendeeResultListByUserId(userId); - return ResponseEntity.ok(ApiResponse.of(new GetStudyAttendeeResultResponse(attendeeDtoList))); + List attendeeDtoList = studyAttendeeService + .getStudyAttendeeResultListByUserId(userId); + GetStudyAttendeeResultResponse response = new GetStudyAttendeeResultResponse( + attendeeDtoList + ); + + return ResponseEntity.ok(ApiResponse.ok(APPLIED_STUDY_LIST_RETRIEVED_SUCCESS, response)); } @GetMapping("/me") - public ResponseEntity> getMyStudyList( + public ResponseEntity> getMyStudyList( Authentication authentication ) { Long userId = authService.getAuthenticationUserId(authentication); - return ResponseEntity.ok(ApiResponse.of(studyService.getMyStudyList(userId))); + MyStudyRecruitResponse response = studyService.getMyStudyList(userId); + + return ResponseEntity.ok(ApiResponse.ok(MY_STUDY_LIST_RETRIEVED_SUCCESS, response)); } @GetMapping("/{studyId}") - public ResponseEntity> getStudy(@PathVariable("studyId") Long id) { - return ResponseEntity.ok(ApiResponse.of(studyService.getStudyById(id))); + public ResponseEntity> getStudy( + @PathVariable("studyId") Long id + ) { + GetDetailedStudyResponse response = studyService.getStudyById(id); + + return ResponseEntity.ok(ApiResponse.ok(STUDY_RETRIEVED_SUCCESS, response)); } @PostMapping - public ResponseEntity> createStudy( + public ResponseEntity> createStudy( Authentication authentication, @RequestBody StudyCreateRequest body ) { Long userId = authService.getAuthenticationUserId(authentication); StudyDto createdStudy = studyService.createStudy(userId, body); - return ResponseEntity.ok(ApiResponse.of(createdStudy)); + + return ResponseEntity.ok(ApiResponse.ok(STUDY_CREATE_SUCCESS, createdStudy)); } } diff --git a/src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java b/src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java new file mode 100644 index 0000000..e5adbef --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java @@ -0,0 +1,8 @@ +package inha.gdgoc.domain.study.controller.message; + +public class StudyAttendeeMessage { + public static final String STUDY_ATTENDEES_RETRIEVED_SUCCESS = "성공적으로 스터디 신청자 목록을 조회했습니다."; + public static final String STUDY_ATTENDEE_RETRIEVED_SUCCESS = "성공적으로 스터디 신청자를 조회했습니다."; + public static final String STUDY_ATTENDEE_SAVE_SUCCESS = "성공적으로 스터디를 신청했습니다."; + public static final String STUDY_ATTENDEE_UPDATE_SUCCESS = "성공적으로 스터디 지원자의 지원 상태를 업데이트했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java b/src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java new file mode 100644 index 0000000..da86bcf --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java @@ -0,0 +1,9 @@ +package inha.gdgoc.domain.study.controller.message; + +public class StudyMessage { + public static final String STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 스터디 목록을 조회했습니다."; + public static final String APPLIED_STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 해당 사용자가 지원한 스터디 목록을 조회했습니다."; + public static final String MY_STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 해당 사용자가 개설한 스터디 목록을 조회했습니다."; + public static final String STUDY_RETRIEVED_SUCCESS = "성공적으로 해당 스터디 정보를 조회했습니다."; + public static final String STUDY_CREATE_SUCCESS = "성공적으로 스터디를 개설했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/study/entity/Study.java b/src/main/java/inha/gdgoc/domain/study/entity/Study.java index 3b98ec0..9630be9 100644 --- a/src/main/java/inha/gdgoc/domain/study/entity/Study.java +++ b/src/main/java/inha/gdgoc/domain/study/entity/Study.java @@ -3,7 +3,7 @@ import inha.gdgoc.domain.study.enums.CreatorType; import inha.gdgoc.domain.study.enums.StudyStatus; import inha.gdgoc.domain.user.entity.User; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/inha/gdgoc/domain/study/entity/StudyAttendee.java b/src/main/java/inha/gdgoc/domain/study/entity/StudyAttendee.java index 641d2b0..a4b260c 100644 --- a/src/main/java/inha/gdgoc/domain/study/entity/StudyAttendee.java +++ b/src/main/java/inha/gdgoc/domain/study/entity/StudyAttendee.java @@ -2,7 +2,7 @@ import inha.gdgoc.domain.study.enums.AttendeeStatus; import inha.gdgoc.domain.user.entity.User; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/inha/gdgoc/domain/user/controller/UserController.java b/src/main/java/inha/gdgoc/domain/user/controller/UserController.java index 179117e..fd36572 100644 --- a/src/main/java/inha/gdgoc/domain/user/controller/UserController.java +++ b/src/main/java/inha/gdgoc/domain/user/controller/UserController.java @@ -1,47 +1,58 @@ package inha.gdgoc.domain.user.controller; +import static inha.gdgoc.domain.user.controller.message.UserMessage.USER_CREATE_SUCCESS; +import static inha.gdgoc.domain.user.controller.message.UserMessage.USER_EMAIL_DUPLICATION_RETRIEVED_SUCCESS; +import static inha.gdgoc.domain.user.controller.message.UserMessage.USER_EMAIL_RETRIEVED_SUCCESS; + import inha.gdgoc.domain.auth.dto.request.FindIdRequest; import inha.gdgoc.domain.user.dto.request.CheckDuplicatedEmailRequest; import inha.gdgoc.domain.user.dto.request.UserSignupRequest; import inha.gdgoc.domain.auth.dto.response.FindIdResponse; import inha.gdgoc.domain.user.dto.response.CheckDuplicatedEmailResponse; import inha.gdgoc.domain.user.service.UserService; -import inha.gdgoc.global.common.ApiResponse; -import inha.gdgoc.global.common.ErrorResponse; +import inha.gdgoc.global.dto.response.ApiResponse; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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; +@RequestMapping("/api/v1") @RequiredArgsConstructor @RestController public class UserController { private final UserService userService; + // TODO 진짜 돌았냐? POST로 바꿔라 @GetMapping("/auth/check") - public ResponseEntity> checkDuplicatedEmail( - @RequestBody CheckDuplicatedEmailRequest checkDuplicatedEmailRequest) { - return ResponseEntity.ok(ApiResponse.of(userService.isExistsByEmail(checkDuplicatedEmailRequest))); + public ResponseEntity> checkDuplicatedEmail( + @RequestBody CheckDuplicatedEmailRequest checkDuplicatedEmailRequest + ) { + CheckDuplicatedEmailResponse response = userService.isExistsByEmail(checkDuplicatedEmailRequest); + + return ResponseEntity.ok(ApiResponse.ok(USER_EMAIL_DUPLICATION_RETRIEVED_SUCCESS, response)); } @PostMapping("/auth/signup") - public ResponseEntity> userSignup( - @RequestBody UserSignupRequest userSignupRequest) throws NoSuchAlgorithmException, InvalidKeyException { + public ResponseEntity> userSignup( + @RequestBody UserSignupRequest userSignupRequest + ) throws NoSuchAlgorithmException, InvalidKeyException { userService.saveUser(userSignupRequest); - return ResponseEntity.ok(ApiResponse.of(null, null)); + + return ResponseEntity.ok(ApiResponse.ok(USER_CREATE_SUCCESS)); } @PostMapping("/auth/findId") - public ResponseEntity findId(@RequestBody FindIdRequest findIdRequest) { + public ResponseEntity> findEmail( + @RequestBody FindIdRequest findIdRequest + ) { FindIdResponse response = userService.findId(findIdRequest); - return ResponseEntity.ok(ApiResponse.of(response)); - + return ResponseEntity.ok(ApiResponse.ok(USER_EMAIL_RETRIEVED_SUCCESS, response)); } } diff --git a/src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java b/src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java new file mode 100644 index 0000000..59d5598 --- /dev/null +++ b/src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java @@ -0,0 +1,7 @@ +package inha.gdgoc.domain.user.controller.message; + +public class UserMessage { + public static final String USER_EMAIL_DUPLICATION_RETRIEVED_SUCCESS = "성공적으로 이메일 중복 조회를 완료했습니다."; + public static final String USER_CREATE_SUCCESS = "성공적으로 회원 가입을 완료했습니다."; + public static final String USER_EMAIL_RETRIEVED_SUCCESS = "성공적으로 사용자의 이메일을 조회했습니다."; +} diff --git a/src/main/java/inha/gdgoc/domain/user/entity/User.java b/src/main/java/inha/gdgoc/domain/user/entity/User.java index f2424b3..f2cfe8f 100644 --- a/src/main/java/inha/gdgoc/domain/user/entity/User.java +++ b/src/main/java/inha/gdgoc/domain/user/entity/User.java @@ -3,7 +3,7 @@ import inha.gdgoc.domain.study.entity.Study; import inha.gdgoc.domain.study.entity.StudyAttendee; import inha.gdgoc.domain.user.enums.UserRole; -import inha.gdgoc.global.common.BaseEntity; +import inha.gdgoc.global.entity.BaseEntity; import inha.gdgoc.global.util.EncryptUtil; import jakarta.persistence.CascadeType; diff --git a/src/main/java/inha/gdgoc/domain/user/service/UserService.java b/src/main/java/inha/gdgoc/domain/user/service/UserService.java index 7fd560e..b4f0485 100644 --- a/src/main/java/inha/gdgoc/domain/user/service/UserService.java +++ b/src/main/java/inha/gdgoc/domain/user/service/UserService.java @@ -10,7 +10,7 @@ import inha.gdgoc.domain.user.dto.response.CheckDuplicatedEmailResponse; import inha.gdgoc.domain.user.entity.User; import inha.gdgoc.domain.user.repository.UserRepository; -import inha.gdgoc.global.exception.NotFoundException; +import inha.gdgoc.global.error.NotFoundException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/inha/gdgoc/global/common/ApiResponse.java b/src/main/java/inha/gdgoc/global/common/ApiResponse.java deleted file mode 100644 index 3644cb6..0000000 --- a/src/main/java/inha/gdgoc/global/common/ApiResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package inha.gdgoc.global.common; - -import lombok.Getter; - -@Getter -public class ApiResponse { - private final T data; - private final Object meta; // meta는 optional - - private ApiResponse(T data, Object meta) { - this.data = data; - this.meta = meta; - } - - public static ApiResponse of(T data) { - return new ApiResponse<>(data, null); - } - - public static ApiResponse of(T data, Object meta) { - return new ApiResponse<>(data, meta); - } -} diff --git a/src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java b/src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java new file mode 100644 index 0000000..ce985dd --- /dev/null +++ b/src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java @@ -0,0 +1,18 @@ +package inha.gdgoc.global.config.jpa; + +import java.time.Instant; +import java.util.Optional; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing(dateTimeProviderRef = "utcDateTimeProvider") +@Configuration +public class JpaAuditingConfig { + + @Bean + public DateTimeProvider utcDateTimeProvider() { + return () -> Optional.of(Instant.now()); + } +} diff --git a/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java b/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java new file mode 100644 index 0000000..6c38760 --- /dev/null +++ b/src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java @@ -0,0 +1,37 @@ +package inha.gdgoc.global.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import inha.gdgoc.global.error.ErrorCode; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ApiResponse(int code, String message, T data, M meta) { + + public static ApiResponse ok(String message, T data) { + return new ApiResponse<>(200, message, data, null); + } + + public static ApiResponse ok(String message, T data, M meta) { + return new ApiResponse<>(200, message, data, meta); + } + + public static ApiResponse ok(String message) { + return new ApiResponse<>(200, message, null, null); + } + + public static ApiResponse created(String message) { + return new ApiResponse<>(201, message, null, null); + } + + public static ApiResponse created(T data, String message) { + return new ApiResponse<>(201, message, data, null); + } + + public static ApiResponse error(ErrorCode errorCode, ErrorMeta meta) { + return new ApiResponse<>(errorCode.getStatus().value(), errorCode.getMessage(), null, meta); + } + + public static ApiResponse error(int code, String message, ErrorMeta meta) { + return new ApiResponse<>(code, message, null, meta); + } +} + diff --git a/src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java b/src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java new file mode 100644 index 0000000..3613c94 --- /dev/null +++ b/src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java @@ -0,0 +1,8 @@ +package inha.gdgoc.global.dto.response; + +public record ErrorMeta( + String path, + long timestamp +) { + +} diff --git a/src/main/java/inha/gdgoc/global/common/ErrorResponse.java b/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java similarity index 92% rename from src/main/java/inha/gdgoc/global/common/ErrorResponse.java rename to src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java index b1a618b..dd0d1e7 100644 --- a/src/main/java/inha/gdgoc/global/common/ErrorResponse.java +++ b/src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.common; +package inha.gdgoc.global.dto.response; import inha.gdgoc.global.error.ErrorCode; import lombok.Getter; diff --git a/src/main/java/inha/gdgoc/global/common/BaseEntity.java b/src/main/java/inha/gdgoc/global/entity/BaseEntity.java similarity index 60% rename from src/main/java/inha/gdgoc/global/common/BaseEntity.java rename to src/main/java/inha/gdgoc/global/entity/BaseEntity.java index 9cb647d..383e396 100644 --- a/src/main/java/inha/gdgoc/global/common/BaseEntity.java +++ b/src/main/java/inha/gdgoc/global/entity/BaseEntity.java @@ -1,11 +1,9 @@ -package inha.gdgoc.global.common; +package inha.gdgoc.global.entity; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.PrePersist; -import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.Instant; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; @@ -18,15 +16,9 @@ public abstract class BaseEntity { @CreatedDate @Column(name = "created_at", updatable = false, nullable = false) - private LocalDateTime createdAt; + private Instant createdAt; @LastModifiedDate @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; - - @PrePersist - public void prePersist() { - this.createdAt = LocalDateTime.now(ZoneId.of("Asia/Seoul")); - this.updatedAt = LocalDateTime.now(ZoneId.of("Asia/Seoul")); - } + private Instant updatedAt; } diff --git a/src/main/java/inha/gdgoc/global/error/ErrorCode.java b/src/main/java/inha/gdgoc/global/error/ErrorCode.java index 18a156c..a3d879a 100644 --- a/src/main/java/inha/gdgoc/global/error/ErrorCode.java +++ b/src/main/java/inha/gdgoc/global/error/ErrorCode.java @@ -3,6 +3,8 @@ import org.springframework.http.HttpStatus; public interface ErrorCode { + HttpStatus getStatus(); + String getMessage(); } diff --git a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java b/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java index 3b63c6d..56799f8 100644 --- a/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java +++ b/src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java @@ -1,10 +1,8 @@ package inha.gdgoc.global.error; import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import inha.gdgoc.global.common.ErrorResponse; +import inha.gdgoc.global.dto.response.ErrorResponse; import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.JwtHandler; import io.jsonwebtoken.MalformedJwtException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -12,7 +10,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException; @RestControllerAdvice diff --git a/src/main/java/inha/gdgoc/global/exception/NotFoundException.java b/src/main/java/inha/gdgoc/global/error/NotFoundException.java similarity index 88% rename from src/main/java/inha/gdgoc/global/exception/NotFoundException.java rename to src/main/java/inha/gdgoc/global/error/NotFoundException.java index 2a1f83c..267199b 100644 --- a/src/main/java/inha/gdgoc/global/exception/NotFoundException.java +++ b/src/main/java/inha/gdgoc/global/error/NotFoundException.java @@ -1,4 +1,4 @@ -package inha.gdgoc.global.exception; +package inha.gdgoc.global.error; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; diff --git a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java index e2ad123..5b01620 100644 --- a/src/main/java/inha/gdgoc/global/security/SecurityConfig.java +++ b/src/main/java/inha/gdgoc/global/security/SecurityConfig.java @@ -1,7 +1,7 @@ package inha.gdgoc.global.security; import com.fasterxml.jackson.databind.ObjectMapper; -import inha.gdgoc.global.common.ErrorResponse; +import inha.gdgoc.global.dto.response.ErrorResponse; import inha.gdgoc.global.error.GlobalErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f818194..7453a2c 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -22,7 +22,8 @@ spring: properties: hibernate: default_batch_fetch_size: 100 - time_zone: Asia/Seoul + jdbc: + time_zone: UTC show-sql: false database-platform: org.hibernate.dialect.PostgreSQLDialect flyway: diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 60b1cd0..3bdb7fa 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -22,12 +22,12 @@ spring: hibernate: default_batch_fetch_size: 100 jdbc: - time_zone: Asia/Seoul + time_zone: UTC flyway: enabled: true locations: classpath:db/migration schemas: public - baseline-on-migrate: true + baseline-on-migrate: false baseline-version: 1 baseline-description: "Baseline existing schema" diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index ac063cb..39ea37c 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -22,7 +22,8 @@ spring: properties: hibernate: default_batch_fetch_size: 100 - time_zone: Asia/Seoul + jdbc: + time_zone: UTC show-sql: false database-platform: org.hibernate.dialect.PostgreSQLDialect flyway: diff --git a/src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql b/src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql new file mode 100644 index 0000000..6ae8dfa --- /dev/null +++ b/src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql @@ -0,0 +1,32 @@ +DO $$ +DECLARE + r RECORD; +BEGIN + FOR r IN + WITH target_cols AS ( + SELECT + n.nspname AS schema_name, + c.relname AS table_name, + a.attname AS column_name + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON c.oid = a.attrelid + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE a.attnum > 0 + AND NOT a.attisdropped + AND c.relkind IN ('r','p') + AND n.nspname NOT IN ('pg_catalog','information_schema') + AND a.attname IN ('created_at','updated_at') + AND pg_catalog.format_type(a.atttypid, a.atttypmod) ILIKE 'timestamp% without time zone%' + ) + SELECT + format( + 'ALTER TABLE %I.%I ALTER COLUMN %I TYPE timestamptz(6) USING (%I AT TIME ZONE %L);', + schema_name, table_name, column_name, column_name, 'Asia/Seoul' + ) AS alter_sql + FROM target_cols + ORDER BY schema_name, table_name, column_name + LOOP + RAISE NOTICE 'Executing: %', r.alter_sql; + EXECUTE r.alter_sql; + END LOOP; +END $$;