diff --git a/.gitignore b/.gitignore index 904e84b..93b290e 100644 --- a/.gitignore +++ b/.gitignore @@ -315,5 +315,4 @@ gradle-app.setting # Java heap dump *.hprof -**/resources/* # End of https://www.toptal.com/developers/gitignore/api/macos,intellij,intellij+iml,intellij+all,gradle,java diff --git a/build.gradle b/build.gradle index 5e199e0..7e7ff77 100644 --- a/build.gradle +++ b/build.gradle @@ -37,8 +37,6 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - //FCM - implementation 'com.google.firebase:firebase-admin:9.2.0' } tasks.named('bootBuildImage') { diff --git a/src/main/java/com/onnoff/onnoff/OnnoffApplication.java b/src/main/java/com/onnoff/onnoff/OnnoffApplication.java index d0d6e99..062589f 100644 --- a/src/main/java/com/onnoff/onnoff/OnnoffApplication.java +++ b/src/main/java/com/onnoff/onnoff/OnnoffApplication.java @@ -5,12 +5,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.scheduling.annotation.EnableScheduling; @EnableFeignClients(defaultConfiguration = FeignConfig.class) @SpringBootApplication @EnableJpaAuditing -@EnableScheduling public class OnnoffApplication { public static void main(String[] args) { diff --git a/src/main/java/com/onnoff/onnoff/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/onnoff/onnoff/apiPayload/code/status/ErrorStatus.java index 7da0f93..311912c 100644 --- a/src/main/java/com/onnoff/onnoff/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/onnoff/onnoff/apiPayload/code/status/ErrorStatus.java @@ -35,9 +35,6 @@ public enum ErrorStatus implements BaseErrorCode { ANSWER_NOT_FOUND(HttpStatus.BAD_REQUEST, "ANSWER4001", "해당하는 회고 답변이 없습니다."), ANSWER_BAD_MATCH(HttpStatus.BAD_REQUEST, "ANSWER4002", "해당하는 회고에 속하는 회고 답변이 아닙니다."), - // 이모티콘 관련 에러 - EMOTICON_NOT_FOUND(HttpStatus.BAD_REQUEST, "EMOTICON4001", "해당하는 이모티콘이 없습니다."), - // 피드 관련 에러 FEED_NOT_FOUND(HttpStatus.BAD_REQUEST, "FEED4001", "해당하는 워라벨 피드가 없습니다."), FEED_NOT_BLANK(HttpStatus.BAD_REQUEST, "FEED4002", "워라벨 피드 내용은 공백일 수 없습니다."), diff --git a/src/main/java/com/onnoff/onnoff/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/onnoff/onnoff/apiPayload/code/status/SuccessStatus.java index 82afe51..2e768ee 100644 --- a/src/main/java/com/onnoff/onnoff/apiPayload/code/status/SuccessStatus.java +++ b/src/main/java/com/onnoff/onnoff/apiPayload/code/status/SuccessStatus.java @@ -10,8 +10,10 @@ @AllArgsConstructor public enum SuccessStatus implements BaseCode { // 일반적인 응답 - _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + _OK(HttpStatus.OK, "COMMON200", "성공입니다."), + // 로그인 응답 + NEED_USER_DETAIL(HttpStatus.OK, "LOGIN200", "토큰이 유효하고 유저의 추가정보가 필요합니다."); private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java b/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java index 53610bb..7fd0606 100644 --- a/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java +++ b/src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java @@ -39,6 +39,6 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInterceptor(userService, jwtUtil)) .addPathPatterns("/**") // 스프링 경로는 /*와 /**이 다름 - .excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health", "/token/**" , "/message/**"); + .excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health", "/stats/**", "/feeds", "/memoirs"); } } diff --git a/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java b/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java index 0cbead2..0d47f5a 100644 --- a/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java +++ b/src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java @@ -3,30 +3,23 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.onnoff.onnoff.apiPayload.ApiResponse; -import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus; -import com.onnoff.onnoff.apiPayload.exception.GeneralException; +import com.onnoff.onnoff.apiPayload.code.status.SuccessStatus; import com.onnoff.onnoff.auth.UserContext; -import com.onnoff.onnoff.auth.dto.LoginRequestDTO; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; +import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO; import com.onnoff.onnoff.auth.jwt.dto.JwtToken; -import com.onnoff.onnoff.auth.jwt.service.JwtTokenProvider; import com.onnoff.onnoff.auth.jwt.service.JwtUtil; -import com.onnoff.onnoff.auth.service.AppleLoginService; -import com.onnoff.onnoff.auth.service.KakaoLoginService; +import com.onnoff.onnoff.auth.service.LoginService; import com.onnoff.onnoff.domain.user.User; import com.onnoff.onnoff.domain.user.converter.UserConverter; import com.onnoff.onnoff.domain.user.dto.UserResponseDTO; import com.onnoff.onnoff.domain.user.service.UserService; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.InvalidClaimException; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.UnsupportedJwtException; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -36,123 +29,82 @@ @Controller @RequiredArgsConstructor public class LoginController { - private final KakaoLoginService kakaoLoginService; - private final AppleLoginService appleLoginService; + private final LoginService loginService; private final UserService userService; - private final JwtTokenProvider jwtTokenProvider; private final JwtUtil jwtUtil; - @Value("${kakao.redirect-uri}") - private String redirectUri; - - /* 테스트용 API, CORS 때문에 직접 호출하지 않고 redirect */ @GetMapping("/oauth2/authorize/kakao") public String login(){ - String toRedirectUri = UriComponentsBuilder.fromUriString("https://kauth.kakao.com/oauth/authorize") + String redirectUri = UriComponentsBuilder.fromUriString("https://kauth.kakao.com/oauth/authorize") .queryParam("response_type", "code") .queryParam("client_id", "32c0787d1b1e9fcabcc24af247903ba8") - .queryParam("redirect_uri", redirectUri) + .queryParam("redirect_uri", "http://localhost:8080/oauth2/login/kakao") .toUriString(); - return "redirect:" + toRedirectUri; + return "redirect:" + redirectUri; } /* 테스트용 API */ @GetMapping("/oauth2/login/kakao") public ResponseEntity getAccessToken(@RequestParam(name = "code") String code){ - TokenResponse tokenResponse = kakaoLoginService.getAccessTokenByCode(code); - return ResponseEntity.ok("accessToken="+ tokenResponse.getAccessToken() + - "\n\nidToken=" + tokenResponse.getIdToken()); + String accessToken = loginService.getAccessToken(code); + return ResponseEntity.ok("http://localhost:8080/oauth2/kakao/token/validate?accessToken="+accessToken); } /* - 1. ID 토큰 유효성 검증 + 1. 토큰 유효성 검증 2. 사용자 정보 얻어오기 3. DB 조회 및 추가 4. 응답 헤더에 Jwt 토큰 추가 */ - @Operation(summary = "소셜 토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.") + @Operation(summary = "토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "202", description = "토큰 검증 성공," + " 추가 정보 기입이 필요합니다.", + content = @Content(schema = @Schema(implementation = UserResponseDTO.ApiResponseLoginDTO.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "토큰 검증 성공", + content = @Content(schema = @Schema(implementation = UserResponseDTO.ApiResponseUserDetailDTO.class))) + }) @ResponseBody @PostMapping("/oauth2/kakao/token/validate") - public ApiResponse validateKakoToken(HttpServletResponse response, @RequestBody LoginRequestDTO.KakaoTokenValidateDTO requestDTO) { - // identity 토큰 검증 - kakaoLoginService.validate(requestDTO.getIdentityToken()); + public ApiResponse validateToken(HttpServletResponse response, @RequestBody String accessToken) { + // 토큰 검증 + loginService.validate(accessToken); // ok -> 유저 정보 가져오기 - KakaoOauth2DTO.UserInfoResponseDTO userInfo; + KakaoOauth2DTO.UserInfoResponseDTO userInfoResponseDTO = null; try { - userInfo = kakaoLoginService.getUserInfo(requestDTO.getAccessToken()); + userInfoResponseDTO = loginService.getUserInfo(accessToken); } catch (JsonProcessingException e) { e.printStackTrace(); throw new RuntimeException(e); } // 유저 정보에 DB 조회하고 정보 있으면 응답만, 없으면 저장까지, 추가정보 입력 여부에 따라서 응답 다르게 - String oauthId = userInfo.getSub(); - User user; - if( userService.isExistByOauthId(oauthId)){ - user = userService.getUserByOauthId(oauthId); - } - else{ - user = UserConverter.toUser(userInfo); - user = userService.create(user); - } - // 응답본문에 토큰 추가 - JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId())); - return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken())); - } - - @ResponseBody - @PostMapping("/oauth2/apple/token/validate") - public ApiResponse validateAppleToken(HttpServletResponse response, @RequestBody LoginRequestDTO.AppleTokenValidateDTO requestDTO) { - // 검증하기 - appleLoginService.validate(requestDTO.getIdentityToken()); - // 검증 성공 시 리프레시 토큰 발급받아 저장(기한 무제한, 회원탈퇴 시 필요) - TokenResponse tokenResponse = appleLoginService.getAccessTokenByCode(requestDTO.getAuthorizationCode()); - // 유저 정보 조회 및 저장 - String oauthId = requestDTO.getOauthId(); - User user; + Long oauthId = userInfoResponseDTO.getId(); if( userService.isExistByOauthId(oauthId)){ - user = userService.getUserByOauthId(oauthId); + User user = userService.getUserByOauthId(oauthId); + // 응답헤더에 토큰 추가 + JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId())); + response.addHeader("Access-Token", token.getAccessToken()); + response.addHeader("Refresh-Token", token.getRefreshToken()); + if(user.isInfoSet()){ + return ApiResponse.onSuccess(UserConverter.toUserDetailDTO(user)); + } + return ApiResponse.of(SuccessStatus.NEED_USER_DETAIL, UserConverter.toLoginDTO(user)); } else{ - user = UserConverter.toUser(requestDTO); - user.setAppleRefreshToken(tokenResponse.getRefreshToken()); - user = userService.create(user); + User user = UserConverter.toUser(userInfoResponseDTO); + Long id = userService.create(user); + // 응답헤더에 토큰 추가 + JwtToken token = jwtUtil.generateToken(String.valueOf(id)); + response.addHeader("Access-Token", token.getAccessToken()); + response.addHeader("Refresh-Token", token.getRefreshToken()); + return ApiResponse.of(SuccessStatus.NEED_USER_DETAIL, UserConverter.toLoginDTO(user)); } - // 응답헤더에 토큰 추가 - JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId())); - return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken())); } - @Operation(summary = "서버 토큰 검증 API",description = "토큰의 유효성을 검증하고 액세스 토큰이 만료되었으면" + - "재발급, 리프레시 토큰까지 만료되었으면 오류 응답 추가. 정보 입력 여부도 같이 응답 합니다.") - @ResponseBody - @PostMapping("/token/validate") - public ApiResponse validateServerToken(@RequestBody JwtToken tokenDTO){ - String accessToken = tokenDTO.getAccessToken(); - String refreshToken = tokenDTO.getRefreshToken(); - if( jwtTokenProvider.verifyToken(accessToken) ){ - // accessToken 유효 - String userId = jwtUtil.getUserId(accessToken); - User user = userService.getUser(Long.valueOf(userId)); - UserResponseDTO.LoginDTO loginDTO = UserConverter.toLoginDTO(user, accessToken, refreshToken); - return ApiResponse.onSuccess(loginDTO); - } - if (jwtTokenProvider.verifyToken(refreshToken)) { - //refreshToken 유효 - String userId = jwtUtil.getUserId(refreshToken); - User user = userService.getUser(Long.valueOf(userId)); - - String newRefreshToken = jwtUtil.generateRefreshToken(jwtUtil.getUserId(refreshToken)); - String newAccessToken = jwtUtil.generateAccessToken(jwtUtil.getUserId(refreshToken)); - return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, newAccessToken, newRefreshToken)); - } - // 둘 다 유효하지 않은 경우 - throw new GeneralException(ErrorStatus.INVALID_TOKEN_ERROR); - } /* 테스트용 API */ diff --git a/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java b/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java deleted file mode 100644 index d9e021b..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onnoff.onnoff.auth.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; - -public class LoginRequestDTO { - - @Getter - public static class AppleTokenValidateDTO{ - private String oauthId; - private String fullName; - private String email; - private String identityToken; - private String authorizationCode; - } - @Getter - public static class KakaoTokenValidateDTO{ - private String identityToken; - private String accessToken; - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/CustomErrorDecoder.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/CustomErrorDecoder.java index dfefe80..0c6f43e 100644 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/CustomErrorDecoder.java +++ b/src/main/java/com/onnoff/onnoff/auth/feignClient/CustomErrorDecoder.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus; import com.onnoff.onnoff.apiPayload.exception.GeneralException; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.ErrorResponseDTO; +import com.onnoff.onnoff.auth.feignClient.dto.ErrorResponseDTO; import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/AppleAuthClient.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/client/AppleAuthClient.java deleted file mode 100644 index 58d30c3..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/AppleAuthClient.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onnoff.onnoff.auth.feignClient.client; - - -import com.onnoff.onnoff.auth.feignClient.dto.JwkResponse; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "apple-auth-client",url = "https://appleid.apple.com/auth") -public interface AppleAuthClient{ - @GetMapping("/keys") - JwkResponse.JwkSet getKeys(); - - @GetMapping(value = "/token", consumes = "application/x-www-form-urlencoded") - TokenResponse getToken(MultiValueMap requestBody); - - //회원 탈퇴 메서드 -// @GetMapping("/revoke") -// KakaoOauth2DTO.TokenValidateResponseDTO getTokenValidate(@RequestHeader("Authorization") String accessToken); -} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoApiClient.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoApiClient.java index 7a5a304..f837d51 100644 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoApiClient.java +++ b/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoApiClient.java @@ -1,19 +1,20 @@ package com.onnoff.onnoff.auth.feignClient.client; import com.onnoff.onnoff.auth.feignClient.config.FeignConfig; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; +import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO; +import feign.Headers; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; /* - 토큰 유효성 검증 하고 사용자 정보 가져오는 client + 토큰 유효성 검증하고 사용자 정보 가져오는 client */ @FeignClient(name = "kakao-api-client", url = "https://kapi.kakao.com", configuration = FeignConfig.class) public interface KakaoApiClient { @GetMapping("v1/user/access_token_info") KakaoOauth2DTO.TokenValidateResponseDTO getTokenValidate(@RequestHeader("Authorization") String accessToken); - @GetMapping(value = "/v1/oidc/userinfo") - KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(@RequestHeader("Authorization") String accessToken); + @GetMapping(value = "/v2/user/me") + KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(@RequestHeader("Authorization") String accessToken, @RequestParam(name = "property_keys") String propertyKeys); } diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoOauth2Client.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoOauth2Client.java index 577a305..6eed05e 100644 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoOauth2Client.java +++ b/src/main/java/com/onnoff/onnoff/auth/feignClient/client/KakaoOauth2Client.java @@ -2,9 +2,7 @@ import com.onnoff.onnoff.auth.feignClient.config.FeignConfig; -import com.onnoff.onnoff.auth.feignClient.dto.JwkResponse; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; +import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO; import feign.Headers; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; @@ -13,11 +11,10 @@ public interface KakaoOauth2Client { @Headers("Content-Type: application/x-www-form-urlencoded") @PostMapping("/oauth/token") - TokenResponse getAccessToken(@RequestParam(name = "grant_type") String grantType, - @RequestParam(name = "client_id") String clientId, - @RequestParam(name = "redirect_uri") String redirectUri, - @RequestParam(name = "code") String code + KakaoOauth2DTO.TokenResponseDTO getAccessToken(@RequestParam(name = "grant_type") String grantType, + @RequestParam(name = "client_id") String clientId, + @RequestParam(name = "redirect_uri") String redirectUri, + @RequestParam(name = "code") String code ); - @GetMapping("/.well-known/jwks.json") - JwkResponse.JwkSet getKeys(); + } diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/ErrorResponseDTO.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/ErrorResponseDTO.java similarity index 67% rename from src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/ErrorResponseDTO.java rename to src/main/java/com/onnoff/onnoff/auth/feignClient/dto/ErrorResponseDTO.java index 7d8acea..562134d 100644 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/ErrorResponseDTO.java +++ b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/ErrorResponseDTO.java @@ -1,4 +1,4 @@ -package com.onnoff.onnoff.auth.feignClient.dto.kakao; +package com.onnoff.onnoff.auth.feignClient.dto; import lombok.Getter; diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/JwkResponse.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/JwkResponse.java deleted file mode 100644 index f06353c..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/JwkResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.onnoff.onnoff.auth.feignClient.dto; - - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; - -public class JwkResponse { - @Getter - @Builder - @AllArgsConstructor - @NoArgsConstructor - public static class Jwk { - private String alg; - private String e; - private String kid; - private String kty; - private String n; - private String use; - } - @Getter - public static class JwkSet{ - @JsonProperty("keys") - private List jwkList; - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/KakaoOauth2DTO.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/KakaoOauth2DTO.java new file mode 100644 index 0000000..e9214a7 --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/KakaoOauth2DTO.java @@ -0,0 +1,55 @@ +package com.onnoff.onnoff.auth.feignClient.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.onnoff.onnoff.domain.user.enums.SocialType; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDateTime; + +public class KakaoOauth2DTO { + + @Getter + public static class TokenResponseDTO{ + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("access_token") + private String accessToken; + } + + @Getter + public static class TokenValidateResponseDTO{ + private Long id; + @JsonProperty("expires_in") + private Integer expiresIn; + @JsonProperty("app_id") + private Integer appId; + } + + @Getter + @ToString + public static class UserInfoResponseDTO{ + private Long id; + @JsonProperty("connected_at") + private LocalDateTime connectedAt; + @JsonProperty("kakao_account") + private KakaoAccountDTO kakaoAccount; + private SocialType socialType; + } + @Getter + @ToString + public static class KakaoAccountDTO{ + // 이메일 동의항목 +// private boolean has_email; +// private boolean email_needs_agreement; +// private boolean is_email_valid; +// private boolean is_email_verified; + private String email; + + // 이름 동의 항목 +// private boolean name_needs_agreement; + private String name; + + + } +} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/TokenResponse.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/TokenResponse.java deleted file mode 100644 index cdb336f..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/TokenResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onnoff.onnoff.auth.feignClient.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; - -@Getter -public class TokenResponse { - @JsonProperty("access_token") - private String accessToken; - @JsonProperty("expires_in") - private Integer expiresIn; - @JsonProperty("id_token") - private String idToken; - @JsonProperty("refresh_token") - private String refreshToken; - @JsonProperty("token_type") - private String tokenType; -} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/apple/TokenRequest.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/apple/TokenRequest.java deleted file mode 100644 index 3acbec7..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/apple/TokenRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.onnoff.onnoff.auth.feignClient.dto.apple; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -@Builder -@AllArgsConstructor -public class TokenRequest { - @JsonProperty("client_id") - private String clientId; - @JsonProperty("client_secret") - private String clientSecret; - private String code; - @JsonProperty("grant_type") - private String grantType; - @JsonProperty("redirect_uri") - private String redirectUri; - @JsonProperty("refresh_token") - private String refreshToken; - public MultiValueMap toUrlEncoded(){ - LinkedMultiValueMap urlEncoded = new LinkedMultiValueMap<>(); - urlEncoded.add("client_id", clientId); - urlEncoded.add("client_secret", clientSecret); - urlEncoded.add("code", code); - urlEncoded.add("grant_type", grantType); - urlEncoded.add("redirect_uri", redirectUri); - return urlEncoded; - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/KakaoOauth2DTO.java b/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/KakaoOauth2DTO.java deleted file mode 100644 index 20186ce..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/feignClient/dto/kakao/KakaoOauth2DTO.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onnoff.onnoff.auth.feignClient.dto.kakao; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.ToString; - - -public class KakaoOauth2DTO { - - @Getter - public static class TokenValidateResponseDTO{ - private Long id; - @JsonProperty("expires_in") - private Integer expiresIn; - @JsonProperty("app_id") - private Integer appId; - } - - @Getter - @ToString - public static class UserInfoResponseDTO{ - private String sub; - private String name; - private String email; - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java b/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java index c5b3fbc..a208ddc 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/dto/JwtToken.java @@ -1,10 +1,12 @@ package com.onnoff.onnoff.auth.jwt.dto; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; @Getter @AllArgsConstructor -@NoArgsConstructor @Builder public class JwtToken { private String accessToken; diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java index c7ee977..8fecfec 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/JwtAuthFilter.java @@ -25,7 +25,7 @@ @RequiredArgsConstructor public class JwtAuthFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; - private final static String[] ignorePrefix = {"/swagger-ui", "/v3/api-docs", "/oauth2", "/on", "/health", "/token/validate" , "/message"}; + private final static String[] ignorePrefix = {"/swagger-ui", "/v3/api-docs", "/oauth2", "/on", "/health", "/stats", "/feeds", "/memoirs"}; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { log.info("url ={}", request.getRequestURI()); diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java index b208310..d214769 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/filter/UserInterceptor.java @@ -36,7 +36,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return true; } catch (IllegalArgumentException exception){ - log.info("url={} 인터셉터 인증 실패", request.getRequestURI() ); // jwt 필터에서 검증된 토큰이라 예외 안나겠지만 없지만 혹시 모르니 response.sendError(HttpStatus.UNAUTHORIZED.value()); return false; diff --git a/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java b/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java index 35226fd..87d668a 100644 --- a/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java +++ b/src/main/java/com/onnoff/onnoff/auth/jwt/service/JwtTokenProvider.java @@ -43,7 +43,7 @@ public boolean verifyToken(String token) { .getExpiration() .after(new Date()); // 만료 시간이 현재 시간 이후인지 확인하여 유효성 검사 결과를 반환 } catch (Exception e) { - log.info("토큰 검증 실패 = {}", token); + log.info("토큰 만료 = {}", token); return false; } } diff --git a/src/main/java/com/onnoff/onnoff/auth/service/AppleLoginService.java b/src/main/java/com/onnoff/onnoff/auth/service/AppleLoginService.java deleted file mode 100644 index 045358c..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/service/AppleLoginService.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.onnoff.onnoff.auth.service; - - -import com.onnoff.onnoff.auth.UserContext; -import com.onnoff.onnoff.auth.feignClient.client.AppleAuthClient; -import com.onnoff.onnoff.auth.feignClient.dto.apple.TokenRequest; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -import com.onnoff.onnoff.auth.service.tokenValidator.SocialTokenValidator; -import com.onnoff.onnoff.domain.user.User; -import com.onnoff.onnoff.domain.user.enums.SocialType; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ClassPathResource; -import org.springframework.stereotype.Service; -import org.springframework.util.MultiValueMap; - -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.PrivateKey; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class AppleLoginService implements LoginService{ - private final AppleAuthClient appleAuthClient; - private final SocialTokenValidator validator; - @Value("${apple.key.id}") - private String kid; - @Value("${apple.key.path}") - private String keyPath; - @Value("${apple.client-id}") - private String clientId; - @Value("${apple.iss}") - private String iss; - @Value("${apple.team-id}") - private String teamId; - @Value("${apple.redirect-uri}") - private String redirectUri; - @Override - public TokenResponse getAccessTokenByCode(String code) { - // client secret 만들기 - String clientSecret = createClientSecret(); - // 요청 - MultiValueMap urlEncoded = TokenRequest.builder() - .clientId(clientId) - .clientSecret(clientSecret) - .code("authorization_code_value") - .grantType("authorization_code") - .redirectUri(redirectUri) - .build().toUrlEncoded(); - return appleAuthClient.getToken(urlEncoded); - } - private String createClientSecret() { - Date expirationDate = Date.from(LocalDateTime.now().plusDays(30).atZone(ZoneId.systemDefault()).toInstant()); - Map jwtHeader = new HashMap<>(); - jwtHeader.put("kid", kid); - jwtHeader.put("alg", "ES256"); - - try { - return Jwts.builder() - .setHeaderParams(jwtHeader) - .setIssuer(teamId) // 토큰 발행자 = 우리 팀 - .setIssuedAt(new Date(System.currentTimeMillis())) // 발행 시간 - UNIX 시간 - .setExpiration(expirationDate) // 만료 시간 - .setAudience(iss) // 애플이 수신자 - .setSubject(clientId) // 토큰의 주체 = 우리 앱 - .signWith(SignatureAlgorithm.ES256, getPrivateKey()) - .compact(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public String getAccessTokenByRfToken(String code) { - // client secret 만들기 - String clientSecret = createClientSecret(); - // refreshToken 가져오기 - User user = UserContext.getUser(); - String appleRefreshToken = user.getAppleRefreshToken(); - // 요청 - MultiValueMap urlEncoded = TokenRequest.builder() - .clientId(clientId) - .clientSecret(clientSecret) - .refreshToken(appleRefreshToken) - .grantType("refresh_token") - .redirectUri(redirectUri) - .build().toUrlEncoded(); - TokenResponse response = appleAuthClient.getToken(urlEncoded); - return null; - } - private PrivateKey getPrivateKey() throws IOException { - ClassPathResource resource = new ClassPathResource(keyPath); - String privateKey = new String(Files.readAllBytes(Paths.get(resource.getURI()))); - - Reader pemReader = new StringReader(privateKey); - PEMParser pemParser = new PEMParser(pemReader); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); - PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject(); - return converter.getPrivateKey(object); - } - - - @Override - public void validate(String identityToken){ - String cleanedIdentityToken = cleanToken(identityToken); - validator.validate(cleanedIdentityToken, SocialType.APPLE); - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/service/KakaoLoginService.java b/src/main/java/com/onnoff/onnoff/auth/service/KakaoLoginService.java deleted file mode 100644 index 7d3fa77..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/service/KakaoLoginService.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.onnoff.onnoff.auth.service; - - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.onnoff.onnoff.auth.feignClient.client.KakaoApiClient; -import com.onnoff.onnoff.auth.feignClient.client.KakaoOauth2Client; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; -import com.onnoff.onnoff.auth.service.tokenValidator.SocialTokenValidator; -import com.onnoff.onnoff.domain.user.enums.SocialType; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - - -@Service -@RequiredArgsConstructor -@Slf4j -public class KakaoLoginService implements LoginService{ - private final KakaoOauth2Client kakaoOauth2Client; - private final KakaoApiClient kakaoApiClient; - private final SocialTokenValidator validator; - @Value("${kakao.client-id}") - private String clientId; - @Value("${kakao.redirect-uri}") - private String redirectUri; - /* - 테스트 용으로 만든거, 실제로는 프론트에서 처리해서 액세스 토큰만 가져다 줌 - */ - @Override - public TokenResponse getAccessTokenByCode(String code){ - return kakaoOauth2Client.getAccessToken("authorization_code", - clientId, - redirectUri, - code); - } - // id 토큰 유효성 검증 - @Override - public void validate(String idToken){ - String cleanedAccessToken = cleanToken(idToken); - validator.validate(cleanedAccessToken, SocialType.KAKAO); - } - /* - 토큰으로 유저정보를 가져오는 메서드 - */ - public KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(String accessToken) throws JsonProcessingException { - String cleanedAccessToken = cleanToken(accessToken); - accessToken = "bearer " + cleanedAccessToken; - KakaoOauth2DTO.UserInfoResponseDTO userInfo = kakaoApiClient.getUserInfo(accessToken); - return userInfo; - } -} diff --git a/src/main/java/com/onnoff/onnoff/auth/service/LoginService.java b/src/main/java/com/onnoff/onnoff/auth/service/LoginService.java index 4a5691c..6edfc1a 100644 --- a/src/main/java/com/onnoff/onnoff/auth/service/LoginService.java +++ b/src/main/java/com/onnoff/onnoff/auth/service/LoginService.java @@ -1,13 +1,63 @@ package com.onnoff.onnoff.auth.service; -import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse; -public interface LoginService { - default String cleanToken(String token){ - token = token.replaceAll("[\u0000-\u001F\u007F-\u00FF:]", ""); // 헤더에 있으면 안되는 값 대체 - token = token.trim(); // 앞뒤 공백 제거 - return token; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.onnoff.onnoff.auth.feignClient.client.KakaoApiClient; +import com.onnoff.onnoff.auth.feignClient.client.KakaoOauth2Client; +import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Arrays; + +@Service +@RequiredArgsConstructor +@Slf4j +public class LoginService { + private final KakaoOauth2Client kakaoOauth2Client; + private final KakaoApiClient kakaoApiClient; + + /* + 테스트 용으로 만든거, 실제로는 프론트에서 처리해서 액세스 토큰만 가져다 줌 + */ + public String getAccessToken(String code){ + KakaoOauth2DTO.TokenResponseDTO tokenResponseDTO = kakaoOauth2Client.getAccessToken("authorization_code", + "32c0787d1b1e9fcabcc24af247903ba8", + "http://localhost:8080/oauth2/login/kakao", + code); + return tokenResponseDTO.getAccessToken(); + } + /* + 토큰 유효성 검증, 유효하지 않으면 예외를 발생시키도록 처리, 예외는 CustomErrorDecoder에서 처리 + */ + public void validate(String accessToken){ + String cleanedAccessToken = cleanAccessToken(accessToken); + kakaoApiClient.getTokenValidate(cleanedAccessToken); + } + + private String cleanAccessToken(String accessToken){ + accessToken = accessToken.replaceAll("[\u0000-\u001F\u007F-\u00FF:]", ""); // 헤더에 있으면 안되는 값 대체 + accessToken = accessToken.trim(); // 앞뒤 공백 제거 + accessToken = "Bearer " + accessToken; // 토큰 기반 인증 형식 + return accessToken; + } + /* + 토큰으로 유저정보를 가져오는 메서드 + */ + public KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(String accessToken) throws JsonProcessingException { + String cleanedAccessToken = cleanAccessToken(accessToken); + String emailProperty = "kakao_account.email"; + String nameProperty = "kakao_account.name"; + List propertyKeysList = Arrays.asList(emailProperty, nameProperty); + + // List -> JSON 형식으로 바꾸어 전달 + ObjectMapper objectMapper = new ObjectMapper(); + String propertyKeys = objectMapper.writeValueAsString(propertyKeysList); + KakaoOauth2DTO.UserInfoResponseDTO userInfoResponseDTO = kakaoApiClient.getUserInfo(cleanedAccessToken, propertyKeys); + return userInfoResponseDTO; } - public TokenResponse getAccessTokenByCode(String code); - public void validate(String accessToken); } diff --git a/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidator.java b/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidator.java deleted file mode 100644 index 1aef7e8..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidator.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onnoff.onnoff.auth.service.tokenValidator; - -import com.onnoff.onnoff.domain.user.enums.SocialType; - -/** - * 토큰 검증을 처리하는 역할 - */ -public interface SocialTokenValidator { - void validate(String token, SocialType socialType); -} diff --git a/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidatorImpl.java b/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidatorImpl.java deleted file mode 100644 index 08ae6da..0000000 --- a/src/main/java/com/onnoff/onnoff/auth/service/tokenValidator/SocialTokenValidatorImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.onnoff.onnoff.auth.service.tokenValidator; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus; -import com.onnoff.onnoff.apiPayload.exception.GeneralException; -import com.onnoff.onnoff.auth.feignClient.client.AppleAuthClient; -import com.onnoff.onnoff.auth.feignClient.client.KakaoOauth2Client; -import com.onnoff.onnoff.auth.feignClient.dto.JwkResponse; -import com.onnoff.onnoff.domain.user.enums.SocialType; -import io.jsonwebtoken.*; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPublicKeySpec; -import java.util.Base64; -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class SocialTokenValidatorImpl implements SocialTokenValidator{ - private final KakaoOauth2Client kakaoOauth2Client; - private final AppleAuthClient appleAuthClient; - @Value("${kakao.iss}") - private String kakaoIss; - @Value("${kakao.client-id}") - private String kakaoAud; - @Value("${apple.iss}") - private String appleIss; - @Value("${apple.client-id}") - private String appleAud; - - /** - * 1. 공개키(JWK) 목록 조회하여 맞는 공개키 정보 획득 - * 2. 검증에 사용할 수 있는 공개키로 변환 - * 3. 공개키로 ID토큰 검증 - */ - @Override - public void validate(String token, SocialType socialType) { - JwkResponse.Jwk matchingJwk = getMatchingJwk(token, socialType); - PublicKey publicKey = jwkToPublickey(matchingJwk); - if( socialType.equals(SocialType.KAKAO)){ - verifyToken(token, kakaoIss, kakaoAud, publicKey); - } - else { - verifyToken(token, appleIss, appleAud, publicKey); - } - } - - // 발급자, 수신자, 만료 기한, 시그니처를 검증 - private Jwt verifyToken(String token, String iss, String aud, PublicKey publicKey) { - try { - return (Jwt) Jwts.parser() - .requireAudience(aud) //수신자 검증 - .requireIssuer(iss) // 발급자 검증 - .verifyWith(publicKey) // 시그니처 검증 - .build() - .parse(token); - } - catch (MalformedJwtException | UnsupportedJwtException parseEx){ - throw new GeneralException(ErrorStatus.INVALID_ARGUMENT_ERROR); - } - catch (InvalidClaimException | ExpiredJwtException validateEx){ - throw new GeneralException(ErrorStatus.INVALID_TOKEN_ERROR); - } - } - private JwkResponse.Jwk getMatchingJwk(String token, SocialType socialType){ - // header 부분만 디코드 해서 kid 가져오기 - String keyId = getKeyId(token); - JwkResponse.JwkSet keys; - if( socialType.equals(SocialType.KAKAO)) { - keys = kakaoOauth2Client.getKeys(); - } - else{ - keys = appleAuthClient.getKeys(); - } - return keys.getJwkList().stream() - .filter(jwk -> jwk.getKid().equals(keyId)) - .findFirst() - .orElseThrow(); - - } - private String getKeyId(String token){ - Base64.Decoder decoder = Base64.getUrlDecoder(); - String[] splitToken = token.split("\\."); - String header = splitToken[0]; - String headerJson = new String(decoder.decode(header)); - - ObjectMapper mapper = new ObjectMapper(); - Map headerMap = null; - try { - headerMap = mapper.readValue(headerJson, Map.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - return headerMap.get("kid"); - } - private PublicKey jwkToPublickey(JwkResponse.Jwk jwk){ - Base64.Decoder decoder = Base64.getUrlDecoder(); - byte[] decodeN = decoder.decode(jwk.getN()); - byte[] decodeE = decoder.decode(jwk.getE()); - BigInteger n = new BigInteger(1, decodeN); - BigInteger e = new BigInteger(1, decodeE); - RSAPublicKeySpec keySpec = new RSAPublicKeySpec(n, e); - try { - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { - throw new RuntimeException(ex); - } - } -} diff --git a/src/main/java/com/onnoff/onnoff/config/FirebaseConfig.java b/src/main/java/com/onnoff/onnoff/config/FirebaseConfig.java deleted file mode 100644 index 70a9fc0..0000000 --- a/src/main/java/com/onnoff/onnoff/config/FirebaseConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onnoff.onnoff.config; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -@Configuration -@Slf4j -public class FirebaseConfig { - private final Logger logger = LoggerFactory.getLogger(FirebaseConfig.class); - @Value("${fcm.firebase-sdk-path}") // your firebase sdk path - private String firebaseSdkPath; - @Value("${fcm.project-id}") - private String projectId; - @PostConstruct - public void initialize() { - try { - ClassPathResource resource = new ClassPathResource(firebaseSdkPath); - InputStream serviceAccount = resource.getInputStream(); - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(serviceAccount)) - .build(); - if (FirebaseApp.getApps().isEmpty()) { - FirebaseApp.initializeApp(options); - log.info("Successfully firebase application initialized!"); - } - } catch (FileNotFoundException e) { - logger.error("Firebase ServiceAccountKey FileNotFoundException" + e.getMessage()); - } catch (IOException e) { - logger.error("FirebaseOptions IOException" + e.getMessage()); - } - } -} diff --git a/src/main/java/com/onnoff/onnoff/domain/off/feed/repository/FeedRepository.java b/src/main/java/com/onnoff/onnoff/domain/off/feed/repository/FeedRepository.java index 3aaeb46..2c6648d 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/feed/repository/FeedRepository.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/feed/repository/FeedRepository.java @@ -10,4 +10,6 @@ public interface FeedRepository extends JpaRepository { List findAllByUserAndDateOrderByCreatedAtAsc(User user, LocalDate date); + Integer countByUserAndDate(User user, LocalDate date); + Integer countByUserAndDateAndIsChecked(User user, LocalDate date, Boolean t); } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/controller/MemoirController.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/controller/MemoirController.java index 21bde92..e66cda4 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/controller/MemoirController.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/controller/MemoirController.java @@ -4,14 +4,12 @@ import com.onnoff.onnoff.domain.off.memoir.converter.MemoirConverter; import com.onnoff.onnoff.domain.off.memoir.dto.MemoirRequestDTO; import com.onnoff.onnoff.domain.off.memoir.dto.MemoirResponseDTO; -import com.onnoff.onnoff.domain.off.memoir.entity.Emoticon; import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirQuestion; import com.onnoff.onnoff.domain.off.memoir.service.MemoirService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -23,66 +21,42 @@ public class MemoirController { private final MemoirService memoirService; - @PostMapping("/memoirs") - @Operation(summary = "회고 작성 API", description = "새로운 회고를 작성하는 API입니다.") - public ApiResponse writeMemoir(@RequestBody @Valid MemoirRequestDTO.MemoirWriteDTO request) { - Memoir memoir = memoirService.writeMemoir(request); - return ApiResponse.onSuccess(MemoirConverter.toMemoirDTO(memoir)); + @GetMapping("/memoir-questions") + @Operation(summary = "회고 질문 조회 API",description = "회고 질문 목록을 조회하는 API입니다. Query String으로 사용자 아이디를 입력해 주세요.") + public ApiResponse> getMemoirQuestion(@RequestParam(name = "userId") Long userId){ + List memoirQuestionList = memoirService.getMemoirQuestion(userId); + return ApiResponse.onSuccess(MemoirConverter.toQuestionResultDTOList(memoirQuestionList)); } - @GetMapping("/memoirs/previews") - @Operation(summary = "회고 미리보기 조회 API", description = "특정 날짜의 회고 미리보기를 조회하는 API입니다. Query String으로 날짜를 입력해 주세요.") - public ApiResponse getMemoirPreview(@RequestParam(name = "date") LocalDate date) { - Memoir memoir = memoirService.getMemoirPreview(date); - return ApiResponse.onSuccess(MemoirConverter.toMemoirPreviewDTO(memoir)); + @PostMapping("/memoirs") + @Operation(summary = "회고 작성 API",description = "새로운 회고를 작성하는 API입니다.") + public ApiResponse writeMemoir(@RequestBody @Valid MemoirRequestDTO.WriteDTO request){ + Memoir memoir = memoirService.writeMemoir(request); + return ApiResponse.onSuccess(MemoirConverter.toResultDTO(memoir)); } - @GetMapping("/memoirs/{memoirId}") - @Operation(summary = "회고 조회 API", description = "특정 회고를 조회하는 API입니다.") - public ApiResponse getMemoir(@PathVariable(name = "memoirId") Long memoirId) { - Memoir memoir = memoirService.getMemoir(memoirId); - return ApiResponse.onSuccess(MemoirConverter.toMemoirDTO(memoir)); - } + @GetMapping("/memoirs") + @Operation(summary = "회고 조회 API",description = "특정한 날짜의 회고를 조회하는 API입니다. Query String으로 사용자 아이디와 날짜를 입력해 주세요.") + public ApiResponse writeMemoir(@RequestParam(name = "userId") Long userId, @RequestParam(name = "date") LocalDate date){ + Memoir memoir = memoirService.getMemoir(userId, date); - @GetMapping("/memoirs/bookmarks") - @Operation(summary = "북마크 회고 조회 API", description = "북마크 상태의 회고를 조회하는 API이며, 페이징을 포함합니다. Query String으로 페이지 번호를 입력해 주세요. 0번이 1페이지입니다.") - public ApiResponse getBookmarkedMemoir(@RequestParam(name = "pageNumber") Integer pageNumber) { - Page memoirList = memoirService.getBookmarkedMemoir(pageNumber); - return ApiResponse.onSuccess(MemoirConverter.toBookmarkedMemoirPreviewListDTO(memoirList)); + if (memoir == null) { + return ApiResponse.onSuccess(null); + } + return ApiResponse.onSuccess(MemoirConverter.toResultDTO(memoir)); } - @PatchMapping("/memoirs/{memoirId}") - @Operation(summary = "회고 내용 수정 API", description = "기존의 회고 내용을 수정하는 API입니다.") - public ApiResponse modifyMemoir(@PathVariable(name = "memoirId") Long memoirId, - @RequestBody @Valid MemoirRequestDTO.MemoirUpdateDTO request) { - Memoir memoir = memoirService.modifyMemoir(memoirId, request); - return ApiResponse.onSuccess(MemoirConverter.toMemoirDTO(memoir)); - } - - @PatchMapping("/memoirs/{memoirId}/bookmark") - @Operation(summary = "회고 북마크 및 해제 API", description = "회고를 북마크하거나 북마크 해제하는 API입니다.") - public ApiResponse bookmarkMemoir(@PathVariable(name = "memoirId") Long memoirId) { - Memoir memoir = memoirService.bookmarkMemoir(memoirId); - return ApiResponse.onSuccess(MemoirConverter.toMemoirDTO(memoir)); + @PatchMapping("/memoirs") + @Operation(summary = "회고 수정 API",description = "기존의 회고를 수정하는 API입니다.") + public ApiResponse updateMemoir(@RequestBody @Valid MemoirRequestDTO.UpdateDTO request){ + Memoir memoir = memoirService.updateMemoir(request); + return ApiResponse.onSuccess(MemoirConverter.toResultDTO(memoir)); } @DeleteMapping("/memoirs/{memoirId}") - @Operation(summary = "회고 삭제 API", description = "기존의 회고를 삭제하는 API입니다.") - public ApiResponse deleteMemoir(@PathVariable(name = "memoirId") Long memoirId) { - return ApiResponse.onSuccess(memoirService.deleteMemoir(memoirId)); - } - - @GetMapping("/memoir-questions") - @Operation(summary = "회고 질문 조회 API", description = "회고 질문 목록을 조회하는 API입니다.") - public ApiResponse> getMemoirQuestion() { - List memoirQuestionList = memoirService.getMemoirQuestion(); - return ApiResponse.onSuccess(MemoirConverter.toMemoirQuestionDTOList(memoirQuestionList)); - } - - @GetMapping("/emoticons") - @Operation(summary = "이모티콘 조회 API", description = "이모티콘 목록을 조회하는 API입니다.") - public ApiResponse> getEmoticon() { - List emoticonList = memoirService.getEmoticon(); - return ApiResponse.onSuccess(MemoirConverter.toEmoticonDTOList(emoticonList)); + @Operation(summary = "회고 삭제 API",description = "기존의 회고를 삭제하는 API입니다.") + public ApiResponse deleteMemoir(@PathVariable(name = "memoirId") Long memoirId){ + Memoir memoir = memoirService.deleteMemoir(memoirId); + return ApiResponse.onSuccess(MemoirConverter.toResultDTO(memoir)); } } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/converter/MemoirConverter.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/converter/MemoirConverter.java index c2dc91d..10fb0d6 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/converter/MemoirConverter.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/converter/MemoirConverter.java @@ -1,45 +1,47 @@ package com.onnoff.onnoff.domain.off.memoir.converter; +import com.onnoff.onnoff.domain.off.memoir.dto.MemoirRequestDTO; import com.onnoff.onnoff.domain.off.memoir.dto.MemoirResponseDTO; -import com.onnoff.onnoff.domain.off.memoir.entity.Emoticon; import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirAnswer; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirQuestion; -import org.springframework.data.domain.Page; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class MemoirConverter { - public static MemoirResponseDTO.MemoirPreviewDTO toMemoirPreviewDTO(Memoir memoir) { - if (memoir == null) { - return MemoirResponseDTO.MemoirPreviewDTO.builder() - .memoirId(null) - .written(false) - .build(); - } else { - return MemoirResponseDTO.MemoirPreviewDTO.builder() - .memoirId(memoir.getId()) - .written(true) - .build(); - } + public static List toQuestionResultDTOList(List memoirQuestionList) { + return memoirQuestionList.stream() + .map(x -> MemoirResponseDTO.QuestionResultDTO.builder() + .questionId(x.getId()) + .question(x.getQuestion()) + .summary(x.getSummary()) + .build()) + .collect(Collectors.toList()); + } + + public static Memoir toMemoir(MemoirRequestDTO.WriteDTO request) { + return Memoir.builder() + .date(request.getDate()) + .icon(request.getIcon()) + .isBookmarked(false) + .build(); } - public static MemoirResponseDTO.MemoirDTO toMemoirDTO(Memoir memoir) { - return MemoirResponseDTO.MemoirDTO.builder() + public static MemoirResponseDTO.ResultDTO toResultDTO(Memoir memoir) { + return MemoirResponseDTO.ResultDTO.builder() .memoirId(memoir.getId()) .date(memoir.getDate()) - .emoticonUrl(memoir.getEmoticon().getImageUrl()) + .icon(memoir.getIcon()) .isBookmarked(memoir.getIsBookmarked()) - .memoirAnswerList(toMemoirAnswerDTOList(memoir.getMemoirAnswerList())) + .memoirAnswerList(toAnswerResultDTOList(memoir.getMemoirAnswerList())) .build(); } - public static List toMemoirAnswerDTOList(List memoirAnswerList) { + public static List toAnswerResultDTOList(List memoirAnswerList) { return memoirAnswerList.stream() - .map(memoirAnswer -> MemoirResponseDTO.MemoirAnswerDTO.builder() + .map(memoirAnswer -> MemoirResponseDTO.AnswerResultDTO.builder() .answerId(memoirAnswer.getId()) .question(memoirAnswer.getMemoirQuestion().getQuestion()) .summary(memoirAnswer.getMemoirQuestion().getSummary()) @@ -47,47 +49,4 @@ public static List toMemoirAnswerDTOList(List .build()) .collect(Collectors.toList()); } - - public static MemoirResponseDTO.BookmarkedMemoirListDTO toBookmarkedMemoirPreviewListDTO(Page memoirList) { - AtomicInteger index = new AtomicInteger(); - - List memoirDTOList = memoirList.stream() - .map(memoir -> MemoirResponseDTO.BookmarkedMemoirDTO.builder() - .memoirId(memoir.getId()) - .date(memoir.getDate()) - .emoticonUrl(memoir.getEmoticon().getImageUrl()) - .remain(index.getAndIncrement() % 2) - .build()) - .toList(); - - return MemoirResponseDTO.BookmarkedMemoirListDTO.builder() - .memoirList(memoirDTOList) - .pageNumber(memoirList.getNumber()) - .pageSize(memoirList.getSize()) - .totalPages(memoirList.getTotalPages()) - .totalElements(memoirList.getTotalElements()) - .isFirst(memoirList.isFirst()) - .isLast(memoirList.isLast()) - .build(); - } - - public static List toMemoirQuestionDTOList(List memoirQuestionList) { - return memoirQuestionList.stream() - .map(memoirQuestion -> MemoirResponseDTO.MemoirQuestionDTO.builder() - .questionId(memoirQuestion.getId()) - .question(memoirQuestion.getQuestion()) - .summary(memoirQuestion.getSummary()) - .build()) - .collect(Collectors.toList()); - } - - public static List toEmoticonDTOList(List emoticonList) { - return emoticonList.stream() - .map(emoticon -> MemoirResponseDTO.EmoticonDTO.builder() - .emoticonId(emoticon.getId()) - .imageUrl(emoticon.getImageUrl()) - .build()) - .collect(Collectors.toList()); - } - } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirRequestDTO.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirRequestDTO.java index 3b414ca..c0a2438 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirRequestDTO.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirRequestDTO.java @@ -10,18 +10,19 @@ public class MemoirRequestDTO { @Getter - public static class MemoirWriteDTO { + public static class WriteDTO { @NotNull - @PastOrPresent - LocalDate date; - @NotNull - Long emoticonId; + Long userId; @NotNull - List<@Valid MemoirWriteAnswerDTO> memoirAnswerList; + LocalDate date; + @Size(max = 255) + String icon; + @NotEmpty + List<@Valid WriteAnswerDTO> memoirAnswerList; } @Getter - public static class MemoirWriteAnswerDTO { + public static class WriteAnswerDTO { @NotNull Long questionId; @Size(max = 500) @@ -29,15 +30,17 @@ public static class MemoirWriteAnswerDTO { } @Getter - public static class MemoirUpdateDTO { - @NotNull - Long emoticonId; + public static class UpdateDTO { @NotNull - List<@Valid MemoirUpdateAnswerDTO> memoirAnswerList; + Long memoirId; + @Size(max = 255) + String icon; + Boolean isBookmarked; + List<@Valid UpdateAnswerDTO> memoirAnswerList; } @Getter - public static class MemoirUpdateAnswerDTO { + public static class UpdateAnswerDTO { @NotNull Long answerId; @Size(max = 500) diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirResponseDTO.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirResponseDTO.java index 14b6cb0..4d41e90 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirResponseDTO.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/dto/MemoirResponseDTO.java @@ -14,76 +14,33 @@ public class MemoirResponseDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class MemoirDTO { - Long memoirId; - LocalDate date; - String emoticonUrl; - Boolean isBookmarked; - List memoirAnswerList; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class MemoirAnswerDTO { - Long answerId; + public static class QuestionResultDTO{ + Long questionId; String question; String summary; - String answer; } @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class MemoirPreviewDTO { - Boolean written; - Long memoirId; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class BookmarkedMemoirListDTO { - List memoirList; - Integer pageNumber; - Integer pageSize; - Integer totalPages; - Long totalElements; - Boolean isFirst; - Boolean isLast; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class BookmarkedMemoirDTO { + public static class ResultDTO { Long memoirId; LocalDate date; - String emoticonUrl; - Integer remain; + String icon; + Boolean isBookmarked; + List memoirAnswerList; } @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class MemoirQuestionDTO { - Long questionId; + public static class AnswerResultDTO{ + Long answerId; String question; String summary; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class EmoticonDTO { - Long emoticonId; - String imageUrl; + String answer; } } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Emoticon.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Emoticon.java deleted file mode 100644 index f4de4ec..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Emoticon.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.onnoff.onnoff.domain.off.memoir.entity; - -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Emoticon { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, length = 1024) - private String imageUrl; -} diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Memoir.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Memoir.java index bb45994..37f5383 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Memoir.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/entity/Memoir.java @@ -22,9 +22,7 @@ public class Memoir extends BaseEntity { private LocalDate date; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "emoticon_id") - private Emoticon emoticon; + private String icon; @Column(columnDefinition = "boolean default false") private Boolean isBookmarked; @@ -36,8 +34,8 @@ public class Memoir extends BaseEntity { @OneToMany(mappedBy = "memoir", cascade = CascadeType.ALL) private List memoirAnswerList = new ArrayList<>(); - public void setEmoticon(Emoticon emoticon) { - this.emoticon = emoticon; + public void setIcon(String icon) { + this.icon = icon; } public void setIsBookmarked(Boolean isBookmarked) { diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/EmoticonRepository.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/EmoticonRepository.java deleted file mode 100644 index dab6a2c..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/EmoticonRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.onnoff.onnoff.domain.off.memoir.repository; - -import com.onnoff.onnoff.domain.off.memoir.entity.Emoticon; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface EmoticonRepository extends JpaRepository { -} diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirAnswerRepository.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirAnswerRepository.java index 622335e..448a659 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirAnswerRepository.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirAnswerRepository.java @@ -3,5 +3,9 @@ import com.onnoff.onnoff.domain.off.memoir.entity.MemoirAnswer; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface MemoirAnswerRepository extends JpaRepository { + List findAllByMemoirId(Long memoirid); + } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirRepository.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirRepository.java index 5fe5ab4..38bbd69 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirRepository.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/repository/MemoirRepository.java @@ -2,17 +2,12 @@ import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; import com.onnoff.onnoff.domain.user.User; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import java.time.LocalDate; -import java.util.List; import java.util.Optional; public interface MemoirRepository extends JpaRepository { Optional findByUserAndDate(User user, LocalDate date); - - Page findByUserAndIsBookmarkedOrderByDateDesc(User user, Boolean isBookmarked, PageRequest pageRequest); } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirService.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirService.java index 2c2d6be..0466a19 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirService.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirService.java @@ -1,32 +1,24 @@ package com.onnoff.onnoff.domain.off.memoir.service; import com.onnoff.onnoff.domain.off.memoir.dto.MemoirRequestDTO; -import com.onnoff.onnoff.domain.off.memoir.entity.Emoticon; import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirQuestion; -import org.springframework.data.domain.Page; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; public interface MemoirService { + List getMemoirQuestion(Long userId); - Memoir writeMemoir(MemoirRequestDTO.MemoirWriteDTO request); + @Transactional + Memoir writeMemoir(MemoirRequestDTO.WriteDTO request); - Memoir getMemoirPreview(LocalDate date); + Memoir getMemoir(Long userId, LocalDate date); - Memoir getMemoir(Long memoirId); + @Transactional + Memoir updateMemoir(MemoirRequestDTO.UpdateDTO request); - Page getBookmarkedMemoir(Integer pageNumber); - - Memoir modifyMemoir(Long memoirId, MemoirRequestDTO.MemoirUpdateDTO request); - - Memoir bookmarkMemoir(Long memoirId); - - Long deleteMemoir(Long memoirId); - - List getMemoirQuestion(); - - List getEmoticon(); + @Transactional + Memoir deleteMemoir(Long memoirId); } diff --git a/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirServiceImpl.java b/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirServiceImpl.java index 40c3e1d..eade991 100644 --- a/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirServiceImpl.java +++ b/src/main/java/com/onnoff/onnoff/domain/off/memoir/service/MemoirServiceImpl.java @@ -2,20 +2,17 @@ import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus; import com.onnoff.onnoff.apiPayload.exception.GeneralException; -import com.onnoff.onnoff.auth.UserContext; +import com.onnoff.onnoff.domain.off.memoir.converter.MemoirConverter; import com.onnoff.onnoff.domain.off.memoir.dto.MemoirRequestDTO; import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirAnswer; -import com.onnoff.onnoff.domain.off.memoir.entity.Emoticon; import com.onnoff.onnoff.domain.off.memoir.entity.MemoirQuestion; import com.onnoff.onnoff.domain.off.memoir.repository.MemoirAnswerRepository; -import com.onnoff.onnoff.domain.off.memoir.repository.EmoticonRepository; import com.onnoff.onnoff.domain.off.memoir.repository.MemoirQuestionRepository; import com.onnoff.onnoff.domain.off.memoir.repository.MemoirRepository; import com.onnoff.onnoff.domain.user.User; +import com.onnoff.onnoff.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,21 +28,24 @@ public class MemoirServiceImpl implements MemoirService { private final MemoirRepository memoirRepository; private final MemoirAnswerRepository memoirAnswerRepository; private final MemoirQuestionRepository memoirQuestionRepository; - private final EmoticonRepository emoticonRepository; + + private final UserRepository userRepository; + + @Override + @Transactional(readOnly = true) + public List getMemoirQuestion(Long userId) { + return memoirQuestionRepository.findAll(); + } @Override @Transactional - public Memoir writeMemoir(MemoirRequestDTO.MemoirWriteDTO request) { - User user = UserContext.getUser(); + public Memoir writeMemoir(MemoirRequestDTO.WriteDTO request) { + User user = userRepository.findById(request.getUserId()).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); if (memoirRepository.findByUserAndDate(user, request.getDate()).isPresent()) { throw new GeneralException(ErrorStatus.MEMOIR_EXIST); } - Memoir newMemoir = Memoir.builder() - .date(request.getDate()) - .emoticon(emoticonRepository.findById(request.getEmoticonId()).orElseThrow(() -> new GeneralException(ErrorStatus.EMOTICON_NOT_FOUND))) - .isBookmarked(false) - .build(); + Memoir newMemoir = MemoirConverter.toMemoir(request); List newMemoirAnswerList = request.getMemoirAnswerList().stream() .map(memoirAnswer -> MemoirAnswer.builder() @@ -63,32 +63,27 @@ public Memoir writeMemoir(MemoirRequestDTO.MemoirWriteDTO request) { @Override @Transactional(readOnly = true) - public Memoir getMemoirPreview(LocalDate date) { - User user = UserContext.getUser(); + public Memoir getMemoir(Long userId, LocalDate date) { + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); return memoirRepository.findByUserAndDate(user, date).orElse(null); } @Override - @Transactional(readOnly = true) - public Memoir getMemoir(Long memoirId) { - return memoirRepository.findById(memoirId).orElseThrow(() -> new GeneralException(ErrorStatus.MEMOIR_NOT_FOUND)); - } + @Transactional + public Memoir updateMemoir(MemoirRequestDTO.UpdateDTO request) { + Memoir memoir = memoirRepository.findById(request.getMemoirId()).orElseThrow(() -> new GeneralException(ErrorStatus.MEMOIR_NOT_FOUND)); - @Override - @Transactional(readOnly = true) - public Page getBookmarkedMemoir(Integer pageNumber) { - User user = UserContext.getUser(); - return memoirRepository.findByUserAndIsBookmarkedOrderByDateDesc(user, true, PageRequest.of(pageNumber, 10)); - } + if (request.getIcon() != null) { + memoir.setIcon(request.getIcon()); + } - @Override - @Transactional - public Memoir modifyMemoir(Long memoirId, MemoirRequestDTO.MemoirUpdateDTO request) { - Memoir memoir = memoirRepository.findById(memoirId).orElseThrow(() -> new GeneralException(ErrorStatus.MEMOIR_NOT_FOUND)); + if (request.getIsBookmarked() != null) { + memoir.setIsBookmarked(request.getIsBookmarked()); + } - memoir.setEmoticon(emoticonRepository.findById(request.getEmoticonId()).orElseThrow(() -> new GeneralException(ErrorStatus.EMOTICON_NOT_FOUND))); + List requestMemoirAnswerList = request.getMemoirAnswerList() == null ? new ArrayList<>() : request.getMemoirAnswerList(); - for (MemoirRequestDTO.MemoirUpdateAnswerDTO memoirAnswer : request.getMemoirAnswerList()) { + for (MemoirRequestDTO.UpdateAnswerDTO memoirAnswer: requestMemoirAnswerList) { MemoirAnswer findMemoirAnswer = memoirAnswerRepository.findById(memoirAnswer.getAnswerId()).orElseThrow(() -> new GeneralException(ErrorStatus.ANSWER_NOT_FOUND)); if (findMemoirAnswer.getMemoir() != memoir) { throw new GeneralException(ErrorStatus.ANSWER_BAD_MATCH); @@ -96,36 +91,14 @@ public Memoir modifyMemoir(Long memoirId, MemoirRequestDTO.MemoirUpdateDTO reque findMemoirAnswer.setAnswer(memoirAnswer.getAnswer()); } - return memoir; - } - - @Override - @Transactional - public Memoir bookmarkMemoir(Long memoirId) { - Memoir memoir = memoirRepository.findById(memoirId).orElseThrow(() -> new GeneralException(ErrorStatus.MEMOIR_NOT_FOUND)); - memoir.setIsBookmarked(memoir.getIsBookmarked().equals(false)); - - return memoir; + return memoirRepository.save(memoir); } @Override @Transactional - public Long deleteMemoir(Long memoirId) { + public Memoir deleteMemoir(Long memoirId) { Memoir memoir = memoirRepository.findById(memoirId).orElseThrow(() -> new GeneralException(ErrorStatus.MEMOIR_NOT_FOUND)); memoirRepository.delete(memoir); - - return memoir.getId(); - } - - @Override - @Transactional(readOnly = true) - public List getMemoirQuestion() { - return memoirQuestionRepository.findAll(); - } - - @Override - @Transactional(readOnly = true) - public List getEmoticon() { - return emoticonRepository.findAll(); + return memoir; } } diff --git a/src/main/java/com/onnoff/onnoff/domain/on/worklog/repository/WorklogRepository.java b/src/main/java/com/onnoff/onnoff/domain/on/worklog/repository/WorklogRepository.java index ff03c03..f94216e 100644 --- a/src/main/java/com/onnoff/onnoff/domain/on/worklog/repository/WorklogRepository.java +++ b/src/main/java/com/onnoff/onnoff/domain/on/worklog/repository/WorklogRepository.java @@ -1,6 +1,5 @@ package com.onnoff.onnoff.domain.on.worklog.repository; -import com.onnoff.onnoff.domain.on.resolution.entity.Resolution; import com.onnoff.onnoff.domain.on.worklog.entity.Worklog; import com.onnoff.onnoff.domain.user.User; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,4 +9,6 @@ public interface WorklogRepository extends JpaRepository { List findAllByUserAndDate(User user, LocalDate date); + Integer countByUserAndDate(User user, LocalDate date); + Integer countByUserAndDateAndIsChecked(User user, LocalDate date, Boolean t); } diff --git a/src/main/java/com/onnoff/onnoff/domain/push/controller/FcmController.java b/src/main/java/com/onnoff/onnoff/domain/push/controller/FcmController.java deleted file mode 100644 index d4fa68c..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/controller/FcmController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onnoff.onnoff.domain.push.controller; - -import com.onnoff.onnoff.domain.push.service.FcmSendNotificationService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@Slf4j -public class FcmController { - private final FcmSendNotificationService fcmSendNotificationService; - - @GetMapping("/message/test") - public ResponseEntity pushTest(){ - fcmSendNotificationService.sendTestNotification(); - return ResponseEntity.ok("good"); - } -} diff --git a/src/main/java/com/onnoff/onnoff/domain/push/dto/FcmServiceDto.java b/src/main/java/com/onnoff/onnoff/domain/push/dto/FcmServiceDto.java deleted file mode 100644 index bb8ed27..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/dto/FcmServiceDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onnoff.onnoff.domain.push.dto; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class FcmServiceDto { - private String username; - private Long contentId; - private NotificationType type; - private String title; - private String content; - - public static FcmServiceDto of(String username, Long contentId, NotificationType type, String title, String content){ - FcmServiceDto dto = new FcmServiceDto(); - dto.username = username; - dto.contentId = contentId; - dto.type = type; - dto.title = title; - dto.content = content; - return dto; - } -} diff --git a/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationMessage.java b/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationMessage.java deleted file mode 100644 index c5e43bc..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationMessage.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.onnoff.onnoff.domain.push.dto; - -import com.google.firebase.messaging.Notification; -import com.onnoff.onnoff.domain.user.User; -import lombok.Builder; - -@Builder -public record NotificationMessage( - String title, - String message, - NotificationType type -) { - public static NotificationMessage toGoHomeNotification(User user) { - return NotificationMessage.builder() - .title(user.getNickname() + "님, 이제 퇴근하실 시간이에요.") - .message("") - .type(NotificationType.NOTIFY) - .build(); - } - public static NotificationMessage toGoHomeNotificationTest() { - return NotificationMessage.builder() - .title("예진" + "님, 이제 퇴근하실 시간이에요.") - .message("") - .type(NotificationType.NOTIFY) - .build(); - } - - - public Notification toNotification() { - return Notification.builder() - .setTitle(title) - .setBody(message) - .build(); - } -} diff --git a/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationType.java b/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationType.java deleted file mode 100644 index 87be077..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/dto/NotificationType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.onnoff.onnoff.domain.push.dto; - -public enum NotificationType { - NOTIFY -} diff --git a/src/main/java/com/onnoff/onnoff/domain/push/service/FcmSendNotificationService.java b/src/main/java/com/onnoff/onnoff/domain/push/service/FcmSendNotificationService.java deleted file mode 100644 index e202d43..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/service/FcmSendNotificationService.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onnoff.onnoff.domain.push.service; - -import com.google.firebase.messaging.Message; -import com.onnoff.onnoff.domain.push.dto.NotificationMessage; -import com.onnoff.onnoff.domain.user.User; -import com.onnoff.onnoff.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; - -@Service -@Slf4j -@RequiredArgsConstructor -public class FcmSendNotificationService { - private final FcmService fcmService; - private final UserRepository userRepository; - public void sendTestNotification() { - NotificationMessage notificationMessage = - NotificationMessage.toGoHomeNotificationTest(); - final Message message = - fcmService.createMessage("testtoken", notificationMessage); - fcmService.send(message); - } - //1분 단위로 푸시알람 보내는 함수 - @Scheduled(cron = "0 * * * * *", zone = "Asia/Seoul") - public void sendGoHomeNotificationByMinute() { - LocalDateTime currentTime = LocalDateTime.now(); - LocalTime startTime = currentTime.minusSeconds(30).toLocalTime(); - LocalTime endTime = currentTime.plusSeconds(30).toLocalTime(); - List userList = userRepository.findByPushNotificationTimeBetween(startTime, endTime); - for (User user : userList) { - NotificationMessage notificationMessage = NotificationMessage.toGoHomeNotification(user); - Message message = fcmService.createMessage(user.getFcmToken(), notificationMessage); - fcmService.send(message); - } - } - -} diff --git a/src/main/java/com/onnoff/onnoff/domain/push/service/FcmService.java b/src/main/java/com/onnoff/onnoff/domain/push/service/FcmService.java deleted file mode 100644 index db1fa29..0000000 --- a/src/main/java/com/onnoff/onnoff/domain/push/service/FcmService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.onnoff.onnoff.domain.push.service; - -import com.google.firebase.messaging.*; -import com.onnoff.onnoff.domain.push.dto.NotificationMessage; -import lombok.Builder; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.io.IOException; - -@Service -@Builder -@RequiredArgsConstructor -public class FcmService { - public void send(Message message) { - FirebaseMessaging.getInstance() - .sendAsync(message); - } - public Message createMessage(String deviceToken, NotificationMessage notificationMessage) { - return Message.builder() - .setToken(deviceToken) - .setNotification(notificationMessage.toNotification()) - .putData("title", notificationMessage.title()) - .putData("body", notificationMessage.message()) - .putData("type", notificationMessage.type().name()) - .build(); - } - - //다음 행동 유도가 필요할 때 path를 쓴다..! - public Message createMessage(String deviceToken, NotificationMessage notificationMessage, String path) { - - Notification notification = notificationMessage.toNotification(); - return Message.builder() - .setToken(deviceToken) - .setNotification(notification) - .setApnsConfig( - ApnsConfig.builder() - .setAps( - Aps.builder() - .setSound("default") - .build() - ) - .build() - ) - .putData("title", notificationMessage.title()) - .putData("body", notificationMessage.message()) - .putData("type", notificationMessage.type().name()) - .putData("path", path) - .putData("sound", "default") - .build(); - } -} diff --git a/src/main/java/com/onnoff/onnoff/domain/stats/controller/StatsController.java b/src/main/java/com/onnoff/onnoff/domain/stats/controller/StatsController.java new file mode 100644 index 0000000..56b4538 --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/domain/stats/controller/StatsController.java @@ -0,0 +1,41 @@ +package com.onnoff.onnoff.domain.stats.controller; + +import com.onnoff.onnoff.apiPayload.ApiResponse; +import com.onnoff.onnoff.domain.stats.dto.StatsResponseDTO; +import com.onnoff.onnoff.domain.stats.service.StatsService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/stats") +public class StatsController { + private final StatsService statsService; + + @GetMapping("/week") + @Operation(summary = "요일별 달성률") + public ApiResponse getWeekStats(){ + return ApiResponse.onSuccess(statsService.getWeekStats()); + } + + @GetMapping("/month") + @Operation(summary = "월별 달성률") + public ApiResponse getMonthStats(){ + return ApiResponse.onSuccess(statsService.getMonthStats(null)); + } + + @GetMapping("/month/prev") + @Operation(summary = "이전 달 달성률") + public ApiResponse moveToPrevMonth(@RequestParam(name = "date") LocalDate date){ + return ApiResponse.onSuccess(statsService.getMonthStats(date.minusMonths(1))); + } + + @GetMapping("/month/next") + @Operation(summary = "다음 달 달성률") + public ApiResponse moveToNextMonth(@RequestParam(name = "date") LocalDate date){ + return ApiResponse.onSuccess(statsService.getMonthStats(date.plusMonths(1))); + } +} \ No newline at end of file diff --git a/src/main/java/com/onnoff/onnoff/domain/stats/converter/StatsConverter.java b/src/main/java/com/onnoff/onnoff/domain/stats/converter/StatsConverter.java new file mode 100644 index 0000000..5f42f89 --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/domain/stats/converter/StatsConverter.java @@ -0,0 +1,37 @@ +package com.onnoff.onnoff.domain.stats.converter; + +import com.onnoff.onnoff.domain.stats.dto.StatsResponseDTO; + +import java.time.LocalDate; +import java.util.List; + +public class StatsConverter { + public static StatsResponseDTO.WeekDTO getWeekStatsDTO(LocalDate today, Double on, Double off, + Integer weekOfMonth, List weekStats){ + StatsResponseDTO.WeekStatsDTO weekStatsDTO = StatsResponseDTO.WeekStatsDTO.builder() + .mon(weekStats.get(0)) + .tue(weekStats.get(1)) + .wed(weekStats.get(2)) + .thu(weekStats.get(3)) + .fri(weekStats.get(4)) + .sat(weekStats.get(5)) + .sun(weekStats.get(6)) + .build(); + + return StatsResponseDTO.WeekDTO.builder() + .today(today) + .on(on) + .off(off) + .weekOfMonth(weekOfMonth) + .weekStatsDTO(weekStatsDTO) + .build(); + } + + public static StatsResponseDTO.MonthDTO getMonthStatsDTO(LocalDate date, Integer avg, List monthStatsList){ + return StatsResponseDTO.MonthDTO.builder() + .date(date) + .avg(avg) + .monthStatsList(monthStatsList) + .build(); + } +} diff --git a/src/main/java/com/onnoff/onnoff/domain/stats/dto/StatsResponseDTO.java b/src/main/java/com/onnoff/onnoff/domain/stats/dto/StatsResponseDTO.java new file mode 100644 index 0000000..a5593ad --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/domain/stats/dto/StatsResponseDTO.java @@ -0,0 +1,57 @@ +package com.onnoff.onnoff.domain.stats.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class StatsResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class WeekStatsDTO{ + Double mon; + Double tue; + Double wed; + Double thu; + Double fri; + Double sat; + Double sun; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class WeekDTO{ + LocalDate today; + Double on; + Double off; + Integer weekOfMonth; + WeekStatsDTO weekStatsDTO; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MonthStatsDTO{ + LocalDate date; + Double rate; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MonthDTO{ + LocalDate date; + Integer avg; + List monthStatsList; + } +} diff --git a/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsService.java b/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsService.java new file mode 100644 index 0000000..9161e58 --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsService.java @@ -0,0 +1,10 @@ +package com.onnoff.onnoff.domain.stats.service; + +import com.onnoff.onnoff.domain.stats.dto.StatsResponseDTO; + +import java.time.LocalDate; + +public interface StatsService { + StatsResponseDTO.WeekDTO getWeekStats(); + StatsResponseDTO.MonthDTO getMonthStats(LocalDate date); +} diff --git a/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsServiceImpl.java b/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsServiceImpl.java new file mode 100644 index 0000000..3b5f490 --- /dev/null +++ b/src/main/java/com/onnoff/onnoff/domain/stats/service/StatsServiceImpl.java @@ -0,0 +1,114 @@ +package com.onnoff.onnoff.domain.stats.service; + +import com.onnoff.onnoff.auth.UserContext; +import com.onnoff.onnoff.domain.off.feed.repository.FeedRepository; +import com.onnoff.onnoff.domain.off.memoir.entity.Memoir; +import com.onnoff.onnoff.domain.off.memoir.entity.MemoirAnswer; +import com.onnoff.onnoff.domain.off.memoir.repository.MemoirAnswerRepository; +import com.onnoff.onnoff.domain.off.memoir.repository.MemoirRepository; +import com.onnoff.onnoff.domain.on.worklog.repository.WorklogRepository; +import com.onnoff.onnoff.domain.stats.converter.StatsConverter; +import com.onnoff.onnoff.domain.stats.dto.StatsResponseDTO; +import com.onnoff.onnoff.domain.user.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.temporal.WeekFields; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class StatsServiceImpl implements StatsService{ + private final WorklogRepository worklogRepository; + private final FeedRepository feedRepository; + private final MemoirRepository memoirRepository; + private final MemoirAnswerRepository memoirAnswerRepository; + + @Override + public StatsResponseDTO.WeekDTO getWeekStats(){ + LocalDate today = LocalDate.now(); + + User user = UserContext.getUser(); + + Double on = worklogRepository.countByUserAndDateAndIsChecked(user, today, true) / worklogRepository.countByUserAndDate(user, today).doubleValue(); + Double off = feedRepository.countByUserAndDateAndIsChecked(user, today, true) / feedRepository.countByUserAndDate(user, today).doubleValue(); + + WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 1); + Integer weekOfMonth = today.get(weekFields.weekOfMonth()); + + List weekStats = new ArrayList<>(); + LocalDate monday = today.minusDays(today.getDayOfWeek().getValue() - 1); + for (int i=0; i<7; i++){ + if(i > today.getDayOfWeek().getValue() - 1){ + weekStats.add(0.0); + continue; + } + LocalDate date = monday.plusDays(i); + Double offW = feedRepository.countByUserAndDateAndIsChecked(user, date, true) / feedRepository.countByUserAndDate(user, date).doubleValue(); + + weekStats.add(offW); + } + + return StatsConverter.getWeekStatsDTO(today, on, off, weekOfMonth, weekStats); + } + + @Override + public StatsResponseDTO.MonthDTO getMonthStats(LocalDate date){ + if(date == null){ + date = LocalDate.now(); + } + + Double avg = 0.0; + + User user = UserContext.getUser(); + + List monthStatsList = new ArrayList<>(); + LocalDate localDate = date.minusDays(date.getDayOfMonth() - 1); + for(int i=0; i memoirAnswerList = memoirAnswerRepository.findAllByMemoirId(memoir.getId()).stream().toList(); + for(MemoirAnswer memoirAnswer: memoirAnswerList){ + if(memoirAnswer.getAnswer() != null){ + rate++; + } + } + + StatsResponseDTO.MonthStatsDTO stats = StatsResponseDTO.MonthStatsDTO.builder() + .date(localDate.plusDays(i)) + .rate(rate / 4.0) + .build(); + monthStatsList.add(stats); + + avg += rate / 4.0 * 100; + } + + if(date.equals(LocalDate.now())){ + //이번달은 오늘 기준으로 계산 + avg = avg / date.getDayOfMonth(); + } + else{ + //이외에는 한 달 기준으로 계산 + avg = avg / date.lengthOfMonth(); + } + + return StatsConverter.getMonthStatsDTO(date, Integer.parseInt(String.valueOf(Math.round(avg))), monthStatsList); + } +} diff --git a/src/main/java/com/onnoff/onnoff/domain/user/User.java b/src/main/java/com/onnoff/onnoff/domain/user/User.java index 6bdd92d..7dc6203 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/User.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/User.java @@ -13,7 +13,6 @@ import org.hibernate.annotations.DynamicInsert; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -32,7 +31,7 @@ public class User extends BaseEntity { private Long id; @Column(nullable = false) - private String oauthId; //토큰으로 얻어온 정보로 유저를 조회할 때 사용 + private Long oauthId; //토큰으로 얻어온 정보로 유저를 조회할 때 사용 @Column(columnDefinition = "TINYINT(1) DEFAULT 0") private boolean infoSet; //추가 정보 기입 여부 @@ -68,11 +67,6 @@ public class User extends BaseEntity { private String fcmToken; - @Column(nullable = true) - private LocalTime pushNotificationTime; - - private String appleRefreshToken; - @Enumerated(EnumType.STRING) private SocialType socialType; @@ -90,8 +84,4 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List worklogList = new ArrayList<>(); - - public void setAppleRefreshToken(String appleRefreshToken) { - this.appleRefreshToken = appleRefreshToken; - } } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java b/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java index d42d469..64b850d 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/converter/UserConverter.java @@ -1,33 +1,26 @@ package com.onnoff.onnoff.domain.user.converter; -import com.onnoff.onnoff.auth.dto.LoginRequestDTO; -import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO; +import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO; import com.onnoff.onnoff.domain.user.User; import com.onnoff.onnoff.domain.user.dto.UserResponseDTO; import com.onnoff.onnoff.domain.user.enums.SocialType; public class UserConverter { public static User toUser(KakaoOauth2DTO.UserInfoResponseDTO response){ + KakaoOauth2DTO.KakaoAccountDTO kakaoAccount = response.getKakaoAccount(); return User.builder() - .oauthId(response.getSub()) - .email(response.getEmail()) - .name(response.getName()) + .oauthId(response.getId()) + .email(kakaoAccount.getEmail()) + .name(kakaoAccount.getName()) .socialType(SocialType.KAKAO) .build(); } - public static User toUser(LoginRequestDTO.AppleTokenValidateDTO request){ - return User.builder() - .oauthId(request.getOauthId()) - .email(request.getEmail()) - .name(request.getFullName()) - .socialType(SocialType.APPLE) - .build(); - } - public static UserResponseDTO.LoginDTO toLoginDTO(User user, String accessToken, String refreshToken){ + + public static UserResponseDTO.LoginDTO toLoginDTO(User user){ return UserResponseDTO.LoginDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) + .id(user.getId()) .infoSet(user.isInfoSet()) + .createdAt(user.getCreatedAt()) .build(); } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java b/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java index b870810..8482817 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/dto/UserResponseDTO.java @@ -16,9 +16,9 @@ public class UserResponseDTO { @NoArgsConstructor @AllArgsConstructor public static class LoginDTO{ + private Long id; private boolean infoSet; - private String accessToken; - private String refreshToken; + private LocalDateTime createdAt; } @Builder diff --git a/src/main/java/com/onnoff/onnoff/domain/user/repository/UserRepository.java b/src/main/java/com/onnoff/onnoff/domain/user/repository/UserRepository.java index 751beb3..09246a5 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/repository/UserRepository.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/repository/UserRepository.java @@ -3,13 +3,8 @@ import com.onnoff.onnoff.domain.user.User; import org.springframework.data.jpa.repository.JpaRepository; -import java.time.LocalTime; -import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepository { - Optional findByOauthId(String oauthId); - - List findByPushNotificationTimeBetween(LocalTime startTime, LocalTime endTime); - + Optional findByOauthId(Long oauthId); } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/service/UserService.java b/src/main/java/com/onnoff/onnoff/domain/user/service/UserService.java index b0cf32a..d970991 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/service/UserService.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/service/UserService.java @@ -5,13 +5,13 @@ import java.util.List; public interface UserService { - public User create(User user); + public Long create(User user); public List getUserList(); public User getUser(Long id); - public boolean isExistByOauthId(String oauthId); + public boolean isExistByOauthId(Long oauthId); - public User getUserByOauthId(String oauthId); + public User getUserByOauthId(Long oauthId); } diff --git a/src/main/java/com/onnoff/onnoff/domain/user/service/UserServiceImpl.java b/src/main/java/com/onnoff/onnoff/domain/user/service/UserServiceImpl.java index 76ab25f..74be39a 100644 --- a/src/main/java/com/onnoff/onnoff/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/onnoff/onnoff/domain/user/service/UserServiceImpl.java @@ -16,8 +16,8 @@ public class UserServiceImpl implements UserService{ private final UserRepository userRepository; @Transactional @Override - public User create(User user) { - return userRepository.save(user); + public Long create(User user) { + return userRepository.save(user).getId(); } @Transactional(readOnly = true) @@ -36,13 +36,13 @@ public User getUser(Long id) { } @Transactional(readOnly = true) @Override - public boolean isExistByOauthId(String oauthId) { - return userRepository.findByOauthId(oauthId).isPresent(); + public boolean isExistByOauthId(Long oauthId) { + return userRepository.findById(oauthId).isPresent(); } @Transactional(readOnly = true) @Override - public User getUserByOauthId(String oauthId) { + public User getUserByOauthId(Long oauthId) { User user = userRepository.findByOauthId(oauthId).orElseThrow( () -> new GeneralException(ErrorStatus.USER_NOT_FOUND) ); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7c25337..c4e98b6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,7 +19,4 @@ kakao: iss: https://kauth.kakao.com client-id: ${KAKAO_CLIENT_ID} admin-key: ${KAKAO_ADMIN_KEY} -fcm: - firebase-sdk-path : ${FIREBASE_SDK_PATH} - project-id : ${FIREBASE_PROJECT_ID} diff --git a/src/test/java/com/onnoff/onnoff/domain/user/service/UserServiceImplTest.java b/src/test/java/com/onnoff/onnoff/domain/user/service/UserServiceImplTest.java index 76268a5..dadbfa8 100644 --- a/src/test/java/com/onnoff/onnoff/domain/user/service/UserServiceImplTest.java +++ b/src/test/java/com/onnoff/onnoff/domain/user/service/UserServiceImplTest.java @@ -1,12 +1,17 @@ package com.onnoff.onnoff.domain.user.service; import com.onnoff.onnoff.domain.user.User; +import com.onnoff.onnoff.domain.user.repository.UserRepository; import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @@ -18,9 +23,9 @@ class UserServiceImplTest { void 생성_및_조회() { User user = User.builder().name("우성").nickname("우스").build(); - User createdUser = userService.create(user); + Long userId = userService.create(user); - User findUser = userService.getUser(createdUser.getId()); + User findUser = userService.getUser(userId); Assertions.assertThat(user.getName()).isEqualTo(findUser.getName()); }