Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YEL-213 [feat] google admob 구현 #446

Merged
merged 6 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ dependencies {
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation "jakarta.annotation:jakarta.annotation-api"
implementation "com.querydsl:querydsl-codegen:${queryDslVersion}"

// tink
implementation 'com.google.crypto.tink:tink-android:1.4.0-rc1'
implementation 'com.google.crypto.tink:apps-rewardedads:1.10.0'
}

asciidoctor {
Expand Down
5 changes: 2 additions & 3 deletions src/docs/asciidoc/create-event-history.adoc
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
:reproducible:
== 공지 조회
== 이벤트 참여

=== 요청

include::{snippets}/api/v1/event/join/1/http-request.adoc[]
include::{snippets}/api/v1/event/join/2/http-request.adoc[]

=== 응답

include::{snippets}/api/v1/event/join/1/http-response.adoc[]

=== 주의

- "tag": "LUNCH_EVENT" | "ADMOB"
- "tag": "LUNCH_EVENT"

=== NOTE

Expand Down
4 changes: 2 additions & 2 deletions src/docs/asciidoc/find-event.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
:reproducible:
== 공지 조회
== 이벤트 조회

=== 요청

Expand All @@ -17,7 +17,7 @@ include::{snippets}/api/v1/event/3/http-response.adoc[]

- data: *Response*[]
- *Response*
- tag : "LUNCH_EVENT" | "ADMOB"
- tag : "LUNCH_EVENT"
* LUNCH_EVENT에 해당하는 *Response*가 없으면 Render 해주지 말아주세요
- startDate : "2024-01-01T00:00:00+09:00"
- endDate : "2024-12-31T00:00:00+09:00"
Expand Down
6 changes: 4 additions & 2 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@

=== Event API

* 🆕 link:find-event.html[공지 조회, 2024-02-06]
* 🆕 link:find-event.html[이벤트 조회, 2024-02-06]

* 🆕 link:create-event-history.html[이벤트 참여, 2024-02-07]

* 🆕 link:reward-event.html[이벤트 보상, 2024-02-07]
* 🆕 link:reward-event.html[이벤트 보상, 2024-02-07]

* 🆕 link:reward-admob.html[광고보고 보상 얻기, 2024-02-11]
35 changes: 35 additions & 0 deletions src/docs/asciidoc/reward-admob.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:reproducible:
== 이벤트 참여

=== 요청

include::{snippets}/api/v1/admob/reward/http-request.adoc[]

=== request body

- "rewardType": String -> "ADMOB_POINT" | "ADMOB_MULTIPLE_POINT"
* ADMOB_POINT : 광고 보고 10 포인트
* ADMOB_MULTIPLE_POINT : 광고 보고 포인트 2배 이벤트

- "randomType" : String -> "FIXED" | "ADMOB_RANDOM"
* FIXED : 고정값 (현재 이것만 사용)
* ADMOB_RANDOM : 랜덤값 (추후 랜덤으로 바뀔 것 고려)
- "uuid" : String -> UUID4 형식만 적용
- "rewardNumber" : Integer -> 포인트인 경우 10, 투표 포인트 2배 이벤트인 경우 현재 투표 후 받은 포인트 보내줘야함

=== 응답

include::{snippets}/api/v1/admob/reward/http-response.adoc[]

=== NOTE

- Header에 무작위한 UUID4 값을 넣어주세요
* 예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87
- 주의사항
* 같은 멱등성키를 2번 요청하면, 400번 에러.
- ADMOB
* ADMOB 서버에 SSV(ServerSideVerification) Options의 customData에 입력한 것과 동일한 멱등성 키를 넘겨주세요.

=== CHANGELOG

- 2024.02.11 릴리즈
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected void doFilterInternal(
|| requestPath.startsWith("/api/v1/admin/login")
|| requestPath.startsWith("/v2/apple/notifications")
|| requestPath.startsWith("/v2/google/notifications")
|| requestPath.startsWith("/api/v1/admob/verify")
|| (requestPath.startsWith("/api/v1/auth")
&& !requestPath.startsWith("/api/v1/auth/token/issue"))) {
filterChain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
|| requestPath.startsWith("/api/v1/admin/login")
|| requestPath.startsWith("/api/v1/auth")
|| requestPath.startsWith("/v2/apple/notifications")
|| requestPath.startsWith("/v2/google/notifications")) {
|| requestPath.startsWith("/v2/google/notifications")
|| requestPath.startsWith("/api/v1/admob/verify")) {
filterChain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ public void recommendUser(String recommendYelloId, String userYelloId) {
UserDataType.RECOMMENDED);

recommendedUser.addRecommendCount(1L);
recommendedUser.addPoint(RECOMMEND_POINT);
user.addPoint(RECOMMEND_POINT);
recommendedUser.addPointBySubscribe(RECOMMEND_POINT);
user.addPointBySubscribe(RECOMMEND_POINT);
if (recommended.isEmpty()) {
recommendedUser.addTicketCount(1);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.yello.server.domain.event.controller;

import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_BAD_REQUEST_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_INVALID_FORM_BAD_REQUEST_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.ADMOB_URI_BAD_REQUEST_EXCEPTION;
import static com.yello.server.global.common.SuccessCode.EVENT_JOIN_SUCCESS;
import static com.yello.server.global.common.SuccessCode.EVENT_NOTICE_SUCCESS;
import static com.yello.server.global.common.SuccessCode.EVENT_REWARD_SUCCESS;
import static com.yello.server.global.common.SuccessCode.REWARD_ADMOB_SUCCESS;
import static com.yello.server.global.common.SuccessCode.VERIFY_ADMOB_SSV_SUCCESS;
import static com.yello.server.global.common.util.ConstantUtil.IdempotencyKeyHeader;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ser.Serializers.Base;
import com.yello.server.domain.event.dto.request.AdmobRewardRequest;
import com.yello.server.domain.event.dto.request.EventJoinRequest;
import com.yello.server.domain.event.dto.response.EventResponse;
import com.yello.server.domain.event.dto.response.EventRewardResponse;
Expand All @@ -16,12 +19,16 @@
import com.yello.server.domain.user.entity.User;
import com.yello.server.global.common.annotation.AccessTokenUser;
import com.yello.server.global.common.dto.BaseResponse;
import com.yello.server.global.common.factory.UuidFactory;
import jakarta.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.util.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -36,46 +43,48 @@ public class EventController {
private final EventService eventService;

@GetMapping("/v1/event")
public BaseResponse<List<EventResponse>> getEvents(@AccessTokenUser User user) throws JsonProcessingException {
public BaseResponse<List<EventResponse>> getEvents(@AccessTokenUser User user)
throws JsonProcessingException {
val data = eventService.getEvents(user.getId());
return BaseResponse.success(EVENT_NOTICE_SUCCESS, data);
}

@PostMapping("/v1/event")
public BaseResponse joinEvent(@AccessTokenUser User user, HttpServletRequest requestServlet,
@RequestBody EventJoinRequest request) {
final String idempotencyKey = requestServlet.getHeader(IdempotencyKeyHeader);
if (!StringUtils.hasText(idempotencyKey)) {
throw new EventBadRequestException(IDEMPOTENCY_KEY_BAD_REQUEST_EXCEPTION);
}

UUID uuidIdempotencyKey;
try {
uuidIdempotencyKey = UUID.fromString(idempotencyKey);
} catch (IllegalArgumentException e) {
throw new EventBadRequestException(IDEMPOTENCY_KEY_INVALID_FORM_BAD_REQUEST_EXCEPTION);
}

UUID uuidIdempotencyKey =
UuidFactory.checkUuid(requestServlet.getHeader(IdempotencyKeyHeader));
Comment on lines +55 to +56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 처리👍

eventService.joinEvent(user.getId(), uuidIdempotencyKey, request);
return BaseResponse.success(EVENT_JOIN_SUCCESS);
}

@PostMapping("/v1/event/reward")
public BaseResponse<EventRewardResponse> rewardEvent(@AccessTokenUser User user,
HttpServletRequest requestServlet) throws JsonProcessingException {
final String idempotencyKey = requestServlet.getHeader(IdempotencyKeyHeader);
if (!StringUtils.hasText(idempotencyKey)) {
throw new EventBadRequestException(IDEMPOTENCY_KEY_BAD_REQUEST_EXCEPTION);
}
UUID uuidIdempotencyKey =
UuidFactory.checkUuid(requestServlet.getHeader(IdempotencyKeyHeader));
val data = eventService.rewardEvent(user.getId(), uuidIdempotencyKey);
return BaseResponse.success(EVENT_REWARD_SUCCESS, data);
}

UUID uuidIdempotencyKey;
@GetMapping("/v1/admob/verify")
public ResponseEntity<?> verifyAdmob(HttpServletRequest request) {
URI uri;
try {
uuidIdempotencyKey = UUID.fromString(idempotencyKey);
} catch (IllegalArgumentException e) {
throw new EventBadRequestException(IDEMPOTENCY_KEY_INVALID_FORM_BAD_REQUEST_EXCEPTION);
uri =
new URI(request.getScheme(), null, request.getServerName(), request.getServerPort(),
request.getRequestURI(), request.getQueryString(), null);
} catch (URISyntaxException e) {
throw new EventBadRequestException(ADMOB_URI_BAD_REQUEST_EXCEPTION);
}
eventService.verifyAdmobReward(uri, request);

val data = eventService.rewardEvent(user.getId(), uuidIdempotencyKey);
return BaseResponse.success(EVENT_REWARD_SUCCESS, data);
return new ResponseEntity<>(HttpStatus.OK);
}

@PostMapping("/v1/admob/reward")
public BaseResponse<EventRewardResponse> rewardAdmob(@AccessTokenUser User user, @RequestBody AdmobRewardRequest request) {
val data = eventService.rewardAdmob(user.getId(), request);
return BaseResponse.success(REWARD_ADMOB_SUCCESS, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yello.server.domain.event.dto.request;

import lombok.Builder;

@Builder
public record AdmobRewardRequest(
String rewardType,
String randomType,
String uuid,
Integer rewardNumber
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.yello.server.domain.event.dto.request;

import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import lombok.Builder;

@Builder
public record AdmobSsvRequest(
String customData,
String signature,
Long keyId,
String transactionId,
String rewardItem,
Integer rewardAmount

) {
public static AdmobSsvRequest of(Map<String, String[]> parameters ) {
Function<String, String> getParameter = (key) ->
Optional.ofNullable(parameters.get(key))
.flatMap(arr -> Arrays.stream(arr).findFirst())
.orElse("");

long keyId = Optional.ofNullable(parameters.get("key_id"))
.flatMap(arr -> Arrays.stream(arr).findFirst())
.map(Long::parseLong)
.orElse(0L);

int rewardAmount = Optional.ofNullable(parameters.get("reward_amount"))
.flatMap(arr -> Arrays.stream(arr).findFirst())
.map(Integer::parseInt)
.orElse(0);

return AdmobSsvRequest.builder()
.customData(getParameter.apply("custom_data"))
.signature(getParameter.apply("signature"))
.keyId(keyId)
.transactionId(getParameter.apply("transaction_id"))
.rewardItem(getParameter.apply("reward_item"))
.rewardAmount(rewardAmount)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,15 @@ public class EventHistory extends AuditingTimeEntity {

@Column
private UUID idempotencyKey;

public static EventHistory of(User user, UUID uuidIdempotencyKey) {
return EventHistory.builder()
.user(user)
.idempotencyKey(uuidIdempotencyKey)
.build();
}

public void update(User user) {
this.user = user;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.yello.server.domain.event.entity;

import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId;

import com.yello.server.global.common.entity.ZonedDateTimeConverter;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
Expand All @@ -10,6 +12,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -48,6 +51,15 @@ public class EventInstance {
@Builder.Default
private Long remainEventCount = 0L;

public static EventInstance of(EventTime eventTime, EventHistory eventHistory) {
ZonedDateTime now = ZonedDateTime.now(GlobalZoneId);
return EventInstance.builder()
.eventHistory(eventHistory)
.eventTime(eventTime)
.instanceDate(now)
.build();
}

public void subRemainEventCount(Long amount) {
this.remainEventCount -= amount;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,14 @@ public class EventInstanceReward extends AuditingTimeEntity {

@Column
private String rewardImage;
public static EventInstanceReward of(EventInstance eventInstance, EventReward eventReward) {
return EventInstanceReward.builder()
.eventInstance(eventInstance)
.rewardTag(eventReward.getTag())
.rewardValue(eventReward.getMinRewardValue())
.rewardTitle(String.format(eventReward.getRewardTitle(), eventReward.getMinRewardValue()))
.rewardImage(eventReward.getRewardImage())
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public class EventReward {

@Column
private String rewardImage;

public void updateMinRewardValue(Long rewardValue) {
this.minRewardValue = rewardValue;
}
}
Loading
Loading