From 089b7c05c3e120884489138494fb744791b1fc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A0=9C?= Date: Fri, 18 Aug 2023 02:51:55 +0900 Subject: [PATCH 1/4] =?UTF-8?q?YEL-109=20[feat]=20GCP=20oauth2.0=20key=20f?= =?UTF-8?q?ile=20ignore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e6d7f52d..f0bc2dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ application.yml application-dev.yml application-apple.yml firebase*.json +*client_secret*.json ### monitoring ### monitoring/prometheus/volume From 9cde2a949dc03edf8b7680317fda00b6da818e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A0=9C?= Date: Fri, 18 Aug 2023 02:52:11 +0900 Subject: [PATCH 2/4] =?UTF-8?q?YEL-109=20[feat]=20Google=20Rest=20Util=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 ++ .../vote/controller/VoteController.java | 2 +- .../server/global/HealthCheckController.java | 27 ++++ .../global/common/util/ConstantUtil.java | 4 + .../server/global/common/util/RestUtil.java | 118 ++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/yello/server/global/common/util/RestUtil.java diff --git a/build.gradle b/build.gradle index 85fc0019..5098fcf6 100644 --- a/build.gradle +++ b/build.gradle @@ -83,6 +83,13 @@ dependencies { // slack-webhook implementation "net.gpedro.integrations.slack:slack-webhook:1.4.0" + + // google play android developer api + implementation 'com.google.apis:google-api-services-androidpublisher:v3-rev20211125-1.32.1' + implementation "com.google.api-client:google-api-client:1.33.0" + + // gson + implementation 'com.google.code.gson:gson:2.10.1' } tasks.named('test') { diff --git a/src/main/java/com/yello/server/domain/vote/controller/VoteController.java b/src/main/java/com/yello/server/domain/vote/controller/VoteController.java index 6d52ef75..80f92168 100644 --- a/src/main/java/com/yello/server/domain/vote/controller/VoteController.java +++ b/src/main/java/com/yello/server/domain/vote/controller/VoteController.java @@ -152,7 +152,7 @@ public BaseResponse checkVoteAvailable( @Operation(summary = "투표 생성 API", responses = { @ApiResponse( responseCode = "201", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = VoteAvailableResponse.class))), + content = @Content(mediaType = "application/json", schema = @Schema(implementation = VoteCreateResponse.class))), }) @PostMapping public BaseResponse createVote( diff --git a/src/main/java/com/yello/server/global/HealthCheckController.java b/src/main/java/com/yello/server/global/HealthCheckController.java index 01f00f37..b1923e34 100644 --- a/src/main/java/com/yello/server/global/HealthCheckController.java +++ b/src/main/java/com/yello/server/global/HealthCheckController.java @@ -1,6 +1,9 @@ package com.yello.server.global; +import com.yello.server.global.common.util.ConstantUtil; +import com.yello.server.global.common.util.RestUtil; import io.swagger.v3.oas.annotations.Hidden; +import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -14,4 +17,28 @@ public class HealthCheckController { public String healthCheck() { return "Yell:o world!"; } + + @GetMapping("/check1") + public String check1() { + return RestUtil.getSubscribeCheck( + "omfefngneagfjbbiilkfklma.AO-J1OzpoLwvaghTYTf3fmdWnGshzvmO7tWgLS1csfUH4t9jALlrkS4mdO0xnUaKLiFg8GDwB65WjY_P3Kgl7KMs0o5gP7aRPA", + "ya29.a0AfB_byCNJouXhS9xk2xBRG6GOFecUVBpVI0-7ro1pziHD7LhmGGinDm4nOnHbuKyjN_fJnlPRsSv7AzchF4dy4QxF0s9_KUQtKBaaagrpcoscBd6WaTgKxc28V6gvbzHK_NkR_0hVu2kXlRqdBUgU-l4u9JKkSnZYmZqXHvb0gaCgYKAaMSARISFQHsvYlsIAie_CKHJcpEqFfWmG6DjA0177" + ).toString(); + } + + @GetMapping("/check2") + public String check2() { + return RestUtil.getTicketCheck( + ConstantUtil.GOOGLE_FIVE_TICKET_ID, + "iokechedcfehajgklkmhooil.AO-J1Owu16cgCtqfJeVim7uG6WDeJ7hkmmeq7fbzU05PdwzZx9lMK473z1BJOGvrVynngJHhGTQDqLCPFCze9LvpDV0TmT21PQ", + "ya29.a0AfB_byCNJouXhS9xk2xBRG6GOFecUVBpVI0-7ro1pziHD7LhmGGinDm4nOnHbuKyjN_fJnlPRsSv7AzchF4dy4QxF0s9_KUQtKBaaagrpcoscBd6WaTgKxc28V6gvbzHK_NkR_0hVu2kXlRqdBUgU-l4u9JKkSnZYmZqXHvb0gaCgYKAaMSARISFQHsvYlsIAie_CKHJcpEqFfWmG6DjA0177" + ).toString(); + } + + @GetMapping("/check3") + public String check3() throws IOException { + return RestUtil.postGoogleTokenReissue( + "1//0eyLATSiDsTNjCgYIARAAGA4SNwF-L9Ir1pNWYBG1lS2i0Bl6kapR9_NQzhwuXd1NpYRZBuFKVMHbXlqHZyo1kybr_sPw0ijeO20" + ).toString(); + } } diff --git a/src/main/java/com/yello/server/global/common/util/ConstantUtil.java b/src/main/java/com/yello/server/global/common/util/ConstantUtil.java index dabcef90..908b1b57 100644 --- a/src/main/java/com/yello/server/global/common/util/ConstantUtil.java +++ b/src/main/java/com/yello/server/global/common/util/ConstantUtil.java @@ -25,6 +25,10 @@ public class ConstantUtil { public static final String ONE_TICKET_ID = "YELLO.iOS.nameKey.one"; public static final String TWO_TICKET_ID = "YELLO.iOS.nameKey.two"; public static final String FIVE_TICKET_ID = "YELLO.iOS.nameKey.five"; + public static final String GOOGLE_YELLO_PLUS_ID = "yello_plus_subscribe"; + public static final String GOOGLE_ONE_TICKET_ID = "yello_ticket_one"; + public static final String GOOGLE_TWO_TICKET_ID = "yello_ticket_two"; + public static final String GOOGLE_FIVE_TICKET_ID = "yello_ticket_five"; public static final int ONE_TICKET = 1400; public static final int TWO_TICKET = 2800; public static final int FIVE_TICKET = 5900; diff --git a/src/main/java/com/yello/server/global/common/util/RestUtil.java b/src/main/java/com/yello/server/global/common/util/RestUtil.java new file mode 100644 index 00000000..56b22569 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/util/RestUtil.java @@ -0,0 +1,118 @@ +package com.yello.server.global.common.util; + +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.yello.server.domain.authorization.dto.kakao.KakaoTokenInfo; +import com.yello.server.global.common.dto.response.GoogleInAppGetResponse; +import com.yello.server.global.common.dto.response.GoogleTokenIssueResponse; +import java.io.IOException; +import java.io.InputStreamReader; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class RestUtil { + + private static final String KAKAO_TOKEN_INFO_URL = + "https://kapi.kakao.com/v1/user/access_token_info"; + private static final String KAKAO_FRIEND_LIST_URL = + "https://kapi.kakao.com/v1/api/talk/friends"; + private static final String GOOGLE_TOKEN_ISSUE_URL = "https://oauth2.googleapis.com/token"; + private static String GOOGLE_CLIENT_SECRET_PATH; + private static String ANDROID_PACKAGE_NAME; + + private static String GOOGLE_PLAY_SUBSCRIPTIONS_GET_PATH(String packageName, String purchaseToken) { + return "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/" + + packageName + "/purchases/subscriptionsv2/tokens/" + + purchaseToken; + } + + private static String GOOGLE_PLAY_INAPP_GET_PATH(String packageName, String ticketType, String purchaseToken) { + return "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/" + + packageName + "/purchases/products/" + + ticketType + "/tokens/" + + purchaseToken; + } + + public static ResponseEntity getKakaoTokenInfo(String kakaoAccessToken) { + WebClient webClient = WebClient.builder() + .baseUrl(KAKAO_TOKEN_INFO_URL) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + kakaoAccessToken) + .defaultHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_VALUE) + .build(); + + return webClient.get() + .exchangeToMono(clientResponse -> clientResponse.toEntity(KakaoTokenInfo.class)) + .block(); + } + + public static ResponseEntity postGoogleTokenReissue(String refreshToken) + throws IOException { + Gson gson = new Gson(); + ClassPathResource resource = new ClassPathResource(GOOGLE_CLIENT_SECRET_PATH); + JsonObject object = gson.fromJson(new InputStreamReader(resource.getInputStream()), JsonObject.class); + object = (JsonObject) object.get("web"); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + String clientSecret = String.valueOf(object.get("client_secret")); + String clientId = String.valueOf(object.get("client_id")); + + formData.add("client_secret", clientSecret.replaceAll("\"", "")); + formData.add("client_id", clientId.replaceAll("\"", "")); + formData.add("grant_type", "refresh_token"); + formData.add("refresh_token", refreshToken); + System.out.println("formData = " + formData); + WebClient webClient = WebClient.builder() + .baseUrl(GOOGLE_TOKEN_ISSUE_URL) + .defaultHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE) + .build(); + + return webClient.post() + .bodyValue(formData) + .exchangeToMono(clientResponse -> clientResponse.toEntity(GoogleTokenIssueResponse.class)) + .block(); + } + + public static ResponseEntity getSubscribeCheck(String purchaseToken, + String accessToken) { + WebClient webClient = WebClient.builder() + .baseUrl(GOOGLE_PLAY_SUBSCRIPTIONS_GET_PATH(ANDROID_PACKAGE_NAME, purchaseToken)) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .build(); + + return webClient.get() + .exchangeToMono(clientResponse -> clientResponse.toEntity(String.class)) + .block(); + } + + public static ResponseEntity getTicketCheck(String ticketType, String purchaseToken, + String accessToken) { + WebClient webClient = WebClient.builder() + .baseUrl(GOOGLE_PLAY_INAPP_GET_PATH(ANDROID_PACKAGE_NAME, ticketType, purchaseToken)) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .build(); + + return webClient.get() + .exchangeToMono(clientResponse -> clientResponse.toEntity(GoogleInAppGetResponse.class)) + .block(); + } + + @Value("${google.developer.key}") + public void setGoogleClientSecretPath(String value) { + GOOGLE_CLIENT_SECRET_PATH = value; + } + + @Value("${google.android.package-name}") + public void setAndroidPackageName(String value) { + ANDROID_PACKAGE_NAME = value; + } +} From 8dd4205cc02ffff9c9dbd0a6b536479ed418c825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A0=9C?= Date: Fri, 18 Aug 2023 02:53:57 +0900 Subject: [PATCH 3/4] =?UTF-8?q?YEL-109=20[feat]=20Google=20Rest=20Util=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/GoogleInAppGetResponse.java | 15 +++++++++++ .../response/GoogleTokenIssueResponse.java | 13 ++++++++++ .../global/common/entity/GoogleToken.java | 26 +++++++++++++++++++ .../repository/GoogleTokenJpaRepository.java | 8 ++++++ .../repository/GoogleTokenRepository.java | 5 ++++ .../repository/GoogleTokenRepositoryImpl.java | 11 ++++++++ 6 files changed, 78 insertions(+) create mode 100644 src/main/java/com/yello/server/global/common/dto/response/GoogleInAppGetResponse.java create mode 100644 src/main/java/com/yello/server/global/common/dto/response/GoogleTokenIssueResponse.java create mode 100644 src/main/java/com/yello/server/global/common/entity/GoogleToken.java create mode 100644 src/main/java/com/yello/server/global/common/repository/GoogleTokenJpaRepository.java create mode 100644 src/main/java/com/yello/server/global/common/repository/GoogleTokenRepository.java create mode 100644 src/main/java/com/yello/server/global/common/repository/GoogleTokenRepositoryImpl.java diff --git a/src/main/java/com/yello/server/global/common/dto/response/GoogleInAppGetResponse.java b/src/main/java/com/yello/server/global/common/dto/response/GoogleInAppGetResponse.java new file mode 100644 index 00000000..3bc167eb --- /dev/null +++ b/src/main/java/com/yello/server/global/common/dto/response/GoogleInAppGetResponse.java @@ -0,0 +1,15 @@ +package com.yello.server.global.common.dto.response; + +public record GoogleInAppGetResponse( + String purchaseTimeMillis, + Integer purchaseState, + Integer consumptionState, + String developerPayload, + String orderId, + Integer purchaseType, + Integer acknowledgementState, + String kind, + String regionCode +) { + +} \ No newline at end of file diff --git a/src/main/java/com/yello/server/global/common/dto/response/GoogleTokenIssueResponse.java b/src/main/java/com/yello/server/global/common/dto/response/GoogleTokenIssueResponse.java new file mode 100644 index 00000000..74bb3752 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/dto/response/GoogleTokenIssueResponse.java @@ -0,0 +1,13 @@ +package com.yello.server.global.common.dto.response; + +import lombok.Builder; + +@Builder +public record GoogleTokenIssueResponse( + String access_token, + Integer expires_in, + String scope, + String token_type +) { + +} diff --git a/src/main/java/com/yello/server/global/common/entity/GoogleToken.java b/src/main/java/com/yello/server/global/common/entity/GoogleToken.java new file mode 100644 index 00000000..0b7fbfd9 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/entity/GoogleToken.java @@ -0,0 +1,26 @@ +package com.yello.server.global.common.entity; + +import com.yello.server.global.common.dto.AuditingTimeEntity; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class GoogleToken extends AuditingTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + String accessToken; + + String refreshToken; +} diff --git a/src/main/java/com/yello/server/global/common/repository/GoogleTokenJpaRepository.java b/src/main/java/com/yello/server/global/common/repository/GoogleTokenJpaRepository.java new file mode 100644 index 00000000..f4007c25 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/repository/GoogleTokenJpaRepository.java @@ -0,0 +1,8 @@ +package com.yello.server.global.common.repository; + +import com.yello.server.global.common.entity.GoogleToken; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GoogleTokenJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepository.java b/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepository.java new file mode 100644 index 00000000..e9ecad80 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepository.java @@ -0,0 +1,5 @@ +package com.yello.server.global.common.repository; + +public interface GoogleTokenRepository { + +} diff --git a/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepositoryImpl.java b/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepositoryImpl.java new file mode 100644 index 00000000..941a3d36 --- /dev/null +++ b/src/main/java/com/yello/server/global/common/repository/GoogleTokenRepositoryImpl.java @@ -0,0 +1,11 @@ +package com.yello.server.global.common.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class GoogleTokenRepositoryImpl implements GoogleTokenRepository { + + private final GoogleTokenJpaRepository googleTokenJpaRepository; +} From 8170e7e797b1dec723596c6f5a6e5e7b91d470f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A0=9C?= Date: Fri, 18 Aug 2023 02:59:21 +0900 Subject: [PATCH 4/4] =?UTF-8?q?YEL-109=20[feat]=20CI=20-=20Google=20Rest?= =?UTF-8?q?=20Util=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 ++ .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c76f1fc5..ae2a492e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,6 +35,8 @@ jobs: echo "$APPLICATION" > ./application.yml touch ./firebase_key.json echo "$FIREBASE_JSON" > ./firebase_key.json + touch ./google_client_secret.json + echo "$GOOGLE_CLIENT_SECRET" > ./google_client_secret.json sed -i 's/#/"/g' ./firebase_key.json env: APPLICATION: ${{ secrets.APPLICATION }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 17f4645d..b63454e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: SLACK_CHANNEL: general SLACK_COLOR: ${{ job.status }} SLACK_ICON: https://github.com/rtCamp.png?size=48 - SLACK_MESSAGE: 빌드에 ${{ job.status }} 했습니다. + SLACK_MESSAGE: 테스트에 ${{ job.status }} 했습니다. SLACK_TITLE: 📊 YELL:O 테스트 결과 📊 SLACK_USERNAME: Notification-Bot SLACK_WEBHOOK: ${{ secrets.SLACK_URL }}