diff --git a/src/main/java/sopt/org/hmh/domain/admin/service/AdminFacade.java b/src/main/java/sopt/org/hmh/domain/admin/service/AdminFacade.java index 6fbdc22d..ac11f6f9 100644 --- a/src/main/java/sopt/org/hmh/domain/admin/service/AdminFacade.java +++ b/src/main/java/sopt/org/hmh/domain/admin/service/AdminFacade.java @@ -19,7 +19,7 @@ import sopt.org.hmh.domain.dailychallenge.service.DailyChallengeService; import sopt.org.hmh.domain.user.domain.User; import sopt.org.hmh.domain.user.service.UserService; -import sopt.org.hmh.global.auth.jwt.TokenService; +import sopt.org.hmh.global.auth.jwt.service.TokenService; @Service @RequiredArgsConstructor diff --git a/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppApi.java b/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppApi.java new file mode 100644 index 00000000..8c0dd3c5 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppApi.java @@ -0,0 +1,59 @@ +package sopt.org.hmh.domain.app.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import sopt.org.hmh.domain.app.dto.request.AppRemoveRequest; +import sopt.org.hmh.domain.app.dto.request.ChallengeAppArrayRequest; +import sopt.org.hmh.global.auth.jwt.JwtConstants; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@Tag(name = "챌린지 관련 API") +@SecurityRequirement(name = JwtConstants.AUTHORIZATION) +public interface ChallengeAppApi { + + @Operation( + summary = "스크린타임 설정할 앱을 추가하는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지 정보 조회에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderAddApps( + @Parameter(hidden = true) Long userId, + @RequestHeader("OS") String os, + @RequestBody ChallengeAppArrayRequest requests); + + @Operation( + summary = "스크린타임 설정한 앱을 삭제하는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지 정보 조회에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderRemoveApp( + @Parameter(hidden = true) Long userId, + @RequestHeader("OS") String os, + @RequestBody AppRemoveRequest request); +} \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppController.java b/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppController.java new file mode 100644 index 00000000..274b2fa9 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/app/controller/ChallengeAppController.java @@ -0,0 +1,54 @@ +package sopt.org.hmh.domain.app.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sopt.org.hmh.domain.app.domain.exception.AppSuccess; +import sopt.org.hmh.domain.app.dto.request.AppRemoveRequest; +import sopt.org.hmh.domain.app.dto.request.ChallengeAppArrayRequest; +import sopt.org.hmh.domain.challenge.service.ChallengeFacade; +import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.constant.CustomHeaderType; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/challenge/app") +public class ChallengeAppController implements ChallengeAppApi { + + private final ChallengeFacade challengeFacade; + + @Override + @PostMapping + public ResponseEntity> orderAddApps( + @UserId final Long userId, + @RequestHeader(CustomHeaderType.OS) final String os, + @RequestBody @Valid final ChallengeAppArrayRequest requests) { + challengeFacade.addAppsToCurrentChallenge(userId, requests.apps(), os); + + return ResponseEntity + .status(AppSuccess.ADD_APP_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(AppSuccess.ADD_APP_SUCCESS, new EmptyJsonResponse())); + + } + + @Override + @DeleteMapping + public ResponseEntity> orderRemoveApp( + @UserId final Long userId, + @RequestHeader(CustomHeaderType.OS) final String os, + @RequestBody @Valid final AppRemoveRequest request) { + challengeFacade.removeAppFromCurrentChallenge(userId, request.appCode(), os); + + return ResponseEntity + .status(AppSuccess.REMOVE_APP_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(AppSuccess.REMOVE_APP_SUCCESS, new EmptyJsonResponse())); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/app/service/HistoryAppService.java b/src/main/java/sopt/org/hmh/domain/app/service/HistoryAppService.java index c4a79d9a..a0c218bd 100644 --- a/src/main/java/sopt/org/hmh/domain/app/service/HistoryAppService.java +++ b/src/main/java/sopt/org/hmh/domain/app/service/HistoryAppService.java @@ -24,7 +24,7 @@ public void addHistoryApp( } private List supplementAdditionalInfo(List currentChallengeApps, - List apps, DailyChallenge dailyChallenge, String os) { + List apps, DailyChallenge dailyChallenge, String os) { return apps.stream().map(app -> HistoryApp.builder() .goalTime(this.getGoalTime(currentChallengeApps, app.appCode())) .appCode(app.appCode()) diff --git a/src/main/java/sopt/org/hmh/domain/auth/controller/AuthApi.java b/src/main/java/sopt/org/hmh/domain/auth/controller/AuthApi.java index 77878853..fb41a48f 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/controller/AuthApi.java +++ b/src/main/java/sopt/org/hmh/domain/auth/controller/AuthApi.java @@ -9,7 +9,8 @@ import org.springframework.web.bind.annotation.RequestHeader; import sopt.org.hmh.domain.auth.dto.request.SocialPlatformRequest; import sopt.org.hmh.domain.auth.dto.request.SocialSignUpRequest; -import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.domain.auth.dto.response.LoginResponse; +import sopt.org.hmh.domain.auth.dto.response.ReissueResponse; import sopt.org.hmh.global.auth.jwt.JwtConstants; import sopt.org.hmh.global.common.response.BaseResponse; @@ -18,20 +19,28 @@ public interface AuthApi { @Operation(summary = "소셜 로그인") - ResponseEntity> orderLogin( + ResponseEntity> orderLogin( @Parameter(hidden = true) @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, @RequestBody final SocialPlatformRequest request ); @Operation(summary = "회원 가입") - ResponseEntity> orderSignup( + ResponseEntity> orderSignupDeprecated( @Parameter(hidden = true) @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, @RequestHeader("OS") final String os, @RequestBody final SocialSignUpRequest request ); + @Operation(summary = "회원 가입 V2") + ResponseEntity> orderSignup( + @Parameter(hidden = true) @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, + @RequestHeader("OS") final String os, + @RequestHeader("Time-Zone") final String timeZone, + @RequestBody final SocialSignUpRequest request + ); + @Operation(summary = "토큰 재발급") - ResponseEntity> orderReissue( + ResponseEntity> orderReissue( @Parameter(hidden = true) @RequestHeader(JwtConstants.AUTHORIZATION) final String refreshToken ); } diff --git a/src/main/java/sopt/org/hmh/domain/auth/controller/AuthController.java b/src/main/java/sopt/org/hmh/domain/auth/controller/AuthController.java index 39a86dcd..01671257 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/controller/AuthController.java +++ b/src/main/java/sopt/org/hmh/domain/auth/controller/AuthController.java @@ -10,24 +10,28 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import sopt.org.hmh.domain.auth.dto.response.LoginResponse; +import sopt.org.hmh.domain.auth.dto.response.ReissueResponse; import sopt.org.hmh.domain.auth.exception.AuthSuccess; import sopt.org.hmh.domain.auth.dto.request.SocialPlatformRequest; import sopt.org.hmh.domain.auth.dto.request.SocialSignUpRequest; import sopt.org.hmh.domain.auth.service.AuthFacade; +import sopt.org.hmh.global.auth.jwt.JwtConstants; import sopt.org.hmh.global.auth.social.SocialAccessTokenResponse; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/user") +@RequestMapping("/api") public class AuthController implements AuthApi { private final AuthFacade authFacade; - @PostMapping("/login") + @PostMapping("/v1/user/login") @Override - public ResponseEntity> orderLogin( - @RequestHeader("Authorization") final String socialAccessToken, + public ResponseEntity> orderLogin( + @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, @RequestBody final SocialPlatformRequest request ) { return ResponseEntity @@ -38,25 +42,42 @@ public ResponseEntity> orderLogin( )); } - @PostMapping("/signup") @Override - public ResponseEntity> orderSignup( - @RequestHeader("Authorization") final String socialAccessToken, - @RequestHeader("OS") final String os, + @Deprecated + @PostMapping("/v1/user/signup") + public ResponseEntity> orderSignupDeprecated( + @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, + @RequestHeader(CustomHeaderType.OS) final String os, @RequestBody @Valid final SocialSignUpRequest request ) { return ResponseEntity .status(AuthSuccess.SIGNUP_SUCCESS.getHttpStatus()) .body(BaseResponse.success( AuthSuccess.SIGNUP_SUCCESS, - authFacade.signup(request, socialAccessToken, os) + authFacade.signup(request, socialAccessToken, os, "Asia/Seoul") )); } - @PostMapping("/reissue") @Override - public ResponseEntity> orderReissue( - @RequestHeader("Authorization") final String refreshToken + @PostMapping("/v2/user/signup") + public ResponseEntity> orderSignup( + @RequestHeader(JwtConstants.AUTHORIZATION) final String socialAccessToken, + @RequestHeader(CustomHeaderType.OS) final String os, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, + @RequestBody @Valid final SocialSignUpRequest request + ) { + return ResponseEntity + .status(AuthSuccess.SIGNUP_SUCCESS.getHttpStatus()) + .body(BaseResponse.success( + AuthSuccess.SIGNUP_SUCCESS, + authFacade.signup(request, socialAccessToken, os, timeZone) + )); + } + + @Override + @PostMapping("/v1/user/reissue") + public ResponseEntity> orderReissue( + @RequestHeader(JwtConstants.AUTHORIZATION) final String refreshToken ) { return ResponseEntity .status(AuthSuccess.REISSUE_SUCCESS.getHttpStatus()) @@ -66,7 +87,7 @@ public ResponseEntity> orderReissue( )); } - @GetMapping("/social/token/kakao") + @GetMapping("/v1/user/social/token/kakao") public ResponseEntity> orderGetKakaoAccessToken( @RequestParam("code") final String code ) { diff --git a/src/main/java/sopt/org/hmh/domain/auth/dto/request/SocialSignUpRequest.java b/src/main/java/sopt/org/hmh/domain/auth/dto/request/SocialSignUpRequest.java index ef3d4511..f8ebcc51 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/dto/request/SocialSignUpRequest.java +++ b/src/main/java/sopt/org/hmh/domain/auth/dto/request/SocialSignUpRequest.java @@ -3,7 +3,6 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import java.util.List; -import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; import sopt.org.hmh.domain.challenge.dto.request.ChallengeSignUpRequest; import sopt.org.hmh.domain.user.domain.OnboardingInfo; import sopt.org.hmh.domain.user.domain.OnboardingProblem; diff --git a/src/main/java/sopt/org/hmh/domain/auth/dto/response/LoginResponse.java b/src/main/java/sopt/org/hmh/domain/auth/dto/response/LoginResponse.java index e5c16e3c..3c434bfd 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/dto/response/LoginResponse.java +++ b/src/main/java/sopt/org/hmh/domain/auth/dto/response/LoginResponse.java @@ -1,7 +1,7 @@ package sopt.org.hmh.domain.auth.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; -import sopt.org.hmh.global.auth.jwt.TokenResponse; +import sopt.org.hmh.global.auth.jwt.dto.TokenResponse; public record LoginResponse( Long userId, diff --git a/src/main/java/sopt/org/hmh/domain/auth/dto/response/ReissueResponse.java b/src/main/java/sopt/org/hmh/domain/auth/dto/response/ReissueResponse.java index 119600c9..b0a79787 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/dto/response/ReissueResponse.java +++ b/src/main/java/sopt/org/hmh/domain/auth/dto/response/ReissueResponse.java @@ -1,7 +1,7 @@ package sopt.org.hmh.domain.auth.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; -import sopt.org.hmh.global.auth.jwt.TokenResponse; +import sopt.org.hmh.global.auth.jwt.dto.TokenResponse; public record ReissueResponse( @JsonProperty(value = "token") diff --git a/src/main/java/sopt/org/hmh/domain/auth/service/AuthFacade.java b/src/main/java/sopt/org/hmh/domain/auth/service/AuthFacade.java index d52f691d..a63ef773 100644 --- a/src/main/java/sopt/org/hmh/domain/auth/service/AuthFacade.java +++ b/src/main/java/sopt/org/hmh/domain/auth/service/AuthFacade.java @@ -4,12 +4,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import sopt.org.hmh.domain.auth.dto.response.ReissueResponse; +import sopt.org.hmh.domain.challenge.dto.request.NewChallengeOrder; import sopt.org.hmh.domain.challenge.service.ChallengeFacade; import sopt.org.hmh.domain.user.domain.User; import sopt.org.hmh.domain.auth.dto.request.SocialSignUpRequest; import sopt.org.hmh.domain.auth.dto.response.LoginResponse; import sopt.org.hmh.domain.user.service.UserService; -import sopt.org.hmh.global.auth.jwt.TokenService; +import sopt.org.hmh.global.auth.jwt.service.TokenService; import sopt.org.hmh.global.auth.jwt.exception.JwtError; import sopt.org.hmh.global.auth.jwt.exception.JwtException; import sopt.org.hmh.global.auth.social.SocialPlatform; @@ -36,7 +37,7 @@ public LoginResponse login(String socialAccessToken, SocialPlatform socialPlatfo } @Transactional - public LoginResponse signup(SocialSignUpRequest request, String socialAccessToken, String os) { + public LoginResponse signup(SocialSignUpRequest request, String socialAccessToken, String os, String timeZone) { SocialPlatform socialPlatform = request.socialPlatform(); String socialId = this.getSocialIdBySocialAccessToken(socialPlatform, socialAccessToken); @@ -45,7 +46,10 @@ public LoginResponse signup(SocialSignUpRequest request, String socialAccessToke userService.registerOnboardingInfo(request, newUserId); - challengeFacade.startFirstChallengeWithChallengeSignUpRequest(request.challenge(), newUser , os); + challengeFacade.startNewChallenge(NewChallengeOrder.createFirstChallengeOrder( + request.challenge().toChallengeRequest(), request.challenge().apps(), + newUserId, os, timeZone + )); return performLogin(newUser, socialAccessToken, socialPlatform); } diff --git a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApi.java b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApi.java index ffad1577..eeebfa19 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApi.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApi.java @@ -8,20 +8,17 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import sopt.org.hmh.domain.app.dto.request.ChallengeAppArrayRequest; -import sopt.org.hmh.domain.app.dto.request.AppRemoveRequest; import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; import sopt.org.hmh.domain.challenge.dto.response.ChallengeResponse; import sopt.org.hmh.domain.challenge.dto.response.DailyChallengeResponse; -import sopt.org.hmh.global.auth.UserId; import sopt.org.hmh.global.auth.jwt.JwtConstants; import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; @Tag(name = "챌린지 관련 API") @SecurityRequirement(name = JwtConstants.AUTHORIZATION) public interface ChallengeApi { - @PostMapping @Operation( summary = "챌린지가 끝난 후 새 챌린지 생성하는 API", responses = { @@ -36,12 +33,12 @@ public interface ChallengeApi { responseCode = "500", description = "서버 내부 오류입니다.", content = @Content)}) - ResponseEntity> orderAddChallenge( - @UserId @Parameter(hidden = true) final Long userId, - @RequestHeader("OS") final String os, - @RequestBody final ChallengeRequest request); + ResponseEntity> orderAddChallenge( + @Parameter(hidden = true) final Long userId, + final String os, + final String timeZone, + final ChallengeRequest request); - @GetMapping @Operation( summary = "달성현황뷰 챌린지 정보를 불러오는 API", responses = { @@ -57,10 +54,9 @@ ResponseEntity> orderAddChallenge( description = "서버 내부 오류입니다.", content = @Content)}) ResponseEntity> orderGetChallenge( - @UserId @Parameter(hidden = true) final Long userId, - @RequestHeader("OS") final String os); + @Parameter(hidden = true) final Long userId, + @RequestHeader final String timeZone); - @GetMapping("/home") @Operation( summary = "이용시간 통계 정보를 불러오는 API", responses = { @@ -76,45 +72,7 @@ ResponseEntity> orderGetChallenge( description = "서버 내부 오류입니다.", content = @Content)}) ResponseEntity> orderGetDailyChallenge( - @UserId @Parameter(hidden = true) final Long userId, - @RequestHeader("OS") final String os); - - @PostMapping("/app") - @Operation( - summary = "스크린타임 설정할 앱을 추가하는 API", - responses = { - @ApiResponse( - responseCode = "200", - description = "챌린지 정보 조회에 성공했습니다."), - @ApiResponse( - responseCode = "400", - description = "잘못된 요청입니다.", - content = @Content), - @ApiResponse( - responseCode = "500", - description = "서버 내부 오류입니다.", - content = @Content)}) - ResponseEntity> orderAddApps(@UserId @Parameter(hidden = true) Long userId, - @RequestHeader("OS") String os, - @RequestBody ChallengeAppArrayRequest requests); - - @GetMapping("/app") - @Operation( - summary = "스크린타임 설정한 앱을 삭제하는 API", - responses = { - @ApiResponse( - responseCode = "200", - description = "챌린지 정보 조회에 성공했습니다."), - @ApiResponse( - responseCode = "400", - description = "잘못된 요청입니다.", - content = @Content), - @ApiResponse( - responseCode = "500", - description = "서버 내부 오류입니다.", - content = @Content)}) - ResponseEntity> orderRemoveApp(@UserId Long userId, - @RequestHeader("OS") String os, - @RequestBody AppRemoveRequest request); + @Parameter(hidden = true) final Long userId, + @RequestHeader final String timeZone); } diff --git a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApiDeprecated.java b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApiDeprecated.java new file mode 100644 index 00000000..72acaed3 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeApiDeprecated.java @@ -0,0 +1,76 @@ +package sopt.org.hmh.domain.challenge.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; +import sopt.org.hmh.domain.challenge.dto.response.ChallengeResponse; +import sopt.org.hmh.domain.challenge.dto.response.DailyChallengeResponse; +import sopt.org.hmh.global.auth.jwt.JwtConstants; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@Deprecated +@Tag(name = "챌린지 관련 API") +@SecurityRequirement(name = JwtConstants.AUTHORIZATION) +public interface ChallengeApiDeprecated { + + @Operation( + summary = "챌린지가 끝난 후 새 챌린지 생성하는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지가 성공적으로 추가되었습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderAddChallenge( + @Parameter(hidden = true) final Long userId, + @RequestHeader("OS") final String os, + @RequestBody final ChallengeRequest request); + + @Operation( + summary = "달성현황뷰 챌린지 정보를 불러오는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지 정보 조회에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderGetChallenge( + @Parameter(hidden = true) final Long userId); + + @Operation( + summary = "이용시간 통계 정보를 불러오는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지 정보 조회에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderGetDailyChallenge( + @Parameter(hidden = true) final Long userId); +} + diff --git a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeController.java b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeController.java index 5c44f385..a42d1af1 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeController.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeController.java @@ -1,87 +1,60 @@ package sopt.org.hmh.domain.challenge.controller; +import static sopt.org.hmh.domain.challenge.dto.request.NewChallengeOrder.createNextChallengeOrder; + import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import sopt.org.hmh.domain.app.domain.exception.AppSuccess; -import sopt.org.hmh.domain.app.dto.request.ChallengeAppArrayRequest; -import sopt.org.hmh.domain.app.dto.request.AppRemoveRequest; import sopt.org.hmh.domain.challenge.domain.exception.ChallengeSuccess; import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; import sopt.org.hmh.domain.challenge.dto.response.ChallengeResponse; import sopt.org.hmh.domain.challenge.dto.response.DailyChallengeResponse; import sopt.org.hmh.domain.challenge.service.ChallengeFacade; import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; import sopt.org.hmh.global.common.response.EmptyJsonResponse; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/challenge") +@RequestMapping("/api/v2/challenge") public class ChallengeController implements ChallengeApi { private final ChallengeFacade challengeFacade; - @PostMapping @Override - public ResponseEntity> orderAddChallenge( + @PostMapping + public ResponseEntity> orderAddChallenge( @UserId final Long userId, - @RequestHeader("OS") final String os, + @RequestHeader(CustomHeaderType.OS) final String os, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, @RequestBody @Valid final ChallengeRequest request) { - challengeFacade.startNewChallengeByPreviousChallenge(userId, request, os); - + challengeFacade.startNewChallenge(createNextChallengeOrder(request, userId, os, timeZone)); return ResponseEntity .status(ChallengeSuccess.ADD_CHALLENGE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(ChallengeSuccess.ADD_CHALLENGE_SUCCESS, new EmptyJsonResponse())); } - @GetMapping @Override + @GetMapping public ResponseEntity> orderGetChallenge( @UserId final Long userId, - @RequestHeader("OS") final String os) { + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone) { return ResponseEntity .status(ChallengeSuccess.GET_CHALLENGE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(ChallengeSuccess.GET_CHALLENGE_SUCCESS, - challengeFacade.getCurrentChallengeInfo(userId))); + challengeFacade.getCurrentChallengeInfo(userId, timeZone))); } - @GetMapping("/home") @Override + @GetMapping("/home") public ResponseEntity> orderGetDailyChallenge( @UserId final Long userId, - @RequestHeader("OS") final String os) { + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone) { return ResponseEntity .status(ChallengeSuccess.GET_DAILY_CHALLENGE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(ChallengeSuccess.GET_DAILY_CHALLENGE_SUCCESS, - challengeFacade.getDailyChallengeInfo(userId))); - } - - @PostMapping("/app") - @Override - public ResponseEntity> orderAddApps( - @UserId final Long userId, - @RequestHeader("OS") final String os, - @RequestBody @Valid final ChallengeAppArrayRequest requests) { - challengeFacade.addAppsToCurrentChallenge(userId, requests.apps(), os); - - return ResponseEntity - .status(AppSuccess.ADD_APP_SUCCESS.getHttpStatus()) - .body(BaseResponse.success(AppSuccess.ADD_APP_SUCCESS, new EmptyJsonResponse())); - - } - - @DeleteMapping("/app") - @Override - public ResponseEntity> orderRemoveApp( - @UserId final Long userId, - @RequestHeader("OS") final String os, - @RequestBody @Valid final AppRemoveRequest request) { - challengeFacade.removeAppFromCurrentChallenge(userId, request.appCode(), os); - - return ResponseEntity - .status(AppSuccess.REMOVE_APP_SUCCESS.getHttpStatus()) - .body(BaseResponse.success(AppSuccess.REMOVE_APP_SUCCESS, new EmptyJsonResponse())); + challengeFacade.getDailyChallengeInfo(userId, timeZone))); } } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeControllerDeprecated.java b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeControllerDeprecated.java new file mode 100644 index 00000000..8545fecf --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/challenge/controller/ChallengeControllerDeprecated.java @@ -0,0 +1,61 @@ +package sopt.org.hmh.domain.challenge.controller; + +import static sopt.org.hmh.domain.challenge.dto.request.NewChallengeOrder.createNextChallengeOrder; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sopt.org.hmh.domain.challenge.domain.exception.ChallengeSuccess; +import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; +import sopt.org.hmh.domain.challenge.dto.response.ChallengeResponse; +import sopt.org.hmh.domain.challenge.dto.response.DailyChallengeResponse; +import sopt.org.hmh.domain.challenge.service.ChallengeFacade; +import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@Deprecated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/challenge") +public class ChallengeControllerDeprecated implements ChallengeApiDeprecated { + + private final ChallengeFacade challengeFacade; + + @PostMapping + @Override + @Deprecated + public ResponseEntity> orderAddChallenge( + @UserId final Long userId, + @RequestHeader("OS") final String os, + @RequestBody @Valid final ChallengeRequest request) { + challengeFacade.startNewChallenge(createNextChallengeOrder(request, userId, os, "Asia/Seoul")); + + return ResponseEntity + .status(ChallengeSuccess.ADD_CHALLENGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(ChallengeSuccess.ADD_CHALLENGE_SUCCESS, new EmptyJsonResponse())); + } + + @GetMapping + @Override + @Deprecated + public ResponseEntity> orderGetChallenge( + @UserId final Long userId) { + return ResponseEntity + .status(ChallengeSuccess.GET_CHALLENGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(ChallengeSuccess.GET_CHALLENGE_SUCCESS, + challengeFacade.getCurrentChallengeInfo(userId, "Asia/Seoul"))); + } + + @GetMapping("/home") + @Override + @Deprecated + public ResponseEntity> orderGetDailyChallenge( + @UserId final Long userId) { + return ResponseEntity + .status(ChallengeSuccess.GET_DAILY_CHALLENGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(ChallengeSuccess.GET_DAILY_CHALLENGE_SUCCESS, + challengeFacade.getDailyChallengeInfo(userId, "Asia/Seoul"))); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/domain/Challenge.java b/src/main/java/sopt/org/hmh/domain/challenge/domain/Challenge.java index a33a1f61..53ce7817 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/domain/Challenge.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/domain/Challenge.java @@ -4,6 +4,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -11,7 +12,6 @@ import sopt.org.hmh.domain.app.domain.ChallengeApp; import sopt.org.hmh.global.common.domain.BaseTimeEntity; import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; -import java.util.ArrayList; import java.util.List; @Entity @@ -32,6 +32,9 @@ public class Challenge extends BaseTimeEntity { @NotNull(message = "목표 시간은 null일 수 없습니다.") private Long goalTime; + @NotNull(message = "시작 날짜는 null일 수 없습니다.") + private LocalDate startDate; + @OneToMany(mappedBy = "challenge", cascade = CascadeType.REMOVE, orphanRemoval = true) private List apps; @@ -39,10 +42,11 @@ public class Challenge extends BaseTimeEntity { private List historyDailyChallenges; @Builder - private Challenge(Integer period, Long userId, Long goalTime, List apps) { + private Challenge(Integer period, Long userId, Long goalTime, List apps, LocalDate startDate) { this.period = period; this.userId = userId; this.goalTime = goalTime; this.apps = apps; + this.startDate = startDate; } } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/dto/request/NewChallengeOrder.java b/src/main/java/sopt/org/hmh/domain/challenge/dto/request/NewChallengeOrder.java new file mode 100644 index 00000000..61cb274e --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/challenge/dto/request/NewChallengeOrder.java @@ -0,0 +1,52 @@ +package sopt.org.hmh.domain.challenge.dto.request; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import sopt.org.hmh.domain.app.domain.ChallengeApp; +import sopt.org.hmh.domain.app.dto.request.ChallengeAppRequest; +import sopt.org.hmh.domain.challenge.domain.Challenge; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class NewChallengeOrder { + + private final ChallengeRequest challengeRequest; + private final List challengeAppRequests; + private final Long userId; + private final String os; + private final ZoneId zoneId; + private final boolean isFirstChallenge; + + public static NewChallengeOrder createFirstChallengeOrder( + ChallengeRequest challengeRequest, List challengeAppRequests, + Long userId, String os, String timeZone) { + return new NewChallengeOrder(challengeRequest, challengeAppRequests, + userId, os, ZoneId.of(timeZone), true); + } + + public static NewChallengeOrder createNextChallengeOrder( + ChallengeRequest challengeRequest, Long userId, String os, String timeZone + ) { + return new NewChallengeOrder( + challengeRequest, List.of(), userId, os, ZoneId.of(timeZone), false); + } + + public Challenge toChallengeEntity() { + return Challenge.builder() + .period(challengeRequest.period()) + .userId(userId) + .goalTime(challengeRequest.goalTime()) + .startDate(LocalDate.now(zoneId)) + .build(); + } + + public List toChallengeAppEntities(Challenge challenge) { + return challengeAppRequests.stream() + .map(challengeAppRequest -> challengeAppRequest.toEntity(challenge, os)) + .toList(); + } +} diff --git a/src/main/java/sopt/org/hmh/domain/challenge/dto/response/ChallengeResponse.java b/src/main/java/sopt/org/hmh/domain/challenge/dto/response/ChallengeResponse.java index 62406901..cebed4f1 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/dto/response/ChallengeResponse.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/dto/response/ChallengeResponse.java @@ -1,7 +1,12 @@ package sopt.org.hmh.domain.challenge.dto.response; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import lombok.Builder; import sopt.org.hmh.domain.app.dto.response.ChallengeAppResponse; +import sopt.org.hmh.domain.challenge.domain.Challenge; +import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; import sopt.org.hmh.domain.dailychallenge.domain.Status; import java.util.List; @@ -11,8 +16,28 @@ public record ChallengeResponse( Integer period, List statuses, Integer todayIndex, - String startDate, + LocalDate startDate, Long goalTime, List apps ) { + public static ChallengeResponse of(Challenge challenge, String timeZone) { + return ChallengeResponse.builder() + .period(challenge.getPeriod()) + .statuses(challenge.getHistoryDailyChallenges() + .stream() + .map(DailyChallenge::getStatus) + .toList()) + .todayIndex(calculateTodayIndex(challenge, LocalDate.now(ZoneId.of(timeZone)))) + .startDate(challenge.getStartDate()) + .goalTime(challenge.getGoalTime()) + .apps(challenge.getApps().stream() + .map(app -> new ChallengeAppResponse(app.getAppCode(), app.getGoalTime())).toList()) + .build(); + } + + private static Integer calculateTodayIndex(Challenge challenge, LocalDate now) { + final int COMPLETED_CHALLENGE_INDEX = -1; + int daysBetween = (int) ChronoUnit.DAYS.between(challenge.getStartDate(), now); + return (daysBetween >= challenge.getPeriod()) ? COMPLETED_CHALLENGE_INDEX : daysBetween; + } } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/dto/response/DailyChallengeResponse.java b/src/main/java/sopt/org/hmh/domain/challenge/dto/response/DailyChallengeResponse.java index 35e4ef40..7a47e49c 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/dto/response/DailyChallengeResponse.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/dto/response/DailyChallengeResponse.java @@ -1,5 +1,6 @@ package sopt.org.hmh.domain.challenge.dto.response; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import sopt.org.hmh.domain.app.dto.response.ChallengeAppResponse; import sopt.org.hmh.domain.dailychallenge.domain.Status; @@ -8,8 +9,8 @@ @Builder public record DailyChallengeResponse( - Status status, - Long goalTime, + @NotNull(message = "상태 값은 null일 수 없습니다.") Status status, + @NotNull(message = "목표시간은 null일 수 없습니다.") Long goalTime, List apps ) { } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeJpaRepository.java b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeJpaRepository.java new file mode 100644 index 00000000..452aaeb0 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeJpaRepository.java @@ -0,0 +1,15 @@ +package sopt.org.hmh.domain.challenge.repository; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import sopt.org.hmh.domain.challenge.domain.Challenge; + +public interface ChallengeJpaRepository extends JpaRepository { + + Optional findById(Long id); + + void deleteByUserIdIn(List userId); + + void deleteByUserId(Long userId); +} diff --git a/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepository.java b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepository.java index 1126f585..23972380 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepository.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepository.java @@ -2,14 +2,14 @@ import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; import sopt.org.hmh.domain.challenge.domain.Challenge; -public interface ChallengeRepository extends JpaRepository { - +public interface ChallengeRepository { Optional findById(Long id); void deleteByUserIdIn(List userId); void deleteByUserId(Long userId); + + Challenge save(Challenge challenge); } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepositoryImpl.java b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepositoryImpl.java new file mode 100644 index 00000000..d4f69d3d --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/challenge/repository/ChallengeRepositoryImpl.java @@ -0,0 +1,35 @@ +package sopt.org.hmh.domain.challenge.repository; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import sopt.org.hmh.domain.challenge.domain.Challenge; + +@Repository +@RequiredArgsConstructor +public class ChallengeRepositoryImpl implements ChallengeRepository{ + + private final ChallengeJpaRepository challengeJpaRepository; + + @Override + public Challenge save(Challenge challenge) { + return challengeJpaRepository.save(challenge); + } + + @Override + public Optional findById(Long id) { + return challengeJpaRepository.findById(id); + } + + @Override + public void deleteByUserIdIn(List userId) { + challengeJpaRepository.deleteByUserIdIn(userId); + } + + @Override + public void deleteByUserId(Long userId) { + challengeJpaRepository.deleteByUserId(userId); + } + +} diff --git a/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeFacade.java b/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeFacade.java index 64fd461a..fe3b415e 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeFacade.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeFacade.java @@ -1,5 +1,7 @@ package sopt.org.hmh.domain.challenge.service; +import java.time.LocalDate; +import java.time.ZoneId; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -7,18 +9,12 @@ import sopt.org.hmh.domain.app.dto.response.ChallengeAppResponse; import sopt.org.hmh.domain.app.service.ChallengeAppService; import sopt.org.hmh.domain.challenge.domain.Challenge; -import sopt.org.hmh.domain.challenge.dto.request.ChallengeRequest; -import sopt.org.hmh.domain.challenge.dto.request.ChallengeSignUpRequest; +import sopt.org.hmh.domain.challenge.dto.request.NewChallengeOrder; import sopt.org.hmh.domain.challenge.dto.response.ChallengeResponse; import sopt.org.hmh.domain.challenge.dto.response.DailyChallengeResponse; import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; import sopt.org.hmh.domain.dailychallenge.service.DailyChallengeService; -import sopt.org.hmh.domain.user.domain.User; import sopt.org.hmh.domain.user.service.UserService; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; @Service @@ -31,72 +27,51 @@ public class ChallengeFacade { private final ChallengeAppService challengeAppService; @Transactional - public void startNewChallengeByPreviousChallenge(Long userId, ChallengeRequest challengeRequest, String os) { - User user = userService.findByIdOrThrowException(userId); - Long previousChallengeId = userService.getCurrentChallengeIdByUser(user); + public void startNewChallenge(NewChallengeOrder newChallengeOrder) { + Challenge newChallenge = challengeService.addChallenge(newChallengeOrder.toChallengeEntity()); + userService.changeCurrentChallengeIdByUserId(newChallengeOrder.getUserId(), newChallenge.getId()); - Challenge newChallenge = challengeService.addChallengeAndUpdateUserCurrentChallenge( - challengeRequest.toEntity(userId), user); + dailyChallengeService.addDailyChallenge(newChallenge); - dailyChallengeService.addDailyChallenge(userId, newChallenge); - - challengeAppService.addAppsByPreviousChallengeApp(os, previousChallengeId, newChallenge); + this.addAppsByNewChallengeOrder(newChallengeOrder, newChallenge); } - @Transactional - public void startFirstChallengeWithChallengeSignUpRequest( - ChallengeSignUpRequest challengeSignUpRequest, User user, String os) { - Long userId = user.getId(); - - Challenge newChallenge = challengeService.addChallengeAndUpdateUserCurrentChallenge( - challengeSignUpRequest.toChallengeRequest().toEntity(userId), user); - - dailyChallengeService.addDailyChallenge(userId, newChallenge); - - challengeAppService.addApps( - challengeSignUpRequest.apps().stream() - .map(challengeAppRequest -> challengeAppRequest.toEntity(newChallenge, os)) - .toList() - ); + private void addAppsByNewChallengeOrder(NewChallengeOrder newChallengeOrder, Challenge newChallenge) { + if (newChallengeOrder.isFirstChallenge()) { + challengeAppService.addApps(newChallengeOrder.toChallengeAppEntities(newChallenge)); + return; + } + Long previousChallengeId = userService.getCurrentChallengeIdByUserId(newChallengeOrder.getUserId()); + challengeAppService.addAppsByPreviousChallengeApp(newChallengeOrder.getOs(), previousChallengeId, newChallenge); } @Transactional(readOnly = true) - public ChallengeResponse getCurrentChallengeInfo(Long userId) { - Challenge challenge = this.findCurrentChallengeByUserId(userId); - - return ChallengeResponse.builder() - .period(challenge.getPeriod()) - .statuses(challenge.getHistoryDailyChallenges() - .stream() - .map(DailyChallenge::getStatus) - .toList()) - .todayIndex(this.calculateTodayIndex(challenge.getCreatedAt(), challenge.getPeriod())) - .startDate(challenge.getCreatedAt().toLocalDate().toString()) - .goalTime(challenge.getGoalTime()) - .apps(challenge.getApps().stream() - .map(app -> new ChallengeAppResponse(app.getAppCode(), app.getGoalTime())).toList()) - .build(); + public ChallengeResponse getCurrentChallengeInfo(Long userId, String timeZone) { + Challenge currentChallenge = this.findCurrentChallengeByUserId(userId); + return ChallengeResponse.of(currentChallenge, timeZone); } @Transactional(readOnly = true) - public DailyChallengeResponse getDailyChallengeInfo(Long userId) { + public DailyChallengeResponse getDailyChallengeInfo(Long userId, String timeZone) { Challenge challenge = this.findCurrentChallengeByUserId(userId); + LocalDate localDateNow = LocalDate.now(ZoneId.of(timeZone)); + DailyChallenge todayChallenge = + dailyChallengeService.findDailyChallengeByChallengeAndChallengeDate(challenge, localDateNow); return DailyChallengeResponse.builder() + .status(todayChallenge.getStatus()) .goalTime(challenge.getGoalTime()) .apps(challenge.getApps().stream() - .map(app -> new ChallengeAppResponse(app.getAppCode(), app.getGoalTime())).toList()) + .map(challengeApp -> new ChallengeAppResponse( + challengeApp.getAppCode(), + challengeApp.getGoalTime() + )).toList()) .build(); } public Challenge findCurrentChallengeByUserId(Long userId) { - User user = userService.findByIdOrThrowException(userId); - return challengeService.findByIdOrElseThrow(user.getCurrentChallengeId()); - } - - private Integer calculateTodayIndex(LocalDateTime challengeCreateAt, int period) { - int daysBetween = (int) ChronoUnit.DAYS.between(challengeCreateAt.toLocalDate(), LocalDate.now()); - return (daysBetween >= period) ? -1 : daysBetween; + Long currentChallengeId = userService.getCurrentChallengeIdByUserId(userId); + return challengeService.findByIdOrElseThrow(currentChallengeId); } @Transactional diff --git a/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeService.java b/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeService.java index 2576fea1..1a986df7 100644 --- a/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeService.java +++ b/src/main/java/sopt/org/hmh/domain/challenge/service/ChallengeService.java @@ -8,7 +8,6 @@ import sopt.org.hmh.domain.challenge.domain.exception.ChallengeError; import sopt.org.hmh.domain.challenge.domain.exception.ChallengeException; import sopt.org.hmh.domain.challenge.repository.ChallengeRepository; -import sopt.org.hmh.domain.user.domain.User; @Service @RequiredArgsConstructor @@ -33,13 +32,11 @@ public List getCurrentChallengeAppByChallengeId(Long challengeId) return this.findByIdOrElseThrow(challengeId).getApps(); } - public Challenge addChallengeAndUpdateUserCurrentChallenge(Challenge challenge, User user) { - Challenge newChallenge = challengeRepository.save(challenge); - user.changeCurrentChallengeId(newChallenge.getId()); - return newChallenge; + public Challenge addChallenge(Challenge challenge) { + return challengeRepository.save(challenge); } public Integer getChallengePeriod(Long challengeId) { - return findByIdOrElseThrow(challengeId).getPeriod(); + return this.findByIdOrElseThrow(challengeId).getPeriod(); } } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApi.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApi.java index 62c85780..486d1562 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApi.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApi.java @@ -7,9 +7,12 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeListRequest; import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeStatusListRequest; import sopt.org.hmh.global.auth.jwt.JwtConstants; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; import sopt.org.hmh.global.common.response.EmptyJsonResponse; @@ -18,11 +21,11 @@ public interface DailyChallengeApi { @Operation( - summary = "사용시간과 함께 일별챌린지 정보 업데이트 요청하는 API", + summary = "완료된 챌린지 정보 리스트 전송 API (Android)", responses = { @ApiResponse( responseCode = "200", - description = "일별 챌린지 정보 업데이트에 성공했습니다."), + description = "완료된 챌린지 정보 리스트 전송에 성공했습니다."), @ApiResponse( responseCode = "400", description = "잘못된 요청입니다.", @@ -33,14 +36,29 @@ public interface DailyChallengeApi { content = @Content)}) ResponseEntity> orderAddHistoryDailyChallenge( @Parameter(hidden = true) final Long userId, - final String os, - final FinishedDailyChallengeListRequest request + @RequestHeader(CustomHeaderType.TIME_ZONE) final String os, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, + @RequestBody final FinishedDailyChallengeListRequest request ); + @Operation( + summary = "챌린지 성공 여부 리스트 전송 API (iOS)", + responses = { + @ApiResponse( + responseCode = "200", + description = "챌린지 성공 여부 리스트 전송에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) ResponseEntity> orderChangeStatusDailyChallenge( @Parameter(hidden = true) final Long userId, - final String os, - final FinishedDailyChallengeStatusListRequest request + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, + @RequestBody final FinishedDailyChallengeStatusListRequest request ); } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApiDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApiDeprecated.java new file mode 100644 index 00000000..a5cc76a4 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeApiDeprecated.java @@ -0,0 +1,46 @@ +package sopt.org.hmh.domain.dailychallenge.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeListRequestDeprecated; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeStatusListRequestDeprecated; +import sopt.org.hmh.global.auth.jwt.JwtConstants; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@Tag(name = "일별챌린지 관련 API") +@Deprecated +@SecurityRequirement(name = JwtConstants.AUTHORIZATION) +public interface DailyChallengeApiDeprecated { + + @Operation( + summary = "사용시간과 함께 일별챌린지 정보 업데이트 요청하는 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "일별 챌린지 정보 업데이트에 성공했습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderAddHistoryDailyChallenge( + @Parameter(hidden = true) final Long userId, + final String os, + final FinishedDailyChallengeListRequestDeprecated request + ); + + ResponseEntity> orderChangeStatusDailyChallenge( + @Parameter(hidden = true) final Long userId, + final String os, + final FinishedDailyChallengeStatusListRequestDeprecated request + ); + } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeController.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeController.java index 63c045c5..06e6a6fc 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeController.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeController.java @@ -9,12 +9,13 @@ import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeStatusListRequest; import sopt.org.hmh.domain.dailychallenge.service.DailyChallengeFacade; import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; import sopt.org.hmh.global.common.response.EmptyJsonResponse; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/challenge/daily") +@RequestMapping("/api/v2/challenge/daily") public class DailyChallengeController implements DailyChallengeApi { private final DailyChallengeFacade dailyChallengeFacade; @@ -23,7 +24,8 @@ public class DailyChallengeController implements DailyChallengeApi { @PostMapping("/finish") public ResponseEntity> orderAddHistoryDailyChallenge( @UserId final Long userId, - @RequestHeader("OS") final String os, + @RequestHeader(CustomHeaderType.OS) final String os, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, @RequestBody @Valid final FinishedDailyChallengeListRequest request ) { dailyChallengeFacade.addFinishedDailyChallengeHistory(userId, request, os); @@ -36,7 +38,7 @@ public ResponseEntity> orderAddHistoryDailyChall @PostMapping("/success") public ResponseEntity> orderChangeStatusDailyChallenge( @UserId final Long userId, - @RequestHeader("OS") final String os, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone, @RequestBody final FinishedDailyChallengeStatusListRequest request ) { dailyChallengeFacade.changeDailyChallengeStatusByIsSuccess(userId, request); diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeControllerDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeControllerDeprecated.java new file mode 100644 index 00000000..a789e286 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/controller/DailyChallengeControllerDeprecated.java @@ -0,0 +1,54 @@ +package sopt.org.hmh.domain.dailychallenge.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sopt.org.hmh.domain.dailychallenge.domain.exception.DailyChallengeSuccess; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeListRequestDeprecated; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeStatusListRequestDeprecated; +import sopt.org.hmh.domain.dailychallenge.service.DailyChallengeFacadeDeprecated; +import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.response.BaseResponse; +import sopt.org.hmh.global.common.response.EmptyJsonResponse; + +@Deprecated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/challenge/daily") +public class DailyChallengeControllerDeprecated implements DailyChallengeApiDeprecated { + + private final DailyChallengeFacadeDeprecated dailyChallengeFacadeDeprecated; + + @Override + @PostMapping("/finish") + public ResponseEntity> orderAddHistoryDailyChallenge( + @UserId final Long userId, + @RequestHeader("OS") final String os, + @RequestBody @Valid final FinishedDailyChallengeListRequestDeprecated request + ) { + dailyChallengeFacadeDeprecated.addFinishedDailyChallengeHistory(userId, request, os); + return ResponseEntity + .status(DailyChallengeSuccess.SEND_FINISHED_DAILY_CHALLENGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(DailyChallengeSuccess.SEND_FINISHED_DAILY_CHALLENGE_SUCCESS, + new EmptyJsonResponse())); + } + + @Override + @PostMapping("/success") + public ResponseEntity> orderChangeStatusDailyChallenge( + @UserId final Long userId, + @RequestHeader("OS") final String os, + @RequestBody final FinishedDailyChallengeStatusListRequestDeprecated request + ) { + dailyChallengeFacadeDeprecated.changeDailyChallengeStatusByIsSuccess(userId, request); + return ResponseEntity + .status(DailyChallengeSuccess.SEND_FINISHED_DAILY_CHALLENGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(DailyChallengeSuccess.SEND_FINISHED_DAILY_CHALLENGE_SUCCESS, + new EmptyJsonResponse())); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/domain/exception/DailyChallengeError.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/domain/exception/DailyChallengeError.java index 60c5b7aa..4333ca2c 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/domain/exception/DailyChallengeError.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/domain/exception/DailyChallengeError.java @@ -8,6 +8,7 @@ public enum DailyChallengeError implements ErrorBase { DAILY_CHALLENGE_NOT_FOUND(HttpStatus.NOT_FOUND, "일별 챌린지를 찾을 수 없습니다."), + DAILY_CHALLENGE_PERIOD_INDEX_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인덱스의 일별 챌린지를 찾을 수 없습니다."), DAILY_CHALLENGE_ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "이미 처리된 일별 챌린지입니다.") ; diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeListRequestDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeListRequestDeprecated.java new file mode 100644 index 00000000..fca2cd29 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeListRequestDeprecated.java @@ -0,0 +1,11 @@ +package sopt.org.hmh.domain.dailychallenge.dto.request; + +import jakarta.validation.Valid; +import java.util.List; + +@Deprecated +public record FinishedDailyChallengeListRequestDeprecated( + List<@Valid FinishedDailyChallengeRequestDeprecated> finishedDailyChallenges +) { + +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequest.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequest.java index 540bc927..774ae010 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequest.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequest.java @@ -2,13 +2,12 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import java.util.List; import sopt.org.hmh.domain.app.dto.request.HistoryAppRequest; public record FinishedDailyChallengeRequest( - @NotNull(message = "챌린지 날짜는 null일 수 없습니다.") - LocalDate challengeDate, - List<@Valid HistoryAppRequest> apps + @NotNull(message = "챌린지 기간 인덱스는 null일 수 없습니다.") + Integer challengePeriodIndex, + List<@Valid HistoryAppRequest> apps ) { } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequestDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequestDeprecated.java new file mode 100644 index 00000000..490a9052 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeRequestDeprecated.java @@ -0,0 +1,15 @@ +package sopt.org.hmh.domain.dailychallenge.dto.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.util.List; +import sopt.org.hmh.domain.app.dto.request.HistoryAppRequest; + +@Deprecated +public record FinishedDailyChallengeRequestDeprecated( + @NotNull(message = "챌린지 날짜는 null일 수 없습니다.") + LocalDate challengeDate, + List<@Valid HistoryAppRequest> apps +) { +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequest.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequest.java index d5692b78..c49ae992 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequest.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequest.java @@ -1,9 +1,9 @@ package sopt.org.hmh.domain.dailychallenge.dto.request; +import jakarta.validation.Valid; import java.util.List; public record FinishedDailyChallengeStatusListRequest( - List finishedDailyChallenges + List<@Valid FinishedDailyChallengeStatusRequest> finishedDailyChallenges ) { - } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequestDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequestDeprecated.java new file mode 100644 index 00000000..e0c130bc --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusListRequestDeprecated.java @@ -0,0 +1,10 @@ +package sopt.org.hmh.domain.dailychallenge.dto.request; + +import java.util.List; + +@Deprecated +public record FinishedDailyChallengeStatusListRequestDeprecated( + List finishedDailyChallenges +) { + +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequest.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequest.java index a2baadc0..9c9a6f60 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequest.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequest.java @@ -1,10 +1,10 @@ package sopt.org.hmh.domain.dailychallenge.dto.request; -import java.time.LocalDate; +import jakarta.validation.constraints.NotNull; public record FinishedDailyChallengeStatusRequest( - LocalDate challengeDate, + @NotNull(message = "챌린지 기간 인덱스는 null일 수 없습니다.") + Integer challengePeriodIndex, boolean isSuccess ) { - } diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequestDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequestDeprecated.java new file mode 100644 index 00000000..65e65d38 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/dto/request/FinishedDailyChallengeStatusRequestDeprecated.java @@ -0,0 +1,11 @@ +package sopt.org.hmh.domain.dailychallenge.dto.request; + +import java.time.LocalDate; + +@Deprecated +public record FinishedDailyChallengeStatusRequestDeprecated( + LocalDate challengeDate, + boolean isSuccess +) { + +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeJpaRepository.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeJpaRepository.java new file mode 100644 index 00000000..56f1ce01 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeJpaRepository.java @@ -0,0 +1,14 @@ +package sopt.org.hmh.domain.dailychallenge.repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; + +public interface DailyChallengeJpaRepository extends JpaRepository { + + Optional findByChallengeDateAndUserId(LocalDate challengeDate, Long userId); + + List findAllByChallengeIdOrderByChallengeDate(Long challengeId); +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepository.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepository.java index ca5bd335..b7614769 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepository.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepository.java @@ -3,12 +3,14 @@ import java.time.LocalDate; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; -public interface DailyChallengeRepository extends JpaRepository { +public interface DailyChallengeRepository { + + void saveAll(List dailyChallengeByChallengePeriod); Optional findByChallengeDateAndUserId(LocalDate challengeDate, Long userId); - List findAllByChallengeId(Long challengeId); + List findAllByChallengeIdOrderByChallengeDate(Long challengeId); + } \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepositoryImpl.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepositoryImpl.java new file mode 100644 index 00000000..6f6ba996 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/repository/DailyChallengeRepositoryImpl.java @@ -0,0 +1,30 @@ +package sopt.org.hmh.domain.dailychallenge.repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; + +@Repository +@RequiredArgsConstructor +public class DailyChallengeRepositoryImpl implements DailyChallengeRepository { + + private final DailyChallengeJpaRepository dailyChallengeJpaRepository; + + @Override + public void saveAll(List dailyChallengeByChallengePeriod) { + dailyChallengeJpaRepository.saveAll(dailyChallengeByChallengePeriod); + } + + @Override + public Optional findByChallengeDateAndUserId(LocalDate challengeDate, Long userId) { + return dailyChallengeJpaRepository.findByChallengeDateAndUserId(challengeDate, userId); + } + + @Override + public List findAllByChallengeIdOrderByChallengeDate(Long challengeId) { + return dailyChallengeJpaRepository.findAllByChallengeIdOrderByChallengeDate(challengeId); + } +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacade.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacade.java index b4ba716e..f6f3bbbf 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacade.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacade.java @@ -30,7 +30,8 @@ public void addFinishedDailyChallengeHistory(Long userId, FinishedDailyChallenge requests.finishedDailyChallenges().forEach(request -> { DailyChallenge dailyChallenge = - dailyChallengeService.findByChallengeDateAndUserIdOrThrowException(request.challengeDate(), userId); + dailyChallengeService.findDailyChallengeByChallengeIdAndChallengePeriodIndex( + currentChallengeId, request.challengePeriodIndex()); dailyChallengeService.changeStatusByCurrentStatus(dailyChallenge); historyAppService.addHistoryApp(currentChallengeApps, request.apps(), dailyChallenge, os); }); @@ -38,9 +39,11 @@ public void addFinishedDailyChallengeHistory(Long userId, FinishedDailyChallenge @Transactional public void changeDailyChallengeStatusByIsSuccess(Long userId, FinishedDailyChallengeStatusListRequest requests) { + Long currentChallengeId = userService.getCurrentChallengeIdByUserId(userId); requests.finishedDailyChallenges().forEach(request -> { DailyChallenge dailyChallenge = - dailyChallengeService.findByChallengeDateAndUserIdOrThrowException(request.challengeDate(), userId); + dailyChallengeService.findDailyChallengeByChallengeIdAndChallengePeriodIndex( + currentChallengeId, request.challengePeriodIndex()); if (request.isSuccess()) { dailyChallengeService.validateDailyChallengeStatus(dailyChallenge.getStatus(), List.of(Status.NONE)); dailyChallenge.changeStatus(Status.UNEARNED); diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacadeDeprecated.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacadeDeprecated.java new file mode 100644 index 00000000..b9be5f99 --- /dev/null +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeFacadeDeprecated.java @@ -0,0 +1,57 @@ +package sopt.org.hmh.domain.dailychallenge.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sopt.org.hmh.domain.app.domain.ChallengeApp; +import sopt.org.hmh.domain.app.service.HistoryAppService; +import sopt.org.hmh.domain.challenge.service.ChallengeService; +import sopt.org.hmh.domain.dailychallenge.domain.DailyChallenge; +import sopt.org.hmh.domain.dailychallenge.domain.Status; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeListRequestDeprecated; +import sopt.org.hmh.domain.dailychallenge.dto.request.FinishedDailyChallengeStatusListRequestDeprecated; +import sopt.org.hmh.domain.user.service.UserService; + +@Service +@Deprecated +@RequiredArgsConstructor +public class DailyChallengeFacadeDeprecated { + + private final DailyChallengeService dailyChallengeService; + private final HistoryAppService historyAppService; + private final ChallengeService challengeService; + private final UserService userService; + + @Transactional + @Deprecated + public void addFinishedDailyChallengeHistory(Long userId, FinishedDailyChallengeListRequestDeprecated requests, String os) { + Long currentChallengeId = userService.getCurrentChallengeIdByUserId(userId); + List currentChallengeApps = + challengeService.getCurrentChallengeAppByChallengeId(currentChallengeId); + + requests.finishedDailyChallenges().forEach(request -> { + DailyChallenge dailyChallenge = + dailyChallengeService.findDailyChallengeByChallengeDateAndUserIdOrElseThrow(request.challengeDate(), userId); + dailyChallengeService.changeStatusByCurrentStatus(dailyChallenge); + historyAppService.addHistoryApp(currentChallengeApps, request.apps(), dailyChallenge, os); + }); + } + + @Transactional + @Deprecated + public void changeDailyChallengeStatusByIsSuccess(Long userId, FinishedDailyChallengeStatusListRequestDeprecated requests) { + requests.finishedDailyChallenges().forEach(request -> { + DailyChallenge dailyChallenge = + dailyChallengeService.findDailyChallengeByChallengeDateAndUserIdOrElseThrow(request.challengeDate(), userId); + if (request.isSuccess()) { + dailyChallengeService.validateDailyChallengeStatus(dailyChallenge.getStatus(), List.of(Status.NONE)); + dailyChallenge.changeStatus(Status.UNEARNED); + } else { + dailyChallengeService.validateDailyChallengeStatus( + dailyChallenge.getStatus(), List.of(Status.NONE, Status.FAILURE)); + dailyChallenge.changeStatus(Status.FAILURE); + } + }); + } +} diff --git a/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeService.java b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeService.java index 1932deeb..739f15a1 100644 --- a/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeService.java +++ b/src/main/java/sopt/org/hmh/domain/dailychallenge/service/DailyChallengeService.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.Optional; import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,11 +19,24 @@ public class DailyChallengeService { private final DailyChallengeRepository dailyChallengeRepository; - public DailyChallenge findByChallengeDateAndUserIdOrThrowException(LocalDate challengeDate, Long userId) { + public DailyChallenge findDailyChallengeByChallengeDateAndUserIdOrElseThrow(LocalDate challengeDate, Long userId) { return dailyChallengeRepository.findByChallengeDateAndUserId(challengeDate, userId) .orElseThrow(() -> new DailyChallengeException(DailyChallengeError.DAILY_CHALLENGE_NOT_FOUND)); } + public DailyChallenge findDailyChallengeByChallengeIdAndChallengePeriodIndex(Long challengeId, Integer challengePeriodIndex) { + return Optional.ofNullable( + dailyChallengeRepository.findAllByChallengeIdOrderByChallengeDate(challengeId).get(challengePeriodIndex) + ).orElseThrow(() -> new DailyChallengeException(DailyChallengeError.DAILY_CHALLENGE_PERIOD_INDEX_NOT_FOUND)); + } + + public DailyChallenge findDailyChallengeByChallengeAndChallengeDate(Challenge challenge, LocalDate challengeDate) { + return challenge.getHistoryDailyChallenges().stream() + .filter(dailyChallenge -> dailyChallenge.getChallengeDate().equals(challengeDate)) + .findFirst() + .orElseThrow(() -> new DailyChallengeException(DailyChallengeError.DAILY_CHALLENGE_NOT_FOUND)); + } + public void validateDailyChallengeStatus(Status dailyChallengeStatus, List expectedStatuses) { if (!expectedStatuses.contains(dailyChallengeStatus)) { throw new DailyChallengeException(DailyChallengeError.DAILY_CHALLENGE_ALREADY_PROCESSED); @@ -44,23 +58,28 @@ private void handleAlreadyProcessedDailyChallenge(DailyChallenge dailyChallenge) throw new DailyChallengeException(DailyChallengeError.DAILY_CHALLENGE_ALREADY_PROCESSED); } - public void addDailyChallenge(Long userId, Challenge challenge) { - LocalDate startDate = challenge.getCreatedAt().toLocalDate(); // TODO: startDate CreatedAt에서 가져오지 않고 새로 만들기 - dailyChallengeRepository.saveAll(IntStream.range(0, challenge.getPeriod()) + public void addDailyChallenge(Challenge challenge) { + dailyChallengeRepository.saveAll(createDailyChallengeByChallengePeriod(challenge)); + } + + private List createDailyChallengeByChallengePeriod(Challenge challenge) { + LocalDate startDate = challenge.getStartDate(); + Long userId = challenge.getUserId(); + return IntStream.range(0, challenge.getPeriod()) .mapToObj(i -> DailyChallenge.builder() .challengeDate(startDate.plusDays(i)) .challenge(challenge) .userId(userId) .goalTime(challenge.getGoalTime()).build()) - .toList()); + .toList(); } - public List getDailyChallengesByChallengeId(Long challengeId) { - return dailyChallengeRepository.findAllByChallengeId(challengeId); + public List getDailyChallengesByChallengeIdOrderByChallengeDate(Long challengeId) { + return dailyChallengeRepository.findAllByChallengeIdOrderByChallengeDate(challengeId); } public void changeInfoOfDailyChallenges(Long challengeId, List statuses, LocalDate challengeDate) { - List dailyChallenges = getDailyChallengesByChallengeId(challengeId); + List dailyChallenges = this.getDailyChallengesByChallengeIdOrderByChallengeDate(challengeId); changeStatusOfDailyChallenges(dailyChallenges, statuses); changeChallengeDateOfDailyChallenges(dailyChallenges, challengeDate); } diff --git a/src/main/java/sopt/org/hmh/domain/point/controller/PointApi.java b/src/main/java/sopt/org/hmh/domain/point/controller/PointApi.java index 45bbb357..a7db78a0 100644 --- a/src/main/java/sopt/org/hmh/domain/point/controller/PointApi.java +++ b/src/main/java/sopt/org/hmh/domain/point/controller/PointApi.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestHeader; import sopt.org.hmh.domain.challenge.dto.request.ChallengeDateRequest; import sopt.org.hmh.domain.point.dto.response.ChallengePointStatusListResponse; import sopt.org.hmh.domain.point.dto.response.EarnPointResponse; @@ -14,6 +15,7 @@ import sopt.org.hmh.domain.point.dto.response.UsagePointResponse; import sopt.org.hmh.domain.point.dto.response.UsePointResponse; import sopt.org.hmh.global.auth.jwt.JwtConstants; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; @Tag(name = "포인트 관련 API") @@ -37,6 +39,24 @@ public interface PointApi { ResponseEntity> orderGetChallengePointStatusList( @Parameter(hidden = true) Long userId); + @Operation( + summary = "포인트 사용 API Deprecated", + responses = { + @ApiResponse( + responseCode = "200", + description = "포인트 사용에 성공하였습니다."), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청입니다.", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류입니다.", + content = @Content)}) + ResponseEntity> orderUsagePointAndChallengeFailedDeprecated( + @Parameter(hidden = true) Long userId, + ChallengeDateRequest challengeDateRequest); + @Operation( summary = "포인트 사용 API", responses = { @@ -52,7 +72,8 @@ ResponseEntity> orderGetChallenge description = "서버 내부 오류입니다.", content = @Content)}) ResponseEntity> orderUsagePointAndChallengeFailed( - @Parameter(hidden = true) Long userId, ChallengeDateRequest challengeDateRequest); + @Parameter(hidden = true) Long userId, + @RequestHeader(CustomHeaderType.TIME_ZONE) String timeZone); @Operation( summary = "포인트 받기 API", @@ -69,7 +90,8 @@ ResponseEntity> orderUsagePointAndChallengeFailed description = "서버 내부 오류입니다.", content = @Content)}) ResponseEntity> orderEarnPointAndChallengeEarned( - @Parameter(hidden = true) Long userId, ChallengeDateRequest challengeDateRequest); + @Parameter(hidden = true) Long userId, + ChallengeDateRequest challengeDateRequest); @Operation( summary = "사용할 포인트 받기 API", diff --git a/src/main/java/sopt/org/hmh/domain/point/controller/PointController.java b/src/main/java/sopt/org/hmh/domain/point/controller/PointController.java index 7e7b9558..844f65c0 100644 --- a/src/main/java/sopt/org/hmh/domain/point/controller/PointController.java +++ b/src/main/java/sopt/org/hmh/domain/point/controller/PointController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import sopt.org.hmh.domain.challenge.dto.request.ChallengeDateRequest; @@ -14,17 +15,18 @@ import sopt.org.hmh.domain.point.exception.PointSuccess; import sopt.org.hmh.domain.point.service.PointFacade; import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/point") +@RequestMapping("/api") public class PointController implements PointApi { private final PointFacade pointFacade; @Override - @GetMapping("/list") + @GetMapping("/v1/point/list") public ResponseEntity> orderGetChallengePointStatusList( @UserId final Long userId ) { @@ -32,23 +34,35 @@ public ResponseEntity> orderGetCh .status(PointSuccess.GET_CHALLENGE_POINT_STATUS_LIST_SUCCESS.getHttpStatus()) .body(BaseResponse.success(PointSuccess.GET_CHALLENGE_POINT_STATUS_LIST_SUCCESS, pointFacade.getChallengePointStatusList(userId))); + } + @Override + @Deprecated + @PatchMapping("/v1/point/use") + public ResponseEntity> orderUsagePointAndChallengeFailedDeprecated( + @UserId final Long userId, + @RequestBody @Valid final ChallengeDateRequest challengeDateRequest + ) { + return ResponseEntity + .status(PointSuccess.POINT_USAGE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(PointSuccess.POINT_USAGE_SUCCESS, + pointFacade.usePointAndChallengeFailedDeprecated(userId, challengeDateRequest.challengeDate()))); } @Override - @PatchMapping("/use") + @PatchMapping("/v2/point/use") public ResponseEntity> orderUsagePointAndChallengeFailed( @UserId final Long userId, - @RequestBody @Valid final ChallengeDateRequest challengeDateRequest + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone ) { return ResponseEntity .status(PointSuccess.POINT_USAGE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(PointSuccess.POINT_USAGE_SUCCESS, - pointFacade.usePointAndChallengeFailed(userId, challengeDateRequest.challengeDate()))); + pointFacade.usePointAndTodayDailyChallengeFailed(userId, timeZone))); } @Override - @PatchMapping("/earn") + @PatchMapping("/v1/point/earn") public ResponseEntity> orderEarnPointAndChallengeEarned( @UserId final Long userId, @RequestBody final ChallengeDateRequest challengeDateRequest @@ -60,7 +74,7 @@ public ResponseEntity> orderEarnPointAndChalleng } @Override - @GetMapping("/earn") + @GetMapping("/v1/point/earn") public ResponseEntity> orderGetEarnedPoint() { return ResponseEntity .status(PointSuccess.GET_EARNED_POINT_SUCCESS.getHttpStatus()) @@ -69,7 +83,7 @@ public ResponseEntity> orderGetEarnedPoint() { } @Override - @GetMapping("/use") + @GetMapping("/v1/point/use") public ResponseEntity> orderGetUsagePoint() { return ResponseEntity .status(PointSuccess.GET_USAGE_POINT_SUCCESS.getHttpStatus()) diff --git a/src/main/java/sopt/org/hmh/domain/point/service/PointFacade.java b/src/main/java/sopt/org/hmh/domain/point/service/PointFacade.java index 295caa80..06308bd4 100644 --- a/src/main/java/sopt/org/hmh/domain/point/service/PointFacade.java +++ b/src/main/java/sopt/org/hmh/domain/point/service/PointFacade.java @@ -1,6 +1,7 @@ package sopt.org.hmh.domain.point.service; import java.time.LocalDate; +import java.time.ZoneId; import java.util.List; import lombok.RequiredArgsConstructor; @@ -25,9 +26,26 @@ public class PointFacade { private final ChallengeService challengeService; @Transactional - public UsePointResponse usePointAndChallengeFailed(Long userId, LocalDate challengeDate) { + @Deprecated + public UsePointResponse usePointAndChallengeFailedDeprecated(Long userId, LocalDate challengeDate) { DailyChallenge dailyChallenge = - dailyChallengeService.findByChallengeDateAndUserIdOrThrowException(challengeDate, userId); + dailyChallengeService.findDailyChallengeByChallengeDateAndUserIdOrElseThrow(challengeDate, userId); + User user = userService.findByIdOrThrowException(userId); + + dailyChallengeService.validateDailyChallengeStatus(dailyChallenge.getStatus(), List.of(Status.NONE)); + dailyChallenge.changeStatus(Status.FAILURE); + + return new UsePointResponse( + ChallengeConstants.USAGE_POINT, + user.decreasePoint(ChallengeConstants.USAGE_POINT) + ); + } + + @Transactional + public UsePointResponse usePointAndTodayDailyChallengeFailed(Long userId, String timeZone) { + LocalDate challengeDate = LocalDate.now(ZoneId.of(timeZone)); + DailyChallenge dailyChallenge = + dailyChallengeService.findDailyChallengeByChallengeDateAndUserIdOrElseThrow(challengeDate, userId); User user = userService.findByIdOrThrowException(userId); dailyChallengeService.validateDailyChallengeStatus(dailyChallenge.getStatus(), List.of(Status.NONE)); @@ -42,7 +60,7 @@ public UsePointResponse usePointAndChallengeFailed(Long userId, LocalDate challe @Transactional public EarnPointResponse earnPointAndChallengeEarned(Long userId, LocalDate challengeDate) { DailyChallenge dailyChallenge = - dailyChallengeService.findByChallengeDateAndUserIdOrThrowException(challengeDate, userId); + dailyChallengeService.findDailyChallengeByChallengeDateAndUserIdOrElseThrow(challengeDate, userId); User user = userService.findByIdOrThrowException(userId); dailyChallengeService.validateDailyChallengeStatus(dailyChallenge.getStatus(), List.of(Status.UNEARNED)); diff --git a/src/main/java/sopt/org/hmh/domain/user/controller/UserApi.java b/src/main/java/sopt/org/hmh/domain/user/controller/UserApi.java index e390f419..3d0c2bc0 100644 --- a/src/main/java/sopt/org/hmh/domain/user/controller/UserApi.java +++ b/src/main/java/sopt/org/hmh/domain/user/controller/UserApi.java @@ -4,9 +4,11 @@ import io.swagger.v3.oas.annotations.Parameter; import java.time.LocalDate; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestHeader; import sopt.org.hmh.domain.user.dto.request.UserRequest.LockDateRequest; import sopt.org.hmh.domain.user.dto.response.UserResponse.IsLockTodayResponse; import sopt.org.hmh.domain.user.dto.response.UserResponse.UserInfoResponse; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; import sopt.org.hmh.global.common.response.EmptyJsonResponse; @@ -26,10 +28,21 @@ public interface UserApi { @Operation(summary = "당일 잠금 여부 전송") ResponseEntity> orderChangeRecentLockDate( - @Parameter(hidden = true) final Long userId, final LockDateRequest request); + @Parameter(hidden = true) final Long userId, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone); + + @Operation(summary = "당일 잠금 여부 전송") + ResponseEntity> orderChangeRecentLockDateDeprecated( + @Parameter(hidden = true) final Long userId, + final LockDateRequest request); @Operation(summary = "당일 잠금 여부 확인") ResponseEntity> orderGetRecentLockDate( - @Parameter(hidden = true) final Long userId, final LocalDate lockCheckDate); + @Parameter(hidden = true) final Long userId, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone); + @Operation(summary = "당일 잠금 여부 확인") + ResponseEntity> orderGetRecentLockDateDeprecated( + @Parameter(hidden = true) final Long userId, + final LocalDate lockCheckDate); } diff --git a/src/main/java/sopt/org/hmh/domain/user/controller/UserController.java b/src/main/java/sopt/org/hmh/domain/user/controller/UserController.java index 9e5d80f9..cba390a6 100644 --- a/src/main/java/sopt/org/hmh/domain/user/controller/UserController.java +++ b/src/main/java/sopt/org/hmh/domain/user/controller/UserController.java @@ -9,6 +9,7 @@ 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.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -18,17 +19,18 @@ import sopt.org.hmh.domain.user.dto.response.UserResponse.UserInfoResponse; import sopt.org.hmh.domain.user.service.UserService; import sopt.org.hmh.global.auth.UserId; +import sopt.org.hmh.global.common.constant.CustomHeaderType; import sopt.org.hmh.global.common.response.BaseResponse; import sopt.org.hmh.global.common.response.EmptyJsonResponse; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/user") +@RequestMapping("/api") public class UserController implements UserApi { private final UserService userService; - @PostMapping("/logout") + @PostMapping("/v1/user/logout") @Override public ResponseEntity> orderLogout() { return ResponseEntity @@ -44,7 +46,7 @@ public ResponseEntity> orderGetUserInfo(@UserId f .body(BaseResponse.success(UserSuccess.GET_USER_INFO_SUCCESS, userService.getUserInfo(userId))); } - @GetMapping("/point") + @GetMapping("/v1/user/point") @Override public ResponseEntity> orderGetUserPoint(@UserId final Long userId) { return ResponseEntity @@ -53,7 +55,7 @@ public ResponseEntity> orderGetUserPoint(@UserId final Lon userService.getUserInfo(userId).point())); } - @DeleteMapping + @DeleteMapping("/v1/user") public ResponseEntity> orderWithdraw(@UserId final Long userId) { userService.withdraw(userId); return ResponseEntity @@ -61,25 +63,49 @@ public ResponseEntity> orderWithdraw(@UserId fin .body(BaseResponse.success(UserSuccess.WITHDRAW_SUCCESS, new EmptyJsonResponse())); } - @PostMapping("/daily/lock") @Override + @Deprecated + @PostMapping("/v1/user/daily/lock") + public ResponseEntity> orderChangeRecentLockDateDeprecated( + @UserId final Long userId, + @Valid @RequestBody final LockDateRequest request) { + userService.changeRecentLockDateDeprecated(userId, request.lockDate()); + return ResponseEntity + .status(UserSuccess.CHANGE_RECENT_LOCK_DATE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(UserSuccess.CHANGE_RECENT_LOCK_DATE_SUCCESS, new EmptyJsonResponse())); + } + + @Override + @PostMapping("/v2/user/daily/lock") public ResponseEntity> orderChangeRecentLockDate( - @UserId final Long userId, @Valid @RequestBody final LockDateRequest request) { - userService.changeRecentLockDate(userId, request.lockDate()); + @UserId final Long userId, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone) { + userService.changeRecentLockDateToToday(userId, timeZone); return ResponseEntity .status(UserSuccess.CHANGE_RECENT_LOCK_DATE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(UserSuccess.CHANGE_RECENT_LOCK_DATE_SUCCESS, new EmptyJsonResponse())); } - @GetMapping("/daily/lock") @Override - public ResponseEntity> orderGetRecentLockDate( + @Deprecated + @GetMapping("/v1/user/daily/lock") + public ResponseEntity> orderGetRecentLockDateDeprecated( @UserId final Long userId, @RequestParam(name = "lockCheckDate") @DateTimeFormat(pattern = "yyyy-MM-dd") final LocalDate lockCheckDate) { return ResponseEntity .status(UserSuccess.GET_RECENT_LOCK_DATE_SUCCESS.getHttpStatus()) .body(BaseResponse.success(UserSuccess.GET_RECENT_LOCK_DATE_SUCCESS, - userService.checkIsTodayLock(userId, lockCheckDate))); + userService.checkIsTodayLockDeprecated(userId, lockCheckDate))); } + @Override + @GetMapping("/v2/user/daily/lock") + public ResponseEntity> orderGetRecentLockDate( + @UserId final Long userId, + @RequestHeader(CustomHeaderType.TIME_ZONE) final String timeZone) { + return ResponseEntity + .status(UserSuccess.GET_RECENT_LOCK_DATE_SUCCESS.getHttpStatus()) + .body(BaseResponse.success(UserSuccess.GET_RECENT_LOCK_DATE_SUCCESS, + userService.checkIsTodayLock(userId, timeZone))); + } } diff --git a/src/main/java/sopt/org/hmh/domain/user/service/UserService.java b/src/main/java/sopt/org/hmh/domain/user/service/UserService.java index 3c9bf910..6682d188 100644 --- a/src/main/java/sopt/org/hmh/domain/user/service/UserService.java +++ b/src/main/java/sopt/org/hmh/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package sopt.org.hmh.domain.user.service; import java.time.LocalDate; +import java.time.ZoneId; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -12,8 +13,6 @@ import sopt.org.hmh.domain.auth.exception.AuthException; import sopt.org.hmh.domain.auth.repository.OnboardingInfoRepository; import sopt.org.hmh.domain.auth.repository.ProblemRepository; -import sopt.org.hmh.domain.challenge.domain.exception.ChallengeError; -import sopt.org.hmh.domain.challenge.domain.exception.ChallengeException; import sopt.org.hmh.domain.user.domain.User; import sopt.org.hmh.domain.user.domain.UserConstants; import sopt.org.hmh.domain.user.domain.exception.UserError; @@ -106,22 +105,35 @@ public Long getCurrentChallengeIdByUserId(Long userId) { .orElseThrow(() -> new UserException(UserError.NOT_FOUND_CURRENT_CHALLENGE_ID)); } - public Long getCurrentChallengeIdByUser(User user) { - return Optional.ofNullable(user.getCurrentChallengeId()) - .orElseThrow(() -> new ChallengeException(ChallengeError.CHALLENGE_NOT_FOUND)); + public void changeCurrentChallengeIdByUserId(Long userId, Long challengeId) { + this.findByIdOrThrowException(userId).changeCurrentChallengeId(challengeId); } + @Deprecated @Transactional - public void changeRecentLockDate(Long userId, LocalDate lockDate) { + public void changeRecentLockDateDeprecated(Long userId, LocalDate lockDate) { this.findByIdOrThrowException(userId).changeRecentLockDate(lockDate); } + @Transactional + public void changeRecentLockDateToToday(Long userId, String timeZone) { + this.findByIdOrThrowException(userId).changeRecentLockDate(LocalDate.now(ZoneId.of(timeZone))); + } + @Transactional(readOnly = true) - public IsLockTodayResponse checkIsTodayLock(Long userId, LocalDate lockCheckDate) { + @Deprecated + public IsLockTodayResponse checkIsTodayLockDeprecated(Long userId, LocalDate lockCheckDate) { LocalDate userRecentLockDate = this.findByIdOrThrowException(userId).getRecentLockDate(); return new IsLockTodayResponse(lockCheckDate.equals(userRecentLockDate)); } + @Transactional(readOnly = true) + public IsLockTodayResponse checkIsTodayLock(Long userId, String timeZone) { + LocalDate now = LocalDate.now(ZoneId.of(timeZone)); + LocalDate userRecentLockDate = this.findByIdOrThrowException(userId).getRecentLockDate(); + return new IsLockTodayResponse(now.equals(userRecentLockDate)); + } + public void withdrawImmediately(Long userId) { userRepository.deleteById(userId); } diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/TokenResponse.java b/src/main/java/sopt/org/hmh/global/auth/jwt/dto/TokenResponse.java similarity index 68% rename from src/main/java/sopt/org/hmh/global/auth/jwt/TokenResponse.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/dto/TokenResponse.java index a0751843..9de94aed 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/TokenResponse.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/dto/TokenResponse.java @@ -1,4 +1,4 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.dto; public record TokenResponse( String accessToken, diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidationType.java b/src/main/java/sopt/org/hmh/global/auth/jwt/exception/JwtValidationType.java similarity index 91% rename from src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidationType.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/exception/JwtValidationType.java index 49051e80..f35cf633 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidationType.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/exception/JwtValidationType.java @@ -1,4 +1,4 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.exception; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtGenerator.java b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtGenerator.java similarity index 98% rename from src/main/java/sopt/org/hmh/global/auth/jwt/JwtGenerator.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtGenerator.java index f30eba8e..6df887aa 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtGenerator.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtGenerator.java @@ -1,4 +1,4 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.service; import static sopt.org.hmh.global.auth.jwt.JwtConstants.ADMIN_ROLE; diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtPrefixExtractor.java b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtPrefixExtractor.java similarity index 86% rename from src/main/java/sopt/org/hmh/global/auth/jwt/JwtPrefixExtractor.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtPrefixExtractor.java index bf825e6b..5d5b70ec 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtPrefixExtractor.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtPrefixExtractor.java @@ -1,8 +1,9 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.service; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.springframework.util.StringUtils; +import sopt.org.hmh.global.auth.jwt.JwtConstants; import sopt.org.hmh.global.auth.jwt.exception.JwtError; import sopt.org.hmh.global.auth.jwt.exception.JwtException; diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtProvider.java b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtProvider.java similarity index 81% rename from src/main/java/sopt/org/hmh/global/auth/jwt/JwtProvider.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtProvider.java index ade8b1df..b05b299b 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtProvider.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtProvider.java @@ -1,10 +1,11 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.service; -import static sopt.org.hmh.global.auth.jwt.JwtPrefixExtractor.extractPrefix; +import static sopt.org.hmh.global.auth.jwt.service.JwtPrefixExtractor.extractPrefix; import io.jsonwebtoken.JwtParser; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import sopt.org.hmh.global.auth.jwt.dto.TokenResponse; @Component @RequiredArgsConstructor diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidator.java b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtValidator.java similarity index 92% rename from src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidator.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtValidator.java index c964c791..8cdf2431 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/JwtValidator.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/service/JwtValidator.java @@ -1,7 +1,7 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.service; import static sopt.org.hmh.global.auth.jwt.JwtConstants.ADMIN_ROLE; -import static sopt.org.hmh.global.auth.jwt.JwtPrefixExtractor.extractPrefix; +import static sopt.org.hmh.global.auth.jwt.service.JwtPrefixExtractor.extractPrefix; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; diff --git a/src/main/java/sopt/org/hmh/global/auth/jwt/TokenService.java b/src/main/java/sopt/org/hmh/global/auth/jwt/service/TokenService.java similarity index 84% rename from src/main/java/sopt/org/hmh/global/auth/jwt/TokenService.java rename to src/main/java/sopt/org/hmh/global/auth/jwt/service/TokenService.java index b589abac..d305ce51 100644 --- a/src/main/java/sopt/org/hmh/global/auth/jwt/TokenService.java +++ b/src/main/java/sopt/org/hmh/global/auth/jwt/service/TokenService.java @@ -1,11 +1,12 @@ -package sopt.org.hmh.global.auth.jwt; +package sopt.org.hmh.global.auth.jwt.service; -import static sopt.org.hmh.global.auth.jwt.JwtPrefixExtractor.extractPrefix; +import static sopt.org.hmh.global.auth.jwt.service.JwtPrefixExtractor.extractPrefix; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import sopt.org.hmh.domain.auth.dto.response.ReissueResponse; +import sopt.org.hmh.global.auth.jwt.dto.TokenResponse; @Service @RequiredArgsConstructor diff --git a/src/main/java/sopt/org/hmh/global/auth/security/JwtAuthenticationFilter.java b/src/main/java/sopt/org/hmh/global/auth/security/JwtAuthenticationFilter.java index db01f72f..6e19c6d7 100644 --- a/src/main/java/sopt/org/hmh/global/auth/security/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/hmh/global/auth/security/JwtAuthenticationFilter.java @@ -14,8 +14,8 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import sopt.org.hmh.global.auth.jwt.JwtConstants; -import sopt.org.hmh.global.auth.jwt.JwtProvider; -import sopt.org.hmh.global.auth.jwt.JwtValidator; +import sopt.org.hmh.global.auth.jwt.service.JwtProvider; +import sopt.org.hmh.global.auth.jwt.service.JwtValidator; @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { diff --git a/src/main/java/sopt/org/hmh/global/auth/security/ValidateAdminInterceptor.java b/src/main/java/sopt/org/hmh/global/auth/security/ValidateAdminInterceptor.java index 4a08fac4..8802a076 100644 --- a/src/main/java/sopt/org/hmh/global/auth/security/ValidateAdminInterceptor.java +++ b/src/main/java/sopt/org/hmh/global/auth/security/ValidateAdminInterceptor.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import sopt.org.hmh.global.auth.jwt.JwtConstants; -import sopt.org.hmh.global.auth.jwt.TokenService; +import sopt.org.hmh.global.auth.jwt.service.TokenService; @Component @RequiredArgsConstructor diff --git a/src/main/java/sopt/org/hmh/global/common/constant/CustomHeaderType.java b/src/main/java/sopt/org/hmh/global/common/constant/CustomHeaderType.java new file mode 100644 index 00000000..62dcf727 --- /dev/null +++ b/src/main/java/sopt/org/hmh/global/common/constant/CustomHeaderType.java @@ -0,0 +1,10 @@ +package sopt.org.hmh.global.common.constant; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public abstract class CustomHeaderType { + public static final String OS = "OS"; + public static final String TIME_ZONE = "Time-Zone"; +} diff --git a/src/main/java/sopt/org/hmh/global/config/QuerydslConfiguration.java b/src/main/java/sopt/org/hmh/global/config/QuerydslConfiguration.java new file mode 100644 index 00000000..52b71f91 --- /dev/null +++ b/src/main/java/sopt/org/hmh/global/config/QuerydslConfiguration.java @@ -0,0 +1,19 @@ +package sopt.org.hmh.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfiguration { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/hmh/global/config/SecurityConfig.java b/src/main/java/sopt/org/hmh/global/config/SecurityConfig.java index 7a8a4908..e1c75631 100644 --- a/src/main/java/sopt/org/hmh/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/hmh/global/config/SecurityConfig.java @@ -10,8 +10,8 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import sopt.org.hmh.global.auth.jwt.JwtProvider; -import sopt.org.hmh.global.auth.jwt.JwtValidator; +import sopt.org.hmh.global.auth.jwt.service.JwtProvider; +import sopt.org.hmh.global.auth.jwt.service.JwtValidator; import sopt.org.hmh.global.auth.security.JwtAuthenticationEntryPoint; import sopt.org.hmh.global.auth.security.JwtAuthenticationFilter; import sopt.org.hmh.global.auth.security.exception.ExceptionHandlerFilter; @@ -41,6 +41,7 @@ public class SecurityConfig { "/api/v1/admin/login", "/api/v1/user/reissue", "/api/v1/user/signup", + "/api/v2/user/signup", "/api/v1/user/social/token/kakao", };