Skip to content

Commit

Permalink
Merge pull request #446 from team-yello/feat/YEL-213
Browse files Browse the repository at this point in the history
YEL-213 [feat] google admob 구현
  • Loading branch information
euije authored Feb 13, 2024
2 parents c854cd2 + 1ed2d88 commit b7e56c1
Show file tree
Hide file tree
Showing 81 changed files with 4,750 additions and 3,700 deletions.
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));
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

0 comments on commit b7e56c1

Please sign in to comment.