From eb5710837b90eb7643c16339ac324fc53ace77a3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 19 Mar 2024 20:37:26 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20?= =?UTF-8?q?=EB=B0=98=EB=B3=B5=EB=AC=B8=EC=9C=BC=EB=A1=9C=20=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=ED=83=9C=EA=B7=B8,=20=EC=BB=A4=EB=B2=84=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=9E=91=EC=97=85=EC=9D=84=20db=EC=AA=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=84=98=EA=B2=A8=EC=84=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/content/AdminContentRepository.kt | 21 +++++++++++++--- .../admin/content/AdminContentService.kt | 24 ++----------------- .../content/GetAdminContentListResponse.kt | 5 ++-- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt index 1a0288e..34a375b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt @@ -19,7 +19,13 @@ interface AdminContentRepository : JpaRepository, AdminAudio interface AdminAudioContentQueryRepository { fun getAudioContentTotalCount(searchWord: String = ""): Int - fun getAudioContentList(offset: Long, limit: Long, searchWord: String = ""): List + fun getAudioContentList( + imageHost: String, + offset: Long, + limit: Long, + searchWord: String = "" + ): List + fun getHashTagList(audioContentId: Long): List } @@ -46,7 +52,12 @@ class AdminAudioContentQueryRepositoryImpl( .size } - override fun getAudioContentList(offset: Long, limit: Long, searchWord: String): List { + override fun getAudioContentList( + imageHost: String, + offset: Long, + limit: Long, + searchWord: String + ): List { var where = audioContent.duration.isNotNull .and(audioContent.member.isNotNull) .and(audioContent.isActive.isTrue.or(audioContent.releaseDate.isNotNull)) @@ -64,9 +75,10 @@ class AdminAudioContentQueryRepositoryImpl( audioContent.id, audioContent.title, audioContent.detail, + Expressions.stringTemplate("GROUP_CONCAT({0} SEPARATOR ' '", hashTag.tag), audioContentCuration.title, audioContentCuration.id.nullif(0), - audioContent.coverImage, + audioContent.coverImage.prepend("/").prepend(imageHost), audioContent.member!!.nickname, audioContentTheme.theme, audioContentTheme.id, @@ -81,8 +93,11 @@ class AdminAudioContentQueryRepositoryImpl( ) .from(audioContent) .leftJoin(audioContent.curation, audioContentCuration) + .leftJoin(audioContent.audioContentHashTags, audioContentHashTag) + .leftJoin(audioContentHashTag.hashTag, hashTag) .innerJoin(audioContent.theme, audioContentTheme) .where(where) + .groupBy(audioContent.id) .offset(offset) .limit(limit) .orderBy(audioContent.releaseDate.desc()) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt index a421759..9c645eb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt @@ -23,19 +23,13 @@ class AdminContentService( fun getAudioContentList(pageable: Pageable): GetAdminContentListResponse { val totalCount = repository.getAudioContentTotalCount() val audioContentAndThemeList = repository.getAudioContentList( + imageHost = coverImageHost, offset = pageable.offset, limit = pageable.pageSize.toLong() ) val audioContentList = audioContentAndThemeList .asSequence() - .map { - val tags = repository - .getHashTagList(audioContentId = it.audioContentId) - .joinToString(" ") { tag -> tag } - it.tags = tags - it - } .map { it.contentUrl = audioContentCloudFront.generateSignedURL( resourcePath = it.contentUrl, @@ -43,10 +37,6 @@ class AdminContentService( ) it } - .map { - it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}" - it - } .toList() return GetAdminContentListResponse(totalCount, audioContentList) @@ -56,6 +46,7 @@ class AdminContentService( if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.") val totalCount = repository.getAudioContentTotalCount(searchWord) val audioContentAndThemeList = repository.getAudioContentList( + imageHost = coverImageHost, offset = pageable.offset, limit = pageable.pageSize.toLong(), searchWord = searchWord @@ -63,13 +54,6 @@ class AdminContentService( val audioContentList = audioContentAndThemeList .asSequence() - .map { - val tags = repository - .getHashTagList(audioContentId = it.audioContentId) - .joinToString(" ") { tag -> tag } - it.tags = tags - it - } .map { it.contentUrl = audioContentCloudFront.generateSignedURL( resourcePath = it.contentUrl, @@ -77,10 +61,6 @@ class AdminContentService( ) it } - .map { - it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}" - it - } .toList() return GetAdminContentListResponse(totalCount, audioContentList) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt index 40f974a..38c6974 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt @@ -11,6 +11,7 @@ data class GetAdminContentListItem @QueryProjection constructor( val audioContentId: Long, val title: String, val detail: String, + var tags: String, val curationTitle: String?, val curationId: Long, var coverImageUrl: String, @@ -24,6 +25,4 @@ data class GetAdminContentListItem @QueryProjection constructor( val isCommentAvailable: Boolean, val date: String, val releaseDate: String? -) { - var tags: String = "" -} +) From 788a121994fa0237df384c161f4d6a2e80a1cbe8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 19 Mar 2024 21:58:15 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20group=5F?= =?UTF-8?q?concat=20=EB=8B=AB=EB=8A=94=20=EA=B4=84=ED=98=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/admin/content/AdminContentRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt index 34a375b..3473b1b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt @@ -75,7 +75,7 @@ class AdminAudioContentQueryRepositoryImpl( audioContent.id, audioContent.title, audioContent.detail, - Expressions.stringTemplate("GROUP_CONCAT({0} SEPARATOR ' '", hashTag.tag), + Expressions.stringTemplate("GROUP_CONCAT({0} SEPARATOR ' ')", hashTag.tag), audioContentCuration.title, audioContentCuration.id.nullif(0), audioContent.coverImage.prepend("/").prepend(imageHost), From 7c1c4b907bd3a3b6ca115bc9252ca76f85088cae Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 19 Mar 2024 22:04:40 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20group=5F?= =?UTF-8?q?concat=20SEPARATOR=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/admin/content/AdminContentRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt index 3473b1b..7b7118f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt @@ -75,7 +75,7 @@ class AdminAudioContentQueryRepositoryImpl( audioContent.id, audioContent.title, audioContent.detail, - Expressions.stringTemplate("GROUP_CONCAT({0} SEPARATOR ' ')", hashTag.tag), + Expressions.stringTemplate("GROUP_CONCAT({0})", hashTag.tag), audioContentCuration.title, audioContentCuration.id.nullif(0), audioContent.coverImage.prepend("/").prepend(imageHost), From 69331edabb5369a1326e72961e4cbbf00dca4645 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 19 Mar 2024 22:16:07 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20group=5F?= =?UTF-8?q?concat=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/content/AdminContentRepository.kt | 4 ---- .../sodalive/admin/content/AdminContentService.kt | 14 ++++++++++++++ .../admin/content/GetAdminContentListResponse.kt | 5 +++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt index 7b7118f..4164c93 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt @@ -75,7 +75,6 @@ class AdminAudioContentQueryRepositoryImpl( audioContent.id, audioContent.title, audioContent.detail, - Expressions.stringTemplate("GROUP_CONCAT({0})", hashTag.tag), audioContentCuration.title, audioContentCuration.id.nullif(0), audioContent.coverImage.prepend("/").prepend(imageHost), @@ -93,11 +92,8 @@ class AdminAudioContentQueryRepositoryImpl( ) .from(audioContent) .leftJoin(audioContent.curation, audioContentCuration) - .leftJoin(audioContent.audioContentHashTags, audioContentHashTag) - .leftJoin(audioContentHashTag.hashTag, hashTag) .innerJoin(audioContent.theme, audioContentTheme) .where(where) - .groupBy(audioContent.id) .offset(offset) .limit(limit) .orderBy(audioContent.releaseDate.desc()) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt index 9c645eb..5b3e8a7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt @@ -30,6 +30,13 @@ class AdminContentService( val audioContentList = audioContentAndThemeList .asSequence() + .map { + val tags = repository + .getHashTagList(audioContentId = it.audioContentId) + .joinToString(" ") { tag -> tag } + it.tags = tags + it + } .map { it.contentUrl = audioContentCloudFront.generateSignedURL( resourcePath = it.contentUrl, @@ -54,6 +61,13 @@ class AdminContentService( val audioContentList = audioContentAndThemeList .asSequence() + .map { + val tags = repository + .getHashTagList(audioContentId = it.audioContentId) + .joinToString(" ") { tag -> tag } + it.tags = tags + it + } .map { it.contentUrl = audioContentCloudFront.generateSignedURL( resourcePath = it.contentUrl, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt index 38c6974..40f974a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt @@ -11,7 +11,6 @@ data class GetAdminContentListItem @QueryProjection constructor( val audioContentId: Long, val title: String, val detail: String, - var tags: String, val curationTitle: String?, val curationId: Long, var coverImageUrl: String, @@ -25,4 +24,6 @@ data class GetAdminContentListItem @QueryProjection constructor( val isCommentAvailable: Boolean, val date: String, val releaseDate: String? -) +) { + var tags: String = "" +} From 1c8f5ef7ac72c8ac8b03ffe6a80a61cedb61c58b Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 21 Mar 2024 16:10:19 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=EC=9D=B8=20=EC=95=B1?= =?UTF-8?q?=20=EA=B2=B0=EC=A0=9C=20=EA=B2=80=EC=A6=9D=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 ++ .../sodalive/can/charge/ChargeController.kt | 18 +++++++ .../sodalive/can/charge/ChargeData.kt | 10 ++++ .../sodalive/can/charge/ChargeService.kt | 50 +++++++++++++++++++ .../configs/AndroidPublisherConfig.kt | 31 ++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index e34510a..078e33d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,6 +59,9 @@ dependencies { // firebase admin sdk implementation("com.google.firebase:firebase-admin:9.2.0") + // android publisher + implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20240319-2.0.0") + implementation("org.apache.poi:poi-ooxml:5.2.3") developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt index 6f6291b..27b6cea 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt @@ -49,4 +49,22 @@ class ChargeController(private val service: ChargeService) { @RequestBody verifyRequest: AppleVerifyRequest, @AuthenticationPrincipal user: User ) = ApiResponse.ok(service.appleVerify(user, verifyRequest)) + + @PostMapping("/google") + fun googleCharge( + @RequestBody request: GoogleChargeRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) { + throw SodaException("로그인 정보를 확인해주세요.") + } + + ApiResponse.ok(service.googleCharge(member, request)) + } + + @PostMapping("/google/verify") + fun googleVerify( + @RequestBody request: GoogleVerifyRequest, + @AuthenticationPrincipal user: User + ) = ApiResponse.ok(service.googleVerify(user, request)) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt index 7e7374a..a494333 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt @@ -33,3 +33,13 @@ data class AppleChargeRequest( data class AppleVerifyRequest(val receiptString: String, val chargeId: Long) data class AppleVerifyResponse(val status: Int) + +data class GoogleChargeRequest( + val title: String, + val chargeCan: Int, + val price: Double, + val currencyCode: String, + val paymentGateway: PaymentGateway +) + +data class GoogleVerifyRequest(val productId: String, val purchaseToken: String, val chargeId: Long) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt index d711cb5..294ee97 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.can.charge import com.fasterxml.jackson.databind.ObjectMapper +import com.google.api.services.androidpublisher.AndroidPublisher import kr.co.bootpay.Bootpay import kr.co.vividnext.sodalive.can.CanRepository import kr.co.vividnext.sodalive.can.charge.event.ChargeSpringEvent @@ -36,6 +37,8 @@ class ChargeService( private val okHttpClient: OkHttpClient, private val applicationEventPublisher: ApplicationEventPublisher, + private val androidPublisher: AndroidPublisher, + @Value("\${bootpay.application-id}") private val bootpayApplicationId: String, @Value("\${bootpay.private-key}") @@ -183,6 +186,53 @@ class ChargeService( } } + @Transactional + fun googleCharge(member: Member, request: GoogleChargeRequest): ChargeResponse { + val charge = Charge(request.chargeCan, 0) + charge.title = request.title + charge.member = member + + val payment = Payment(paymentGateway = request.paymentGateway) + payment.locale = request.currencyCode + payment.price = request.price + + charge.payment = payment + chargeRepository.save(charge) + + return ChargeResponse(chargeId = charge.id!!) + } + + fun googleVerify(user: User, request: GoogleVerifyRequest) { + val charge = chargeRepository.findByIdOrNull(request.chargeId) + ?: throw SodaException("결제정보에 오류가 있습니다.") + val member = memberRepository.findByEmail(user.username) + ?: throw SodaException("로그인 정보를 확인해주세요.") + + if (charge.payment!!.paymentGateway == PaymentGateway.GOOGLE_IAP) { + val response = androidPublisher.purchases().products() + .get("kr.co.vividnext.sodalive", request.productId, request.purchaseToken) + .execute() + + if (response.purchaseState == 0) { + charge.payment?.receiptId = response.purchaseToken + charge.payment?.method = "구글(인 앱 결제)" + charge.payment?.status = PaymentStatus.COMPLETE + member.charge(charge.chargeCan, charge.rewardCan, "aos") + + applicationEventPublisher.publishEvent( + ChargeSpringEvent( + chargeId = charge.id!!, + memberId = member.id!! + ) + ) + } else { + throw SodaException("결제정보에 오류가 있습니다.") + } + } else { + throw SodaException("결제정보에 오류가 있습니다.") + } + } + private fun requestRealServerVerify(verifyRequest: AppleVerifyRequest): Boolean { val body = JSONObject() body.put("receipt-data", verifyRequest.receiptString) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt new file mode 100644 index 0000000..8dada68 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt @@ -0,0 +1,31 @@ +package kr.co.vividnext.sodalive.configs + +import com.google.api.client.http.javanet.NetHttpTransport +import com.google.api.client.json.gson.GsonFactory +import com.google.api.services.androidpublisher.AndroidPublisher +import com.google.api.services.androidpublisher.AndroidPublisherScopes +import com.google.auth.http.HttpCredentialsAdapter +import com.google.auth.oauth2.GoogleCredentials +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.io.FileInputStream + +@Configuration +class AndroidPublisherConfig( + @Value("\${firebase.secret-key-path}") + private val secretKeyPath: String +) { + @Bean + fun androidPublisher(): AndroidPublisher { + val jsonFactory = GsonFactory.getDefaultInstance() + val httpTransport = NetHttpTransport() + + val credential = GoogleCredentials.fromStream(FileInputStream(secretKeyPath)) + .createScoped(listOf(AndroidPublisherScopes.ANDROIDPUBLISHER)) + + return AndroidPublisher.Builder(httpTransport, jsonFactory, HttpCredentialsAdapter(credential)) + .setApplicationName("소다라이브") + .build() + } +}