From e898b61faf5baf2628c48d3bb23127dbf50dedb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 10 Dec 2023 22:14:00 +0900 Subject: [PATCH 001/112] =?UTF-8?q?[hotfix]=20token=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 649665627ef58de0a5ee5b5c7c155ab053710a05. --- .../domain/authorization/service/TokenJwtProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/yello/server/domain/authorization/service/TokenJwtProvider.java b/src/main/java/com/yello/server/domain/authorization/service/TokenJwtProvider.java index f510a2b7..91d004a5 100644 --- a/src/main/java/com/yello/server/domain/authorization/service/TokenJwtProvider.java +++ b/src/main/java/com/yello/server/domain/authorization/service/TokenJwtProvider.java @@ -1,8 +1,8 @@ package com.yello.server.domain.authorization.service; import static io.jsonwebtoken.SignatureAlgorithm.HS256; -import static java.time.Duration.ofDays; import static java.time.Duration.ofMinutes; +import static java.time.Duration.ofSeconds; import com.yello.server.domain.authorization.dto.ServiceTokenVO; import io.jsonwebtoken.Claims; @@ -23,8 +23,8 @@ public class TokenJwtProvider implements TokenProvider { public static final String ACCESS_TOKEN = "accessToken"; public static final String REFRESH_TOKEN = "refreshToken"; - private static final Long ACCESS_TOKEN_VALID_TIME = ofMinutes(30).toMillis(); - private static final Long REFRESH_TOKEN_VALID_TIME = ofDays(14).toMillis(); + private static final Long ACCESS_TOKEN_VALID_TIME = ofSeconds(30).toMillis(); + private static final Long REFRESH_TOKEN_VALID_TIME = ofMinutes(1).toMillis(); public String secretKey; From eee9c7e72ca4aea3631e67963f29d843a525e754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sat, 6 Jan 2024 20:53:51 +0900 Subject: [PATCH 002/112] =?UTF-8?q?YEL-181=20[feat]=20Notice,=20UserData?= =?UTF-8?q?=20entity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/domain/notice/entity/Notice.java | 34 +++++++++++++++++++ .../repository/NoticeJpaRepository.java | 8 +++++ .../notice/repository/NoticeRepository.java | 5 +++ .../repository/NoticeRepositoryImpl.java | 15 ++++++++ .../server/domain/user/entity/UserData.java | 32 +++++++++++++++++ .../domain/user/entity/UserDataConverter.java | 5 +++ .../domain/user/entity/UserDataType.java | 4 +++ 7 files changed, 103 insertions(+) create mode 100644 src/main/java/com/yello/server/domain/notice/entity/Notice.java create mode 100644 src/main/java/com/yello/server/domain/notice/repository/NoticeJpaRepository.java create mode 100644 src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java create mode 100644 src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java create mode 100644 src/main/java/com/yello/server/domain/user/entity/UserData.java create mode 100644 src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java create mode 100644 src/main/java/com/yello/server/domain/user/entity/UserDataType.java diff --git a/src/main/java/com/yello/server/domain/notice/entity/Notice.java b/src/main/java/com/yello/server/domain/notice/entity/Notice.java new file mode 100644 index 00000000..c8e3b96a --- /dev/null +++ b/src/main/java/com/yello/server/domain/notice/entity/Notice.java @@ -0,0 +1,34 @@ +package com.yello.server.domain.notice.entity; + +import com.yello.server.global.common.dto.AuditingTimeEntity; +import java.time.ZonedDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.Getter; + +@Entity +@Getter +public class Notice extends AuditingTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String imageUrl; + + @Column(nullable = false) + private String redirectUrl; + + @Column(nullable = false) + private ZonedDateTime startDate; + + @Column(nullable = false) + private ZonedDateTime endDate; + + @Column(nullable = false) + private Boolean isAvailable; +} diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeJpaRepository.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeJpaRepository.java new file mode 100644 index 00000000..3652fa1a --- /dev/null +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeJpaRepository.java @@ -0,0 +1,8 @@ +package com.yello.server.domain.notice.repository; + +import com.yello.server.domain.notice.entity.Notice; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoticeJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java new file mode 100644 index 00000000..c993de02 --- /dev/null +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java @@ -0,0 +1,5 @@ +package com.yello.server.domain.notice.repository; + +public interface NoticeRepository { + +} diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java new file mode 100644 index 00000000..cec407a7 --- /dev/null +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java @@ -0,0 +1,15 @@ +package com.yello.server.domain.notice.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NoticeRepositoryImpl implements NoticeRepository { + + private final NoticeJpaRepository noticeJpaRepository; + + +} diff --git a/src/main/java/com/yello/server/domain/user/entity/UserData.java b/src/main/java/com/yello/server/domain/user/entity/UserData.java new file mode 100644 index 00000000..d2d6424f --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/entity/UserData.java @@ -0,0 +1,32 @@ +package com.yello.server.domain.user.entity; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import lombok.Getter; + +@Getter +@Entity +public class UserData { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId") + private User user; + + @Column(nullable = false) + @Convert(converter = UserDataConverter.class) + private UserDataType tag; + + @Column(nullable = false) + private String value; +} diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java b/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java new file mode 100644 index 00000000..8cfe67e5 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java @@ -0,0 +1,5 @@ +package com.yello.server.domain.user.entity; + +public class UserDataConverter { + +} diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataType.java b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java new file mode 100644 index 00000000..7f7cbe21 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java @@ -0,0 +1,4 @@ +package com.yello.server.domain.user.entity; + +public enum UserDataType { +} From ab1a664d802f5d1a4406719275d054be0b6a2994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sat, 6 Jan 2024 21:03:15 +0900 Subject: [PATCH 003/112] =?UTF-8?q?YEL-181=20[feat]=20UserDataType=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 --- .../server/domain/user/entity/UserData.java | 2 +- .../domain/user/entity/UserDataConverter.java | 5 ---- .../domain/user/entity/UserDataType.java | 24 +++++++++++++++ .../user/entity/UserDataTypeConverter.java | 30 +++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) delete mode 100644 src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java create mode 100644 src/main/java/com/yello/server/domain/user/entity/UserDataTypeConverter.java diff --git a/src/main/java/com/yello/server/domain/user/entity/UserData.java b/src/main/java/com/yello/server/domain/user/entity/UserData.java index d2d6424f..e9aef630 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserData.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserData.java @@ -24,7 +24,7 @@ public class UserData { private User user; @Column(nullable = false) - @Convert(converter = UserDataConverter.class) + @Convert(converter = UserDataTypeConverter.class) private UserDataType tag; @Column(nullable = false) diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java b/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java deleted file mode 100644 index 8cfe67e5..00000000 --- a/src/main/java/com/yello/server/domain/user/entity/UserDataConverter.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.yello.server.domain.user.entity; - -public class UserDataConverter { - -} diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataType.java b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java index 7f7cbe21..28fd385b 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserDataType.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java @@ -1,4 +1,28 @@ package com.yello.server.domain.user.entity; +import java.text.MessageFormat; +import java.util.Arrays; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum UserDataType { + WITHDRAW_REASON("WITHDRAW_REASON"), + ACCOUNT_UPDATED_AT("ACCOUNT_UPDATED_AT"), + RECOMMENDED("RECOMMENDED"); + + private final String intial; + + public static UserDataType fromCode(String dbData) { + return Arrays.stream(UserDataType.values()) + .filter(v -> v.getIntial().equals(dbData)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + MessageFormat.format("존재하지 않는 유저데이터 타입입니다. {0}", dbData))); + } + + public String intial() { + return intial; + } } diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataTypeConverter.java b/src/main/java/com/yello/server/domain/user/entity/UserDataTypeConverter.java new file mode 100644 index 00000000..2a87a8cc --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/entity/UserDataTypeConverter.java @@ -0,0 +1,30 @@ +package com.yello.server.domain.user.entity; + +import javax.persistence.AttributeConverter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class UserDataTypeConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(UserDataType userData) { + if (userData == null) { + return null; + } + return userData.getIntial(); + } + + @Override + public UserDataType convertToEntityAttribute(String dbData) { + if (dbData == null) { + return null; + } + try { + return UserDataType.fromCode(dbData); + } catch (IllegalArgumentException exception) { + log.error("failure to convert cause unexpected code" + dbData + exception); + throw exception; + } + } + +} From 89a81583f56cd83f931de2d97cb3556800bb2c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 7 Jan 2024 15:12:11 +0900 Subject: [PATCH 004/112] =?UTF-8?q?YEL-180=20[feat]=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20V2=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserV2Controller.java | 28 ++++++++++ .../dto/response/UserDetailV2Response.java | 55 +++++++++++++++++++ .../domain/user/service/UserService.java | 16 ++++-- 3 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java create mode 100644 src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java diff --git a/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java b/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java new file mode 100644 index 00000000..c6fe188a --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java @@ -0,0 +1,28 @@ +package com.yello.server.domain.user.controller; + +import static com.yello.server.global.common.SuccessCode.READ_USER_SUCCESS; + +import com.yello.server.domain.user.dto.response.UserDetailV2Response; +import com.yello.server.domain.user.entity.User; +import com.yello.server.domain.user.service.UserService; +import com.yello.server.global.common.annotation.AccessTokenUser; +import com.yello.server.global.common.dto.BaseResponse; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v2/user") +public class UserV2Controller { + + private final UserService userService; + + @GetMapping + public BaseResponse getUser(@AccessTokenUser User user) { + val data = userService.getUserDetailV2(user.getId()); + return BaseResponse.success(READ_USER_SUCCESS, data); + } +} diff --git a/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java b/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java new file mode 100644 index 00000000..fcfe13a0 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java @@ -0,0 +1,55 @@ +package com.yello.server.domain.user.dto.response; + +import com.yello.server.domain.group.entity.UserGroup; +import com.yello.server.domain.user.entity.User; +import lombok.Builder; + +@Builder +public record UserDetailV2Response( + /* Default */ + Long userId, + String name, + String yelloId, + String gender, + String email, + String profileImageUrl, + /* Device */ + String social, + String uuid, + String deviceToken, + /* Group */ + String group, + String groupType, + String groupName, + String subGroupName, + Integer groupAdmissionYear, + /* Domain */ + Long recommendCount, + Integer ticketCount, + Integer point, + String subscribe +) { + + public static UserDetailV2Response of(User user, UserGroup userGroup) { + return UserDetailV2Response.builder() + .userId(user.getId()) + .name(user.getName()) + .yelloId(user.getYelloId()) + .gender(user.getGender().getIntial()) + .email(user.getEmail()) + .profileImageUrl(user.getProfileImage()) + .social(user.getSocial().toString()) + .uuid(user.getUuid()) + .deviceToken(user.getDeviceToken()) + .group(user.toGroupString()) + .groupType(userGroup.getUserGroupType().getIntial()) + .groupName(userGroup.getGroupName()) + .subGroupName(userGroup.getSubGroupName()) + .groupAdmissionYear(user.getGroupAdmissionYear()) + .recommendCount(user.getRecommendCount()) + .ticketCount(user.getTicketCount()) + .point(user.getPoint()) + .subscribe(user.getSubscribe().getIntial()) + .build(); + } +} diff --git a/src/main/java/com/yello/server/domain/user/service/UserService.java b/src/main/java/com/yello/server/domain/user/service/UserService.java index 38cfde58..4e69aef1 100644 --- a/src/main/java/com/yello/server/domain/user/service/UserService.java +++ b/src/main/java/com/yello/server/domain/user/service/UserService.java @@ -7,9 +7,11 @@ import com.yello.server.domain.cooldown.repository.CooldownRepository; import com.yello.server.domain.friend.entity.Friend; import com.yello.server.domain.friend.repository.FriendRepository; +import com.yello.server.domain.group.repository.UserGroupRepository; import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; +import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.exception.UserConflictException; @@ -28,13 +30,14 @@ @Transactional(readOnly = true) public class UserService { - private final UserRepository userRepository; - private final FriendRepository friendRepository; - private final VoteRepository voteRepository; private final CooldownRepository cooldownRepository; - private final TokenRepository tokenRepository; + private final FriendRepository friendRepository; private final PurchaseRepository purchaseRepository; + private final TokenRepository tokenRepository; private final UserAdminRepository userAdminRepository; + private final UserGroupRepository userGroupRepository; + private final UserRepository userRepository; + private final VoteRepository voteRepository; public UserDetailResponse findMyProfile(Long userId) { final User user = userRepository.getById(userId); @@ -44,6 +47,11 @@ public UserDetailResponse findMyProfile(Long userId) { return UserDetailResponse.of(user, yelloCount, friendCount); } + public UserDetailV2Response getUserDetailV2(Long userId) { + final User user = userRepository.getById(userId); + return UserDetailV2Response.of(user, user.getGroup()); + } + public UserResponse findUserById(Long userId) { final User user = userRepository.getById(userId); final Integer yelloCount = voteRepository.countAllByReceiverUserId(user.getId()); From d0d70f0fdf8d2eedffabc9a2428eadf900313bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 7 Jan 2024 15:12:23 +0900 Subject: [PATCH 005/112] =?UTF-8?q?YEL-180=20[feat]=20application-local=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 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bf013630..611b4643 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ out/ application.yml application-dev.yml application-apple.yml +application-local.yml firebase*.json *client_secret*.json From ea6845d46245c6e246ec03c1363d5d79df03cd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 7 Jan 2024 17:32:26 +0900 Subject: [PATCH 006/112] =?UTF-8?q?YEL-180=20[feat]=20Test=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B0=8F=20docs=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/check-user-v2.adoc | 19 ++++++++ src/docs/asciidoc/index.adoc | 4 +- .../user/medium/UserControllerTest.java | 44 ++++++++++++++++++- .../domain/user/small/UserServiceTest.java | 18 +++++++- 4 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/docs/asciidoc/check-user-v2.adoc diff --git a/src/docs/asciidoc/check-user-v2.adoc b/src/docs/asciidoc/check-user-v2.adoc new file mode 100644 index 00000000..bde16dd2 --- /dev/null +++ b/src/docs/asciidoc/check-user-v2.adoc @@ -0,0 +1,19 @@ +== 내 정보 조회하기 V2 + +=== 요청 + +include::{snippets}/api/v2/user/http-request.adoc[] + +=== 응답 + +include::{snippets}/api/v2/user/http-response.adoc[] + +=== Note + +- 내 정보 조회하기 V1가 제공했던 단편적인 정보를 보완하기 위해 설계된 API입니다. +- `Authroization` 헤더로 제공된 JWT Token에 담긴 유저의 정보가 응답으로 주어집니다. +- 유저 정보가 필요하면 해당 API를 사용하세요! + +=== CHANGELOG + +- 2024.01.07 첫 릴리즈 \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index f5c91a32..0647660b 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -25,7 +25,9 @@ === User API -* link:check-user.html[내 정보 조회하기] +* link:check-user.html[내 정보 조회하기 V1] + +* link:check-user-v2.html[내 정보 조회하기 V2, 2024-01-07] * link:check-user-by-id.html[특정 유저 정보 조회하기] diff --git a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java index 6cda5faf..0e05b236 100644 --- a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java +++ b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java @@ -15,15 +15,21 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.yello.server.domain.authorization.filter.JwtExceptionFilter; import com.yello.server.domain.authorization.filter.JwtFilter; +import com.yello.server.domain.group.entity.UserGroupType; import com.yello.server.domain.user.controller.UserController; +import com.yello.server.domain.user.controller.UserV2Controller; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; +import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.service.UserService; import com.yello.server.global.common.dto.EmptyObject; import com.yello.server.global.exception.ControllerExceptionAdvice; +import com.yello.server.util.TestDataEntityUtil; +import com.yello.server.util.TestDataUtil; import com.yello.server.util.WithAccessTokenUser; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; @@ -43,7 +49,10 @@ @AutoConfigureRestDocs @WebMvcTest( - controllers = UserController.class, + controllers = { + UserController.class, + UserV2Controller.class + }, excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtFilter.class), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtExceptionFilter.class), @@ -67,6 +76,14 @@ class UserControllerTest { @MockBean private UserService userService; + private TestDataUtil testDataUtil = new TestDataEntityUtil(); + private User user; + + @BeforeEach + void init() { + user = testDataUtil.generateUser(1L, 1L, UserGroupType.UNIVERSITY); + } + @Test void 내_정보_조회에_성공합니다() throws Exception { // given @@ -97,6 +114,31 @@ class UserControllerTest { .andExpect(MockMvcResultMatchers.status().isOk()); } + @Test + void 내_정보_조회_V2에_성공합니다() throws Exception { + // given + UserDetailV2Response response = UserDetailV2Response.of(user, user.getGroup()); + + given(userService.getUserDetailV2(anyLong())) + .willReturn(response); + + // when + + // then + mockMvc + .perform( + RestDocumentationRequestBuilders + .get("/api/v2/user") + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + ) + .andDo(print()) + .andDo(document("api/v2/user", + Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + @Test void 다른_유저_정보_조회에_성공합니다() throws Exception { // given diff --git a/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java b/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java index 54f7db51..59ccd807 100644 --- a/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java +++ b/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java @@ -14,6 +14,7 @@ import com.yello.server.domain.question.repository.QuestionRepository; import com.yello.server.domain.user.FakeUserRepository; import com.yello.server.domain.user.dto.response.UserDetailResponse; +import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.exception.UserNotFoundException; @@ -38,11 +39,11 @@ class UserServiceTest { private final CooldownRepository cooldownRepository = new FakeCooldownRepository(); private final FriendRepository friendRepository = new FakeFriendRepository(); private final QuestionRepository questionRepository = new FakeQuestionRepository(); + private final QuestionGroupTypeRepository + questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); private final TokenRepository tokenRepository = new FakeTokenRepository(); private final UserRepository userRepository = new FakeUserRepository(friendRepository); private final VoteRepository voteRepository = new FakeVoteRepository(); - private final QuestionGroupTypeRepository - questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, @@ -80,6 +81,19 @@ void init() { assertThat(result.name()).isEqualTo("name1"); } + @Test + void 내_정보_조회_V2에_성공합니다() { + // given + final Long userId = 1L; + + // when + final UserDetailV2Response result = userService.getUserDetailV2(userId); + + // then + assertThat(result.userId()).isEqualTo(userId); + assertThat(result.name()).isEqualTo("name1"); + } + @Test void 다른_유저_정보_조회에_성공합니다() { // given From 554d7083e5dc9f1aa98fe2690ac02c4c4bac6641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 7 Jan 2024 17:32:39 +0900 Subject: [PATCH 007/112] =?UTF-8?q?YEL-180=20[feat]=20=EC=A0=95=EC=A0=81?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/check-user.adoc | 2 +- .../resources/static/docs/add-friend.html | 5 +- src/main/resources/static/docs/apple.html | 11 +- .../resources/static/docs/check-keyword.html | 5 +- .../static/docs/check-user-by-id.html | 5 +- .../resources/static/docs/check-user-v2.html | 528 +++ .../resources/static/docs/check-user.html | 9 +- .../static/docs/check-vote-available.html | 8 +- .../resources/static/docs/create-vote.html | 5 +- .../resources/static/docs/delete-friend.html | 5 +- .../resources/static/docs/delete-user.html | 5 +- .../resources/static/docs/device-token.html | 5 +- .../static/docs/find-friend-votes.html | 6 +- .../resources/static/docs/find-friends.html | 5 +- .../static/docs/find-group-friends.html | 2664 +++------------ .../static/docs/find-kakao-friends.html | 5 +- .../static/docs/find-onboarding-friends.html | 7 +- .../resources/static/docs/find-question.html | 5 +- src/main/resources/static/docs/find-vote.html | 5 +- .../resources/static/docs/find-votes.html | 5 +- .../static/docs/get-unread-vote.html | 5 +- src/main/resources/static/docs/google.html | 10 +- src/main/resources/static/docs/index.html | 2904 ++++------------- src/main/resources/static/docs/login.html | 5 +- src/main/resources/static/docs/overview.html | 2 +- src/main/resources/static/docs/pay.html | 5 +- .../resources/static/docs/purchase-check.html | 5 +- .../resources/static/docs/reissue-token.html | 8 +- .../static/docs/reveal-full-name.html | 5 +- .../resources/static/docs/reveal-name.html | 5 +- .../static/docs/search-department.html | 2667 +++------------ .../resources/static/docs/search-friend.html | 5 +- .../resources/static/docs/search-school.html | 2659 +++------------ .../static/docs/shuffle-friends.html | 5 +- src/main/resources/static/docs/signup.html | 5 +- src/main/resources/static/docs/sub-check.html | 5 +- .../static/docs/validate-yelloid.html | 5 +- 37 files changed, 2675 insertions(+), 8925 deletions(-) create mode 100644 src/main/resources/static/docs/check-user-v2.html diff --git a/src/docs/asciidoc/check-user.adoc b/src/docs/asciidoc/check-user.adoc index f51ef64f..520d57c7 100644 --- a/src/docs/asciidoc/check-user.adoc +++ b/src/docs/asciidoc/check-user.adoc @@ -1,4 +1,4 @@ -== 내 정보 조회하기 +== 내 정보 조회하기 V1 === 요청 diff --git a/src/main/resources/static/docs/add-friend.html b/src/main/resources/static/docs/add-friend.html index c11c538f..44eb2820 100644 --- a/src/main/resources/static/docs/add-friend.html +++ b/src/main/resources/static/docs/add-friend.html @@ -479,6 +479,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -494,7 +497,7 @@ 

응답

diff --git a/src/main/resources/static/docs/apple.html b/src/main/resources/static/docs/apple.html index 3ff3b5c0..73689b1d 100644 --- a/src/main/resources/static/docs/apple.html +++ b/src/main/resources/static/docs/apple.html @@ -464,6 +464,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -499,6 +502,9 @@ 

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -527,6 +533,9 @@ 

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -542,7 +551,7 @@ 

응답

diff --git a/src/main/resources/static/docs/check-keyword.html b/src/main/resources/static/docs/check-keyword.html index 97598443..e5624ae9 100644 --- a/src/main/resources/static/docs/check-keyword.html +++ b/src/main/resources/static/docs/check-keyword.html @@ -479,6 +479,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -497,7 +500,7 @@ 

응답

diff --git a/src/main/resources/static/docs/check-user-by-id.html b/src/main/resources/static/docs/check-user-by-id.html index a29df723..b8a29253 100644 --- a/src/main/resources/static/docs/check-user-by-id.html +++ b/src/main/resources/static/docs/check-user-by-id.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -481,7 +484,7 @@ 

응답

diff --git a/src/main/resources/static/docs/check-user-v2.html b/src/main/resources/static/docs/check-user-v2.html new file mode 100644 index 00000000..729b78e9 --- /dev/null +++ b/src/main/resources/static/docs/check-user-v2.html @@ -0,0 +1,528 @@ + + + + + + + +내 정보 조회하기 V2 + + + + + +
+
+

내 정보 조회하기 V2

+
+
+

요청

+
+
+
GET /api/v2/user HTTP/1.1
+Authorization: Bearer your-access-token
+
+
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
+Content-Type: application/json
+
+{
+  "status" : 200,
+  "message" : "유저 조회에 성공했습니다.",
+  "data" : {
+    "userId" : 1,
+    "name" : "name1",
+    "yelloId" : "yelloId1",
+    "gender" : "M",
+    "email" : "test1@test.com",
+    "profileImageUrl" : "test image",
+    "social" : "KAKAO",
+    "uuid" : "1",
+    "deviceToken" : "deviceToken#1",
+    "group" : "테스트 대학교 1 테스트 학과 1 20학번",
+    "groupType" : "UNIVERSITY",
+    "groupName" : "테스트 대학교 1",
+    "subGroupName" : "테스트 학과 1",
+    "groupAdmissionYear" : 20,
+    "recommendCount" : 0,
+    "ticketCount" : 0,
+    "point" : 200,
+    "subscribe" : "normal"
+  }
+}
+
+
+
+
+

Note

+
+
    +
  • +

    내 정보 조회하기 V1가 제공했던 단편적인 정보를 보완하기 위해 설계된 API입니다.

    +
  • +
  • +

    Authroization 헤더로 제공된 JWT Token에 담긴 유저의 정보가 응답으로 주어집니다.

    +
  • +
  • +

    유저 정보가 필요하면 해당 API를 사용하세요!

    +
  • +
+
+
+
+

CHANGELOG

+
+
    +
  • +

    2024.01.07 첫 릴리즈

    +
  • +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/docs/check-user.html b/src/main/resources/static/docs/check-user.html index 4b0acfd6..5035328c 100644 --- a/src/main/resources/static/docs/check-user.html +++ b/src/main/resources/static/docs/check-user.html @@ -5,7 +5,7 @@ -내 정보 조회하기 +내 정보 조회하기 V1 + + + + +그룹 추천 친구 조회하기 + +
-
-

그룹 추천 친구 조회하기

-
-
-

요청

-
-
-
GET /api/v1/friend/recommend/userGroup?page=0 HTTP/1.1
+
+

그룹 추천 친구 조회하기

+
+
+

요청

+
+
+
GET /api/v1/friend/recommend/school?page=0 HTTP/1.1
 Authorization: Bearer your-access-token
-
-
-
-
-

요청 파라미터

- - - - - - - - - - - - - - - - - -
ParameterDescription

page -

페이지네이션 페이지 번호

-
-
-
-

응답

-
-
+
+
+
+
+

요청 파라미터

+ ++++ + + + + + + + + + + + + +
ParameterDescription

page

페이지네이션 페이지 번호

+
+
+

응답

+
+
HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -2196,17 +496,17 @@ 

응답

} ] } }
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/static/docs/find-kakao-friends.html b/src/main/resources/static/docs/find-kakao-friends.html index fe65ed1e..852f73ed 100644 --- a/src/main/resources/static/docs/find-kakao-friends.html +++ b/src/main/resources/static/docs/find-kakao-friends.html @@ -484,6 +484,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -499,7 +502,7 @@ 

응답

diff --git a/src/main/resources/static/docs/find-onboarding-friends.html b/src/main/resources/static/docs/find-onboarding-friends.html index 3e3723d7..29e8719a 100644 --- a/src/main/resources/static/docs/find-onboarding-friends.html +++ b/src/main/resources/static/docs/find-onboarding-friends.html @@ -483,6 +483,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -495,7 +498,7 @@ 

응답

"id" : 1, "name" : "name1", "profileImage" : "test image", - "groupName" : "테스트 대학교 1 테스트 학과 1" + "groupName" : "테스트 대학교 1 테스트 학과 1 20학번" } ] } }
@@ -508,7 +511,7 @@

응답

diff --git a/src/main/resources/static/docs/find-question.html b/src/main/resources/static/docs/find-question.html index e41d8086..52ed5b3d 100644 --- a/src/main/resources/static/docs/find-question.html +++ b/src/main/resources/static/docs/find-question.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -489,7 +492,7 @@ 

응답

diff --git a/src/main/resources/static/docs/find-vote.html b/src/main/resources/static/docs/find-vote.html index 8e583c19..39f79bab 100644 --- a/src/main/resources/static/docs/find-vote.html +++ b/src/main/resources/static/docs/find-vote.html @@ -479,6 +479,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -511,7 +514,7 @@ 

응답

diff --git a/src/main/resources/static/docs/find-votes.html b/src/main/resources/static/docs/find-votes.html index 72e36426..6a3d6df4 100644 --- a/src/main/resources/static/docs/find-votes.html +++ b/src/main/resources/static/docs/find-votes.html @@ -478,6 +478,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -517,7 +520,7 @@ 

응답

diff --git a/src/main/resources/static/docs/get-unread-vote.html b/src/main/resources/static/docs/get-unread-vote.html index 332c20f5..8c9653d6 100644 --- a/src/main/resources/static/docs/get-unread-vote.html +++ b/src/main/resources/static/docs/get-unread-vote.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -475,7 +478,7 @@ 

응답

diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index 20ac5249..04296e74 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -471,6 +471,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -478,7 +481,7 @@ 

응답

"message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2023-08-27T23:11:58.392011" + "expiredAt" : "2024-01-07T17:29:20.656319" } }
@@ -516,6 +519,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -535,7 +541,7 @@ 

응답

diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 8d48ed14..bd05be42 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -1,2316 +1,616 @@ - - - - - YELL:O API 문서 - - - + + + + +YELL:O API 문서 + + +
- +
- + diff --git a/src/main/resources/static/docs/login.html b/src/main/resources/static/docs/login.html index c5f4577b..d7a4b9b4 100644 --- a/src/main/resources/static/docs/login.html +++ b/src/main/resources/static/docs/login.html @@ -464,6 +464,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -484,7 +487,7 @@ 

응답

diff --git a/src/main/resources/static/docs/overview.html b/src/main/resources/static/docs/overview.html index 8b891673..3800630c 100644 --- a/src/main/resources/static/docs/overview.html +++ b/src/main/resources/static/docs/overview.html @@ -515,7 +515,7 @@

HTTP status codes

diff --git a/src/main/resources/static/docs/pay.html b/src/main/resources/static/docs/pay.html index 87595924..8fee4209 100644 --- a/src/main/resources/static/docs/pay.html +++ b/src/main/resources/static/docs/pay.html @@ -463,6 +463,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -478,7 +481,7 @@ 

응답

diff --git a/src/main/resources/static/docs/purchase-check.html b/src/main/resources/static/docs/purchase-check.html index 400081bb..0d6d5e79 100644 --- a/src/main/resources/static/docs/purchase-check.html +++ b/src/main/resources/static/docs/purchase-check.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -477,7 +480,7 @@ 

응답

diff --git a/src/main/resources/static/docs/reissue-token.html b/src/main/resources/static/docs/reissue-token.html index 20957548..e61b4419 100644 --- a/src/main/resources/static/docs/reissue-token.html +++ b/src/main/resources/static/docs/reissue-token.html @@ -458,6 +458,9 @@

HTTP response

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -486,6 +489,9 @@ 

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -505,7 +511,7 @@ 

응답

diff --git a/src/main/resources/static/docs/reveal-full-name.html b/src/main/resources/static/docs/reveal-full-name.html index 0972a3b8..3656e677 100644 --- a/src/main/resources/static/docs/reveal-full-name.html +++ b/src/main/resources/static/docs/reveal-full-name.html @@ -479,6 +479,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -497,7 +500,7 @@ 

응답

diff --git a/src/main/resources/static/docs/reveal-name.html b/src/main/resources/static/docs/reveal-name.html index d07b83d4..d99c569b 100644 --- a/src/main/resources/static/docs/reveal-name.html +++ b/src/main/resources/static/docs/reveal-name.html @@ -479,6 +479,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -498,7 +501,7 @@ 

응답

diff --git a/src/main/resources/static/docs/search-department.html b/src/main/resources/static/docs/search-department.html index 740d483b..19791bff 100644 --- a/src/main/resources/static/docs/search-department.html +++ b/src/main/resources/static/docs/search-department.html @@ -1,2219 +1,474 @@ - - - - - 대학교 학과 검색하기 - - + + + + +대학교 학과 검색하기 + +
-
-

대학교 학과 검색하기

-
-
-

요청

-
-
-
GET /api/v1/auth/userGroup/department?page=0&userGroup=userGroup+name+here&keyword=keyword+here HTTP/1.1
-
-
-
-
-

요청 파라미터

- - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterDescription

page -

페이지네이션 페이지 번호

-

- userGroup

학교 이름

keyword -

검색할 쿼리

-
-
-

응답

-
-
-
HTTP/1.1 200 OK
-Content-Type: application/json
-
-{
-  "status" : 200,
-  "message" : "학과 검색에 성공했습니다.",
-  "data" : {
-    "totalCount" : 0,
-    "groupList" : [ {
-      "groupId" : 1,
-      "departmentName" : "테스트 학과 1"
-    } ]
-  }
-}
-
-
-
-
-
+
+

대학교 학과 검색하기

+
+
+

요청

+
+

Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/http-request.adoc[]

+
+
+
+

요청 파라미터

+
+

Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/request-parameters.adoc[]

+
+
+
+

응답

+
+

Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/http-response.adoc[]

+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/static/docs/search-friend.html b/src/main/resources/static/docs/search-friend.html index faa74135..138a82d8 100644 --- a/src/main/resources/static/docs/search-friend.html +++ b/src/main/resources/static/docs/search-friend.html @@ -482,6 +482,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -508,7 +511,7 @@ 

응답

diff --git a/src/main/resources/static/docs/search-school.html b/src/main/resources/static/docs/search-school.html index bb65a5eb..1171f810 100644 --- a/src/main/resources/static/docs/search-school.html +++ b/src/main/resources/static/docs/search-school.html @@ -1,2211 +1,474 @@ - - - - - 대학교 검색하기 - - + + + + +대학교 검색하기 + +
-
-

대학교 검색하기

-
-
-

요청

-
-
-
GET /api/v1/auth/userGroup?page=0&keyword=keyword+here HTTP/1.1
-
-
-
-
-

요청 파라미터

- - - - - - - - - - - - - - - - - - - - - -
ParameterDescription

page -

페이지네이션 페이지 번호

-

keyword -

검색할 쿼리

-
-
-

응답

-
-
-
HTTP/1.1 200 OK
-Content-Type: application/json
-
-{
-  "status" : 200,
-  "message" : "학교 검색에 성공했습니다.",
-  "data" : {
-    "totalCount" : 0,
-    "groupNameList" : [ "groupName" ]
-  }
-}
-
-
-
-
-
+
+

대학교 검색하기

+
+
+

요청

+
+

Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/http-request.adoc[]

+
+
+
+

요청 파라미터

+
+

Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/request-parameters.adoc[]

+
+
+
+

응답

+
+

Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/http-response.adoc[]

+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/static/docs/shuffle-friends.html b/src/main/resources/static/docs/shuffle-friends.html index 6dd6752a..83a981f8 100644 --- a/src/main/resources/static/docs/shuffle-friends.html +++ b/src/main/resources/static/docs/shuffle-friends.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -477,7 +480,7 @@ 

응답

diff --git a/src/main/resources/static/docs/signup.html b/src/main/resources/static/docs/signup.html index 81c5ec72..61a92e9e 100644 --- a/src/main/resources/static/docs/signup.html +++ b/src/main/resources/static/docs/signup.html @@ -473,6 +473,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -493,7 +496,7 @@ 

응답

diff --git a/src/main/resources/static/docs/sub-check.html b/src/main/resources/static/docs/sub-check.html index 6b02b3be..de5f6eae 100644 --- a/src/main/resources/static/docs/sub-check.html +++ b/src/main/resources/static/docs/sub-check.html @@ -457,6 +457,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -476,7 +479,7 @@ 

응답

diff --git a/src/main/resources/static/docs/validate-yelloid.html b/src/main/resources/static/docs/validate-yelloid.html index 89a3508a..d30fab5e 100644 --- a/src/main/resources/static/docs/validate-yelloid.html +++ b/src/main/resources/static/docs/validate-yelloid.html @@ -477,6 +477,9 @@

응답

HTTP/1.1 200 OK
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
 Content-Type: application/json
 
 {
@@ -493,7 +496,7 @@ 

응답

From 233eac4a71846cf5b3f1add69050103b671be8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sun, 7 Jan 2024 17:36:17 +0900 Subject: [PATCH 008/112] =?UTF-8?q?YEL-181=20[feat]=20=EB=B3=B4=EC=9D=BC?= =?UTF-8?q?=EB=9F=AC=ED=94=8C=EB=A0=88=EC=9D=B4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yello/server/domain/notice/entity/Notice.java | 7 +++++++ .../java/com/yello/server/domain/user/entity/UserData.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/yello/server/domain/notice/entity/Notice.java b/src/main/java/com/yello/server/domain/notice/entity/Notice.java index c8e3b96a..1337fbc7 100644 --- a/src/main/java/com/yello/server/domain/notice/entity/Notice.java +++ b/src/main/java/com/yello/server/domain/notice/entity/Notice.java @@ -7,10 +7,17 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Notice extends AuditingTimeEntity { @Id diff --git a/src/main/java/com/yello/server/domain/user/entity/UserData.java b/src/main/java/com/yello/server/domain/user/entity/UserData.java index e9aef630..ce378fc4 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserData.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserData.java @@ -9,10 +9,17 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserData { @Id From 4f344ba6ff0f41ec78bf9dc1de9bf8c46dc3cab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Tue, 9 Jan 2024 01:04:28 +0900 Subject: [PATCH 009/112] =?UTF-8?q?YEL-180=20[feat]=20V2=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20docs=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + src/docs/asciidoc/check-user-v2.adoc | 24 ++++++- .../server/domain/notice/entity/Notice.java | 2 +- .../user/controller/UserController.java | 17 +++-- .../user/controller/UserV2Controller.java | 28 -------- .../resources/static/docs/check-user-v2.html | 66 ++++++++++++++++++- .../resources/static/docs/check-user.html | 2 +- src/main/resources/static/docs/index.html | 2 +- .../user/medium/UserControllerTest.java | 4 +- 9 files changed, 106 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java diff --git a/.gitignore b/.gitignore index 611b4643..29c585e5 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ firebase*.json ### monitoring ### monitoring/prometheus/volume monitoring/grafana + +src/main/resources/application-local.yml diff --git a/src/docs/asciidoc/check-user-v2.adoc b/src/docs/asciidoc/check-user-v2.adoc index bde16dd2..e536e83a 100644 --- a/src/docs/asciidoc/check-user-v2.adoc +++ b/src/docs/asciidoc/check-user-v2.adoc @@ -8,6 +8,27 @@ include::{snippets}/api/v2/user/http-request.adoc[] include::{snippets}/api/v2/user/http-response.adoc[] +*필드 타입* + +- "userId": Long +- "name": String +- "yelloId": String +- "gender": "M" | "F" +- "email": String +- "profileImageUrl": String +- "social": "KAKAO" | "APPLE" +- "uuid": String(10) +- "deviceToken": String +- "group": String +- "groupType": "UNIVERSITY" | "HIGH_SCHOOL" | "MIDDLE_SCHOOL" | "SOPT" +- "groupName": String +- "subGroupName": String +- "groupAdmissionYear": Integer +- "recommendCount": Long +- "ticketCount": Integer +- "point": Integer +- "subscribe": "normal" | "active" | "canceled" + === Note - 내 정보 조회하기 V1가 제공했던 단편적인 정보를 보완하기 위해 설계된 API입니다. @@ -16,4 +37,5 @@ include::{snippets}/api/v2/user/http-response.adoc[] === CHANGELOG -- 2024.01.07 첫 릴리즈 \ No newline at end of file +- 2024.01.07 첫 릴리즈 +- 2024.01.09 필드 타입 추가 \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/notice/entity/Notice.java b/src/main/java/com/yello/server/domain/notice/entity/Notice.java index c8e3b96a..c578200c 100644 --- a/src/main/java/com/yello/server/domain/notice/entity/Notice.java +++ b/src/main/java/com/yello/server/domain/notice/entity/Notice.java @@ -31,4 +31,4 @@ public class Notice extends AuditingTimeEntity { @Column(nullable = false) private Boolean isAvailable; -} +} \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/user/controller/UserController.java b/src/main/java/com/yello/server/domain/user/controller/UserController.java index 49ebff1d..9ee35812 100644 --- a/src/main/java/com/yello/server/domain/user/controller/UserController.java +++ b/src/main/java/com/yello/server/domain/user/controller/UserController.java @@ -6,6 +6,7 @@ import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; +import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.service.UserService; @@ -24,24 +25,30 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/user") +@RequestMapping("/api") public class UserController { private final UserService userService; - @GetMapping + @GetMapping("/v1/user") public BaseResponse findUser(@AccessTokenUser User user) { val data = userService.findMyProfile(user.getId()); return BaseResponse.success(READ_USER_SUCCESS, data); } - @GetMapping("/{userId}") + @GetMapping("/v2/user") + public BaseResponse getUser(@AccessTokenUser User user) { + val data = userService.getUserDetailV2(user.getId()); + return BaseResponse.success(READ_USER_SUCCESS, data); + } + + @GetMapping("/v1/user/{userId}") public BaseResponse findUserById(@PathVariable Long userId) { val data = userService.findUserById(userId); return BaseResponse.success(READ_USER_SUCCESS, data); } - @PutMapping("/device") + @PutMapping("/v1/user/device") public BaseResponse putUserDeviceToken( @AccessTokenUser User user, @RequestBody UserDeviceTokenRequest request @@ -50,7 +57,7 @@ public BaseResponse putUserDeviceToken( return BaseResponse.success(UPDATE_DEVICE_TOKEN_USER_SUCCESS, data); } - @DeleteMapping + @DeleteMapping("/v1/user") public BaseResponse deleteUser(@AccessTokenUser User user) { userService.delete(user); return BaseResponse.success(DELETE_USER_SUCCESS); diff --git a/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java b/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java deleted file mode 100644 index c6fe188a..00000000 --- a/src/main/java/com/yello/server/domain/user/controller/UserV2Controller.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.yello.server.domain.user.controller; - -import static com.yello.server.global.common.SuccessCode.READ_USER_SUCCESS; - -import com.yello.server.domain.user.dto.response.UserDetailV2Response; -import com.yello.server.domain.user.entity.User; -import com.yello.server.domain.user.service.UserService; -import com.yello.server.global.common.annotation.AccessTokenUser; -import com.yello.server.global.common.dto.BaseResponse; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v2/user") -public class UserV2Controller { - - private final UserService userService; - - @GetMapping - public BaseResponse getUser(@AccessTokenUser User user) { - val data = userService.getUserDetailV2(user.getId()); - return BaseResponse.success(READ_USER_SUCCESS, data); - } -} diff --git a/src/main/resources/static/docs/check-user-v2.html b/src/main/resources/static/docs/check-user-v2.html index 729b78e9..80dd6036 100644 --- a/src/main/resources/static/docs/check-user-v2.html +++ b/src/main/resources/static/docs/check-user-v2.html @@ -488,6 +488,67 @@

응답

}
+
+

필드 타입

+
+
+
    +
  • +

    "userId": Long

    +
  • +
  • +

    "name": String

    +
  • +
  • +

    "yelloId": String

    +
  • +
  • +

    "gender": "M" | "F"

    +
  • +
  • +

    "email": String

    +
  • +
  • +

    "profileImageUrl": String

    +
  • +
  • +

    "social": "KAKAO" | "APPLE"

    +
  • +
  • +

    "uuid": String(10)

    +
  • +
  • +

    "deviceToken": String

    +
  • +
  • +

    "group": String

    +
  • +
  • +

    "groupType": "UNIVERSITY" | "HIGH_SCHOOL" | "MIDDLE_SCHOOL" | "SOPT"

    +
  • +
  • +

    "groupName": String

    +
  • +
  • +

    "subGroupName": String

    +
  • +
  • +

    "groupAdmissionYear": Integer

    +
  • +
  • +

    "recommendCount": Long

    +
  • +
  • +

    "ticketCount": Integer

    +
  • +
  • +

    "point": Integer

    +
  • +
  • +

    "subscribe": "normal" | "active" | "canceled"

    +
  • +
+

Note

@@ -512,6 +573,9 @@

CHANGELOG

  • 2024.01.07 첫 릴리즈

  • +
  • +

    2024.01.09 필드 타입 추가

    +
  • @@ -521,7 +585,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/check-user.html b/src/main/resources/static/docs/check-user.html index 5035328c..7924d305 100644 --- a/src/main/resources/static/docs/check-user.html +++ b/src/main/resources/static/docs/check-user.html @@ -485,7 +485,7 @@

    응답

    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index bd05be42..c41f669c 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -607,7 +607,7 @@

    Pay API

    diff --git a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java index 0e05b236..d936777e 100644 --- a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java +++ b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java @@ -17,7 +17,6 @@ import com.yello.server.domain.authorization.filter.JwtFilter; import com.yello.server.domain.group.entity.UserGroupType; import com.yello.server.domain.user.controller.UserController; -import com.yello.server.domain.user.controller.UserV2Controller; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; @@ -50,8 +49,7 @@ @AutoConfigureRestDocs @WebMvcTest( controllers = { - UserController.class, - UserV2Controller.class + UserController.class }, excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtFilter.class), From d5bf815f206c3e87485fc26679276acacac8d02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 10 Jan 2024 00:09:42 +0900 Subject: [PATCH 010/112] =?UTF-8?q?YEL-182=20[feat]=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/edit-user.adoc | 60 ++ src/docs/asciidoc/find-friend-votes.adoc | 39 +- src/docs/asciidoc/find-notice.adoc | 55 ++ src/docs/asciidoc/index.adoc | 25 +- src/docs/asciidoc/purchase-info.adoc | 43 ++ src/docs/asciidoc/search-department.adoc | 6 +- src/docs/asciidoc/search-high-class.adoc | 13 + src/docs/asciidoc/search-high-name.adoc | 13 + src/docs/asciidoc/search-school.adoc | 6 +- src/docs/asciidoc/user-data-get.adoc | 53 ++ src/docs/asciidoc/user-data-post.adoc | 48 ++ .../resources/static/docs/check-user-v2.html | 2 +- .../resources/static/docs/check-user.html | 2 +- .../static/docs/check-vote-available.html | 2 +- src/main/resources/static/docs/edit-user.html | 568 +++++++++++++++++ .../static/docs/find-friend-votes.html | 118 +++- .../resources/static/docs/find-notice.html | 572 ++++++++++++++++++ src/main/resources/static/docs/google.html | 2 +- src/main/resources/static/docs/index.html | 46 +- .../resources/static/docs/purchase-info.html | 524 ++++++++++++++++ .../static/docs/search-department.html | 59 +- .../static/docs/search-high-class.html | 509 ++++++++++++++++ .../static/docs/search-high-name.html | 510 ++++++++++++++++ .../resources/static/docs/search-school.html | 52 +- .../resources/static/docs/user-data-get.html | 560 +++++++++++++++++ .../resources/static/docs/user-data-post.html | 540 +++++++++++++++++ 26 files changed, 4388 insertions(+), 39 deletions(-) create mode 100644 src/docs/asciidoc/edit-user.adoc create mode 100644 src/docs/asciidoc/find-notice.adoc create mode 100644 src/docs/asciidoc/purchase-info.adoc create mode 100644 src/docs/asciidoc/search-high-class.adoc create mode 100644 src/docs/asciidoc/search-high-name.adoc create mode 100644 src/docs/asciidoc/user-data-get.adoc create mode 100644 src/docs/asciidoc/user-data-post.adoc create mode 100644 src/main/resources/static/docs/edit-user.html create mode 100644 src/main/resources/static/docs/find-notice.html create mode 100644 src/main/resources/static/docs/purchase-info.html create mode 100644 src/main/resources/static/docs/search-high-class.html create mode 100644 src/main/resources/static/docs/search-high-name.html create mode 100644 src/main/resources/static/docs/user-data-get.html create mode 100644 src/main/resources/static/docs/user-data-post.html diff --git a/src/docs/asciidoc/edit-user.adoc b/src/docs/asciidoc/edit-user.adoc new file mode 100644 index 00000000..6b014355 --- /dev/null +++ b/src/docs/asciidoc/edit-user.adoc @@ -0,0 +1,60 @@ +== 유저 프로필 수정 (명세) + +=== 요청 + +[http,json] +---- +POST /api/v1/user HTTP/1.1 +Authorization: Bearer your-access-token +Content-Type: application/json + +{ + "name": "name1", + "yelloId": "yelloId1", + "gender": "M", + "email": "test1@test.com", + "profileImageUrl": "test image", + "groupId": 30000, + "groupAdmissionYear" : 20 +} +---- + +*필드 타입* + +- "name": String +- "yelloId": String +- "gender": "M" | "F" +- "email": String +- "profileImageUrl": String +- "groupId": Long +* 대학교 검색 또는 고등학교 검색 API를 이용하여 유저가 선택한 groupId를 입력해주세요. +* 해당 groupId에 해당하는 group이 고등학교면, 해당 유저는 고등학생 / 대학교면 대학생이 됩니다. +- "groupAdmissionYear": Integer +* 대학생이면 학번 / 고등학생이면 '반(class)'를 넣어주세요. + +=== 응답 + +[http,json] +---- +HTTP/1.1 200 OK +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +Content-Type: application/json + +{ + "status" : 200, + "message" : "유저 프로필 조회에 성공하였습니다." +} +---- + +*필드 타입* + +=== NOTE + +- 포인트 / 구독정보 / 로그인 정보와 같이 user-pure하지 않은 정보는 수정할 수 없도록 설계하였습니다. +* 해당 정보 수정API는 도메인 별로 만들 예정입니다. + +=== CHANGELOG + +- 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/docs/asciidoc/find-friend-votes.adoc b/src/docs/asciidoc/find-friend-votes.adoc index c6bbe0d4..82292814 100644 --- a/src/docs/asciidoc/find-friend-votes.adoc +++ b/src/docs/asciidoc/find-friend-votes.adoc @@ -1,4 +1,4 @@ -== 친구 투표 전체 조회 +== 친구 투표 전체 조회 (업데이트) === 요청 @@ -8,6 +8,43 @@ include::{snippets}/api/v1/vote/findAllFriendVotes/http-request.adoc[] include::{snippets}/api/v1/vote/findAllFriendVotes/request-parameters.adoc[] +*업데이트 예정* +- "type": "send" | null + +|=== +|`+type+`|조회할 쪽지 종류 +|=== + === 응답 include::{snippets}/api/v1/vote/findAllFriendVotes/http-response.adoc[] + +*필드 타입* + +- "totalCount": Integer +- "friendVotes": *FriendVote*[] +- *FriendVote* +* "id": Long +* "receiverName": String +* "senderGender": "MALE" | "FEMALE" +* "receiverProfileImage": String +* "vote": *Vote* +* "isHintUsed": Boolean +* "createdAt": "{0}초 전" | "{0}분 전" | "{0}시간 전" | "{0}일 전" +- *Vote* +* "nameHead": String +* "nameFoot": String +* "keywordHead": String +* "keyword": String +* "keywordFoot": String + +=== NOTE + +- 모든 종류의 쪽지를 조회할 때 `/api/v1/vote/friend?page=0` 으로 요청해주세요 +* `type=` 을 명시하지 마세요 +- 내가 보낸 쪽지를 조회할 때 `/api/v1/vote/friend?page=0&type=send` 으로 요청해주세요 +- `senderGender` 필드가 다른 API와 일관되지 못한점 미안해요 + +=== CHANGELOG + +- 2024.01.09 `type` 명세 업데이트 \ No newline at end of file diff --git a/src/docs/asciidoc/find-notice.adoc b/src/docs/asciidoc/find-notice.adoc new file mode 100644 index 00000000..fe53da12 --- /dev/null +++ b/src/docs/asciidoc/find-notice.adoc @@ -0,0 +1,55 @@ +== 공지 조회 (명세) + +=== 요청 + +[http] +---- +GET /api/v1/notice HTTP/1.1 +Authorization: Bearer your-access-token +---- + +=== 응답 + +[http,json] +---- +HTTP/1.1 200 OK +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +Content-Type: application/json + +{ + "status": 200, + "message": "공지 조회에 성공하였습니다.", + "data": { + "imageUrl" : "url here", + "redirectUrl": "redirect url here", + "startDate": "2021.01.12", + "endDate": "2021.01.17", + "isAvailable": true + } +} +---- + +*필드 타입* + +- "imageUrl": String +* 공지로 보여줄 이미지 URL입니다. +- "redirectUrl: String +* 클릭시 이동할 웹 URL입니다. +- "startDate": String(10) +* YYYY-MM-DD (ISO-8601) +- "endDate": String(10) +* YYYY-MM-DD (ISO-8601) +- "isAvailable": Boolean + +=== NOTE + +- 공지 정보를 조회하는 API입니다. +- 유효한 1개의 공지를 반환합니다. +* 요구사항에 따라 여러개의 공지를 반환할 수 있도록 염두하고 있습니다. +* 반환되는 공지를 무조건 View에 띄워주시면 되겠습니다. + +=== CHANGELOG + +- 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 0647660b..318c7f1f 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -17,17 +17,27 @@ * link:find-onboarding-friends.html[가입한 친구 목록 불러오기] -* link:search-userGroup.html[대학교 검색하기] +* link:search-school.html[대학교 검색하기] * link:search-department.html[대학교 학과 검색하기] +* link:search-high-name.html[고등학교 이름 검색하기] + +* link:search-high-class.html[고등학교 이름으로 학반 검색하기] + * link:reissue-token.html[토큰 재발급] === User API * link:check-user.html[내 정보 조회하기 V1] -* link:check-user-v2.html[내 정보 조회하기 V2, 2024-01-07] +* 🆕 link:check-user-v2.html[내 정보 조회하기 V2, 2024-01-07] + +* 🆕 link:edit-user.html[유저 정보 수정 (명세), 2024-01-09] + +* 🆕 link:user-data-get.html[프로필 수정 가능 여부 조회 (명세), 2024-01-09] + +* 🆕 link:user-data-post.html[탈퇴 사유 저장 (명세), 2024-01-09] * link:check-user-by-id.html[특정 유저 정보 조회하기] @@ -39,9 +49,9 @@ * link:find-votes.html[내 투표 전체 조회하기] -* link:find-friend-votes.html[친구 투표 전체 조회하기] +* ⬆️ link:find-friend-votes.html[친구 투표 전체 조회하기, 2024-01-09] -* link:find-friend-votes.html[읽지 않은 쪽지 개수 조회하기] +* link:get-unread-vote.html[읽지 않은 쪽지 개수 조회하기] * link:find-vote.html[투표 상세 조회하기] @@ -73,6 +83,8 @@ === Purchase API +* 🆕 link:purchase-info.html[유저 구독 정보 (명세), 2024-01-09] + * link:apple.html[Apple 결제 관련 API] * link:google.html[Google 결제 관련 API] @@ -83,4 +95,9 @@ === Pay API +- @Deprecated at 2024.03 * link:pay.html[결제 전환율 체크] + +=== Notice API + +* 🆕 link:find-notice.html[공지 조회 (명세), 2024-01-09] \ No newline at end of file diff --git a/src/docs/asciidoc/purchase-info.adoc b/src/docs/asciidoc/purchase-info.adoc new file mode 100644 index 00000000..078663a3 --- /dev/null +++ b/src/docs/asciidoc/purchase-info.adoc @@ -0,0 +1,43 @@ +== 유저 구독 정보 (명세) + +=== 요청 + +[http] +---- +GET /api/v1/purchase/subscribe HTTP/1.1 +Authorization: Bearer your-access-token +---- + +=== 응답 + +[http,json] +---- +HTTP/1.1 200 OK +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +Content-Type: application/json + +{ + "status" : 200, + "message" : "구독 정보 조회에 성공하였습니다.", + "data" : { + "id": 5, + "subscribe": "active", + "expiredDate": "2023-02-12" + } +} +---- + +*필드 타입* + +- "id": Long +- "subscribe": "normal" | "active" | "canceled" +- "expireDate": String(10) +* YYYY-MM-DD (ISO-8601) + +=== Note + +=== CHANGELOG + +- 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/docs/asciidoc/search-department.adoc b/src/docs/asciidoc/search-department.adoc index 467e457a..c04cfeb4 100644 --- a/src/docs/asciidoc/search-department.adoc +++ b/src/docs/asciidoc/search-department.adoc @@ -2,12 +2,12 @@ === 요청 -include::{snippets}/api/v1/auth/findDepartmentsByKeyword/http-request.adoc[] +include::{snippets}/api/v1/auth/findAllUnivDepartmentName/http-request.adoc[] === 요청 파라미터 -include::{snippets}/api/v1/auth/findDepartmentsByKeyword/request-parameters.adoc[] +include::{snippets}/api/v1/auth/findAllUnivDepartmentName/request-parameters.adoc[] === 응답 -include::{snippets}/api/v1/auth/findDepartmentsByKeyword/http-response.adoc[] +include::{snippets}/api/v1/auth/findAllUnivDepartmentName/http-response.adoc[] diff --git a/src/docs/asciidoc/search-high-class.adoc b/src/docs/asciidoc/search-high-class.adoc new file mode 100644 index 00000000..0de824e5 --- /dev/null +++ b/src/docs/asciidoc/search-high-class.adoc @@ -0,0 +1,13 @@ +== 고등학교 이름으로 학반 검색하기 + +=== 요청 + +include::{snippets}/api/v1/auth/findGroupIdByName/http-request.adoc[] + +=== 요청 파라미터 + +include::{snippets}/api/v1/auth/findGroupIdByName/request-parameters.adoc[] + +=== 응답 + +include::{snippets}/api/v1/auth/findGroupIdByName/http-response.adoc[] diff --git a/src/docs/asciidoc/search-high-name.adoc b/src/docs/asciidoc/search-high-name.adoc new file mode 100644 index 00000000..c3f3e2ab --- /dev/null +++ b/src/docs/asciidoc/search-high-name.adoc @@ -0,0 +1,13 @@ +== 고등학교 이름 검색하기 + +=== 요청 + +include::{snippets}/api/v1/auth/findAllHighSchoolName/http-request.adoc[] + +=== 요청 파라미터 + +include::{snippets}/api/v1/auth/findAllHighSchoolName/request-parameters.adoc[] + +=== 응답 + +include::{snippets}/api/v1/auth/findAllHighSchoolName/http-response.adoc[] diff --git a/src/docs/asciidoc/search-school.adoc b/src/docs/asciidoc/search-school.adoc index e3713fa6..5fe8941e 100644 --- a/src/docs/asciidoc/search-school.adoc +++ b/src/docs/asciidoc/search-school.adoc @@ -2,12 +2,12 @@ === 요청 -include::{snippets}/api/v1/auth/findSchoolsByKeyword/http-request.adoc[] +include::{snippets}/api/v1/auth/findAllUnivName/http-request.adoc[] === 요청 파라미터 -include::{snippets}/api/v1/auth/findSchoolsByKeyword/request-parameters.adoc[] +include::{snippets}/api/v1/auth/findAllUnivName/request-parameters.adoc[] === 응답 -include::{snippets}/api/v1/auth/findSchoolsByKeyword/http-response.adoc[] +include::{snippets}/api/v1/auth/findAllUnivName/http-response.adoc[] diff --git a/src/docs/asciidoc/user-data-get.adoc b/src/docs/asciidoc/user-data-get.adoc new file mode 100644 index 00000000..b752c515 --- /dev/null +++ b/src/docs/asciidoc/user-data-get.adoc @@ -0,0 +1,53 @@ +== 프로필 수정 가능 여부 조회 (명세) + +=== 요청 + +[http] +---- +GET /api/v1/user/data/account-update-at HTTP/1.1 +Authorization: Bearer your-access-token +Content-Type: application-json +---- + +*필드 타입* + +- "tag": "account-update-at" +* account-update-at 자리가 ENUM으로 대체될 예정입니다. + +=== 응답 + +[http,json] +---- +HTTP/1.1 200 OK +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +Content-Type: application/json + +{ + "status" : 200, + "message" : "프로필 수정 가능 여부 조회에 성공하였습니다.", + "data": { + "tag": "ACCOUNT_UPDATE_AT", + "value": "false|2024-01-09|2023-10-20" + } +} +---- + +*필드 타입* + +- "tag": "ACCOUNT_UPDATE_AT" +- "value": String +* ACCOUNT_UPDATE_AT의 경우 `{boolean}|{updated_at}|{created_at}` 를 반환합니다. +* boolean 및 updated_at 값을 parse하여 사용해주세요. +* 날짜의 경우 YYYY-MM-DD (ISO-8601) + +=== NOTE + +- AccessToken에 해당하는 User의 프로필 수정 가능 여부 조회를 조회하는 API입니다. +- User의 다양한 정보를 조회하는 API로 범용적인 확장할 예정입니다. +* 차후에 tag에 들어갈 수 있는 ENUM의 종류를 다양화 할 예정입니다. + +=== CHANGELOG + +- 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/docs/asciidoc/user-data-post.adoc b/src/docs/asciidoc/user-data-post.adoc new file mode 100644 index 00000000..a5ccf1a0 --- /dev/null +++ b/src/docs/asciidoc/user-data-post.adoc @@ -0,0 +1,48 @@ +== 탈퇴 사유 저장 (명세) + +=== 요청 + +[http] +---- +POST /api/v1/user/data/withdraw-reason HTTP/1.1 +Authorization: Bearer your-access-token +Content-Type: application-json + +{ + "value": "오류가 많아서" +} +---- + +*필드 타입* + +- "tag": "withdraw-reason" | "account-update-at" | "recommended" +- "value": String +* withdraw-reason 자리가 ENUM으로 대체될 예정입니다. + +=== 응답 + +[http,json] +---- +HTTP/1.1 200 OK +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +Content-Type: application/json + +{ + "status" : 200, + "message" : "탈퇴 사유 정보 저장에 성공하였습니다." +} +---- + +*필드 타입* + +=== NOTE + +- AccessToken에 해당하는 User의 탈퇴 사유를 저장하는 API입니다. +- User의 다양한 정보를 저장하는 API로 범용적인 확장할 예정입니다. +* 차후에 tag에 들어갈 수 있는 ENUM의 종류를 다양화 할 예정입니다. + +=== CHANGELOG + +- 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/main/resources/static/docs/check-user-v2.html b/src/main/resources/static/docs/check-user-v2.html index 80dd6036..5aaa4387 100644 --- a/src/main/resources/static/docs/check-user-v2.html +++ b/src/main/resources/static/docs/check-user-v2.html @@ -585,7 +585,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/check-user.html b/src/main/resources/static/docs/check-user.html index 7924d305..5b32b738 100644 --- a/src/main/resources/static/docs/check-user.html +++ b/src/main/resources/static/docs/check-user.html @@ -485,7 +485,7 @@

    응답

    diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index 6da0714b..229be5a1 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

    응답

    "data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-07 17:29:30", + "createdAt" : "2024-01-10 00:06:57", "friendStatus" : 1 } }
    diff --git a/src/main/resources/static/docs/edit-user.html b/src/main/resources/static/docs/edit-user.html new file mode 100644 index 00000000..f1f7c6b8 --- /dev/null +++ b/src/main/resources/static/docs/edit-user.html @@ -0,0 +1,568 @@ + + + + + + + +유저 프로필 수정 (명세) + + + + + +
    +
    +

    유저 프로필 수정 (명세)

    +
    +
    +

    요청

    +
    +
    +
    POST /api/v1/user HTTP/1.1
    +Authorization: Bearer your-access-token
    +Content-Type: application/json
    +
    +{
    +    "name": "name1",
    +    "yelloId": "yelloId1",
    +    "gender": "M",
    +    "email": "test1@test.com",
    +    "profileImageUrl": "test image",
    +    "groupId": 30000,
    +    "groupAdmissionYear" : 20
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "name": String

      +
    • +
    • +

      "yelloId": String

      +
    • +
    • +

      "gender": "M" | "F"

      +
    • +
    • +

      "email": String

      +
    • +
    • +

      "profileImageUrl": String

      +
    • +
    • +

      "groupId": Long

      +
      +
        +
      • +

        대학교 검색 또는 고등학교 검색 API를 이용하여 유저가 선택한 groupId를 입력해주세요.

        +
      • +
      • +

        해당 groupId에 해당하는 group이 고등학교면, 해당 유저는 고등학생 / 대학교면 대학생이 됩니다.

        +
      • +
      +
      +
    • +
    • +

      "groupAdmissionYear": Integer

      +
      +
        +
      • +

        대학생이면 학번 / 고등학생이면 '반(class)'를 넣어주세요.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "유저 프로필 조회에 성공하였습니다."
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      포인트 / 구독정보 / 로그인 정보와 같이 user-pure하지 않은 정보는 수정할 수 없도록 설계하였습니다.

      +
      +
        +
      • +

        해당 정보 수정API는 도메인 별로 만들 예정입니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/find-friend-votes.html b/src/main/resources/static/docs/find-friend-votes.html index 9324f026..f1385de5 100644 --- a/src/main/resources/static/docs/find-friend-votes.html +++ b/src/main/resources/static/docs/find-friend-votes.html @@ -5,7 +5,7 @@ -친구 투표 전체 조회 +친구 투표 전체 조회 (업데이트) + + + +
    +
    +

    공지 조회 (명세)

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/notice HTTP/1.1
    +Authorization: Bearer your-access-token
    +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +    "status": 200,
    +    "message": "공지 조회에 성공하였습니다.",
    +    "data": {
    +		"imageUrl" : "url here",
    +		"redirectUrl": "redirect url here",
    +		"startDate": "2021.01.12",
    +		"endDate": "2021.01.17",
    +		"isAvailable": true
    +	}
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "imageUrl": String

      +
      +
        +
      • +

        공지로 보여줄 이미지 URL입니다.

        +
      • +
      +
      +
    • +
    • +

      "redirectUrl: String

      +
      +
        +
      • +

        클릭시 이동할 웹 URL입니다.

        +
      • +
      +
      +
    • +
    • +

      "startDate": String(10)

      +
      +
        +
      • +

        YYYY-MM-DD (ISO-8601)

        +
      • +
      +
      +
    • +
    • +

      "endDate": String(10)

      +
      +
        +
      • +

        YYYY-MM-DD (ISO-8601)

        +
      • +
      +
      +
    • +
    • +

      "isAvailable": Boolean

      +
    • +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      공지 정보를 조회하는 API입니다.

      +
    • +
    • +

      유효한 1개의 공지를 반환합니다.

      +
      +
        +
      • +

        요구사항에 따라 여러개의 공지를 반환할 수 있도록 염두하고 있습니다.

        +
      • +
      • +

        반환되는 공지를 무조건 View에 띄워주시면 되겠습니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index 04296e74..cd5229e6 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

    응답

    "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-07T17:29:20.656319" + "expiredAt" : "2024-01-10T00:06:48.613074" } }
    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index c41f669c..08f0b9e2 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -454,6 +454,7 @@

    YELL:O API 문서

  • Friend API
  • Purchase API
  • Pay API
  • +
  • Notice API
  • @@ -477,12 +478,18 @@

    Auth API

    가입한 친구 목록 불러오기

  • -

    대학교 검색하기

    +

    대학교 검색하기

  • 대학교 학과 검색하기

  • +

    고등학교 이름 검색하기

    +
  • +
  • +

    고등학교 이름으로 학반 검색하기

    +
  • +
  • 토큰 재발급

  • @@ -496,7 +503,16 @@

    User API

    내 정보 조회하기 V1

  • -

    내 정보 조회하기 V2, 2024-01-07

    +

    🆕 내 정보 조회하기 V2, 2024-01-07

    +
  • +
  • +

    🆕 유저 정보 수정 (명세), 2024-01-09

    +
  • +
  • +

    🆕 프로필 수정 가능 여부 조회 (명세), 2024-01-09

    +
  • +
  • +

    🆕 탈퇴 사유 저장 (명세), 2024-01-09

  • 특정 유저 정보 조회하기

    @@ -518,10 +534,10 @@

    Vote API

    내 투표 전체 조회하기

  • -

    친구 투표 전체 조회하기

    +

    ⬆️ 친구 투표 전체 조회하기, 2024-01-09

  • -

    읽지 않은 쪽지 개수 조회하기

    +

    읽지 않은 쪽지 개수 조회하기

  • 투표 상세 조회하기

    @@ -577,6 +593,9 @@

    Purchase API

  • +
    @@ -607,7 +643,7 @@

    Pay API

    diff --git a/src/main/resources/static/docs/purchase-info.html b/src/main/resources/static/docs/purchase-info.html new file mode 100644 index 00000000..bb176a53 --- /dev/null +++ b/src/main/resources/static/docs/purchase-info.html @@ -0,0 +1,524 @@ + + + + + + + +유저 구독 정보 (명세) + + + + + +
    +
    +

    유저 구독 정보 (명세)

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/purchase/subscribe HTTP/1.1
    +Authorization: Bearer your-access-token
    +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "구독 정보 조회에 성공하였습니다.",
    +  "data" : {
    +    "id": 5,
    +    "subscribe": "active",
    +    "expiredDate": "2023-02-12"
    +  }
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "id": Long

      +
    • +
    • +

      "subscribe": "normal" | "active" | "canceled"

      +
    • +
    • +

      "expireDate": String(10)

      +
      +
        +
      • +

        YYYY-MM-DD (ISO-8601)

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    Note

    + +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/search-department.html b/src/main/resources/static/docs/search-department.html index 19791bff..deced473 100644 --- a/src/main/resources/static/docs/search-department.html +++ b/src/main/resources/static/docs/search-department.html @@ -445,20 +445,63 @@

    대학교 학과 검색하기

    요청

    -
    -

    Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/http-request.adoc[]

    +
    +
    +
    GET /api/v1/auth/group/univ/department?page=0&name=school+name+here&keyword=keyword+here HTTP/1.1
    +

    요청 파라미터

    -
    -

    Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/request-parameters.adoc[]

    -
    + ++++ + + + + + + + + + + + + + + + + + + + + +
    ParameterDescription

    page

    페이지네이션 페이지 번호

    name

    학교 이름

    keyword

    검색할 쿼리

    응답

    -
    -

    Unresolved directive in search-department.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findDepartmentsByKeyword/http-response.adoc[]

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "학과 검색에 성공했습니다.",
    +  "data" : {
    +    "totalCount" : 0,
    +    "groupList" : [ {
    +      "groupId" : 1,
    +      "departmentName" : "테스트 하위 그룹 1"
    +    } ]
    +  }
    +}
    +
    @@ -467,7 +510,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-high-class.html b/src/main/resources/static/docs/search-high-class.html new file mode 100644 index 00000000..616448c6 --- /dev/null +++ b/src/main/resources/static/docs/search-high-class.html @@ -0,0 +1,509 @@ + + + + + + + +고등학교 이름으로 학반 검색하기 + + + + + +
    +
    +

    고등학교 이름으로 학반 검색하기

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/auth/group/high/class?name=school+name+here&keyword=keyword+here HTTP/1.1
    +
    +
    +
    +
    +

    요청 파라미터

    + ++++ + + + + + + + + + + + + + + + + +
    ParameterDescription

    name

    학교 이름

    keyword

    검색할 쿼리

    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "학반 검색에 성공했습니다.",
    +  "data" : {
    +    "groupId" : 1
    +  }
    +}
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/search-high-name.html b/src/main/resources/static/docs/search-high-name.html new file mode 100644 index 00000000..f8d70c7e --- /dev/null +++ b/src/main/resources/static/docs/search-high-name.html @@ -0,0 +1,510 @@ + + + + + + + +고등학교 이름 검색하기 + + + + + +
    +
    +

    고등학교 이름 검색하기

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/auth/group/high/name?page=0&keyword=keyword+here HTTP/1.1
    +
    +
    +
    +
    +

    요청 파라미터

    + ++++ + + + + + + + + + + + + + + + + +
    ParameterDescription

    page

    페이지네이션 페이지 번호

    keyword

    검색할 쿼리

    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "학교 이름 검색에 성공했습니다.",
    +  "data" : {
    +    "totalCount" : 0,
    +    "groupNameList" : [ "groupName" ]
    +  }
    +}
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/search-school.html b/src/main/resources/static/docs/search-school.html index 1171f810..64da6c34 100644 --- a/src/main/resources/static/docs/search-school.html +++ b/src/main/resources/static/docs/search-school.html @@ -445,20 +445,56 @@

    대학교 검색하기

    요청

    -
    -

    Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/http-request.adoc[]

    +
    +
    +
    GET /api/v1/auth/group/univ/name?page=0&keyword=keyword+here HTTP/1.1
    +

    요청 파라미터

    -
    -

    Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/request-parameters.adoc[]

    -
    + ++++ + + + + + + + + + + + + + + + + +
    ParameterDescription

    page

    페이지네이션 페이지 번호

    keyword

    검색할 쿼리

    응답

    -
    -

    Unresolved directive in search-school.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/auth/findSchoolsByKeyword/http-response.adoc[]

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "학교 이름 검색에 성공했습니다.",
    +  "data" : {
    +    "totalCount" : 0,
    +    "groupNameList" : [ "groupName" ]
    +  }
    +}
    +
    @@ -467,7 +503,7 @@

    응답

    diff --git a/src/main/resources/static/docs/user-data-get.html b/src/main/resources/static/docs/user-data-get.html new file mode 100644 index 00000000..635ee63e --- /dev/null +++ b/src/main/resources/static/docs/user-data-get.html @@ -0,0 +1,560 @@ + + + + + + + +프로필 수정 가능 여부 조회 (명세) + + + + + +
    +
    +

    프로필 수정 가능 여부 조회 (명세)

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/user/data/account-update-at HTTP/1.1
    +Authorization: Bearer your-access-token
    +Content-Type: application-json
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "tag": "account-update-at"

      +
      +
        +
      • +

        account-update-at 자리가 ENUM으로 대체될 예정입니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "프로필 수정 가능 여부 조회에 성공하였습니다.",
    +  "data": {
    +    "tag": "ACCOUNT_UPDATE_AT",
    +    "value": "false|2024-01-09|2023-10-20"
    +  }
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "tag": "ACCOUNT_UPDATE_AT"

      +
    • +
    • +

      "value": String

      +
      +
        +
      • +

        ACCOUNT_UPDATE_AT의 경우 {boolean}|{updated_at}|{created_at} 를 반환합니다.

        +
      • +
      • +

        boolean 및 updated_at 값을 parse하여 사용해주세요.

        +
      • +
      • +

        날짜의 경우 YYYY-MM-DD (ISO-8601)

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      AccessToken에 해당하는 User의 프로필 수정 가능 여부 조회를 조회하는 API입니다.

      +
    • +
    • +

      User의 다양한 정보를 조회하는 API로 범용적인 확장할 예정입니다.

      +
      +
        +
      • +

        차후에 tag에 들어갈 수 있는 ENUM의 종류를 다양화 할 예정입니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/user-data-post.html b/src/main/resources/static/docs/user-data-post.html new file mode 100644 index 00000000..478d0a59 --- /dev/null +++ b/src/main/resources/static/docs/user-data-post.html @@ -0,0 +1,540 @@ + + + + + + + +탈퇴 사유 저장 (명세) + + + + + +
    +
    +

    탈퇴 사유 저장 (명세)

    +
    +
    +

    요청

    +
    +
    +
    POST /api/v1/user/data/withdraw-reason HTTP/1.1
    +Authorization: Bearer your-access-token
    +Content-Type: application-json
    +
    +{
    +    "value": "오류가 많아서"
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "tag": "withdraw-reason" | "account-update-at" | "recommended"

      +
    • +
    • +

      "value": String

      +
      +
        +
      • +

        withdraw-reason 자리가 ENUM으로 대체될 예정입니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "탈퇴 사유 정보 저장에 성공하였습니다."
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      AccessToken에 해당하는 User의 탈퇴 사유를 저장하는 API입니다.

      +
    • +
    • +

      User의 다양한 정보를 저장하는 API로 범용적인 확장할 예정입니다.

      +
      +
        +
      • +

        차후에 tag에 들어갈 수 있는 ENUM의 종류를 다양화 할 예정입니다.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file From 1712e214ccf5b9e2eb0a00276e623c63c52d8eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Fri, 12 Jan 2024 02:20:02 +0900 Subject: [PATCH 011/112] =?UTF-8?q?YEL-181=20[fix]=20=ED=95=84=EB=93=9C=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 --- src/docs/asciidoc/check-user-v2.adoc | 2 ++ .../user/dto/response/UserDetailV2Response.java | 8 ++++++-- .../server/domain/user/service/UserService.java | 5 ++++- src/main/resources/static/docs/check-user-v2.html | 12 ++++++++++-- src/main/resources/static/docs/index.html | 2 +- .../domain/user/medium/UserControllerTest.java | 2 +- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/docs/asciidoc/check-user-v2.adoc b/src/docs/asciidoc/check-user-v2.adoc index e536e83a..1924a580 100644 --- a/src/docs/asciidoc/check-user-v2.adoc +++ b/src/docs/asciidoc/check-user-v2.adoc @@ -28,6 +28,8 @@ include::{snippets}/api/v2/user/http-response.adoc[] - "ticketCount": Integer - "point": Integer - "subscribe": "normal" | "active" | "canceled" +- "yelloCount": Integer +- "friendCount": Integer === Note diff --git a/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java b/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java index fcfe13a0..d5185150 100644 --- a/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java +++ b/src/main/java/com/yello/server/domain/user/dto/response/UserDetailV2Response.java @@ -27,10 +27,12 @@ public record UserDetailV2Response( Long recommendCount, Integer ticketCount, Integer point, - String subscribe + String subscribe, + Integer yelloCount, + Integer friendCount ) { - public static UserDetailV2Response of(User user, UserGroup userGroup) { + public static UserDetailV2Response of(User user, UserGroup userGroup, Integer yelloCount, Integer friendCount) { return UserDetailV2Response.builder() .userId(user.getId()) .name(user.getName()) @@ -50,6 +52,8 @@ public static UserDetailV2Response of(User user, UserGroup userGroup) { .ticketCount(user.getTicketCount()) .point(user.getPoint()) .subscribe(user.getSubscribe().getIntial()) + .yelloCount(yelloCount) + .friendCount(friendCount) .build(); } } diff --git a/src/main/java/com/yello/server/domain/user/service/UserService.java b/src/main/java/com/yello/server/domain/user/service/UserService.java index 4e69aef1..6abf05fe 100644 --- a/src/main/java/com/yello/server/domain/user/service/UserService.java +++ b/src/main/java/com/yello/server/domain/user/service/UserService.java @@ -49,7 +49,10 @@ public UserDetailResponse findMyProfile(Long userId) { public UserDetailV2Response getUserDetailV2(Long userId) { final User user = userRepository.getById(userId); - return UserDetailV2Response.of(user, user.getGroup()); + final Integer yelloCount = voteRepository.countAllByReceiverUserId(user.getId()); + final Integer friendCount = friendRepository.findAllByUserId(user.getId()).size(); + + return UserDetailV2Response.of(user, user.getGroup(), yelloCount, friendCount); } public UserResponse findUserById(Long userId) { diff --git a/src/main/resources/static/docs/check-user-v2.html b/src/main/resources/static/docs/check-user-v2.html index 5aaa4387..6aef59d3 100644 --- a/src/main/resources/static/docs/check-user-v2.html +++ b/src/main/resources/static/docs/check-user-v2.html @@ -483,7 +483,9 @@

    응답

    "recommendCount" : 0, "ticketCount" : 0, "point" : 200, - "subscribe" : "normal" + "subscribe" : "normal", + "yelloCount" : 100, + "friendCount" : 200 } }
    @@ -547,6 +549,12 @@

    응답

  • "subscribe": "normal" | "active" | "canceled"

  • +
  • +

    "yelloCount": Integer

    +
  • +
  • +

    "friendCount": Integer

    +
  • @@ -585,7 +593,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 08f0b9e2..fd59a82c 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -643,7 +643,7 @@

    Notice API

    diff --git a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java index d936777e..e9b8236d 100644 --- a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java +++ b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java @@ -115,7 +115,7 @@ void init() { @Test void 내_정보_조회_V2에_성공합니다() throws Exception { // given - UserDetailV2Response response = UserDetailV2Response.of(user, user.getGroup()); + UserDetailV2Response response = UserDetailV2Response.of(user, user.getGroup(), 100, 200); given(userService.getUserDetailV2(anyLong())) .willReturn(response); From acd4719fa92a1e774f9cc8142b9574f9c7127548 Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Thu, 18 Jan 2024 23:31:21 +0900 Subject: [PATCH 012/112] YEL-183 [feat] subscribe docs fix --- src/main/resources/static/docs/purchase-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/docs/purchase-info.html b/src/main/resources/static/docs/purchase-info.html index bb176a53..a92c05e0 100644 --- a/src/main/resources/static/docs/purchase-info.html +++ b/src/main/resources/static/docs/purchase-info.html @@ -447,7 +447,7 @@

    유저 구독 정보 (명세)

    요청

    -
    GET /api/v1/purchase/subscribe HTTP/1.1
    +
    GET /api/v1/user/subscribe HTTP/1.1
     Authorization: Bearer your-access-token
    From 18ef402efebe9a3735705618bade88d51366b9fe Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 20 Jan 2024 21:10:14 +0900 Subject: [PATCH 013/112] =?UTF-8?q?YEL-183=20[feat]=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/purchase-info.adoc | 2 +- src/main/resources/static/docs/purchase-info.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/asciidoc/purchase-info.adoc b/src/docs/asciidoc/purchase-info.adoc index 078663a3..1bd09728 100644 --- a/src/docs/asciidoc/purchase-info.adoc +++ b/src/docs/asciidoc/purchase-info.adoc @@ -4,7 +4,7 @@ [http] ---- -GET /api/v1/purchase/subscribe HTTP/1.1 +GET /api/v1/user/subscribe HTTP/1.1 Authorization: Bearer your-access-token ---- diff --git a/src/main/resources/static/docs/purchase-info.html b/src/main/resources/static/docs/purchase-info.html index a92c05e0..bb176a53 100644 --- a/src/main/resources/static/docs/purchase-info.html +++ b/src/main/resources/static/docs/purchase-info.html @@ -447,7 +447,7 @@

    유저 구독 정보 (명세)

    요청

    -
    GET /api/v1/user/subscribe HTTP/1.1
    +
    GET /api/v1/purchase/subscribe HTTP/1.1
     Authorization: Bearer your-access-token
    From 27455ca6f05a39ccab0894d610df4365f6c06f5e Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 20 Jan 2024 23:43:42 +0900 Subject: [PATCH 014/112] =?UTF-8?q?YEL-183=20[feat]=20=EA=B5=AC=EB=8F=85?= =?UTF-8?q?=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/docs/add-friend.html | 2 +- src/main/resources/static/docs/apple.html | 2 +- src/main/resources/static/docs/check-keyword.html | 2 +- src/main/resources/static/docs/check-user-by-id.html | 2 +- src/main/resources/static/docs/check-user-v2.html | 2 +- src/main/resources/static/docs/check-user.html | 2 +- src/main/resources/static/docs/check-vote-available.html | 4 ++-- src/main/resources/static/docs/create-vote.html | 2 +- src/main/resources/static/docs/delete-friend.html | 2 +- src/main/resources/static/docs/delete-user.html | 2 +- src/main/resources/static/docs/device-token.html | 2 +- src/main/resources/static/docs/edit-user.html | 2 +- src/main/resources/static/docs/find-friend-votes.html | 2 +- src/main/resources/static/docs/find-friends.html | 2 +- src/main/resources/static/docs/find-group-friends.html | 2 +- src/main/resources/static/docs/find-kakao-friends.html | 2 +- src/main/resources/static/docs/find-notice.html | 2 +- src/main/resources/static/docs/find-onboarding-friends.html | 2 +- src/main/resources/static/docs/find-question.html | 2 +- src/main/resources/static/docs/find-vote.html | 2 +- src/main/resources/static/docs/find-votes.html | 2 +- src/main/resources/static/docs/get-unread-vote.html | 2 +- src/main/resources/static/docs/google.html | 4 ++-- src/main/resources/static/docs/index.html | 2 +- src/main/resources/static/docs/login.html | 2 +- src/main/resources/static/docs/overview.html | 2 +- src/main/resources/static/docs/pay.html | 2 +- src/main/resources/static/docs/purchase-check.html | 2 +- src/main/resources/static/docs/purchase-info.html | 4 ++-- src/main/resources/static/docs/reissue-token.html | 2 +- src/main/resources/static/docs/reveal-full-name.html | 2 +- src/main/resources/static/docs/reveal-name.html | 2 +- src/main/resources/static/docs/search-department.html | 2 +- src/main/resources/static/docs/search-friend.html | 2 +- src/main/resources/static/docs/search-high-class.html | 2 +- src/main/resources/static/docs/search-high-name.html | 2 +- src/main/resources/static/docs/search-school.html | 2 +- src/main/resources/static/docs/shuffle-friends.html | 2 +- src/main/resources/static/docs/signup.html | 2 +- src/main/resources/static/docs/sub-check.html | 2 +- src/main/resources/static/docs/user-data-get.html | 2 +- src/main/resources/static/docs/user-data-post.html | 2 +- src/main/resources/static/docs/validate-yelloid.html | 2 +- 43 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/main/resources/static/docs/add-friend.html b/src/main/resources/static/docs/add-friend.html index 44eb2820..8e4b1758 100644 --- a/src/main/resources/static/docs/add-friend.html +++ b/src/main/resources/static/docs/add-friend.html @@ -497,7 +497,7 @@

    응답

    diff --git a/src/main/resources/static/docs/apple.html b/src/main/resources/static/docs/apple.html index 73689b1d..176309dd 100644 --- a/src/main/resources/static/docs/apple.html +++ b/src/main/resources/static/docs/apple.html @@ -551,7 +551,7 @@

    응답

    diff --git a/src/main/resources/static/docs/check-keyword.html b/src/main/resources/static/docs/check-keyword.html index e5624ae9..c64dcaea 100644 --- a/src/main/resources/static/docs/check-keyword.html +++ b/src/main/resources/static/docs/check-keyword.html @@ -500,7 +500,7 @@

    응답

    diff --git a/src/main/resources/static/docs/check-user-by-id.html b/src/main/resources/static/docs/check-user-by-id.html index b8a29253..4274d9e9 100644 --- a/src/main/resources/static/docs/check-user-by-id.html +++ b/src/main/resources/static/docs/check-user-by-id.html @@ -484,7 +484,7 @@

    응답

    diff --git a/src/main/resources/static/docs/check-user-v2.html b/src/main/resources/static/docs/check-user-v2.html index 6aef59d3..f5711740 100644 --- a/src/main/resources/static/docs/check-user-v2.html +++ b/src/main/resources/static/docs/check-user-v2.html @@ -593,7 +593,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/check-user.html b/src/main/resources/static/docs/check-user.html index 5b32b738..9a2505e5 100644 --- a/src/main/resources/static/docs/check-user.html +++ b/src/main/resources/static/docs/check-user.html @@ -485,7 +485,7 @@

    응답

    diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index 229be5a1..a01f3cca 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

    응답

    "data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-10 00:06:57", + "createdAt" : "2024-01-19 00:36:55", "friendStatus" : 1 } }
    @@ -481,7 +481,7 @@

    응답

    diff --git a/src/main/resources/static/docs/create-vote.html b/src/main/resources/static/docs/create-vote.html index 3d5ded3e..5bcec975 100644 --- a/src/main/resources/static/docs/create-vote.html +++ b/src/main/resources/static/docs/create-vote.html @@ -490,7 +490,7 @@

    응답

    diff --git a/src/main/resources/static/docs/delete-friend.html b/src/main/resources/static/docs/delete-friend.html index 489f70c0..d5e524c4 100644 --- a/src/main/resources/static/docs/delete-friend.html +++ b/src/main/resources/static/docs/delete-friend.html @@ -497,7 +497,7 @@

    응답

    diff --git a/src/main/resources/static/docs/delete-user.html b/src/main/resources/static/docs/delete-user.html index da9d4bb6..74092e37 100644 --- a/src/main/resources/static/docs/delete-user.html +++ b/src/main/resources/static/docs/delete-user.html @@ -475,7 +475,7 @@

    응답

    diff --git a/src/main/resources/static/docs/device-token.html b/src/main/resources/static/docs/device-token.html index 5ad1b15b..95b328a2 100644 --- a/src/main/resources/static/docs/device-token.html +++ b/src/main/resources/static/docs/device-token.html @@ -482,7 +482,7 @@

    응답

    diff --git a/src/main/resources/static/docs/edit-user.html b/src/main/resources/static/docs/edit-user.html index f1f7c6b8..e1edc652 100644 --- a/src/main/resources/static/docs/edit-user.html +++ b/src/main/resources/static/docs/edit-user.html @@ -561,7 +561,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/find-friend-votes.html b/src/main/resources/static/docs/find-friend-votes.html index f1385de5..3b10009f 100644 --- a/src/main/resources/static/docs/find-friend-votes.html +++ b/src/main/resources/static/docs/find-friend-votes.html @@ -626,7 +626,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/find-friends.html b/src/main/resources/static/docs/find-friends.html index ada2ab4c..6f7f351c 100644 --- a/src/main/resources/static/docs/find-friends.html +++ b/src/main/resources/static/docs/find-friends.html @@ -508,7 +508,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-group-friends.html b/src/main/resources/static/docs/find-group-friends.html index e11280b7..87e7b189 100644 --- a/src/main/resources/static/docs/find-group-friends.html +++ b/src/main/resources/static/docs/find-group-friends.html @@ -505,7 +505,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-kakao-friends.html b/src/main/resources/static/docs/find-kakao-friends.html index 852f73ed..69ff9525 100644 --- a/src/main/resources/static/docs/find-kakao-friends.html +++ b/src/main/resources/static/docs/find-kakao-friends.html @@ -502,7 +502,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-notice.html b/src/main/resources/static/docs/find-notice.html index b43490a3..fb782e8b 100644 --- a/src/main/resources/static/docs/find-notice.html +++ b/src/main/resources/static/docs/find-notice.html @@ -565,7 +565,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/find-onboarding-friends.html b/src/main/resources/static/docs/find-onboarding-friends.html index 29e8719a..3f5597bc 100644 --- a/src/main/resources/static/docs/find-onboarding-friends.html +++ b/src/main/resources/static/docs/find-onboarding-friends.html @@ -511,7 +511,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-question.html b/src/main/resources/static/docs/find-question.html index 52ed5b3d..0bc0f4f7 100644 --- a/src/main/resources/static/docs/find-question.html +++ b/src/main/resources/static/docs/find-question.html @@ -492,7 +492,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-vote.html b/src/main/resources/static/docs/find-vote.html index 39f79bab..fa901833 100644 --- a/src/main/resources/static/docs/find-vote.html +++ b/src/main/resources/static/docs/find-vote.html @@ -514,7 +514,7 @@

    응답

    diff --git a/src/main/resources/static/docs/find-votes.html b/src/main/resources/static/docs/find-votes.html index 6a3d6df4..fe030161 100644 --- a/src/main/resources/static/docs/find-votes.html +++ b/src/main/resources/static/docs/find-votes.html @@ -520,7 +520,7 @@

    응답

    diff --git a/src/main/resources/static/docs/get-unread-vote.html b/src/main/resources/static/docs/get-unread-vote.html index 8c9653d6..865b571f 100644 --- a/src/main/resources/static/docs/get-unread-vote.html +++ b/src/main/resources/static/docs/get-unread-vote.html @@ -478,7 +478,7 @@

    응답

    diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index cd5229e6..551c9b46 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

    응답

    "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-10T00:06:48.613074" + "expiredAt" : "2024-01-19T00:36:45.411094" } }
    @@ -541,7 +541,7 @@

    응답

    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index fd59a82c..fcdc3caa 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -643,7 +643,7 @@

    Notice API

    diff --git a/src/main/resources/static/docs/login.html b/src/main/resources/static/docs/login.html index d7a4b9b4..172e2a45 100644 --- a/src/main/resources/static/docs/login.html +++ b/src/main/resources/static/docs/login.html @@ -487,7 +487,7 @@

    응답

    diff --git a/src/main/resources/static/docs/overview.html b/src/main/resources/static/docs/overview.html index 3800630c..63855bba 100644 --- a/src/main/resources/static/docs/overview.html +++ b/src/main/resources/static/docs/overview.html @@ -515,7 +515,7 @@

    HTTP status codes

    diff --git a/src/main/resources/static/docs/pay.html b/src/main/resources/static/docs/pay.html index 8fee4209..9842f827 100644 --- a/src/main/resources/static/docs/pay.html +++ b/src/main/resources/static/docs/pay.html @@ -481,7 +481,7 @@

    응답

    diff --git a/src/main/resources/static/docs/purchase-check.html b/src/main/resources/static/docs/purchase-check.html index 0d6d5e79..5f520caf 100644 --- a/src/main/resources/static/docs/purchase-check.html +++ b/src/main/resources/static/docs/purchase-check.html @@ -480,7 +480,7 @@

    응답

    diff --git a/src/main/resources/static/docs/purchase-info.html b/src/main/resources/static/docs/purchase-info.html index bb176a53..299da62c 100644 --- a/src/main/resources/static/docs/purchase-info.html +++ b/src/main/resources/static/docs/purchase-info.html @@ -447,7 +447,7 @@

    유저 구독 정보 (명세)

    요청

    -
    GET /api/v1/purchase/subscribe HTTP/1.1
    +
    GET /api/v1/user/subscribe HTTP/1.1
     Authorization: Bearer your-access-token
    @@ -517,7 +517,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/reissue-token.html b/src/main/resources/static/docs/reissue-token.html index e61b4419..66cbe454 100644 --- a/src/main/resources/static/docs/reissue-token.html +++ b/src/main/resources/static/docs/reissue-token.html @@ -511,7 +511,7 @@

    응답

    diff --git a/src/main/resources/static/docs/reveal-full-name.html b/src/main/resources/static/docs/reveal-full-name.html index 3656e677..272ac887 100644 --- a/src/main/resources/static/docs/reveal-full-name.html +++ b/src/main/resources/static/docs/reveal-full-name.html @@ -500,7 +500,7 @@

    응답

    diff --git a/src/main/resources/static/docs/reveal-name.html b/src/main/resources/static/docs/reveal-name.html index d99c569b..e6cd0cbc 100644 --- a/src/main/resources/static/docs/reveal-name.html +++ b/src/main/resources/static/docs/reveal-name.html @@ -501,7 +501,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-department.html b/src/main/resources/static/docs/search-department.html index deced473..141d936b 100644 --- a/src/main/resources/static/docs/search-department.html +++ b/src/main/resources/static/docs/search-department.html @@ -510,7 +510,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-friend.html b/src/main/resources/static/docs/search-friend.html index 138a82d8..f22dbaaf 100644 --- a/src/main/resources/static/docs/search-friend.html +++ b/src/main/resources/static/docs/search-friend.html @@ -511,7 +511,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-high-class.html b/src/main/resources/static/docs/search-high-class.html index 616448c6..3eb36703 100644 --- a/src/main/resources/static/docs/search-high-class.html +++ b/src/main/resources/static/docs/search-high-class.html @@ -502,7 +502,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-high-name.html b/src/main/resources/static/docs/search-high-name.html index f8d70c7e..1608e146 100644 --- a/src/main/resources/static/docs/search-high-name.html +++ b/src/main/resources/static/docs/search-high-name.html @@ -503,7 +503,7 @@

    응답

    diff --git a/src/main/resources/static/docs/search-school.html b/src/main/resources/static/docs/search-school.html index 64da6c34..f5379b93 100644 --- a/src/main/resources/static/docs/search-school.html +++ b/src/main/resources/static/docs/search-school.html @@ -503,7 +503,7 @@

    응답

    diff --git a/src/main/resources/static/docs/shuffle-friends.html b/src/main/resources/static/docs/shuffle-friends.html index 83a981f8..49357fbb 100644 --- a/src/main/resources/static/docs/shuffle-friends.html +++ b/src/main/resources/static/docs/shuffle-friends.html @@ -480,7 +480,7 @@

    응답

    diff --git a/src/main/resources/static/docs/signup.html b/src/main/resources/static/docs/signup.html index 61a92e9e..74bbba36 100644 --- a/src/main/resources/static/docs/signup.html +++ b/src/main/resources/static/docs/signup.html @@ -496,7 +496,7 @@

    응답

    diff --git a/src/main/resources/static/docs/sub-check.html b/src/main/resources/static/docs/sub-check.html index de5f6eae..12fea6cd 100644 --- a/src/main/resources/static/docs/sub-check.html +++ b/src/main/resources/static/docs/sub-check.html @@ -479,7 +479,7 @@

    응답

    diff --git a/src/main/resources/static/docs/user-data-get.html b/src/main/resources/static/docs/user-data-get.html index 635ee63e..2900a72f 100644 --- a/src/main/resources/static/docs/user-data-get.html +++ b/src/main/resources/static/docs/user-data-get.html @@ -553,7 +553,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/user-data-post.html b/src/main/resources/static/docs/user-data-post.html index 478d0a59..f249c135 100644 --- a/src/main/resources/static/docs/user-data-post.html +++ b/src/main/resources/static/docs/user-data-post.html @@ -533,7 +533,7 @@

    CHANGELOG

    diff --git a/src/main/resources/static/docs/validate-yelloid.html b/src/main/resources/static/docs/validate-yelloid.html index d30fab5e..da1956cf 100644 --- a/src/main/resources/static/docs/validate-yelloid.html +++ b/src/main/resources/static/docs/validate-yelloid.html @@ -496,7 +496,7 @@

    응답

    From 59bc7206f24b08345690f02963f986e54da06e0e Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Tue, 23 Jan 2024 01:32:30 +0900 Subject: [PATCH 015/112] YEL-183 [feat] user subsccribe api --- src/docs/asciidoc/index.adoc | 2 +- src/docs/asciidoc/purchase-info.adoc | 3 +- .../repository/PurchaseJpaRepository.java | 9 ++ .../repository/PurchaseRepository.java | 2 + .../repository/PurchaseRepositoryImpl.java | 10 ++ .../user/controller/UserController.java | 8 ++ .../response/UserSubscribeDetailResponse.java | 24 ++++ .../domain/user/service/UserService.java | 9 ++ .../yello/server/global/common/ErrorCode.java | 1 + .../server/global/common/SuccessCode.java | 1 + .../global/common/factory/TimeFactory.java | 11 ++ .../static/docs/check-vote-available.html | 2 +- src/main/resources/static/docs/google.html | 2 +- .../authorization/small/AuthManagerTest.java | 6 +- .../authorization/small/AuthServiceTest.java | 6 +- .../friend/small/FriendServiceTest.java | 6 +- .../purchase/FakePurchaseRepository.java | 18 +++ .../medium/PurchaseControllerTest.java | 122 +++++++++++------- .../user/medium/UserControllerTest.java | 26 ++++ .../domain/user/small/UserServiceTest.java | 6 +- .../domain/vote/small/VoteManagerTest.java | 6 +- .../domain/vote/small/VoteServiceTest.java | 7 +- .../firebase/small/FcmManagerTest.java | 6 +- .../yello/server/util/TestDataEntityUtil.java | 17 +++ .../server/util/TestDataRepositoryUtil.java | 25 +++- .../com/yello/server/util/TestDataUtil.java | 3 + 26 files changed, 276 insertions(+), 62 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 318c7f1f..c27e7b6d 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -83,7 +83,7 @@ === Purchase API -* 🆕 link:purchase-info.html[유저 구독 정보 (명세), 2024-01-09] +* 🆕 link:purchase-info.html[유저 구독 정보, 2024-01-23] * link:apple.html[Apple 결제 관련 API] diff --git a/src/docs/asciidoc/purchase-info.adoc b/src/docs/asciidoc/purchase-info.adoc index 1bd09728..8c5b78fb 100644 --- a/src/docs/asciidoc/purchase-info.adoc +++ b/src/docs/asciidoc/purchase-info.adoc @@ -1,4 +1,4 @@ -== 유저 구독 정보 (명세) +== 유저 구독 정보 === 요청 @@ -40,4 +40,5 @@ Content-Type: application/json === CHANGELOG +- 2024.01.23 API 릴리즈 - 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseJpaRepository.java b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseJpaRepository.java index 831d590e..eea7f2ff 100644 --- a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseJpaRepository.java +++ b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseJpaRepository.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface PurchaseJpaRepository extends JpaRepository { @@ -17,4 +19,11 @@ public interface PurchaseJpaRepository extends JpaRepository { Optional findTopByUserAndProductTypeOrderByCreatedAtDesc(User user, ProductType productType); + + @Query("select p from Purchase p " + + "where p.state = 'active' " + + "and p.user = :user " + + "and p.productType = 'yello_plus' " + + "order by p.updatedAt DESC") + Optional findTopByStateAndUser(@Param("user") User user); } diff --git a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java index 57d6fee5..abd2d7f1 100644 --- a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java +++ b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java @@ -22,4 +22,6 @@ Optional findTopByUserAndProductTypeOrderByCreatedAtDesc(User user, ProductType productType); void delete(Purchase purchase); + + Purchase findEndByStateAndUserId(User user); } diff --git a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java index daaae69f..de1cbb9d 100644 --- a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java @@ -1,8 +1,12 @@ package com.yello.server.domain.purchase.repository; +import static com.yello.server.global.common.ErrorCode.NOT_FOUND_USER_SUBSCRIBE_EXCEPTION; + import com.yello.server.domain.purchase.entity.ProductType; import com.yello.server.domain.purchase.entity.Purchase; +import com.yello.server.domain.purchase.exception.PurchaseNotFoundException; import com.yello.server.domain.user.entity.User; +import com.yello.server.global.common.ErrorCode; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -50,4 +54,10 @@ public Optional findTopByUserAndProductTypeOrderByCreatedAtDesc(User u public void delete(Purchase purchase) { purchaseJpaRepository.delete(purchase); } + + @Override + public Purchase findEndByStateAndUserId(User user) { + return purchaseJpaRepository.findTopByStateAndUser(user) + .orElseThrow(() -> new PurchaseNotFoundException(NOT_FOUND_USER_SUBSCRIBE_EXCEPTION)); + } } diff --git a/src/main/java/com/yello/server/domain/user/controller/UserController.java b/src/main/java/com/yello/server/domain/user/controller/UserController.java index 9ee35812..7b9ed523 100644 --- a/src/main/java/com/yello/server/domain/user/controller/UserController.java +++ b/src/main/java/com/yello/server/domain/user/controller/UserController.java @@ -1,6 +1,7 @@ package com.yello.server.domain.user.controller; import static com.yello.server.global.common.SuccessCode.DELETE_USER_SUCCESS; +import static com.yello.server.global.common.SuccessCode.READ_USER_SUBSCRIBE_SUCCESS; import static com.yello.server.global.common.SuccessCode.READ_USER_SUCCESS; import static com.yello.server.global.common.SuccessCode.UPDATE_DEVICE_TOKEN_USER_SUCCESS; @@ -8,6 +9,7 @@ import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; +import com.yello.server.domain.user.dto.response.UserSubscribeDetailResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.service.UserService; import com.yello.server.global.common.annotation.AccessTokenUser; @@ -62,4 +64,10 @@ public BaseResponse deleteUser(@AccessTokenUser User user) { userService.delete(user); return BaseResponse.success(DELETE_USER_SUCCESS); } + + @GetMapping("/v1/user/subscribe") + public BaseResponse getUserSubscribe(@AccessTokenUser User user) { + val data = userService.getUserSubscribe(user.getId()); + return BaseResponse.success(READ_USER_SUBSCRIBE_SUCCESS, data); + } } diff --git a/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java b/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java new file mode 100644 index 00000000..2d1ab66e --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java @@ -0,0 +1,24 @@ +package com.yello.server.domain.user.dto.response; + +import static com.yello.server.global.common.factory.TimeFactory.toYearAndMonthFormattedString; + +import com.yello.server.domain.purchase.entity.Purchase; +import com.yello.server.domain.user.entity.Subscribe; +import com.yello.server.domain.user.entity.User; +import lombok.Builder; + +@Builder +public record UserSubscribeDetailResponse( + Long id, + Subscribe subscribe, + String expiredData +) { + public static UserSubscribeDetailResponse of(User user, Purchase purchase) { + return UserSubscribeDetailResponse.builder() + .id(user.getId()) + .subscribe(user.getSubscribe()) + .expiredData(toYearAndMonthFormattedString(purchase.getUpdatedAt())) + .build(); + } + +} diff --git a/src/main/java/com/yello/server/domain/user/service/UserService.java b/src/main/java/com/yello/server/domain/user/service/UserService.java index 6abf05fe..ca258e1f 100644 --- a/src/main/java/com/yello/server/domain/user/service/UserService.java +++ b/src/main/java/com/yello/server/domain/user/service/UserService.java @@ -8,11 +8,13 @@ import com.yello.server.domain.friend.entity.Friend; import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.repository.UserGroupRepository; +import com.yello.server.domain.purchase.entity.Purchase; import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; +import com.yello.server.domain.user.dto.response.UserSubscribeDetailResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.exception.UserConflictException; import com.yello.server.domain.user.repository.UserRepository; @@ -89,4 +91,11 @@ public void delete(User user) { cooldownRepository.findByUserId(target.getId()) .ifPresent(Cooldown::delete); } + + public UserSubscribeDetailResponse getUserSubscribe(Long userId) { + final User user = userRepository.getById(userId); + final Purchase purchase = purchaseRepository.findEndByStateAndUserId(user); + + return UserSubscribeDetailResponse.of(user, purchase); + } } diff --git a/src/main/java/com/yello/server/global/common/ErrorCode.java b/src/main/java/com/yello/server/global/common/ErrorCode.java index fc426e59..985be011 100644 --- a/src/main/java/com/yello/server/global/common/ErrorCode.java +++ b/src/main/java/com/yello/server/global/common/ErrorCode.java @@ -89,6 +89,7 @@ public enum ErrorCode { USER_ADMIN_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 Admin이 존재하지 않습니다."), NOT_EQUAL_TRANSACTION_EXCEPTION(NOT_FOUND, "동일하지 않은 거래입니다."), NOT_FOUND_NOTIFICATION_TYPE_EXCEPTION(NOT_FOUND, "존재하지 않는 알림 타입 입니다"), + NOT_FOUND_USER_SUBSCRIBE_EXCEPTION(NOT_FOUND, "유저의 구독정보가 존재하지 않습니다."), /** * 409 CONFLICT diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 9a4e078f..f7eb5d07 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -47,6 +47,7 @@ public enum SuccessCode { READ_QUESTION_DETAIL_ADMIN_SUCCESS(OK, "어드민 페이지 질문 상세 조회에 성공하였습니다."), DELETE_QUESTION_ADMIN_SUCCESS(OK, "어드민 권환으로 질문지 삭제에 성공하였습니다."), CLASS_NAME_SEARCH_BY_SCHOOL_NAME_SCHOOL_SUCCESS(OK, "학반 검색에 성공했습니다."), + READ_USER_SUBSCRIBE_SUCCESS(OK, "구독 정보 조회에 성공하였습니다."), /** * 201 CREATED diff --git a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java index 662d3e04..8086c42f 100644 --- a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java +++ b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java @@ -51,4 +51,15 @@ public static LocalDateTime plusTime(LocalDateTime localDateTime, int time) { public static LocalDateTime minusTime(LocalDateTime localDateTime, int time) { return localDateTime.minusMinutes(time); } + + public static String toYearAndMonthFormattedString(LocalDateTime localDateTime) { + if (localDateTime == null) { + return ""; + } + LocalDateTime dateTimePlusSevenDays = localDateTime.plusDays(7); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return dateTimePlusSevenDays.format(dateTimeFormatter); + } + + } diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index a01f3cca..0d490628 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

    응답

    "data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-19 00:36:55", + "createdAt" : "2024-01-20 23:42:31", "friendStatus" : 1 } }
    diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index 551c9b46..bc9acba1 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

    응답

    "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-19T00:36:45.411094" + "expiredAt" : "2024-01-20T23:42:21.858430" } }
    diff --git a/src/test/java/com/yello/server/domain/authorization/small/AuthManagerTest.java b/src/test/java/com/yello/server/domain/authorization/small/AuthManagerTest.java index 4e7fb5e0..38190a5e 100644 --- a/src/test/java/com/yello/server/domain/authorization/small/AuthManagerTest.java +++ b/src/test/java/com/yello/server/domain/authorization/small/AuthManagerTest.java @@ -15,6 +15,8 @@ import com.yello.server.domain.friend.FakeFriendRepository; import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.repository.QuestionGroupTypeRepository; @@ -48,12 +50,14 @@ public class AuthManagerTest { private final UserRepository userRepository = new FakeUserRepository(friendRepository); private final VoteRepository voteRepository = new FakeVoteRepository(); private final QuestionGroupTypeRepository questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private AuthManager authManager; diff --git a/src/test/java/com/yello/server/domain/authorization/small/AuthServiceTest.java b/src/test/java/com/yello/server/domain/authorization/small/AuthServiceTest.java index 961a41f7..f7fd5916 100644 --- a/src/test/java/com/yello/server/domain/authorization/small/AuthServiceTest.java +++ b/src/test/java/com/yello/server/domain/authorization/small/AuthServiceTest.java @@ -30,6 +30,8 @@ import com.yello.server.domain.group.entity.UserGroupType; import com.yello.server.domain.group.exception.GroupNotFoundException; import com.yello.server.domain.group.repository.UserGroupRepository; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.repository.QuestionGroupTypeRepository; @@ -98,12 +100,14 @@ public class AuthServiceTest { friendRepository, cooldownRepository, userRepository, tokenRepository, tokenProvider ); private final VoteRepository voteRepository = new FakeVoteRepository(); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private final VoteManager voteManager = new FakeVoteManager( userRepository, questionRepository, voteRepository, friendRepository, diff --git a/src/test/java/com/yello/server/domain/friend/small/FriendServiceTest.java b/src/test/java/com/yello/server/domain/friend/small/FriendServiceTest.java index 0705460d..43580ad4 100644 --- a/src/test/java/com/yello/server/domain/friend/small/FriendServiceTest.java +++ b/src/test/java/com/yello/server/domain/friend/small/FriendServiceTest.java @@ -17,6 +17,8 @@ import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.friend.service.FriendService; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.repository.QuestionGroupTypeRepository; @@ -55,12 +57,14 @@ class FriendServiceTest { private final VoteManager voteManager = new FakeVoteManager(userRepository, questionRepository, voteRepository, friendRepository, userManager); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private FriendService friendService; private User user1; diff --git a/src/test/java/com/yello/server/domain/purchase/FakePurchaseRepository.java b/src/test/java/com/yello/server/domain/purchase/FakePurchaseRepository.java index a9902b85..e2173162 100644 --- a/src/test/java/com/yello/server/domain/purchase/FakePurchaseRepository.java +++ b/src/test/java/com/yello/server/domain/purchase/FakePurchaseRepository.java @@ -1,9 +1,13 @@ package com.yello.server.domain.purchase; +import static com.yello.server.global.common.ErrorCode.NOT_FOUND_USER_SUBSCRIBE_EXCEPTION; + import com.yello.server.domain.purchase.entity.ProductType; import com.yello.server.domain.purchase.entity.Purchase; +import com.yello.server.domain.purchase.exception.PurchaseNotFoundException; import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.user.entity.User; +import com.yello.server.global.common.ErrorCode; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -82,4 +86,18 @@ public Optional findByPurchaseToken(String purchaseToken) { public void delete(Purchase purchase) { data.remove(purchase); } + + @Override + public Purchase findEndByStateAndUserId(User user) { + return data.stream() + .filter(purchase -> { + return purchase.getUser().equals(user) && + purchase.getProductType().equals("yello_plus") && + purchase.getState().equals("active"); + }) + .sorted(Comparator.comparing(Purchase::getUpdatedAt).reversed()) + .findFirst() + .orElseThrow(() -> new PurchaseNotFoundException(NOT_FOUND_USER_SUBSCRIBE_EXCEPTION)); + + } } diff --git a/src/test/java/com/yello/server/domain/purchase/medium/PurchaseControllerTest.java b/src/test/java/com/yello/server/domain/purchase/medium/PurchaseControllerTest.java index effbd862..4d3aca19 100644 --- a/src/test/java/com/yello/server/domain/purchase/medium/PurchaseControllerTest.java +++ b/src/test/java/com/yello/server/domain/purchase/medium/PurchaseControllerTest.java @@ -25,6 +25,7 @@ import com.yello.server.domain.purchase.dto.response.GoogleTicketGetResponse; import com.yello.server.domain.purchase.dto.response.UserSubscribeNeededResponse; import com.yello.server.domain.purchase.service.PurchaseService; +import com.yello.server.domain.user.dto.response.UserSubscribeDetailResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.global.exception.ControllerExceptionAdvice; import com.yello.server.util.TestDataEntityUtil; @@ -63,8 +64,9 @@ class PurchaseControllerTest { final String[] excludeRequestHeaders = {"X-CSRF-TOKEN", "Host"}; - final String[] excludeResponseHeaders = {"X-Content-Type-Options", "X-XSS-Protection", "Cache-Control", "Pragma", - "Expires", "X-Frame-Options", "Content-Length"}; + final String[] excludeResponseHeaders = + {"X-Content-Type-Options", "X-XSS-Protection", "Cache-Control", "Pragma", + "Expires", "X-Frame-Options", "Content-Length"}; @Autowired private MockMvc mockMvc; @@ -83,6 +85,7 @@ class PurchaseControllerTest { void init() { user = testDataUtil.generateUser(1L, 1L, UserGroupType.UNIVERSITY); target = testDataUtil.generateUser(2L, 1L, UserGroupType.UNIVERSITY); + } @Test @@ -95,15 +98,18 @@ void init() { // when // then - mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/purchase/apple/verify/subscribe") - .with(csrf().asHeader()) - .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(appleTransaction))) + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/purchase/apple/verify/subscribe") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(appleTransaction))) .andDo(print()) .andDo(document("api/v1/purchase/verifyAppleSubscriptionTransaction", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); @@ -121,15 +127,18 @@ void init() { // when // then - mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/purchase/apple/verify/ticket") - .with(csrf().asHeader()) - .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(appleTransaction))) + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/purchase/apple/verify/ticket") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(appleTransaction))) .andDo(print()) .andDo(document("api/v1/purchase/verifyAppleTicketTransaction", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); @@ -140,35 +149,40 @@ void init() { @Test void Google_구독_구매_검증에_성공합니다() throws Exception { // given - final GoogleSubscriptionGetRequest googleSubscriptionGetRequest = GoogleSubscriptionGetRequest.builder() - .orderId("orderId") - .packageName("packageName") - .productId("productId") - .purchaseTime(1L) - .purchaseState(1) - .purchaseToken("purchaseToken") - .quantity(1) - .autoRenewing(true) - .acknowledged(true) - .build(); + final GoogleSubscriptionGetRequest googleSubscriptionGetRequest = + GoogleSubscriptionGetRequest.builder() + .orderId("orderId") + .packageName("packageName") + .productId("productId") + .purchaseTime(1L) + .purchaseState(1) + .purchaseToken("purchaseToken") + .quantity(1) + .autoRenewing(true) + .acknowledged(true) + .build(); final GoogleSubscriptionGetResponse response = GoogleSubscriptionGetResponse.of( "productId"); - given(purchaseService.verifyGoogleSubscriptionTransaction(anyLong(), any(GoogleSubscriptionGetRequest.class))) + given(purchaseService.verifyGoogleSubscriptionTransaction(anyLong(), + any(GoogleSubscriptionGetRequest.class))) .willReturn(response); // when // then - mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/purchase/google/verify/subscribe") - .with(csrf().asHeader()) - .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(googleSubscriptionGetRequest))) + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/purchase/google/verify/subscribe") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleSubscriptionGetRequest))) .andDo(print()) .andDo(document("api/v1/purchase/verifyGoogleSubscriptionTransaction", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -192,20 +206,24 @@ void init() { user ); - given(purchaseService.verifyGoogleTicketTransaction(anyLong(), any(GoogleTicketGetRequest.class))) + given(purchaseService.verifyGoogleTicketTransaction(anyLong(), + any(GoogleTicketGetRequest.class))) .willReturn(response); // when // then - mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/purchase/google/verify/ticket") - .with(csrf().asHeader()) - .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(googleTicketGetRequest))) + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/purchase/google/verify/ticket") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleTicketGetRequest))) .andDo(print()) .andDo(document("api/v1/purchase/verifyGoogleTicketTransaction", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -227,8 +245,10 @@ void init() { .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token")) .andDo(print()) .andDo(document("api/v1/purchase/getUserSubscribeNeeded", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -247,8 +267,10 @@ void init() { .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token")) .andDo(print()) .andDo(document("api/v1/purchase/refundInAppApple", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -262,8 +284,10 @@ void init() { .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token")) .andDo(print()) .andDo(document("api/v1/purchase/getUserPurchaseInfo", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } diff --git a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java index e9b8236d..bc9d1e29 100644 --- a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java +++ b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java @@ -21,6 +21,7 @@ import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; +import com.yello.server.domain.user.dto.response.UserSubscribeDetailResponse; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.service.UserService; import com.yello.server.global.common.dto.EmptyObject; @@ -211,4 +212,29 @@ void init() { .andExpect(MockMvcResultMatchers.status().isOk()); } + @Test + void 유저_구독_정보_조회에_성공합니다() throws Exception { + // given + + final UserSubscribeDetailResponse userSubscribeDetailResponse = UserSubscribeDetailResponse.of(user, testDataUtil.generatePurchase(1L, user)); + // when + given(userService.getUserSubscribe(anyLong())) + .willReturn(userSubscribeDetailResponse); + + // then + mockMvc.perform(RestDocumentationRequestBuilders + .get("/api/v1/user/subscribe") + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + ) + .andDo(print()) + .andDo(document("api/v1/user/subscribe", + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()); + + } + } diff --git a/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java b/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java index 59ccd807..a5cd0ce9 100644 --- a/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java +++ b/src/test/java/com/yello/server/domain/user/small/UserServiceTest.java @@ -8,6 +8,8 @@ import com.yello.server.domain.friend.FakeFriendRepository; import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.repository.QuestionGroupTypeRepository; @@ -44,12 +46,14 @@ class UserServiceTest { private final TokenRepository tokenRepository = new FakeTokenRepository(); private final UserRepository userRepository = new FakeUserRepository(friendRepository); private final VoteRepository voteRepository = new FakeVoteRepository(); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private UserService userService; diff --git a/src/test/java/com/yello/server/domain/vote/small/VoteManagerTest.java b/src/test/java/com/yello/server/domain/vote/small/VoteManagerTest.java index 9800db0b..5943c1c6 100644 --- a/src/test/java/com/yello/server/domain/vote/small/VoteManagerTest.java +++ b/src/test/java/com/yello/server/domain/vote/small/VoteManagerTest.java @@ -6,6 +6,8 @@ import com.yello.server.domain.friend.FakeFriendRepository; import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.entity.Question; @@ -42,12 +44,14 @@ public class VoteManagerTest { private final VoteRepository voteRepository = new FakeVoteRepository(); private final QuestionGroupTypeRepository questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private VoteManager voteManager; private List questionData = new ArrayList<>(); diff --git a/src/test/java/com/yello/server/domain/vote/small/VoteServiceTest.java b/src/test/java/com/yello/server/domain/vote/small/VoteServiceTest.java index d96a2611..8b29df7b 100644 --- a/src/test/java/com/yello/server/domain/vote/small/VoteServiceTest.java +++ b/src/test/java/com/yello/server/domain/vote/small/VoteServiceTest.java @@ -11,6 +11,8 @@ import com.yello.server.domain.keyword.FakeKeywordRepository; import com.yello.server.domain.keyword.dto.response.KeywordCheckResponse; import com.yello.server.domain.keyword.repository.KeywordRepository; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.dto.response.QuestionForVoteResponse; @@ -62,7 +64,7 @@ public class VoteServiceTest { private final VoteRepository voteRepository = new FakeVoteRepository(); private final QuestionRepository questionRepository = new FakeQuestionRepository(); private final QuestionGroupTypeRepository questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); - + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final VoteManager voteManager = new FakeVoteManager( userRepository, questionRepository, @@ -75,7 +77,8 @@ public class VoteServiceTest { voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private final CooldownRepository cooldownRepository = new FakeCooldownRepository(); private final KeywordRepository keywordRepository = new FakeKeywordRepository(); diff --git a/src/test/java/com/yello/server/infrastructure/firebase/small/FcmManagerTest.java b/src/test/java/com/yello/server/infrastructure/firebase/small/FcmManagerTest.java index 4f57076b..a43b90f1 100644 --- a/src/test/java/com/yello/server/infrastructure/firebase/small/FcmManagerTest.java +++ b/src/test/java/com/yello/server/infrastructure/firebase/small/FcmManagerTest.java @@ -3,6 +3,8 @@ import com.yello.server.domain.friend.FakeFriendRepository; import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.FakePurchaseRepository; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.FakeQuestionGroupTypeRepository; import com.yello.server.domain.question.FakeQuestionRepository; import com.yello.server.domain.question.entity.Question; @@ -33,12 +35,14 @@ public class FcmManagerTest { private final UserRepository userRepository = new FakeUserRepository(friendRepository); private final QuestionGroupTypeRepository questionGroupTypeRepository = new FakeQuestionGroupTypeRepository(questionRepository); + private final PurchaseRepository purchaseRepository = new FakePurchaseRepository(); private final TestDataRepositoryUtil testDataUtil = new TestDataRepositoryUtil( userRepository, voteRepository, questionRepository, friendRepository, - questionGroupTypeRepository + questionGroupTypeRepository, + purchaseRepository ); private FCMManager fcmManager; diff --git a/src/test/java/com/yello/server/util/TestDataEntityUtil.java b/src/test/java/com/yello/server/util/TestDataEntityUtil.java index c4b39eee..b902c8c3 100644 --- a/src/test/java/com/yello/server/util/TestDataEntityUtil.java +++ b/src/test/java/com/yello/server/util/TestDataEntityUtil.java @@ -3,6 +3,10 @@ import com.yello.server.domain.friend.entity.Friend; import com.yello.server.domain.group.entity.UserGroup; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.entity.Gateway; +import com.yello.server.domain.purchase.entity.ProductType; +import com.yello.server.domain.purchase.entity.Purchase; +import com.yello.server.domain.purchase.entity.PurchaseState; import com.yello.server.domain.question.entity.Question; import com.yello.server.domain.question.entity.QuestionGroupType; import com.yello.server.domain.user.entity.Gender; @@ -119,4 +123,17 @@ public QuestionGroupType generateQuestionGroupType(long index, Question question .question(question) .build(); } + + @Override + public Purchase generatePurchase(long index, User user) { + return Purchase.builder() + .id(index) + .gateway(Gateway.APPLE) + .price(1000) + .productType(ProductType.YELLO_PLUS) + .user(user) + .transactionId("111") + .state(PurchaseState.ACTIVE) + .build(); + } } diff --git a/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java b/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java index 9875b59f..df083f31 100644 --- a/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java +++ b/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java @@ -4,6 +4,11 @@ import com.yello.server.domain.friend.repository.FriendRepository; import com.yello.server.domain.group.entity.UserGroup; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.entity.Gateway; +import com.yello.server.domain.purchase.entity.ProductType; +import com.yello.server.domain.purchase.entity.Purchase; +import com.yello.server.domain.purchase.entity.PurchaseState; +import com.yello.server.domain.purchase.repository.PurchaseRepository; import com.yello.server.domain.question.entity.Question; import com.yello.server.domain.question.entity.QuestionGroupType; import com.yello.server.domain.question.repository.QuestionGroupTypeRepository; @@ -24,15 +29,18 @@ public class TestDataRepositoryUtil implements TestDataUtil { private final QuestionRepository questionRepository; private final FriendRepository friendRepository; private final QuestionGroupTypeRepository questionGroupTypeRepository; + private final PurchaseRepository purchaseRepository; public TestDataRepositoryUtil(UserRepository userRepository, VoteRepository voteRepository, QuestionRepository questionRepository, - FriendRepository friendRepository, QuestionGroupTypeRepository questionGroupTypeRepository) { + FriendRepository friendRepository, QuestionGroupTypeRepository questionGroupTypeRepository, + PurchaseRepository purchaseRepository) { this.userRepository = userRepository; this.voteRepository = voteRepository; this.questionRepository = questionRepository; this.friendRepository = friendRepository; this.questionGroupTypeRepository = questionGroupTypeRepository; + this.purchaseRepository = purchaseRepository; } @Override @@ -137,4 +145,19 @@ public QuestionGroupType generateQuestionGroupType(long index, Question question .build() ); } + + @Override + public Purchase generatePurchase(long index, User user) { + return purchaseRepository.save( + Purchase.builder() + .id(index) + .gateway(Gateway.APPLE) + .price(1000) + .productType(ProductType.YELLO_PLUS) + .user(user) + .transactionId("111") + .state(PurchaseState.ACTIVE) + .build() + ); + } } diff --git a/src/test/java/com/yello/server/util/TestDataUtil.java b/src/test/java/com/yello/server/util/TestDataUtil.java index f95082ee..e4d8db7a 100644 --- a/src/test/java/com/yello/server/util/TestDataUtil.java +++ b/src/test/java/com/yello/server/util/TestDataUtil.java @@ -3,6 +3,7 @@ import com.yello.server.domain.friend.entity.Friend; import com.yello.server.domain.group.entity.UserGroup; import com.yello.server.domain.group.entity.UserGroupType; +import com.yello.server.domain.purchase.entity.Purchase; import com.yello.server.domain.question.entity.Question; import com.yello.server.domain.question.entity.QuestionGroupType; import com.yello.server.domain.user.entity.User; @@ -23,5 +24,7 @@ public interface TestDataUtil { UserGroup generateGroup(long index, UserGroupType userGroupType); QuestionGroupType generateQuestionGroupType(long index, Question question); + + Purchase generatePurchase(long index, User user); } From b4b86049e747ca62e8bfdea2785ebc588e4235bc Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Tue, 23 Jan 2024 22:52:19 +0900 Subject: [PATCH 016/112] YEL-183 [feat] querydsl --- build.gradle | 29 +++++++++++++++++++ .../repository/NoticeRepositoryImpl.java | 3 +- .../repository/PurchaseRepository.java | 2 +- .../repository/PurchaseRepositoryImpl.java | 2 +- .../domain/user/service/UserService.java | 2 +- .../configuration/QueryDslConfiguration.java | 19 ++++++++++++ .../static/docs/check-vote-available.html | 2 +- src/main/resources/static/docs/google.html | 2 +- src/main/resources/static/docs/index.html | 4 +-- .../resources/static/docs/purchase-info.html | 9 ++++-- .../purchase/FakePurchaseRepository.java | 2 +- 11 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/yello/server/global/configuration/QueryDslConfiguration.java diff --git a/build.gradle b/build.gradle index ebad094e..9ebf7fdd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,16 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { id 'java' id 'org.springframework.boot' version '2.7.4' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id "org.asciidoctor.jvm.convert" version "3.3.2" id 'jacoco' + id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" } group = 'com.yello' @@ -88,6 +95,11 @@ dependencies { // AOP implementation 'org.springframework.boot:spring-boot-starter-aop' + + // querydsl + implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" + implementation "com.querydsl:querydsl-apt:${queryDslVersion}" + implementation "com.querydsl:querydsl-core:${queryDslVersion}" } asciidoctor { @@ -112,6 +124,7 @@ configurations { compileOnly { extendsFrom annotationProcessor } + querydsl.extendsFrom compileClasspath } jar.enabled = false @@ -128,6 +141,22 @@ jacocoTestReport { } // finalizedBy 'jacocoTestCoverageVerification' } + +// QueryDsl +def querydslDir = "$buildDir/generated/querydsl" + +querydsl { + jpa = true + querydslSourcesDir = querydslDir +} +sourceSets { + main.java.srcDir querydslDir +} + +compileQuerydsl { + options.annotationProcessorPath = configurations.querydsl +} + //jacocoTestCoverageVerification { // violationRules { // rule { diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java index cec407a7..0659fc5f 100644 --- a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java @@ -1,5 +1,6 @@ package com.yello.server.domain.notice.repository; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -10,6 +11,6 @@ public class NoticeRepositoryImpl implements NoticeRepository { private final NoticeJpaRepository noticeJpaRepository; - + private final JPAQueryFactory jpaQueryFactory; } diff --git a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java index abd2d7f1..624afa70 100644 --- a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java +++ b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepository.java @@ -23,5 +23,5 @@ Optional findTopByUserAndProductTypeOrderByCreatedAtDesc(User user, void delete(Purchase purchase); - Purchase findEndByStateAndUserId(User user); + Purchase getEndByStateAndUserId(User user); } diff --git a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java index de1cbb9d..a91db5db 100644 --- a/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/purchase/repository/PurchaseRepositoryImpl.java @@ -56,7 +56,7 @@ public void delete(Purchase purchase) { } @Override - public Purchase findEndByStateAndUserId(User user) { + public Purchase getEndByStateAndUserId(User user) { return purchaseJpaRepository.findTopByStateAndUser(user) .orElseThrow(() -> new PurchaseNotFoundException(NOT_FOUND_USER_SUBSCRIBE_EXCEPTION)); } diff --git a/src/main/java/com/yello/server/domain/user/service/UserService.java b/src/main/java/com/yello/server/domain/user/service/UserService.java index ca258e1f..62000d1d 100644 --- a/src/main/java/com/yello/server/domain/user/service/UserService.java +++ b/src/main/java/com/yello/server/domain/user/service/UserService.java @@ -94,7 +94,7 @@ public void delete(User user) { public UserSubscribeDetailResponse getUserSubscribe(Long userId) { final User user = userRepository.getById(userId); - final Purchase purchase = purchaseRepository.findEndByStateAndUserId(user); + final Purchase purchase = purchaseRepository.getEndByStateAndUserId(user); return UserSubscribeDetailResponse.of(user, purchase); } diff --git a/src/main/java/com/yello/server/global/configuration/QueryDslConfiguration.java b/src/main/java/com/yello/server/global/configuration/QueryDslConfiguration.java new file mode 100644 index 00000000..7e00b54d --- /dev/null +++ b/src/main/java/com/yello/server/global/configuration/QueryDslConfiguration.java @@ -0,0 +1,19 @@ +package com.yello.server.global.configuration; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import javax.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class QueryDslConfiguration { + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory queryFactory() { + return new JPAQueryFactory(entityManager); + } + +} diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index 0d490628..a02bc5cb 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

    응답

    "data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-20 23:42:31", + "createdAt" : "2024-01-23 22:38:13", "friendStatus" : 1 } }
    diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index bc9acba1..2bf5f620 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

    응답

    "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-20T23:42:21.858430" + "expiredAt" : "2024-01-23T22:38:00.081341" } }
    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index fcdc3caa..477965f2 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -593,7 +593,7 @@

    Purchase API
    • -

      🆕 유저 구독 정보 (명세), 2024-01-09

      +

      🆕 유저 구독 정보, 2024-01-23

    • Apple 결제 관련 API

      @@ -643,7 +643,7 @@

      Notice API

      diff --git a/src/main/resources/static/docs/purchase-info.html b/src/main/resources/static/docs/purchase-info.html index 299da62c..0d46b636 100644 --- a/src/main/resources/static/docs/purchase-info.html +++ b/src/main/resources/static/docs/purchase-info.html @@ -5,7 +5,7 @@ -유저 구독 정보 (명세) +유저 구독 정보 + + + +
      +
      +

      친구 투표 전체 조회 (명세)

      +
      +
      +

      요청

      +
      +
      +
      GET /v2/vote/friend?page={}&type={} HTTP/1.1
      +Authorization: Bearer your-access-token
      +Content-Type: application-json
      +
      +
      +
      +

      업데이트 예정 +- "type": "send" | null

      +
      + ++++ + + + + + + +

      type

      조회할 쪽지 종류 (null → 모든쪽지, send→ 보낸쪽지)

      +
      +
      +

      응답

      +
      +
      +
      {
      +  "status" : 200,
      +  "message" : "투표 조회에 성공했습니다.",
      +  "data" : {
      +    "totalCount" : 1,
      +    "friendVotes" : [ {
      +            "id" : 1,
      +			"senderId" : 1,
      +			"senderName" : "name1",
      +			"senderGender" : "MALE",
      +			"senderYelloId" : "MALE",
      +            "senderProfileImage": "imageUrl",
      +			"receiverId" : 2,
      +            "receiverName" : "name2",
      +			"receiverGender" : "MALE",
      +			"receiverYelloId" : "MALE",
      +			"receiverProfileImage" : "test image",
      +            "vote" : {
      +                "nameHead" : "나는",
      +                "nameFoot" : "와",
      +                "keywordHead" : "멋진",
      +                "keyword" : "test",
      +                "keywordFoot" : "에서 놀고싶어"
      +            },
      +            "isHintUsed" : false,
      +            "createdAt" : "0초 전"
      +            "isUserSenderVote" : true
      +    } ]
      +  }
      +}
      +
      +
      +
      +

      필드 타입

      +
      +
      +
        +
      • +

        "totalCount": Integer

        +
      • +
      • +

        "friendVotes": FriendVote[]

        +
      • +
      • +

        "isUserSenderVote" : Boolean (내가 보냈는지 여부)

        +
      • +
      • +

        FriendVote

        +
        +
          +
        • +

          "id": Long

          +
        • +
        • +

          "senderId" : Long

          +
        • +
        • +

          "senderName" : String

          +
        • +
        • +

          "senderYelloId" : String

          +
        • +
        • +

          "senderGender": "MALE" | "FEMALE"

          +
        • +
        • +

          "senderProfileImage" : String

          +
        • +
        • +

          "receiverId" : Long

          +
        • +
        • +

          "receiverName": String

          +
        • +
        • +

          "receiverYelloId" : String

          +
        • +
        • +

          "receiverGender": "MALE" | "FEMALE"

          +
        • +
        • +

          "receiverProfileImage": String

          +
        • +
        • +

          "vote": Vote

          +
        • +
        • +

          "isHintUsed": Boolean

          +
        • +
        • +

          "createdAt": "{0}초 전" | "{0}분 전" | "{0}시간 전" | "{0}일 전"

          +
        • +
        +
        +
      • +
      • +

        Vote

        +
        +
          +
        • +

          "nameHead": String

          +
        • +
        • +

          "nameFoot": String

          +
        • +
        • +

          "keywordHead": String

          +
        • +
        • +

          "keyword": String

          +
        • +
        • +

          "keywordFoot": String

          +
        • +
        +
        +
      • +
      +
      +
      +
      +

      NOTE

      +
      +
        +
      • +

        모든 종류의 쪽지를 조회할 때 /api/v1/vote/friend?page=0 으로 요청해주세요

        +
        +
          +
        • +

          type= 을 명시하지 마세요

          +
        • +
        +
        +
      • +
      • +

        내가 보낸 쪽지를 조회할 때 /api/v1/vote/friend?page=0&type=send 으로 요청해주세요

        +
      • +
      • +

        senderGender 필드가 다른 API와 일관되지 못한점 미안해요 ㅠ

        +
      • +
      +
      +
      +
      +

      CHANGELOG

      +
      +
        +
      • +

        2024.01.26 필드 명세 업데이트

        +
      • +
      • +

        2024.01.09 type 명세 업데이트

        +
      • +
      +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index b2e1908b..e9146061 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

      응답

      "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-26T22:18:02.473353" + "expiredAt" : "2024-01-26T22:41:37.086752" } }

    diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 246cc595..5b0e3b2b 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -537,7 +537,7 @@

    Vote API

    내 투표 전체 조회하기

  • -

    ⬆️ 친구 투표 전체 조회하기, 2024-01-09

    +

    ⬆️ 친구 투표 전체 조회하기 (명세), 2024-01-09

  • 읽지 않은 쪽지 개수 조회하기

    @@ -632,7 +632,7 @@

    Notice API

    From 61d66d7aa14e9dd359a616ab327342c285b1fd63 Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 27 Jan 2024 14:02:24 +0900 Subject: [PATCH 031/112] =?UTF-8?q?YEL-185=20[feat]=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notice/dto/NoticeDataResponse.java | 4 ++-- .../repository/NoticeRepositoryImpl.java | 21 ++++++++++++------- .../response/UserSubscribeDetailResponse.java | 5 +++-- .../global/common/factory/TimeFactory.java | 14 +++++++------ .../global/common/util/ConstantUtil.java | 2 +- .../domain/notice/FakeNoticeRepository.java | 9 ++++++++ .../yello/server/util/TestDataEntityUtil.java | 7 +++++-- .../server/util/TestDataRepositoryUtil.java | 7 +++++-- 8 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java b/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java index 35a5ce73..9dbbc456 100644 --- a/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java +++ b/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java @@ -20,8 +20,8 @@ public static NoticeDataResponse of(Notice notice, Boolean isAvailable) { return NoticeDataResponse.builder() .imageUrl(notice.getImageUrl()) .redirectUrl(notice.getRedirectUrl()) - .startDate(toYearAndMonthFormattedString(notice.getStartDate().toLocalDateTime(), PLUS_BASIC_TIME)) - .endDate(toYearAndMonthFormattedString(notice.getEndDate().toLocalDateTime(), PLUS_BASIC_TIME)) + .startDate(toYearAndMonthFormattedString(notice.getStartDate().toLocalDateTime())) + .endDate(toYearAndMonthFormattedString(notice.getEndDate().toLocalDateTime())) .isAvailable(isAvailable) .build(); } diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java index 69ec529a..ea6fb173 100644 --- a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java @@ -1,15 +1,15 @@ package com.yello.server.domain.notice.repository; +import static com.yello.server.domain.notice.entity.QNotice.notice; + import com.querydsl.jpa.impl.JPAQueryFactory; import com.yello.server.domain.notice.entity.Notice; +import java.time.ZoneId; +import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - -import static com.yello.server.domain.notice.entity.QNotice.notice; - @Repository @RequiredArgsConstructor @Transactional(readOnly = true) @@ -20,15 +20,20 @@ public class NoticeRepositoryImpl implements NoticeRepository { @Override public Notice getTopNotice() { + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId); return jpaQueryFactory - .selectFrom(notice) - .orderBy(notice.endDate.desc()) - .fetchFirst(); + .selectFrom(notice) + .where(notice.isAvailable.eq(true) + .and(notice.startDate.loe(zonedDateTime)) + .and(notice.endDate.goe(zonedDateTime))) + .orderBy(notice.endDate.desc()) + .fetchFirst(); } @Override public Notice save(Notice notice) { - return null; + return noticeJpaRepository.save(notice); } } diff --git a/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java b/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java index a050f22b..03dbf109 100644 --- a/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java +++ b/src/main/java/com/yello/server/domain/user/dto/response/UserSubscribeDetailResponse.java @@ -1,7 +1,7 @@ package com.yello.server.domain.user.dto.response; import static com.yello.server.global.common.factory.TimeFactory.toYearAndMonthFormattedString; -import static com.yello.server.global.common.util.ConstantUtil.PLUS_SEVEN_TIME; +import static com.yello.server.global.common.util.ConstantUtil.SUBSCRIBE_DAYS; import com.yello.server.domain.purchase.entity.Purchase; import lombok.Builder; @@ -17,7 +17,8 @@ public static UserSubscribeDetailResponse of(Purchase purchase) { return UserSubscribeDetailResponse.builder() .id(purchase.getUser().getId()) .subscribe(purchase.getUser().getSubscribe().getIntial()) - .expiredDate(toYearAndMonthFormattedString(purchase.getUpdatedAt(), PLUS_SEVEN_TIME)) + .expiredDate( + toYearAndMonthFormattedString(purchase.getUpdatedAt().plusDays(SUBSCRIBE_DAYS))) .build(); } } diff --git a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java index 952e11db..93102ccc 100644 --- a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java +++ b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -53,18 +54,19 @@ public static LocalDateTime minusTime(LocalDateTime localDateTime, int time) { return localDateTime.minusMinutes(time); } - public static String toYearAndMonthFormattedString(LocalDateTime localDateTime, int time) { + public static String toYearAndMonthFormattedString(LocalDateTime localDateTime) { if (localDateTime == null) { return ""; } - LocalDateTime dateTimePlusSevenDays = localDateTime.plusDays(time); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - return dateTimePlusSevenDays.format(dateTimeFormatter); + return localDateTime.format(dateTimeFormatter); } - public static Boolean compareNowAndEndData(ZonedDateTime localDateTime) { - ZonedDateTime now = ZonedDateTime.now(); - return now.isBefore(localDateTime); + public static Boolean compareNowAndEndData(ZonedDateTime zonedDateTime) { + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime now = ZonedDateTime.now(zoneId); + + return now.isBefore(zonedDateTime); } } 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 ab76a259..c10af4d5 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 @@ -65,7 +65,7 @@ public class ConstantUtil { public static final int REFUND_TWO_TICKET = 2; public static final int REFUND_FIVE_TICKET = 5; public static final int NO_FRIEND_COUNT = 0; - public static final int PLUS_SEVEN_TIME = 7; + public static final int SUBSCRIBE_DAYS = 7; public static final int PLUS_BASIC_TIME = 0; diff --git a/src/test/java/com/yello/server/domain/notice/FakeNoticeRepository.java b/src/test/java/com/yello/server/domain/notice/FakeNoticeRepository.java index c8c72682..ab94b002 100644 --- a/src/test/java/com/yello/server/domain/notice/FakeNoticeRepository.java +++ b/src/test/java/com/yello/server/domain/notice/FakeNoticeRepository.java @@ -5,6 +5,7 @@ import com.yello.server.domain.notice.entity.Notice; import com.yello.server.domain.notice.exception.NoticeNotFoundException; import com.yello.server.domain.notice.repository.NoticeRepository; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Comparator; @@ -17,7 +18,15 @@ public class FakeNoticeRepository implements NoticeRepository { @Override public Notice getTopNotice() { + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime now = ZonedDateTime.now(zoneId); + return data.stream() + .filter(notice -> + notice.getIsAvailable().equals(true) && + !notice.getStartDate().isAfter(now) && + !notice.getEndDate().isBefore(now) + ) .sorted(Comparator.comparing(Notice::getEndDate).reversed()) .findFirst() .orElseThrow(() -> new NoticeNotFoundException(NOT_FOUND_NOTICE_EXCEPTION)); diff --git a/src/test/java/com/yello/server/util/TestDataEntityUtil.java b/src/test/java/com/yello/server/util/TestDataEntityUtil.java index 4ef9c077..209d6ab4 100644 --- a/src/test/java/com/yello/server/util/TestDataEntityUtil.java +++ b/src/test/java/com/yello/server/util/TestDataEntityUtil.java @@ -16,6 +16,7 @@ import com.yello.server.domain.user.entity.User; import com.yello.server.domain.vote.entity.Vote; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; public class TestDataEntityUtil implements TestDataUtil { @@ -143,11 +144,13 @@ public Purchase generatePurchase(long index, User user) { @Override public Notice genereateNotice(long index) { + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime now = ZonedDateTime.now(zoneId); return Notice.builder() .id(index) - .endDate(ZonedDateTime.now().minusDays(3)) + .endDate(now.plusDays(3)) .imageUrl("imageUrl") - .startDate(ZonedDateTime.now().minusDays(10)) + .startDate(now.minusDays(3)) .redirectUrl("redirectUrl") .isAvailable(true) .build(); diff --git a/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java b/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java index db4a965f..76666943 100644 --- a/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java +++ b/src/test/java/com/yello/server/util/TestDataRepositoryUtil.java @@ -23,6 +23,7 @@ import com.yello.server.domain.vote.entity.Vote; import com.yello.server.domain.vote.repository.VoteRepository; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; public class TestDataRepositoryUtil implements TestDataUtil { @@ -172,11 +173,13 @@ public Purchase generatePurchase(long index, User user) { @Override public Notice genereateNotice(long index) { + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime now = ZonedDateTime.now(zoneId); return noticeRepository.save(Notice.builder() .id(index) - .endDate(ZonedDateTime.now().plusDays(3)) + .endDate(now.plusDays(3)) .imageUrl("imageUrl") - .startDate(ZonedDateTime.now().minusDays(4)) + .startDate(now.minusDays(3)) .redirectUrl("redirectUrl") .isAvailable(true) .build()); From 0bc5b14d4b2160bcfe5566bb6e578b8b33ad44fe Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 27 Jan 2024 15:33:01 +0900 Subject: [PATCH 032/112] =?UTF-8?q?YEL-185=20[feat]=20notice=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/find-notice.adoc | 25 +++++++++++++ .../domain/notice/dto/NoticeDataResponse.java | 34 +++++++++-------- .../server/domain/notice/entity/Notice.java | 6 +++ .../notice/repository/NoticeRepository.java | 2 +- .../repository/NoticeRepositoryImpl.java | 8 ++-- .../domain/notice/service/NoticeService.java | 23 +++++++----- .../global/common/factory/TimeFactory.java | 1 + .../static/docs/check-vote-available.html | 2 +- .../static/docs/find-friend-votes.html | 37 ++----------------- .../resources/static/docs/find-notice.html | 8 ++-- src/main/resources/static/docs/google.html | 2 +- src/main/resources/static/docs/index.html | 5 ++- .../resources/static/docs/purchase-info.html | 2 +- .../domain/notice/FakeNoticeRepository.java | 8 ++-- .../yello/server/util/TestDataEntityUtil.java | 2 + .../server/util/TestDataRepositoryUtil.java | 2 + 16 files changed, 95 insertions(+), 72 deletions(-) diff --git a/src/docs/asciidoc/find-notice.adoc b/src/docs/asciidoc/find-notice.adoc index eceebdbc..0b6390a0 100644 --- a/src/docs/asciidoc/find-notice.adoc +++ b/src/docs/asciidoc/find-notice.adoc @@ -21,6 +21,31 @@ include::{snippets}/api/v1/notice/http-response.adoc[] * YYYY-MM-DD (ISO-8601) - "isAvailable": Boolean + +=== 주의 + +[http, json] + +*유효한 날짜의 공지가 존재하지 않는 경우* + +-> isAvailable은 false로 오고 날짜 제외한 나머지 값은 빈값으로 전달 + +---- +{ + "status": 200, + "message": "공지 조회에 성공하였습니다.", + "data": { + "imageUrl": "", + "redirectUrl": "", + "startDate": "2024-01-27", + "endDate": "2024-01-27", + "isAvailable": false, + "type": null, + "title": "" +} + +---- + === NOTE - 공지 정보를 조회하는 API입니다. diff --git a/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java b/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java index 9dbbc456..41e61948 100644 --- a/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java +++ b/src/main/java/com/yello/server/domain/notice/dto/NoticeDataResponse.java @@ -1,29 +1,31 @@ package com.yello.server.domain.notice.dto; +import static com.yello.server.global.common.factory.TimeFactory.toYearAndMonthFormattedString; + import com.yello.server.domain.notice.entity.Notice; import lombok.Builder; -import java.time.LocalDateTime; - -import static com.yello.server.global.common.factory.TimeFactory.toYearAndMonthFormattedString; -import static com.yello.server.global.common.util.ConstantUtil.PLUS_BASIC_TIME; - @Builder public record NoticeDataResponse( - String imageUrl, - String redirectUrl, - String startDate, - String endDate, - boolean isAvailable + String imageUrl, + String redirectUrl, + String startDate, + String endDate, + boolean isAvailable, + String type, + String title ) { + public static NoticeDataResponse of(Notice notice, Boolean isAvailable) { return NoticeDataResponse.builder() - .imageUrl(notice.getImageUrl()) - .redirectUrl(notice.getRedirectUrl()) - .startDate(toYearAndMonthFormattedString(notice.getStartDate().toLocalDateTime())) - .endDate(toYearAndMonthFormattedString(notice.getEndDate().toLocalDateTime())) - .isAvailable(isAvailable) - .build(); + .imageUrl(notice.getImageUrl()) + .redirectUrl(notice.getRedirectUrl()) + .startDate(toYearAndMonthFormattedString(notice.getStartDate().toLocalDateTime())) + .endDate(toYearAndMonthFormattedString(notice.getEndDate().toLocalDateTime())) + .isAvailable(isAvailable) + .type(notice.getType()) + .title(notice.getTitle()) + .build(); } } diff --git a/src/main/java/com/yello/server/domain/notice/entity/Notice.java b/src/main/java/com/yello/server/domain/notice/entity/Notice.java index 0e03a710..006af830 100644 --- a/src/main/java/com/yello/server/domain/notice/entity/Notice.java +++ b/src/main/java/com/yello/server/domain/notice/entity/Notice.java @@ -38,4 +38,10 @@ public class Notice extends AuditingTimeEntity { @Column(nullable = false) private Boolean isAvailable; + + @Column(nullable = false) + private String type; + + @Column(nullable = false) + private String title; } \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java index 25e6d87f..6fc64acd 100644 --- a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepository.java @@ -6,7 +6,7 @@ public interface NoticeRepository { - Notice getTopNotice(); + Optional findTopNotice(); Notice save(Notice notice); } diff --git a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java index ea6fb173..7d3dae64 100644 --- a/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/notice/repository/NoticeRepositoryImpl.java @@ -6,7 +6,9 @@ import com.yello.server.domain.notice.entity.Notice; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.aspectj.weaver.ast.Not; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -19,16 +21,16 @@ public class NoticeRepositoryImpl implements NoticeRepository { private final JPAQueryFactory jpaQueryFactory; @Override - public Notice getTopNotice() { + public Optional findTopNotice() { ZoneId zoneId = ZoneId.of("Asia/Seoul"); ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId); - return jpaQueryFactory + return Optional.ofNullable(jpaQueryFactory .selectFrom(notice) .where(notice.isAvailable.eq(true) .and(notice.startDate.loe(zonedDateTime)) .and(notice.endDate.goe(zonedDateTime))) .orderBy(notice.endDate.desc()) - .fetchFirst(); + .fetchFirst()); } @Override diff --git a/src/main/java/com/yello/server/domain/notice/service/NoticeService.java b/src/main/java/com/yello/server/domain/notice/service/NoticeService.java index 530f8048..9790a1a8 100644 --- a/src/main/java/com/yello/server/domain/notice/service/NoticeService.java +++ b/src/main/java/com/yello/server/domain/notice/service/NoticeService.java @@ -1,32 +1,37 @@ package com.yello.server.domain.notice.service; +import static com.yello.server.global.common.factory.TimeFactory.compareNowAndEndData; + import com.yello.server.domain.notice.dto.NoticeDataResponse; import com.yello.server.domain.notice.entity.Notice; import com.yello.server.domain.notice.repository.NoticeRepository; -import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.repository.UserRepository; +import java.time.ZoneId; +import java.time.ZonedDateTime; import lombok.Builder; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Optional; - -import static com.yello.server.global.common.factory.TimeFactory.compareNowAndEndData; - @Service @Builder @RequiredArgsConstructor @Transactional(readOnly = true) public class NoticeService { + private final NoticeRepository noticeRepository; private final UserRepository userRepository; public NoticeDataResponse findNotice(Long userId) { - final User user = userRepository.getById(userId); - Notice noticeData = noticeRepository.getTopNotice(); - return NoticeDataResponse.of(noticeData, compareNowAndEndData(noticeData.getEndDate()) && noticeData.getIsAvailable()); + ZoneId zoneId = ZoneId.of("Asia/Seoul"); + ZonedDateTime now = ZonedDateTime.now(zoneId); + userRepository.findById(userId); + Notice noticeData = + noticeRepository.findTopNotice().orElseGet( + () -> Notice.builder().imageUrl("").redirectUrl("").title("").type("").endDate(now) + .startDate(now).isAvailable(false).build()); + return NoticeDataResponse.of(noticeData, + compareNowAndEndData(noticeData.getEndDate()) && noticeData.getIsAvailable()); } diff --git a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java index 93102ccc..a860a13e 100644 --- a/src/main/java/com/yello/server/global/common/factory/TimeFactory.java +++ b/src/main/java/com/yello/server/global/common/factory/TimeFactory.java @@ -66,6 +66,7 @@ public static Boolean compareNowAndEndData(ZonedDateTime zonedDateTime) { ZoneId zoneId = ZoneId.of("Asia/Seoul"); ZonedDateTime now = ZonedDateTime.now(zoneId); + System.out.println(now + " sfsdfsd"); return now.isBefore(zonedDateTime); } diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index d66cb8f6..05de78e0 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

    응답

    "data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-26 22:41:45", + "createdAt" : "2024-01-27 15:00:11", "friendStatus" : 1 } }
  • diff --git a/src/main/resources/static/docs/find-friend-votes.html b/src/main/resources/static/docs/find-friend-votes.html index 4387f3c3..3ed670df 100644 --- a/src/main/resources/static/docs/find-friend-votes.html +++ b/src/main/resources/static/docs/find-friend-votes.html @@ -5,7 +5,7 @@ -친구 투표 전체 조회 (업데이트) +친구 투표 전체 조회 (최신버전 → v2 확인) + + + +
    +
    +

    탈퇴 & 사유 저장 v2

    +
    +
    +

    요청

    +
    +
    +
    DELETE /api/v2/user HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer your-access-token
    +Content-Length: 37
    +
    +{
    +  "value" : "오류가 많아서"
    +}
    +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "유저 탈퇴에 성공했습니다."
    +}
    +
    +
    +
    +

    필드 타입

    +
    +
    +
      +
    • +

      "value": String

      +
      +
        +
      • +

        value는 탈퇴 사유를 보내주시면 됩니다.

        +
      • +
      +
      +
    • +
    +
    +
    +

    필드 타입

    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      AccessToken에 해당하는 User의 탈퇴 처리 및 탈퇴 사유를 저장하는 API입니다.

      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.01.27 API 릴리즈

      +
    • +
    • +

      2024.01.09 명세 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 0edb8563..5425a8dd 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -512,7 +512,7 @@

    User API

    🆕 프로필 수정 가능 여부 조회 (명세), 2024-01-09

  • -

    🆕 탈퇴 사유 저장 (명세), 2024-01-09

    +

    🆕 유저 기타 정보 저장 (명세), 2024-01-30

  • 🆕 유저 구독 정보, 2024-01-25

    @@ -526,6 +526,9 @@

    User API

  • 유저 탈퇴

  • +
  • +

    🆕 유저 탈퇴 V2, 2024-01-30

    +
  • @@ -540,7 +543,7 @@

    Vote API

    친구 투표 전체 조회하기, 2024-01-09

  • -

    ⬆️ 친구 투표 전체 조회하기 v2 (명세), 2024-01-26

    +

    🆕️ 친구 투표 전체 조회하기 v2 (명세), 2024-01-26

  • 읽지 않은 쪽지 개수 조회하기

    diff --git a/src/main/resources/static/docs/user-data-post.html b/src/main/resources/static/docs/user-data-post.html index 9e5c9a7f..006b94cf 100644 --- a/src/main/resources/static/docs/user-data-post.html +++ b/src/main/resources/static/docs/user-data-post.html @@ -5,7 +5,7 @@ -탈퇴 & 사유 저장 v2 +유저 기타 정보 저장 (명세) +
  • - - - + + \ No newline at end of file diff --git a/src/main/resources/static/docs/login.html b/src/main/resources/static/docs/login.html index fd84b612..79046842 100644 --- a/src/main/resources/static/docs/login.html +++ b/src/main/resources/static/docs/login.html @@ -4,25 +4,23 @@ - + 소셜 로그인 + + + +
    +
    +

    공지 조회

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/event HTTP/1.1
    +Authorization: Bearer your-access-token
    +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 전체 조회에 성공하였습니다.",
    +  "data" : [ {
    +    "tag" : "LUNCH_EVENT",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "점심 시간 깜짝 선물!",
    +    "subTitle" : "평일 12-14시 최대 1회까지 참여 가능",
    +    "animationList" : [ {
    +      "v" : "로티1"
    +    }, {
    +      "v" : "로티2"
    +    } ],
    +    "eventReward" : {
    +      "startTime" : "12:00+09:00",
    +      "endTime" : "14:00+09:00",
    +      "rewardCount" : 1,
    +      "eventRewardItem" : [ {
    +        "tag" : "POINT",
    +        "eventRewardTitle" : "최대 200P",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +        "maxRewardValue" : 200,
    +        "minRewardValue" : 10,
    +        "eventRewardProbability" : 10,
    +        "randomTag" : "FIXED"
    +      }, {
    +        "tag" : "TICKET",
    +        "eventRewardTitle" : "열람권 1개",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/key.svg",
    +        "maxRewardValue" : 1,
    +        "minRewardValue" : 1,
    +        "eventRewardProbability" : 90,
    +        "randomTag" : "RANDOM"
    +      } ]
    +    }
    +  }, {
    +    "tag" : "ADMOB",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "ADMOB 광고입니다.",
    +    "subTitle" : "ADMOB 광고는 영구 사용 가능 설정입니다",
    +    "animationList" : [ ],
    +    "eventReward" : {
    +      "startTime" : "00:00+09:00",
    +      "endTime" : "23:59:59.000999999+09:00",
    +      "rewardCount" : 1,
    +      "eventRewardItem" : [ {
    +        "tag" : "ADMOB_POINT",
    +        "eventRewardTitle" : "광고 보고 포인트 얻기",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +        "maxRewardValue" : 50,
    +        "minRewardValue" : 50,
    +        "eventRewardProbability" : 100,
    +        "randomTag" : "FIXED"
    +      } ]
    +    }
    +  } ]
    +}
    +
    +
    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 전체 조회에 성공하였습니다.",
    +  "data" : [ {
    +    "tag" : "LUNCH_EVENT",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "점심 시간 깜짝 선물!",
    +    "subTitle" : "평일 12-14시 최대 1회까지 참여 가능",
    +    "animationList" : [ {
    +      "v" : "로티1"
    +    }, {
    +      "v" : "로티2"
    +    } ],
    +    "eventReward" : {
    +      "startTime" : "22:00+09:00",
    +      "endTime" : "00:00+09:00",
    +      "rewardCount" : 1,
    +      "eventRewardItem" : [ {
    +        "tag" : "POINT",
    +        "eventRewardTitle" : "최대 200P",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +        "maxRewardValue" : 200,
    +        "minRewardValue" : 10,
    +        "eventRewardProbability" : 40,
    +        "randomTag" : "FIXED"
    +      }, {
    +        "tag" : "TICKET",
    +        "eventRewardTitle" : "열람권 1개",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/key.svg",
    +        "maxRewardValue" : 1,
    +        "minRewardValue" : 1,
    +        "eventRewardProbability" : 60,
    +        "randomTag" : "RANDOM"
    +      } ]
    +    }
    +  }, {
    +    "tag" : "ADMOB",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "ADMOB 광고입니다.",
    +    "subTitle" : "ADMOB 광고는 영구 사용 가능 설정입니다",
    +    "animationList" : [ ],
    +    "eventReward" : {
    +      "startTime" : "00:00+09:00",
    +      "endTime" : "23:59:59.000999999+09:00",
    +      "rewardCount" : 1,
    +      "eventRewardItem" : [ {
    +        "tag" : "ADMOB_POINT",
    +        "eventRewardTitle" : "광고 보고 포인트 얻기",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +        "maxRewardValue" : 50,
    +        "minRewardValue" : 50,
    +        "eventRewardProbability" : 100,
    +        "randomTag" : "FIXED"
    +      } ]
    +    }
    +  } ]
    +}
    +
    +
    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 전체 조회에 성공하였습니다.",
    +  "data" : [ {
    +    "tag" : "LUNCH_EVENT",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "점심 시간 깜짝 선물!",
    +    "subTitle" : "평일 12-14시 최대 1회까지 참여 가능",
    +    "animationList" : [ {
    +      "v" : "로티1"
    +    }, {
    +      "v" : "로티2"
    +    } ],
    +    "eventReward" : null
    +  }, {
    +    "tag" : "ADMOB",
    +    "startDate" : "2024-01-01T00:00:00+09:00",
    +    "endDate" : "2024-12-31T00:00:00+09:00",
    +    "title" : "ADMOB 광고입니다.",
    +    "subTitle" : "ADMOB 광고는 영구 사용 가능 설정입니다",
    +    "animationList" : [ ],
    +    "eventReward" : {
    +      "startTime" : "00:00+09:00",
    +      "endTime" : "23:59:59.000999999+09:00",
    +      "rewardCount" : 1,
    +      "eventRewardItem" : [ {
    +        "tag" : "ADMOB_POINT",
    +        "eventRewardTitle" : "광고 보고 포인트 얻기",
    +        "eventRewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +        "maxRewardValue" : 50,
    +        "minRewardValue" : 50,
    +        "eventRewardProbability" : 100,
    +        "randomTag" : "FIXED"
    +      } ]
    +    }
    +  } ]
    +}
    +
    +
    +
    +
    +

    주의

    +
    +
      +
    • +

      data: Response[]

      +
    • +
    • +

      Response

      +
    • +
    • +

      tag : "LUNCH_EVENT" | "ADMOB"

      +
      +
        +
      • +

        LUNCH_EVENT에 해당하는 *Response*가 없으면 Render 해주지 말아주세요

        +
      • +
      +
      +
    • +
    • +

      startDate : "2024-01-01T00:00:00+09:00"

      +
    • +
    • +

      endDate : "2024-12-31T00:00:00+09:00"

      +
    • +
    • +

      title : "점심 시간 깜짝 선물!"

      +
    • +
    • +

      subTitle : "평일 12-14시 최대 1회까지 참여 가능"

      +
    • +
    • +

      animationList : [{…​json1…​}, {…​json2…​}]

      +
    • +
    • +

      eventReward: EventReward | null

      +
      +
        +
      • +

        해당 필드가 null일 시, 이벤트 보여주지 않도록 해주세요.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      ! LUNCH_EVENT에 해당하는 Response가 없거나, LUNCH_EVENT Response의 eventReward가 null이면, 메인화면 접속시, 이벤트 화면을 띄워주지마세요.

      +
    • +
    • +

      이벤트 참여 요청, 이벤트 보상 반환 API 문서 작성중..

      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.02.06 릴리즈

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 7b0b4229..bd302be3 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -456,6 +456,7 @@

    YELL:O API 문서

  • Purchase API
  • Pay API
  • Notice API
  • +
  • Event API
  • @@ -544,7 +545,7 @@

    Vote API

    친구 투표 전체 조회하기, 2024-01-09

  • -

    ⬆️ 친구 투표 전체 조회하기 v2, 2024-01-30

    +

    🆕 친구 투표 전체 조회하기 v2, 2024-01-30

  • 읽지 않은 쪽지 개수 조회하기

    @@ -644,6 +645,16 @@

    Notice API

  • +
    +

    Event API

    +
    + +
    +
    diff --git a/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java new file mode 100644 index 00000000..ea3f9ef1 --- /dev/null +++ b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java @@ -0,0 +1,364 @@ +package com.yello.server.domain.event.medium; + +import static com.yello.server.domain.event.entity.EventRewardRandomType.FIXED; +import static com.yello.server.domain.event.entity.EventRewardRandomType.RANDOM; +import static com.yello.server.domain.event.entity.EventType.ADMOB; +import static com.yello.server.domain.event.entity.EventType.LUNCH_EVENT; +import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yello.server.domain.authorization.filter.JwtExceptionFilter; +import com.yello.server.domain.authorization.filter.JwtFilter; +import com.yello.server.domain.event.controller.EventController; +import com.yello.server.domain.event.dto.response.EventResponse; +import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventReward; +import com.yello.server.domain.event.entity.EventRewardMapping; +import com.yello.server.domain.event.entity.EventTime; +import com.yello.server.domain.event.service.EventService; +import com.yello.server.global.exception.ControllerExceptionAdvice; +import com.yello.server.util.TestDataEntityUtil; +import com.yello.server.util.TestDataUtil; +import com.yello.server.util.WithAccessTokenUser; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.operation.preprocess.OperationPreprocessor; +import org.springframework.restdocs.operation.preprocess.Preprocessors; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@AutoConfigureRestDocs +@WebMvcTest( + controllers = { + EventController.class + }, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtFilter.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtExceptionFilter.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ControllerExceptionAdvice.class) + }) +@WithAccessTokenUser +@DisplayNameGeneration(ReplaceUnderscores.class) +@DisplayName("Event 컨트롤러에서") +class EventControllerTest { + + final OperationPreprocessor[] excludeRequestHeaders = new OperationPreprocessor[]{ + prettyPrint(), + modifyHeaders().remove("X-CSRF-TOKEN"), + modifyHeaders().remove(HttpHeaders.HOST) + }; + + final OperationPreprocessor[] excludeResponseHeaders = new OperationPreprocessor[]{ + prettyPrint(), + modifyHeaders().remove("X-Content-Type-Options"), + modifyHeaders().remove("X-XSS-Protection"), + modifyHeaders().remove("X-Frame-Options"), + modifyHeaders().remove(HttpHeaders.CACHE_CONTROL), + modifyHeaders().remove(HttpHeaders.PRAGMA), + modifyHeaders().remove(HttpHeaders.EXPIRES), + modifyHeaders().remove(HttpHeaders.CONTENT_LENGTH), + }; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private EventService eventService; + + private TestDataUtil testDataUtil = new TestDataEntityUtil(); + + /** + * TODO Event* TestUtil 작성 및 연결 필요 + */ + @Test + void 이벤트_전체_조회에_성공합니다1() throws Exception { + final EventReward ticket = EventReward.builder() + .tag("POINT") + .maxRewardValue(200L) + .minRewardValue(10L) + .title("최대 200P") + .image("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + final EventReward point = EventReward.builder() + .tag("TICKET") + .maxRewardValue(1L) + .minRewardValue(1L) + .title("열람권 1개") + .image("https://storage.googleapis.com/yelloworld/image/key.svg") + .build(); + + final Event lunchEvent = Event.builder() + .tag(LUNCH_EVENT) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("점심 시간 깜짝 선물!") + .subTitle("평일 12-14시 최대 1회까지 참여 가능") + .animation("[{\"v\": \"로티1\"},{\"v\": \"로티2\"}]") + .build(); + + final EventTime eventTime1 = EventTime.builder() + .event(lunchEvent) + .rewardCount(1L) + .startTime(OffsetTime.of(12, 0, 0, 0, ZoneOffset.of("+09:00"))) + .endTime(OffsetTime.of(14, 0, 0, 0, ZoneOffset.of("+09:00"))) + .build(); + + final List rewardList1 = List.of( + EventRewardMapping.builder() + .eventTime(eventTime1) + .eventReward(ticket) + .eventRewardProbability(10) + .randomTag(FIXED) + .build(), + EventRewardMapping.builder() + .eventTime(eventTime1) + .eventReward(point) + .eventRewardProbability(90) + .randomTag(RANDOM) + .build() + ); + + final EventReward admobPoint = EventReward.builder() + .tag("ADMOB_POINT") + .maxRewardValue(50L) + .minRewardValue(50L) + .title("광고 보고 포인트 얻기") + .image("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + final Event admobEvent = Event.builder() + .tag(ADMOB) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("ADMOB 광고입니다.") + .subTitle("ADMOB 광고는 영구 사용 가능 설정입니다") + .animation("[]") + .build(); + final EventTime eventTime3 = EventTime.builder() + .event(admobEvent) + .rewardCount(1L) + .startTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) + .endTime(OffsetTime.of(23, 59, 59, 999999, ZoneOffset.of("+09:00"))) + .build(); + final List rewardList3 = List.of( + EventRewardMapping.builder() + .eventTime(eventTime3) + .eventReward(admobPoint) + .eventRewardProbability(100) + .randomTag(FIXED) + .build() + ); + + final EventResponse response1 = EventResponse.of(lunchEvent, eventTime1, rewardList1); + final EventResponse response3 = EventResponse.of(admobEvent, eventTime3, rewardList3); + final List result = List.of(response1, response3); + + given(eventService.getEvents(anyLong())) + .willReturn(result); + + // when + + // then + mockMvc + .perform( + RestDocumentationRequestBuilders + .get("/api/v1/event") + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + ) + .andDo(print()) + .andDo(document("api/v1/event/1", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @Test + void 이벤트_전체_조회에_성공합니다2() throws Exception { + final EventReward ticket = EventReward.builder() + .tag("POINT") + .maxRewardValue(200L) + .minRewardValue(10L) + .title("최대 200P") + .image("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + final EventReward point = EventReward.builder() + .tag("TICKET") + .maxRewardValue(1L) + .minRewardValue(1L) + .title("열람권 1개") + .image("https://storage.googleapis.com/yelloworld/image/key.svg") + .build(); + + final Event lunchEvent = Event.builder() + .tag(LUNCH_EVENT) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("점심 시간 깜짝 선물!") + .subTitle("평일 12-14시 최대 1회까지 참여 가능") + .animation("[{\"v\": \"로티1\"},{\"v\": \"로티2\"}]") + .build(); + + final EventTime eventTime2 = EventTime.builder() + .event(lunchEvent) + .rewardCount(1L) + .startTime(OffsetTime.of(22, 0, 0, 0, ZoneOffset.of("+09:00"))) + .endTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) + .build(); + + final List rewardList2 = List.of( + EventRewardMapping.builder() + .eventTime(eventTime2) + .eventReward(ticket) + .eventRewardProbability(40) + .randomTag(FIXED) + .build(), + EventRewardMapping.builder() + .eventTime(eventTime2) + .eventReward(point) + .eventRewardProbability(60) + .randomTag(RANDOM) + .build() + ); + + final EventReward admobPoint = EventReward.builder() + .tag("ADMOB_POINT") + .maxRewardValue(50L) + .minRewardValue(50L) + .title("광고 보고 포인트 얻기") + .image("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + final Event admobEvent = Event.builder() + .tag(ADMOB) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("ADMOB 광고입니다.") + .subTitle("ADMOB 광고는 영구 사용 가능 설정입니다") + .animation("[]") + .build(); + final EventTime eventTime3 = EventTime.builder() + .event(admobEvent) + .rewardCount(1L) + .startTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) + .endTime(OffsetTime.of(23, 59, 59, 999999, ZoneOffset.of("+09:00"))) + .build(); + final List rewardList3 = List.of( + EventRewardMapping.builder() + .eventTime(eventTime3) + .eventReward(admobPoint) + .eventRewardProbability(100) + .randomTag(FIXED) + .build() + ); + + final EventResponse response2 = EventResponse.of(lunchEvent, eventTime2, rewardList2); + final EventResponse response3 = EventResponse.of(admobEvent, eventTime3, rewardList3); + final List result = List.of(response2, response3); + + given(eventService.getEvents(anyLong())) + .willReturn(result); + + // when + + // then + mockMvc + .perform( + RestDocumentationRequestBuilders + .get("/api/v1/event") + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + ) + .andDo(print()) + .andDo(document("api/v1/event/2", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @Test + void 이벤트_전체_조회에_성공합니다3() throws Exception { + final Event lunchEvent = Event.builder() + .tag(LUNCH_EVENT) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("점심 시간 깜짝 선물!") + .subTitle("평일 12-14시 최대 1회까지 참여 가능") + .animation("[{\"v\": \"로티1\"},{\"v\": \"로티2\"}]") + .build(); + + final EventReward admobPoint = EventReward.builder() + .tag("ADMOB_POINT") + .maxRewardValue(50L) + .minRewardValue(50L) + .title("광고 보고 포인트 얻기") + .image("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + final Event admobEvent = Event.builder() + .tag(ADMOB) + .startDate(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, GlobalZoneId)) + .endDate(ZonedDateTime.of(2024, 12, 31, 0, 0, 0, 0, GlobalZoneId)) + .title("ADMOB 광고입니다.") + .subTitle("ADMOB 광고는 영구 사용 가능 설정입니다") + .animation("[]") + .build(); + final EventTime eventTime3 = EventTime.builder() + .event(admobEvent) + .rewardCount(1L) + .startTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) + .endTime(OffsetTime.of(23, 59, 59, 999999, ZoneOffset.of("+09:00"))) + .build(); + final List rewardList3 = List.of( + EventRewardMapping.builder() + .eventTime(eventTime3) + .eventReward(admobPoint) + .eventRewardProbability(100) + .randomTag(FIXED) + .build() + ); + + final EventResponse response2 = EventResponse.of(lunchEvent, null, null); + final EventResponse response3 = EventResponse.of(admobEvent, eventTime3, rewardList3); + final List result = List.of(response2, response3); + + given(eventService.getEvents(anyLong())) + .willReturn(result); + + // when + + // then + mockMvc + .perform( + RestDocumentationRequestBuilders + .get("/api/v1/event") + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + ) + .andDo(print()) + .andDo(document("api/v1/event/3", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } +} From 61fef9e608b5bade2201cbbee5e96087ffe6606e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 00:04:06 +0900 Subject: [PATCH 083/112] =?UTF-8?q?YEL-199=20[feat]=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B0=B8=EC=97=AC=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 30 +++++++++++ .../event/dto/request/EventJoinRequest.java | 7 +++ .../domain/event/entity/EventInstance.java | 14 ++++- .../exception/EventBadRequestException.java | 11 ++++ .../repository/EventHistoryJpaRepository.java | 14 +++++ .../EventInstanceJpaRepository.java | 8 +++ .../event/repository/EventJpaRepository.java | 3 ++ .../event/repository/EventRepository.java | 13 +++++ .../event/repository/EventRepositoryImpl.java | 28 ++++++++++ .../domain/event/service/EventService.java | 51 +++++++++++++++++++ .../yello/server/global/common/ErrorCode.java | 5 ++ .../server/global/common/SuccessCode.java | 1 + .../global/common/util/ConstantUtil.java | 1 + .../exception/ControllerExceptionAdvice.java | 4 +- 14 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java create mode 100644 src/main/java/com/yello/server/domain/event/exception/EventBadRequestException.java create mode 100644 src/main/java/com/yello/server/domain/event/repository/EventHistoryJpaRepository.java create mode 100644 src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java diff --git a/src/main/java/com/yello/server/domain/event/controller/EventController.java b/src/main/java/com/yello/server/domain/event/controller/EventController.java index a54ca293..51fcc06f 100644 --- a/src/main/java/com/yello/server/domain/event/controller/EventController.java +++ b/src/main/java/com/yello/server/domain/event/controller/EventController.java @@ -1,17 +1,28 @@ 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.SuccessCode.EVENT_JOIN_SUCCESS; import static com.yello.server.global.common.SuccessCode.EVENT_NOTICE_SUCCESS; +import static com.yello.server.global.common.util.ConstantUtil.IdempotencyKeyHeader; import com.fasterxml.jackson.core.JsonProcessingException; +import com.yello.server.domain.event.dto.request.EventJoinRequest; import com.yello.server.domain.event.dto.response.EventResponse; +import com.yello.server.domain.event.exception.EventBadRequestException; import com.yello.server.domain.event.service.EventService; 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 jakarta.servlet.http.HttpServletRequest; import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.val; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,4 +38,23 @@ public BaseResponse> getEvents(@AccessTokenUser User user) t 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); + } + + eventService.joinEvent(user.getId(), uuidIdempotencyKey, request); + return BaseResponse.success(EVENT_JOIN_SUCCESS); + } } diff --git a/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java b/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java new file mode 100644 index 00000000..5b788fc3 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java @@ -0,0 +1,7 @@ +package com.yello.server.domain.event.dto.request; + +public record EventJoinRequest( + String tag +) { + +} diff --git a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java index ced766c9..63362b59 100644 --- a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java +++ b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java @@ -1,6 +1,9 @@ package com.yello.server.domain.event.entity; +import com.yello.server.global.common.entity.OffsetTimeConverter; +import com.yello.server.global.common.entity.ZonedDateTimeConverter; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -9,6 +12,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import java.time.OffsetTime; +import java.time.ZonedDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -38,10 +42,16 @@ public class EventInstance { @OnDelete(action = OnDeleteAction.RESTRICT) private Event event; - @Column + @Column(nullable = false) + @Convert(converter = ZonedDateTimeConverter.class) + private ZonedDateTime instanceDate; + + @Column(columnDefinition = "varchar(30) NOT NULL") + @Convert(converter = OffsetTimeConverter.class) private OffsetTime startTime; - @Column + @Column(columnDefinition = "varchar(30) NOT NULL") + @Convert(converter = OffsetTimeConverter.class) private OffsetTime endTime; @Column diff --git a/src/main/java/com/yello/server/domain/event/exception/EventBadRequestException.java b/src/main/java/com/yello/server/domain/event/exception/EventBadRequestException.java new file mode 100644 index 00000000..caf16dd6 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/exception/EventBadRequestException.java @@ -0,0 +1,11 @@ +package com.yello.server.domain.event.exception; + +import com.yello.server.global.common.ErrorCode; +import com.yello.server.global.exception.CustomException; + +public class EventBadRequestException extends CustomException { + + public EventBadRequestException(ErrorCode error) { + super(error, "[EventBadRequestException] " + error.getMessage()); + } +} diff --git a/src/main/java/com/yello/server/domain/event/repository/EventHistoryJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventHistoryJpaRepository.java new file mode 100644 index 00000000..f3e2369e --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/repository/EventHistoryJpaRepository.java @@ -0,0 +1,14 @@ +package com.yello.server.domain.event.repository; + +import com.yello.server.domain.event.entity.EventHistory; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface EventHistoryJpaRepository extends JpaRepository { + + @Query("select e from EventHistory e where e.idempotencyKey = ?1") + Optional findTopByIdempotencyKey(UUID idempotencyKey); + +} diff --git a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java new file mode 100644 index 00000000..8fc132e1 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java @@ -0,0 +1,8 @@ +package com.yello.server.domain.event.repository; + +import com.yello.server.domain.event.entity.EventInstance; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventInstanceJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/yello/server/domain/event/repository/EventJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventJpaRepository.java index 6633a5ae..97eec741 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventJpaRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventJpaRepository.java @@ -1,8 +1,11 @@ package com.yello.server.domain.event.repository; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventType; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface EventJpaRepository extends JpaRepository { + Optional findTopByTag(EventType type); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java index 76f72a0a..01637cf1 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java @@ -1,13 +1,22 @@ package com.yello.server.domain.event.repository; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventHistory; +import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; +import com.yello.server.domain.event.entity.EventType; import java.util.List; +import java.util.Optional; +import java.util.UUID; public interface EventRepository { + EventHistory save(EventHistory newEventHistory); + + EventInstance save(EventInstance newEventInstance); + List findAll(); List findAllByEventId(Long eventId); @@ -16,5 +25,9 @@ public interface EventRepository { List findRewardAll(); + Event getByTag(EventType tag); + EventReward getRewardById(Long eventRewardId); + + Optional findByIdempotencyKey(UUID idempotencyKey); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java index 7b820d4b..05478737 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java @@ -3,11 +3,16 @@ import static com.yello.server.global.common.ErrorCode.EVENT_NOT_FOUND_EXCEPTION; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventHistory; +import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; +import com.yello.server.domain.event.entity.EventType; import com.yello.server.domain.event.exception.EventNotFoundException; import java.util.List; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -15,11 +20,23 @@ @RequiredArgsConstructor public class EventRepositoryImpl implements EventRepository { + private final EventHistoryJpaRepository eventHistoryJpaRepository; + private final EventInstanceJpaRepository eventInstanceJpaRepository; private final EventJpaRepository eventJpaRepository; private final EventRewardJpaRepository eventRewardJpaRepository; private final EventRewardMappingJpaRepository eventRewardMappingJpaRepository; private final EventTimeJpaRepository eventTimeJpaRepository; + @Override + public EventHistory save(EventHistory newEventHistory) { + return eventHistoryJpaRepository.save(newEventHistory); + } + + @Override + public EventInstance save(EventInstance newEventInstance) { + return eventInstanceJpaRepository.save(newEventInstance); + } + @Override public List findAll() { return eventJpaRepository.findAll(); @@ -40,9 +57,20 @@ public List findRewardAll() { return eventRewardJpaRepository.findAll(); } + @Override + public Event getByTag(EventType tag) { + return eventJpaRepository.findTopByTag(tag) + .orElseThrow(() -> new EventNotFoundException(EVENT_NOT_FOUND_EXCEPTION)); + } + @Override public EventReward getRewardById(Long eventRewardId) { return eventRewardJpaRepository.findById(eventRewardId) .orElseThrow(() -> new EventNotFoundException(EVENT_NOT_FOUND_EXCEPTION)); } + + @Override + public Optional findByIdempotencyKey(UUID idempotencyKey) { + return eventHistoryJpaRepository.findTopByIdempotencyKey(idempotencyKey); + } } diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index bbc23de3..e63db204 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -1,12 +1,20 @@ package com.yello.server.domain.event.service; +import static com.yello.server.global.common.ErrorCode.EVENT_DATE_BAD_REQUEST_EXCEPTION; +import static com.yello.server.global.common.ErrorCode.EVENT_TIME_BAD_REQUEST_EXCEPTION; +import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_CONFLICT_EXCEPTION; import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; import com.fasterxml.jackson.core.JsonProcessingException; +import com.yello.server.domain.event.dto.request.EventJoinRequest; import com.yello.server.domain.event.dto.response.EventResponse; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventHistory; +import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; +import com.yello.server.domain.event.entity.EventType; +import com.yello.server.domain.event.exception.EventBadRequestException; import com.yello.server.domain.event.repository.EventRepository; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.repository.UserRepository; @@ -14,6 +22,8 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,4 +65,45 @@ public List getEvents(Long userId) throws JsonProcessingException return result; } + + @Transactional + public void joinEvent(Long userId, UUID uuidIdempotencyKey, EventJoinRequest request) { + // exception + final User user = userRepository.getById(userId); + final Optional eventHistory = eventRepository.findByIdempotencyKey(uuidIdempotencyKey); + if (eventHistory.isPresent()) { + throw new EventBadRequestException(IDEMPOTENCY_KEY_CONFLICT_EXCEPTION); + } + + // logic + ZonedDateTime now = ZonedDateTime.now(GlobalZoneId); + OffsetTime nowTime = now.toOffsetDateTime().toOffsetTime(); + final Event event = eventRepository.getByTag(EventType.fromCode(request.tag())); + + if (!(now.isAfter(event.getStartDate()) && now.isBefore(event.getEndDate()))) { + throw new EventBadRequestException(EVENT_DATE_BAD_REQUEST_EXCEPTION); + } + + final List eventTimeList = eventRepository.findAllByEventId(event.getId()).stream() + .filter(eventTime -> nowTime.isAfter(eventTime.getStartTime()) && nowTime.isBefore(eventTime.getEndTime())) + .toList(); + if (eventTimeList.isEmpty()) { + throw new EventBadRequestException(EVENT_TIME_BAD_REQUEST_EXCEPTION); + } + EventTime eventTime = eventTimeList.get(0); + + final EventHistory newEventHistory = eventRepository.save(EventHistory.builder() + .idempotencyKey(uuidIdempotencyKey) + .user(user) + .build()); + + eventRepository.save(EventInstance.builder() + .eventHistory(newEventHistory) + .event(event) + .instanceDate(now) + .startTime(eventTime.getStartTime()) + .endTime(eventTime.getEndTime()) + .remainEventCount(eventTime.getRewardCount()) + .build()); + } } diff --git a/src/main/java/com/yello/server/global/common/ErrorCode.java b/src/main/java/com/yello/server/global/common/ErrorCode.java index 440774b2..37288c7d 100644 --- a/src/main/java/com/yello/server/global/common/ErrorCode.java +++ b/src/main/java/com/yello/server/global/common/ErrorCode.java @@ -40,6 +40,10 @@ public enum ErrorCode { USER_DATA_INVALID_ARGUMENT_EXCEPTION(BAD_REQUEST, "입력한 유저 데이터의 값이 올바르지 않습니다."), ENUM_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "존재하지 않는 열거형 타입입니다."), PROBABILITY_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "확률의 합이 100이 아닙니다."), + IDEMPOTENCY_KEY_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "멱등성 키를 헤더에 명시되어 있지 않습니다."), + IDEMPOTENCY_KEY_INVALID_FORM_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "멱등성 키가 유효한 uuid4 형식이 아닙니다."), + EVENT_DATE_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 날짜가 아닙니다."), + EVENT_TIME_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 시간이 아닙니다."), /** * 401 UNAUTHORIZED @@ -110,6 +114,7 @@ public enum ErrorCode { DEVICE_TOKEN_CONFLICT_USER_EXCEPTION(CONFLICT, "이미 존재하는 deviceToken 입니다."), SUBSCRIBE_ACTIVE_EXCEPTION(CONFLICT, "이미 옐로플러스 구독한 유저입니다."), GOOGLE_SUBSCRIPTIONS_SUBSCRIPTION_EXCEPTION(CONFLICT, "이미 적용한 영수증입니다."), + IDEMPOTENCY_KEY_CONFLICT_EXCEPTION(CONFLICT, "이미 존재하는 멱등키 입니다."), /** * 500 INTERNAL SERVER ERROR diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 34d94520..8220d4e9 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -60,6 +60,7 @@ public enum SuccessCode { EVENT_CREATE_ADMIN_SUCCESS(OK, "어드민 권한으로 이벤트 생성에 성공하였습니다."), EVENT_NOTICE_SUCCESS(OK, "이벤트 전체 조회에 성공하였습니다."), EVENT_REWARD_CREATE_ADMIN_SUCCESS(OK, "어드민 권한으로 이벤트 보상 생성에 성공하였습니다."), + EVENT_JOIN_SUCCESS(OK, "이벤트 참여에 성공하였습니다."), /** * 201 CREATED 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 36140c33..f67a0d7c 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 @@ -5,6 +5,7 @@ public class ConstantUtil { public static final ZoneId GlobalZoneId = ZoneId.of("Asia/Seoul"); + public static final String IdempotencyKeyHeader = "IdempotencyKey"; public static final int RANDOM_COUNT = 4; public static final int VOTE_COUNT = 8; public static final long TIMER_TIME = 2400L; diff --git a/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java b/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java index 1e54d6aa..8c5e9404 100644 --- a/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java +++ b/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java @@ -20,6 +20,7 @@ import com.yello.server.domain.authorization.exception.NotSignedInException; import com.yello.server.domain.authorization.exception.NotValidTokenForbiddenException; import com.yello.server.domain.authorization.exception.OAuthException; +import com.yello.server.domain.event.exception.EventBadRequestException; import com.yello.server.domain.friend.exception.FriendException; import com.yello.server.domain.friend.exception.FriendNotFoundException; import com.yello.server.domain.group.exception.GroupNotFoundException; @@ -141,7 +142,8 @@ public ResponseEntity BadRequestException( AppleBadRequestException.class, UserAdminBadRequestException.class, EnumIllegalArgumentException.class, - IllegalArgumentException.class + IllegalArgumentException.class, + EventBadRequestException.class }) public ResponseEntity BadRequestException(CustomException exception) { return ResponseEntity.status(BAD_REQUEST) From 0d377a275d62e0a622cbd9639253fd9daa36e317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 01:17:24 +0900 Subject: [PATCH 084/112] =?UTF-8?q?YEL-199=20[feat]=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B0=B8=EC=97=AC=20API,=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=B4=EC=83=81=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/create-event-history.adoc | 31 + src/docs/asciidoc/index.adoc | 6 +- src/docs/asciidoc/reward-event.adoc | 28 + .../event/controller/EventController.java | 21 + .../event/dto/request/EventJoinRequest.java | 3 + .../dto/response/EventRewardResponse.java | 13 + .../domain/event/entity/EventInstance.java | 4 + .../event/entity/EventInstanceReward.java | 23 +- .../EventInstanceJpaRepository.java | 5 + .../EventInstanceRewardJpaRepository.java | 8 + .../event/repository/EventRepository.java | 9 +- .../event/repository/EventRepositoryImpl.java | 14 +- .../domain/event/service/EventService.java | 54 +- .../yello/server/global/common/ErrorCode.java | 2 + .../server/global/common/SuccessCode.java | 1 + .../static/docs/create-event-history.html | 555 ++++++++++++++++++ src/main/resources/static/docs/index.html | 6 + .../resources/static/docs/reward-event.html | 547 +++++++++++++++++ .../event/medium/EventControllerTest.java | 69 +++ 19 files changed, 1377 insertions(+), 22 deletions(-) create mode 100644 src/docs/asciidoc/create-event-history.adoc create mode 100644 src/docs/asciidoc/reward-event.adoc create mode 100644 src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java create mode 100644 src/main/java/com/yello/server/domain/event/repository/EventInstanceRewardJpaRepository.java create mode 100644 src/main/resources/static/docs/create-event-history.html create mode 100644 src/main/resources/static/docs/reward-event.html diff --git a/src/docs/asciidoc/create-event-history.adoc b/src/docs/asciidoc/create-event-history.adoc new file mode 100644 index 00000000..00ccbcca --- /dev/null +++ b/src/docs/asciidoc/create-event-history.adoc @@ -0,0 +1,31 @@ +: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" + +=== NOTE + +- Header에 무작위한 UUID4 값을 넣어주세요 +* 예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87 +- 주의사항 +* tag 요청값에 해당하는 이벤트의 날짜와 시간이 모두 유효해야함. +(뭔가 에러나면 서버요청 ㄱㄱ) +* 같은 멱등성키를 2번 요청하면, 400번 에러. +- ADMOB +* 광고를 시청하기 전, 해당 API를 호출. +* ADMOB 서버에 ServerSideVerificationOptions의 customData에 동일한 멱등성 키를 넘겨주세요. + +=== CHANGELOG + +- 2024.02.07 릴리즈 \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index d2d6d8f3..24ffc13a 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -110,4 +110,8 @@ === Event API -* 🆕 link:find-event.html[공지 조회, 2024-02-06] \ No newline at end of file +* 🆕 link:find-event.html[공지 조회, 2024-02-06] + +* 🆕 link:create-event-history.html[이벤트 참여, 2024-02-07] + +* 🆕 link:reward-event.html[이벤트 보상, 2024-02-07] \ No newline at end of file diff --git a/src/docs/asciidoc/reward-event.adoc b/src/docs/asciidoc/reward-event.adoc new file mode 100644 index 00000000..40113680 --- /dev/null +++ b/src/docs/asciidoc/reward-event.adoc @@ -0,0 +1,28 @@ +:reproducible: +== 공지 조회 + +=== 요청 + +include::{snippets}/api/v1/event/reward/1/http-request.adoc[] + +=== 응답 + +include::{snippets}/api/v1/event/reward/1/http-response.adoc[] + +=== 주의 + +=== NOTE + +- Header에 이벤트 참여에 입력했던 멱등키를 넣어주세요. +* 예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87 +- ADMOB +* 광고를 시청한 후, 해당 API를 호출. +* ADMOB 서버에 ServerSideVerificationOptions의 customData에 동일한 멱등성 키를 넘겨주세요. +* 이벤트 참여에 넣어준 멱등키와 동일하여 검증이되면, 정상 보상, 그렇지 않으면 이상으로 판단하여 400번대 에러 +- 보상 +* 지금 랜덤 보상이 구현안되있는데, 차후 서버에서 보상 처리하겠음. +클라측에서는 View과 종속적인 값만 처리하면 됨. + +=== CHANGELOG + +- 2024.02.07 릴리즈 \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/event/controller/EventController.java b/src/main/java/com/yello/server/domain/event/controller/EventController.java index 51fcc06f..618d7d92 100644 --- a/src/main/java/com/yello/server/domain/event/controller/EventController.java +++ b/src/main/java/com/yello/server/domain/event/controller/EventController.java @@ -4,11 +4,13 @@ import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_INVALID_FORM_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.util.ConstantUtil.IdempotencyKeyHeader; import com.fasterxml.jackson.core.JsonProcessingException; 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; import com.yello.server.domain.event.exception.EventBadRequestException; import com.yello.server.domain.event.service.EventService; import com.yello.server.domain.user.entity.User; @@ -57,4 +59,23 @@ public BaseResponse joinEvent(@AccessTokenUser User user, HttpServletRequest req eventService.joinEvent(user.getId(), uuidIdempotencyKey, request); return BaseResponse.success(EVENT_JOIN_SUCCESS); } + + @PostMapping("/v1/event/reward") + public BaseResponse rewardEvent(@AccessTokenUser User user, + HttpServletRequest requestServlet) { + 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); + } + + val data = eventService.rewardEvent(user.getId(), uuidIdempotencyKey); + return BaseResponse.success(EVENT_REWARD_SUCCESS, data); + } } diff --git a/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java b/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java index 5b788fc3..446a71de 100644 --- a/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java +++ b/src/main/java/com/yello/server/domain/event/dto/request/EventJoinRequest.java @@ -1,5 +1,8 @@ package com.yello.server.domain.event.dto.request; +import lombok.Builder; + +@Builder public record EventJoinRequest( String tag ) { diff --git a/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java b/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java new file mode 100644 index 00000000..39ddb706 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java @@ -0,0 +1,13 @@ +package com.yello.server.domain.event.dto.response; + +import lombok.Builder; + +@Builder +public record EventRewardResponse( + String rewardTag, + Long rewardValue, + String rewardTitle, + String rewardImage +) { + +} diff --git a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java index 63362b59..1b2c5764 100644 --- a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java +++ b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java @@ -57,4 +57,8 @@ public class EventInstance { @Column @Builder.Default private Long remainEventCount = 0L; + + public void subRemainEventCount(Long amount) { + this.remainEventCount -= amount; + } } diff --git a/src/main/java/com/yello/server/domain/event/entity/EventInstanceReward.java b/src/main/java/com/yello/server/domain/event/entity/EventInstanceReward.java index d2a11d89..4d8cf899 100644 --- a/src/main/java/com/yello/server/domain/event/entity/EventInstanceReward.java +++ b/src/main/java/com/yello/server/domain/event/entity/EventInstanceReward.java @@ -1,7 +1,7 @@ package com.yello.server.domain.event.entity; +import com.yello.server.global.common.dto.AuditingTimeEntity; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -22,7 +22,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class EventInstanceReward { +public class EventInstanceReward extends AuditingTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -33,28 +33,17 @@ public class EventInstanceReward { @OnDelete(action = OnDeleteAction.CASCADE) private EventInstance eventInstance; - @Column(columnDefinition = "int NOT NULL CHECK (event_reward_probability BETWEEN 0 and 100)") - private Integer eventRewardProbability; - @Column(nullable = false) private String rewardTag; + @Column(nullable = false) + @Builder.Default + private Long rewardValue = 0L; + @Column private String rewardTitle; @Column private String rewardImage; - @Column(nullable = false) - @Convert(converter = EventRewardRandomTypeConverter.class) - private EventRewardRandomType randomTag; - - @Column(nullable = false) - private Long maxRewardValue; - - @Column(nullable = false) - private Long minRewardValue; - - @Column(nullable = false) - private Long sumRewardValue; } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java index 8fc132e1..0e3891e7 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java @@ -1,8 +1,13 @@ package com.yello.server.domain.event.repository; +import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface EventInstanceJpaRepository extends JpaRepository { + @Query("select e from EventInstance e where e.eventHistory = ?1") + Optional findTopByEventHistory(EventHistory eventHistory); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventInstanceRewardJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventInstanceRewardJpaRepository.java new file mode 100644 index 00000000..155ac578 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/repository/EventInstanceRewardJpaRepository.java @@ -0,0 +1,8 @@ +package com.yello.server.domain.event.repository; + +import com.yello.server.domain.event.entity.EventInstanceReward; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventInstanceRewardJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java index 01637cf1..dc6aa904 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java @@ -3,6 +3,7 @@ import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import com.yello.server.domain.event.entity.EventInstanceReward; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -16,7 +17,9 @@ public interface EventRepository { EventHistory save(EventHistory newEventHistory); EventInstance save(EventInstance newEventInstance); - + + EventInstanceReward save(EventInstanceReward newEventInstanceReward); + List findAll(); List findAllByEventId(Long eventId); @@ -29,5 +32,7 @@ public interface EventRepository { EventReward getRewardById(Long eventRewardId); - Optional findByIdempotencyKey(UUID idempotencyKey); + Optional findHistoryByIdempotencyKey(UUID idempotencyKey); + + Optional findInstanceByEventHistory(EventHistory eventHistory); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java index 05478737..5b451b7e 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java @@ -5,6 +5,7 @@ import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import com.yello.server.domain.event.entity.EventInstanceReward; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -22,6 +23,7 @@ public class EventRepositoryImpl implements EventRepository { private final EventHistoryJpaRepository eventHistoryJpaRepository; private final EventInstanceJpaRepository eventInstanceJpaRepository; + private final EventInstanceRewardJpaRepository eventInstanceRewardJpaRepository; private final EventJpaRepository eventJpaRepository; private final EventRewardJpaRepository eventRewardJpaRepository; private final EventRewardMappingJpaRepository eventRewardMappingJpaRepository; @@ -37,6 +39,11 @@ public EventInstance save(EventInstance newEventInstance) { return eventInstanceJpaRepository.save(newEventInstance); } + @Override + public EventInstanceReward save(EventInstanceReward newEventInstanceReward) { + return eventInstanceRewardJpaRepository.save(newEventInstanceReward); + } + @Override public List findAll() { return eventJpaRepository.findAll(); @@ -70,7 +77,12 @@ public EventReward getRewardById(Long eventRewardId) { } @Override - public Optional findByIdempotencyKey(UUID idempotencyKey) { + public Optional findHistoryByIdempotencyKey(UUID idempotencyKey) { return eventHistoryJpaRepository.findTopByIdempotencyKey(idempotencyKey); } + + @Override + public Optional findInstanceByEventHistory(EventHistory eventHistory) { + return eventInstanceJpaRepository.findTopByEventHistory(eventHistory); + } } diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index e63db204..c86957e1 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -1,20 +1,25 @@ package com.yello.server.domain.event.service; +import static com.yello.server.global.common.ErrorCode.EVENT_COUNT_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_DATE_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_TIME_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_CONFLICT_EXCEPTION; +import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION; import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; import com.fasterxml.jackson.core.JsonProcessingException; 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; import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import com.yello.server.domain.event.entity.EventInstanceReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.entity.EventType; import com.yello.server.domain.event.exception.EventBadRequestException; +import com.yello.server.domain.event.exception.EventNotFoundException; import com.yello.server.domain.event.repository.EventRepository; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.repository.UserRepository; @@ -70,7 +75,7 @@ public List getEvents(Long userId) throws JsonProcessingException public void joinEvent(Long userId, UUID uuidIdempotencyKey, EventJoinRequest request) { // exception final User user = userRepository.getById(userId); - final Optional eventHistory = eventRepository.findByIdempotencyKey(uuidIdempotencyKey); + final Optional eventHistory = eventRepository.findHistoryByIdempotencyKey(uuidIdempotencyKey); if (eventHistory.isPresent()) { throw new EventBadRequestException(IDEMPOTENCY_KEY_CONFLICT_EXCEPTION); } @@ -106,4 +111,51 @@ public void joinEvent(Long userId, UUID uuidIdempotencyKey, EventJoinRequest req .remainEventCount(eventTime.getRewardCount()) .build()); } + + @Transactional + public EventRewardResponse rewardEvent(Long userId, UUID uuidIdempotencyKey) { + // exception + final User user = userRepository.getById(userId); + final Optional eventHistory = eventRepository.findHistoryByIdempotencyKey(uuidIdempotencyKey); + if (eventHistory.isEmpty()) { + throw new EventNotFoundException(IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION); + } + final Optional eventInstance = eventRepository.findInstanceByEventHistory(eventHistory.get()); + + // logic + ZonedDateTime now = ZonedDateTime.now(GlobalZoneId); + OffsetTime nowTime = now.toOffsetDateTime().toOffsetTime(); + + if (!now.toLocalDate().isEqual(eventInstance.get().getInstanceDate().toLocalDate())) { + throw new EventBadRequestException(EVENT_DATE_BAD_REQUEST_EXCEPTION); + } + + if (!(nowTime.isAfter(eventInstance.get().getStartTime()) && nowTime.isBefore( + eventInstance.get().getEndTime()))) { + throw new EventBadRequestException(EVENT_TIME_BAD_REQUEST_EXCEPTION); + } + + if (eventInstance.get().getRemainEventCount() <= 0L) { + throw new EventBadRequestException(EVENT_COUNT_BAD_REQUEST_EXCEPTION); + } + + eventInstance.get().subRemainEventCount(1L); + eventRepository.save(EventInstanceReward.builder() + .eventInstance(eventInstance.get()) + .rewardTag("POINT") + .rewardValue(200L) + .rewardTitle("200 포인트를 얻었어요!") + .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build()); + /** + * TODO 랜덤값 구현 + */ + user.addPoint(200); + return EventRewardResponse.builder() + .rewardTag("POINT") + .rewardValue(200L) + .rewardTitle("200 포인트를 얻었어요!") + .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + } } diff --git a/src/main/java/com/yello/server/global/common/ErrorCode.java b/src/main/java/com/yello/server/global/common/ErrorCode.java index 37288c7d..9fbe2dbb 100644 --- a/src/main/java/com/yello/server/global/common/ErrorCode.java +++ b/src/main/java/com/yello/server/global/common/ErrorCode.java @@ -44,6 +44,7 @@ public enum ErrorCode { IDEMPOTENCY_KEY_INVALID_FORM_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "멱등성 키가 유효한 uuid4 형식이 아닙니다."), EVENT_DATE_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 날짜가 아닙니다."), EVENT_TIME_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 시간이 아닙니다."), + EVENT_COUNT_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 보상 횟수가 전부 소진되었습니다."), /** * 401 UNAUTHORIZED @@ -104,6 +105,7 @@ public enum ErrorCode { EVENT_REWARD_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 EventReward가 존재하지 않습니다."), EVENT_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 Event가 존재하지 않습니다."), EVENT_TIME_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 EventTime가 존재하지 않습니다."), + IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION(NOT_FOUND, "멱등키의 이벤트가 존재하지 않습니다. 이벤트 참여를 먼저 해주세요"), /** * 409 CONFLICT diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 8220d4e9..587db04e 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -61,6 +61,7 @@ public enum SuccessCode { EVENT_NOTICE_SUCCESS(OK, "이벤트 전체 조회에 성공하였습니다."), EVENT_REWARD_CREATE_ADMIN_SUCCESS(OK, "어드민 권한으로 이벤트 보상 생성에 성공하였습니다."), EVENT_JOIN_SUCCESS(OK, "이벤트 참여에 성공하였습니다."), + EVENT_REWARD_SUCCESS(OK, "이벤트 보상에 성공하였습니다."), /** * 201 CREATED diff --git a/src/main/resources/static/docs/create-event-history.html b/src/main/resources/static/docs/create-event-history.html new file mode 100644 index 00000000..a66be8a2 --- /dev/null +++ b/src/main/resources/static/docs/create-event-history.html @@ -0,0 +1,555 @@ + + + + + + + +공지 조회 + + + + + +
    +
    +

    공지 조회

    +
    +
    +

    요청

    +
    +
    +
    POST /api/v1/event HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer your-access-token
    +IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87
    +Content-Length: 27
    +
    +{
    +  "tag" : "LUNCH_EVENT"
    +}
    +
    +
    +
    +

    Unresolved directive in create-event-history.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/event/join/2/http-request.adoc[]

    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 참여에 성공하였습니다."
    +}
    +
    +
    +
    +
    +

    주의

    +
    +
      +
    • +

      "tag": "LUNCH_EVENT" | "ADMOB"

      +
    • +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      Header에 무작위한 UUID4 값을 넣어주세요

      +
      +
        +
      • +

        예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87

        +
      • +
      +
      +
    • +
    • +

      주의사항

      +
      +
        +
      • +

        tag 요청값에 해당하는 이벤트의 날짜와 시간이 모두 유효해야함. +(뭔가 에러나면 서버요청 ㄱㄱ)

        +
      • +
      • +

        같은 멱등성키를 2번 요청하면, 400번 에러.

        +
      • +
      +
      +
    • +
    • +

      ADMOB

      +
      +
        +
      • +

        광고를 시청하기 전, 해당 API를 호출.

        +
      • +
      • +

        ADMOB 서버에 ServerSideVerificationOptions의 customData에 동일한 멱등성 키를 넘겨주세요.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.02.07 릴리즈

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index bd302be3..39f4f70d 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -652,6 +652,12 @@

    Event API

  • 🆕 공지 조회, 2024-02-06

  • +
  • +

    🆕 이벤트 참여, 2024-02-07

    +
  • +
  • +

    🆕 이벤트 보상, 2024-02-07

    +
  • diff --git a/src/main/resources/static/docs/reward-event.html b/src/main/resources/static/docs/reward-event.html new file mode 100644 index 00000000..f8bf3973 --- /dev/null +++ b/src/main/resources/static/docs/reward-event.html @@ -0,0 +1,547 @@ + + + + + + + +공지 조회 + + + + + +
    +
    +

    공지 조회

    +
    +
    +

    요청

    +
    +
    +
    POST /api/v1/event/reward HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer your-access-token
    +IdempotencyKey: 87552f7c-9b62-4b12-b567-1bd062b09288
    +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 보상에 성공하였습니다.",
    +  "data" : {
    +    "rewardTag" : "TICKET",
    +    "rewardValue" : 200,
    +    "rewardTitle" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg",
    +    "rewardImage" : "200 포인트를 얻었어요!"
    +  }
    +}
    +
    +
    +
    +
    +

    주의

    + +
    +
    +

    NOTE

    +
    +
      +
    • +

      Header에 이벤트 참여에 입력했던 멱등키를 넣어주세요.

      +
      +
        +
      • +

        예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87

        +
      • +
      +
      +
    • +
    • +

      ADMOB

      +
      +
        +
      • +

        광고를 시청한 후, 해당 API를 호출.

        +
      • +
      • +

        ADMOB 서버에 ServerSideVerificationOptions의 customData에 동일한 멱등성 키를 넘겨주세요.

        +
      • +
      • +

        이벤트 참여에 넣어준 멱등키와 동일하여 검증이되면, 정상 보상, 그렇지 않으면 이상으로 판단하여 400번대 에러

        +
      • +
      +
      +
    • +
    • +

      보상

      +
      +
        +
      • +

        지금 랜덤 보상이 구현안되있는데, 차후 서버에서 보상 처리하겠음. +클라측에서는 View과 종속적인 값만 처리하면 됨.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.02.07 릴리즈

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java index ea3f9ef1..4d88c3d9 100644 --- a/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java +++ b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java @@ -5,23 +5,30 @@ import static com.yello.server.domain.event.entity.EventType.ADMOB; import static com.yello.server.domain.event.entity.EventType.LUNCH_EVENT; import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import com.fasterxml.jackson.databind.ObjectMapper; import com.yello.server.domain.authorization.filter.JwtExceptionFilter; import com.yello.server.domain.authorization.filter.JwtFilter; import com.yello.server.domain.event.controller.EventController; +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; import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.service.EventService; +import com.yello.server.global.common.util.ConstantUtil; import com.yello.server.global.exception.ControllerExceptionAdvice; import com.yello.server.util.TestDataEntityUtil; import com.yello.server.util.TestDataUtil; @@ -30,6 +37,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; @@ -41,6 +49,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.operation.preprocess.OperationPreprocessor; import org.springframework.restdocs.operation.preprocess.Preprocessors; @@ -361,4 +370,64 @@ class EventControllerTest { )) .andExpect(MockMvcResultMatchers.status().isOk()); } + + @Test + void 이벤트_참여에_성공합니다1() throws Exception { + // given + final String idempotencyKey = "0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87"; + final EventJoinRequest request = EventJoinRequest.builder() + .tag(LUNCH_EVENT.getInitial()) + .build(); + + doNothing() + .when(eventService) + .joinEvent(any(Long.class), any(UUID.class), eq(request)); + + // when + + // then + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/event") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .header(ConstantUtil.IdempotencyKeyHeader, idempotencyKey) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("api/v1/event/join/1", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @Test + void 이벤트_참여에_성공합니다2() throws Exception { + // given + final String idempotencyKey = "87552f7c-9b62-4b12-b567-1bd062b09288"; + EventRewardResponse response = EventRewardResponse.builder() + .rewardTag("TICKET") + .rewardValue(200L) + .rewardImage("200 포인트를 얻었어요!") + .rewardTitle("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .build(); + + given(eventService.rewardEvent(any(Long.class), any(UUID.class))) + .willReturn(response); + // when + + // then + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/event/reward") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .header(ConstantUtil.IdempotencyKeyHeader, idempotencyKey) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("api/v1/event/reward/1", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } } From 71259b63042c283985b64bcacc39bce03867764a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 18:00:03 +0900 Subject: [PATCH 085/112] =?UTF-8?q?YEL-211=20[feat]=20boolean=20=EC=97=B0?= =?UTF-8?q?=EC=82=B0=20=EC=BF=BC=EB=A6=AC=EC=9D=98=20is=EB=A5=BC=20=3D?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/vote/repository/VoteJpaRepository.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/yello/server/domain/vote/repository/VoteJpaRepository.java b/src/main/java/com/yello/server/domain/vote/repository/VoteJpaRepository.java index 757c8fde..81753bf9 100644 --- a/src/main/java/com/yello/server/domain/vote/repository/VoteJpaRepository.java +++ b/src/main/java/com/yello/server/domain/vote/repository/VoteJpaRepository.java @@ -16,14 +16,14 @@ public interface VoteJpaRepository extends JpaRepository { + "where v.receiver.id = :userId " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null " - + "and v.isRead is false") + + "and v.isRead = false") Integer countUnreadByReceiverUserId(@Param("userId") Long userId); @Query("select count(v) from Vote v " + "where v.receiver.deviceToken = :deviceToken " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null " - + "and v.isRead is false") + + "and v.isRead = false") Integer countUnreadByReceiverDeviceToken(@Param("deviceToken") String deviceToken); @Query("select v from Vote v " @@ -50,22 +50,22 @@ public interface VoteJpaRepository extends JpaRepository { @Query("select count(v) from Vote v " + "where v.receiver.id = :userId " - + "and v.isRead is true " + + "and v.isRead = true " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null") Integer countReadByReceiverUserId(@Param("userId") Long userId); @Query("select count(v) from Vote v " + "where v.receiver.id = :userId " - + "and v.isRead is true " - + "and v.isAnswerRevealed is true " + + "and v.isRead = true " + + "and v.isAnswerRevealed = true " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null") Integer countOpenKeywordByReceiverUserId(@Param("userId") Long userId); @Query("select count(v) from Vote v " + "where v.receiver.id = :userId " - + "and v.isRead is true " + + "and v.isRead = true " + "and v.nameHint in (0,1) " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null") @@ -74,7 +74,7 @@ public interface VoteJpaRepository extends JpaRepository { @Query("select count(v) from Vote v " + "where v.receiver.id = :userId " - + "and v.isRead is true " + + "and v.isRead = true " + "and v.nameHint = -2 " + "and v.receiver.deletedAt is null " + "and v.sender.deletedAt is null") From 6c4bd7383574bfefd44410d80fe1218ddbb89726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 20:48:23 +0900 Subject: [PATCH 086/112] =?UTF-8?q?YEL-211=20[feat]=20remainCount=20?= =?UTF-8?q?=EB=8B=A4=20=EC=86=8C=EC=A7=84=ED=95=98=EB=A9=B4,=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B0=B8=EC=97=AC=EA=B0=80=EB=8A=A5=20nul?= =?UTF-8?q?l=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventInstanceJpaRepository.java | 6 +++++ .../event/repository/EventRepository.java | 3 +++ .../event/repository/EventRepositoryImpl.java | 6 +++++ .../domain/event/service/EventService.java | 24 +++++++++++++++++-- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java index 0e3891e7..ee417649 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java @@ -1,7 +1,10 @@ package com.yello.server.domain.event.repository; +import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import com.yello.server.domain.user.entity.User; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,4 +13,7 @@ public interface EventInstanceJpaRepository extends JpaRepository findTopByEventHistory(EventHistory eventHistory); + + @Query("select e from EventInstance e, EventHistory eh where e.eventHistory = eh and e.event = ?1 and eh.user = ?2") + List findAllByEventAndUser(Event event, User user); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java index dc6aa904..defb3cfd 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java @@ -8,6 +8,7 @@ import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.entity.EventType; +import com.yello.server.domain.user.entity.User; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -28,6 +29,8 @@ public interface EventRepository { List findRewardAll(); + List findInstanceAllByEventAndUser(Event event, User user); + Event getByTag(EventType tag); EventReward getRewardById(Long eventRewardId); diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java index 5b451b7e..1e7e4c9c 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java @@ -11,6 +11,7 @@ import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.entity.EventType; import com.yello.server.domain.event.exception.EventNotFoundException; +import com.yello.server.domain.user.entity.User; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -64,6 +65,11 @@ public List findRewardAll() { return eventRewardJpaRepository.findAll(); } + @Override + public List findInstanceAllByEventAndUser(Event event, User user) { + return eventInstanceJpaRepository.findAllByEventAndUser(event, user); + } + @Override public Event getByTag(EventType tag) { return eventJpaRepository.findTopByTag(tag) diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index c86957e1..34fc7752 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -50,20 +50,40 @@ public List getEvents(Long userId) throws JsonProcessingException OffsetTime nowTime = now.toOffsetDateTime().toOffsetTime(); List result = new ArrayList<>(); + // 현재 날짜에 유효한 이벤트. final List eventList = eventRepository.findAll().stream() .filter(event -> now.isAfter(event.getStartDate()) && now.isBefore(event.getEndDate())) .toList(); for (Event event : eventList) { + // 현재 시각에 유효한 이벤트 시간대 final List eventTimeList = eventRepository.findAllByEventId(event.getId()).stream() .filter( eventTime -> nowTime.isAfter(eventTime.getStartTime()) && nowTime.isBefore(eventTime.getEndTime()) ) .toList(); - final EventTime eventTime = eventTimeList.isEmpty() ? null : eventTimeList.get(0); + /** + * EventTime을 바꾸면, EventInstance가 바뀌지 않음. + * 이벤트 시간대가 바뀌면, 내일부터 적용된다고 말하기 + */ + // 이벤트 참가한 이력이 오늘이고, 현재 시각이 이벤트 시간에 유효하고, 남은 보상 카운트가 0인 이력 + final List eventInstanceList = eventRepository.findInstanceAllByEventAndUser(event, user) + .stream() + .filter(eventInstance -> eventInstance.getInstanceDate().isAfter(event.getStartDate()) + && eventInstance.getInstanceDate().isBefore(event.getEndDate()) + && nowTime.isAfter(eventInstance.getStartTime()) && nowTime.isBefore(eventInstance.getEndTime()) + && eventInstance.getRemainEventCount() == 0) + .toList(); + + // 해당 변수로 클라가 이벤트를 띄울지 판단하게 됨. + // 이벤트 시간대가 있고, 보상 카운트가 0인 참여이력이 없을 때 + // k번 이벤트 참여는 보상 카운트로 구현한다. + boolean isEventAvailable = !eventTimeList.isEmpty() && eventInstanceList.isEmpty(); + + final EventTime eventTime = isEventAvailable ? eventTimeList.get(0) : null; final List eventRewardMappingList = - eventTimeList.isEmpty() ? null : eventRepository.findAllByEventTimeId(eventTime.getId()); + isEventAvailable ? eventRepository.findAllByEventTimeId(eventTime.getId()) : null; result.add(EventResponse.of(event, eventTime, eventRewardMappingList)); } From e4edc7f2ecabce743d6196d7eb7b65b7afc51ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 23:45:12 +0900 Subject: [PATCH 087/112] =?UTF-8?q?YEL-211=20[feat]=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/AdminEventCreateRequest.java | 3 +- .../admin/repository/AdminRepository.java | 3 + .../admin/repository/AdminRepositoryImpl.java | 10 ++ .../domain/admin/service/AdminService.java | 4 +- .../event/controller/EventController.java | 2 +- .../event/dto/response/EventResponse.java | 5 +- .../dto/response/EventRewardResponse.java | 9 + .../domain/event/entity/EventInstance.java | 16 +- .../domain/event/entity/EventRandom.java | 33 ++++ .../event/entity/EventRewardMapping.java | 11 +- .../event/entity/EventRewardRandomType.java | 31 ---- .../EventRewardRandomTypeConverter.java | 28 --- .../EventInstanceJpaRepository.java | 6 +- .../repository/EventRandomJpaRepository.java | 10 ++ .../event/repository/EventRepository.java | 5 +- .../event/repository/EventRepositoryImpl.java | 13 +- .../domain/event/service/EventService.java | 169 ++++++++++++++---- .../yello/server/global/common/ErrorCode.java | 1 + .../exception/ControllerExceptionAdvice.java | 4 +- 19 files changed, 234 insertions(+), 129 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/event/entity/EventRandom.java delete mode 100644 src/main/java/com/yello/server/domain/event/entity/EventRewardRandomType.java delete mode 100644 src/main/java/com/yello/server/domain/event/entity/EventRewardRandomTypeConverter.java create mode 100644 src/main/java/com/yello/server/domain/event/repository/EventRandomJpaRepository.java diff --git a/src/main/java/com/yello/server/domain/admin/dto/request/AdminEventCreateRequest.java b/src/main/java/com/yello/server/domain/admin/dto/request/AdminEventCreateRequest.java index 058ae120..98702e00 100644 --- a/src/main/java/com/yello/server/domain/admin/dto/request/AdminEventCreateRequest.java +++ b/src/main/java/com/yello/server/domain/admin/dto/request/AdminEventCreateRequest.java @@ -1,6 +1,5 @@ package com.yello.server.domain.admin.dto.request; -import com.yello.server.domain.event.entity.EventRewardRandomType; import com.yello.server.domain.event.entity.EventType; import java.time.OffsetTime; import java.util.List; @@ -27,7 +26,7 @@ public record EventRewardVO( public record EventRewardItemVO( String tag, Integer eventRewardProbability, - EventRewardRandomType randomTag + String randomTag ) { } diff --git a/src/main/java/com/yello/server/domain/admin/repository/AdminRepository.java b/src/main/java/com/yello/server/domain/admin/repository/AdminRepository.java index 758b6e05..f82e1fce 100644 --- a/src/main/java/com/yello/server/domain/admin/repository/AdminRepository.java +++ b/src/main/java/com/yello/server/domain/admin/repository/AdminRepository.java @@ -1,6 +1,7 @@ package com.yello.server.domain.admin.repository; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -20,4 +21,6 @@ public interface AdminRepository { EventTime getEventTimeById(Long eventTimeId); EventReward getByTag(String tag); + + EventRandom getByRandomTag(String randomTag); } diff --git a/src/main/java/com/yello/server/domain/admin/repository/AdminRepositoryImpl.java b/src/main/java/com/yello/server/domain/admin/repository/AdminRepositoryImpl.java index e5b404b2..f2d425c9 100644 --- a/src/main/java/com/yello/server/domain/admin/repository/AdminRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/admin/repository/AdminRepositoryImpl.java @@ -3,16 +3,19 @@ import static com.yello.server.domain.event.entity.QEvent.event; import static com.yello.server.domain.event.entity.QEventTime.eventTime; import static com.yello.server.global.common.ErrorCode.EVENT_NOT_FOUND_EXCEPTION; +import static com.yello.server.global.common.ErrorCode.EVENT_RANDOM_NOT_FOUND_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_REWARD_NOT_FOUND_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_TIME_NOT_FOUND_EXCEPTION; import com.querydsl.jpa.impl.JPAQueryFactory; import com.yello.server.domain.admin.exception.UserAdminNotFoundException; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.repository.EventJpaRepository; +import com.yello.server.domain.event.repository.EventRandomJpaRepository; import com.yello.server.domain.event.repository.EventRewardJpaRepository; import com.yello.server.domain.event.repository.EventRewardMappingJpaRepository; import com.yello.server.domain.event.repository.EventTimeJpaRepository; @@ -25,6 +28,7 @@ public class AdminRepositoryImpl implements AdminRepository { private final EventJpaRepository eventJpaRepository; + private final EventRandomJpaRepository eventRandomJpaRepository; private final EventRewardJpaRepository eventRewardJpaRepository; private final EventRewardMappingJpaRepository eventRewardMappingJpaRepository; private final EventTimeJpaRepository eventTimeJpaRepository; @@ -74,4 +78,10 @@ public EventReward getByTag(String tag) { return eventRewardJpaRepository.findByTag(tag) .orElseThrow(() -> new UserAdminNotFoundException(EVENT_REWARD_NOT_FOUND_EXCEPTION)); } + + @Override + public EventRandom getByRandomTag(String randomTag) { + return eventRandomJpaRepository.findTopByRandomTag(randomTag) + .orElseThrow(() -> new UserAdminNotFoundException(EVENT_RANDOM_NOT_FOUND_EXCEPTION)); + } } diff --git a/src/main/java/com/yello/server/domain/admin/service/AdminService.java b/src/main/java/com/yello/server/domain/admin/service/AdminService.java index a0965c8e..0b35f04f 100644 --- a/src/main/java/com/yello/server/domain/admin/service/AdminService.java +++ b/src/main/java/com/yello/server/domain/admin/service/AdminService.java @@ -41,6 +41,7 @@ import com.yello.server.domain.cooldown.entity.Cooldown; import com.yello.server.domain.cooldown.repository.CooldownRepository; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -443,12 +444,13 @@ public EmptyObject createEvent(Long adminId, AdminEventCreateRequest request) th eventRewardVO.eventRewardItem().forEach(eventRewardItemVO -> { final EventReward eventReward = adminRepository.getByTag(eventRewardItemVO.tag()); + final EventRandom eventRandom = adminRepository.getByRandomTag(eventRewardItemVO.randomTag()); adminRepository.save(EventRewardMapping.builder() .eventTime(newEventTime) .eventReward(eventReward) + .eventRandom(eventRandom) .eventRewardProbability(eventRewardItemVO.eventRewardProbability()) - .randomTag(eventRewardItemVO.randomTag()) .build()); }); }); diff --git a/src/main/java/com/yello/server/domain/event/controller/EventController.java b/src/main/java/com/yello/server/domain/event/controller/EventController.java index 618d7d92..4fe61519 100644 --- a/src/main/java/com/yello/server/domain/event/controller/EventController.java +++ b/src/main/java/com/yello/server/domain/event/controller/EventController.java @@ -62,7 +62,7 @@ public BaseResponse joinEvent(@AccessTokenUser User user, HttpServletRequest req @PostMapping("/v1/event/reward") public BaseResponse rewardEvent(@AccessTokenUser User user, - HttpServletRequest requestServlet) { + HttpServletRequest requestServlet) throws JsonProcessingException { final String idempotencyKey = requestServlet.getHeader(IdempotencyKeyHeader); if (!StringUtils.hasText(idempotencyKey)) { throw new EventBadRequestException(IDEMPOTENCY_KEY_BAD_REQUEST_EXCEPTION); diff --git a/src/main/java/com/yello/server/domain/event/dto/response/EventResponse.java b/src/main/java/com/yello/server/domain/event/dto/response/EventResponse.java index f9862dfd..cababeb6 100644 --- a/src/main/java/com/yello/server/domain/event/dto/response/EventResponse.java +++ b/src/main/java/com/yello/server/domain/event/dto/response/EventResponse.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventRewardMapping; -import com.yello.server.domain.event.entity.EventRewardRandomType; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.entity.EventType; import jakarta.annotation.Nullable; @@ -78,7 +77,7 @@ public record EventRewardItemVO( Long maxRewardValue, Long minRewardValue, Integer eventRewardProbability, - EventRewardRandomType randomTag + String randomTag ) { public static EventRewardItemVO of(EventRewardMapping eventRewardMapping) { @@ -89,7 +88,7 @@ public static EventRewardItemVO of(EventRewardMapping eventRewardMapping) { .maxRewardValue(eventRewardMapping.getEventReward().getMaxRewardValue()) .minRewardValue(eventRewardMapping.getEventReward().getMinRewardValue()) .eventRewardProbability(eventRewardMapping.getEventRewardProbability()) - .randomTag(eventRewardMapping.getRandomTag()) + .randomTag(eventRewardMapping.getEventRandom().getRandomTag()) .build(); } } diff --git a/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java b/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java index 39ddb706..f7ea8670 100644 --- a/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java +++ b/src/main/java/com/yello/server/domain/event/dto/response/EventRewardResponse.java @@ -1,5 +1,6 @@ package com.yello.server.domain.event.dto.response; +import com.yello.server.domain.event.entity.EventInstanceReward; import lombok.Builder; @Builder @@ -10,4 +11,12 @@ public record EventRewardResponse( String rewardImage ) { + public static EventRewardResponse of(EventInstanceReward eventInstanceReward) { + return EventRewardResponse.builder() + .rewardTag(eventInstanceReward.getRewardTag()) + .rewardValue(eventInstanceReward.getRewardValue()) + .rewardTitle(eventInstanceReward.getRewardTitle()) + .rewardImage(eventInstanceReward.getRewardImage()) + .build(); + } } diff --git a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java index 1b2c5764..95f80bd4 100644 --- a/src/main/java/com/yello/server/domain/event/entity/EventInstance.java +++ b/src/main/java/com/yello/server/domain/event/entity/EventInstance.java @@ -1,6 +1,5 @@ package com.yello.server.domain.event.entity; -import com.yello.server.global.common.entity.OffsetTimeConverter; import com.yello.server.global.common.entity.ZonedDateTimeConverter; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -11,7 +10,6 @@ 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; @@ -38,22 +36,14 @@ public class EventInstance { private EventHistory eventHistory; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "eventId") + @JoinColumn(name = "eventTimeId") @OnDelete(action = OnDeleteAction.RESTRICT) - private Event event; + private EventTime eventTime; @Column(nullable = false) @Convert(converter = ZonedDateTimeConverter.class) private ZonedDateTime instanceDate; - - @Column(columnDefinition = "varchar(30) NOT NULL") - @Convert(converter = OffsetTimeConverter.class) - private OffsetTime startTime; - - @Column(columnDefinition = "varchar(30) NOT NULL") - @Convert(converter = OffsetTimeConverter.class) - private OffsetTime endTime; - + @Column @Builder.Default private Long remainEventCount = 0L; diff --git a/src/main/java/com/yello/server/domain/event/entity/EventRandom.java b/src/main/java/com/yello/server/domain/event/entity/EventRandom.java new file mode 100644 index 00000000..7e021a6b --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/entity/EventRandom.java @@ -0,0 +1,33 @@ +package com.yello.server.domain.event.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class EventRandom { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String randomTag; + + /** + * x의 정의역 [0,1] y의 정의역 [minValue, maxValue] 예시) RANDOM [{ x: 0.5, y: 80 }, { x : 1, y: 20}] FIXED [{ x: 1, y: 100}] + */ + @Column + private String probabilityPointList; +} diff --git a/src/main/java/com/yello/server/domain/event/entity/EventRewardMapping.java b/src/main/java/com/yello/server/domain/event/entity/EventRewardMapping.java index d082f40c..cbc9922e 100644 --- a/src/main/java/com/yello/server/domain/event/entity/EventRewardMapping.java +++ b/src/main/java/com/yello/server/domain/event/entity/EventRewardMapping.java @@ -1,7 +1,6 @@ package com.yello.server.domain.event.entity; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -38,11 +37,11 @@ public class EventRewardMapping { @OnDelete(action = OnDeleteAction.RESTRICT) private EventReward eventReward; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "eventRandomId") + @OnDelete(action = OnDeleteAction.RESTRICT) + private EventRandom eventRandom; + @Column(columnDefinition = "int NOT NULL CHECK (event_reward_probability BETWEEN 0 and 100)") private Integer eventRewardProbability; - - - @Column(nullable = false) - @Convert(converter = EventRewardRandomTypeConverter.class) - private EventRewardRandomType randomTag; } diff --git a/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomType.java b/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomType.java deleted file mode 100644 index a1250e6a..00000000 --- a/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomType.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yello.server.domain.event.entity; - -import static com.yello.server.global.common.ErrorCode.ENUM_BAD_REQUEST_EXCEPTION; - -import com.yello.server.global.exception.EnumIllegalArgumentException; -import java.util.Arrays; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum EventRewardRandomType { - RANDOM("RANDOM"), - FIXED("FIXED"); - - private final String initial; - - public static EventRewardRandomType fromCode(String dbData) { - return Arrays.stream(EventRewardRandomType.values()) - .filter(v -> v.getInitial().equals(dbData)) - .findAny() - .orElseThrow(() -> new EnumIllegalArgumentException(ENUM_BAD_REQUEST_EXCEPTION)); - } - - public static EventRewardRandomType fromName(String name) { - return Arrays.stream(EventRewardRandomType.values()) - .filter(v -> v.name().equals(name)) - .findAny() - .orElseThrow(() -> new EnumIllegalArgumentException(ENUM_BAD_REQUEST_EXCEPTION)); - } -} diff --git a/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomTypeConverter.java b/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomTypeConverter.java deleted file mode 100644 index 2f552c02..00000000 --- a/src/main/java/com/yello/server/domain/event/entity/EventRewardRandomTypeConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.yello.server.domain.event.entity; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import lombok.extern.log4j.Log4j2; - -@Converter -@Log4j2 -public class EventRewardRandomTypeConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(EventRewardRandomType type) { - if (type == null) { - return null; - } - return type.name(); - } - - @Override - public EventRewardRandomType convertToEntityAttribute(String dbData) { - if (dbData == null) { - return null; - } - - return EventRewardRandomType.fromName(dbData); - } -} - diff --git a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java index ee417649..93947f3b 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventInstanceJpaRepository.java @@ -1,8 +1,8 @@ package com.yello.server.domain.event.repository; -import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; +import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.user.entity.User; import java.util.List; import java.util.Optional; @@ -14,6 +14,6 @@ public interface EventInstanceJpaRepository extends JpaRepository findTopByEventHistory(EventHistory eventHistory); - @Query("select e from EventInstance e, EventHistory eh where e.eventHistory = eh and e.event = ?1 and eh.user = ?2") - List findAllByEventAndUser(Event event, User user); + @Query("select e from EventInstance e, EventHistory eh where e.eventHistory = eh and e.eventTime = ?1 and eh.user = ?2") + List findAllByEventTimeAndUser(EventTime eventTime, User user); } diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRandomJpaRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventRandomJpaRepository.java new file mode 100644 index 00000000..85c82443 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/repository/EventRandomJpaRepository.java @@ -0,0 +1,10 @@ +package com.yello.server.domain.event.repository; + +import com.yello.server.domain.event.entity.EventRandom; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventRandomJpaRepository extends JpaRepository { + + Optional findTopByRandomTag(String randomTag); +} diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java index defb3cfd..f7d3b768 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepository.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepository.java @@ -4,6 +4,7 @@ import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventInstanceReward; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -29,12 +30,14 @@ public interface EventRepository { List findRewardAll(); - List findInstanceAllByEventAndUser(Event event, User user); + List findInstanceAllByEventTimeAndUser(EventTime eventTime, User user); Event getByTag(EventType tag); EventReward getRewardById(Long eventRewardId); + EventRandom getRandomByRandomTag(String randomTag); + Optional findHistoryByIdempotencyKey(UUID idempotencyKey); Optional findInstanceByEventHistory(EventHistory eventHistory); diff --git a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java index 1e7e4c9c..8de36ba5 100644 --- a/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/event/repository/EventRepositoryImpl.java @@ -1,11 +1,13 @@ package com.yello.server.domain.event.repository; import static com.yello.server.global.common.ErrorCode.EVENT_NOT_FOUND_EXCEPTION; +import static com.yello.server.global.common.ErrorCode.EVENT_RANDOM_NOT_FOUND_EXCEPTION; import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventInstanceReward; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -26,6 +28,7 @@ public class EventRepositoryImpl implements EventRepository { private final EventInstanceJpaRepository eventInstanceJpaRepository; private final EventInstanceRewardJpaRepository eventInstanceRewardJpaRepository; private final EventJpaRepository eventJpaRepository; + private final EventRandomJpaRepository eventRandomJpaRepository; private final EventRewardJpaRepository eventRewardJpaRepository; private final EventRewardMappingJpaRepository eventRewardMappingJpaRepository; private final EventTimeJpaRepository eventTimeJpaRepository; @@ -66,8 +69,8 @@ public List findRewardAll() { } @Override - public List findInstanceAllByEventAndUser(Event event, User user) { - return eventInstanceJpaRepository.findAllByEventAndUser(event, user); + public List findInstanceAllByEventTimeAndUser(EventTime eventTime, User user) { + return eventInstanceJpaRepository.findAllByEventTimeAndUser(eventTime, user); } @Override @@ -82,6 +85,12 @@ public EventReward getRewardById(Long eventRewardId) { .orElseThrow(() -> new EventNotFoundException(EVENT_NOT_FOUND_EXCEPTION)); } + @Override + public EventRandom getRandomByRandomTag(String randomTag) { + return eventRandomJpaRepository.findTopByRandomTag(randomTag) + .orElseThrow(() -> new EventNotFoundException(EVENT_RANDOM_NOT_FOUND_EXCEPTION)); + } + @Override public Optional findHistoryByIdempotencyKey(UUID idempotencyKey) { return eventHistoryJpaRepository.findTopByIdempotencyKey(idempotencyKey); diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index 34fc7752..3ea52c66 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -8,6 +8,8 @@ import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; 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; @@ -15,6 +17,8 @@ import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; import com.yello.server.domain.event.entity.EventInstanceReward; +import com.yello.server.domain.event.entity.EventRandom; +import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; import com.yello.server.domain.event.entity.EventType; @@ -23,12 +27,17 @@ import com.yello.server.domain.event.repository.EventRepository; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.repository.UserRepository; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.time.Duration; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Random; import java.util.UUID; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,6 +48,7 @@ public class EventService { private final EventRepository eventRepository; + private final ObjectMapper objectMapper; private final UserRepository userRepository; public List getEvents(Long userId) throws JsonProcessingException { @@ -63,18 +73,25 @@ public List getEvents(Long userId) throws JsonProcessingException ) .toList(); - /** - * EventTime을 바꾸면, EventInstance가 바뀌지 않음. - * 이벤트 시간대가 바뀌면, 내일부터 적용된다고 말하기 - */ - // 이벤트 참가한 이력이 오늘이고, 현재 시각이 이벤트 시간에 유효하고, 남은 보상 카운트가 0인 이력 - final List eventInstanceList = eventRepository.findInstanceAllByEventAndUser(event, user) - .stream() - .filter(eventInstance -> eventInstance.getInstanceDate().isAfter(event.getStartDate()) - && eventInstance.getInstanceDate().isBefore(event.getEndDate()) - && nowTime.isAfter(eventInstance.getStartTime()) && nowTime.isBefore(eventInstance.getEndTime()) - && eventInstance.getRemainEventCount() == 0) - .toList(); + List eventInstanceList = new ArrayList<>(); + if (!eventTimeList.isEmpty()) { + final EventTime eventTime = eventTimeList.get(0); + + // 현재 시각이 이벤트 시간에 유효하고, 남은 보상 카운트가 0인 이력 + eventInstanceList.addAll( + eventRepository.findInstanceAllByEventTimeAndUser( + eventTime, user) + .stream() + .filter(eventInstance -> + eventInstance.getInstanceDate().isAfter(event.getStartDate()) + && eventInstance.getInstanceDate().isBefore(event.getEndDate()) + && nowTime.isAfter(eventInstance.getEventTime().getStartTime()) + && nowTime.isBefore(eventInstance.getEventTime().getEndTime()) + && eventInstance.getRemainEventCount() == 0 + ) + .toList() + ); + } // 해당 변수로 클라가 이벤트를 띄울지 판단하게 됨. // 이벤트 시간대가 있고, 보상 카운트가 0인 참여이력이 없을 때 @@ -105,10 +122,12 @@ public void joinEvent(Long userId, UUID uuidIdempotencyKey, EventJoinRequest req OffsetTime nowTime = now.toOffsetDateTime().toOffsetTime(); final Event event = eventRepository.getByTag(EventType.fromCode(request.tag())); + // 이벤트 날짜에 유효해야함. if (!(now.isAfter(event.getStartDate()) && now.isBefore(event.getEndDate()))) { throw new EventBadRequestException(EVENT_DATE_BAD_REQUEST_EXCEPTION); } + // 이벤트 시간대에 유효해야함. final List eventTimeList = eventRepository.findAllByEventId(event.getId()).stream() .filter(eventTime -> nowTime.isAfter(eventTime.getStartTime()) && nowTime.isBefore(eventTime.getEndTime())) .toList(); @@ -124,34 +143,34 @@ public void joinEvent(Long userId, UUID uuidIdempotencyKey, EventJoinRequest req eventRepository.save(EventInstance.builder() .eventHistory(newEventHistory) - .event(event) + .eventTime(eventTime) .instanceDate(now) - .startTime(eventTime.getStartTime()) - .endTime(eventTime.getEndTime()) .remainEventCount(eventTime.getRewardCount()) .build()); } @Transactional - public EventRewardResponse rewardEvent(Long userId, UUID uuidIdempotencyKey) { + public EventRewardResponse rewardEvent(Long userId, UUID uuidIdempotencyKey) throws JsonProcessingException { // exception final User user = userRepository.getById(userId); final Optional eventHistory = eventRepository.findHistoryByIdempotencyKey(uuidIdempotencyKey); if (eventHistory.isEmpty()) { throw new EventNotFoundException(IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION); } + // 멱등키에 해당하는 하나의 EventHistory는 반드시 하나의 EventInstance만 가진다. final Optional eventInstance = eventRepository.findInstanceByEventHistory(eventHistory.get()); - - // logic ZonedDateTime now = ZonedDateTime.now(GlobalZoneId); OffsetTime nowTime = now.toOffsetDateTime().toOffsetTime(); - if (!now.toLocalDate().isEqual(eventInstance.get().getInstanceDate().toLocalDate())) { + // 이벤트 참여 요청은 1일 동안만 유효하다. + final Duration duration = Duration.between(now, eventInstance.get().getInstanceDate()); + if (duration.compareTo(Duration.ofDays(1L)) >= 1) { throw new EventBadRequestException(EVENT_DATE_BAD_REQUEST_EXCEPTION); } - if (!(nowTime.isAfter(eventInstance.get().getStartTime()) && nowTime.isBefore( - eventInstance.get().getEndTime()))) { + // 이벤트 시각이 유효해야한다. + if (!(nowTime.isAfter(eventInstance.get().getEventTime().getStartTime()) + && nowTime.isBefore(eventInstance.get().getEventTime().getEndTime()))) { throw new EventBadRequestException(EVENT_TIME_BAD_REQUEST_EXCEPTION); } @@ -159,23 +178,99 @@ public EventRewardResponse rewardEvent(Long userId, UUID uuidIdempotencyKey) { throw new EventBadRequestException(EVENT_COUNT_BAD_REQUEST_EXCEPTION); } - eventInstance.get().subRemainEventCount(1L); - eventRepository.save(EventInstanceReward.builder() + final List eventRewardMappingList = eventRepository.findAllByEventTimeId( + eventInstance.get().getEventTime().getId()); + + // logic + // EventRewardProbability 필드에 따라 보상을 선택한다. + final EventRewardMapping randomRewardMapping = selectRandomly(eventRewardMappingList); + final long randomValue = selectRandomValue(randomRewardMapping); + + final EventInstanceReward eventInstanceReward = eventRepository.save(EventInstanceReward.builder() .eventInstance(eventInstance.get()) - .rewardTag("POINT") - .rewardValue(200L) - .rewardTitle("200 포인트를 얻었어요!") - .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .rewardTag(randomRewardMapping.getEventReward().getTag()) + .rewardValue(randomValue) + .rewardTitle(String.format("%d %s를 얻었어요!", randomValue, randomRewardMapping.getEventReward().getTag())) + .rewardImage(randomRewardMapping.getEventReward().getImage()) .build()); - /** - * TODO 랜덤값 구현 - */ - user.addPoint(200); - return EventRewardResponse.builder() - .rewardTag("POINT") - .rewardValue(200L) - .rewardTitle("200 포인트를 얻었어요!") - .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") - .build(); + + if (randomRewardMapping.getEventReward().getTag().equals("TICKET")) { + user.addTicketCount((int) randomValue); + } else if (randomRewardMapping.getEventReward().getTag().equals("POINT")) { + user.addPoint((int) randomValue); + } + eventInstance.get().subRemainEventCount(1L); + return EventRewardResponse.of(eventInstanceReward); + } + + private @NotNull EventRewardMapping selectRandomly(@NotEmpty List eventRewardMappingList) { + // 전체 확률 합계를 계산합니다. + int totalProbability = eventRewardMappingList.stream() + .mapToInt(EventRewardMapping::getEventRewardProbability) + .sum(); + + // 0에서 totalProbability 사이의 무작위 정수를 생성합니다. + int randomValue = new Random().nextInt(totalProbability); + + // 누적 확률을 계산하며 무작위 값이 어느 구간에 속하는지 찾습니다. + int cumulativeProbability = 0; + for (EventRewardMapping eventRewardMapping : eventRewardMappingList) { + cumulativeProbability += eventRewardMapping.getEventRewardProbability(); + if (randomValue < cumulativeProbability) { + return eventRewardMapping; + } + } + + return eventRewardMappingList.get(0); + } + + private long selectRandomValue(@NotNull EventRewardMapping eventRewardMapping) throws JsonProcessingException { + EventReward eventReward = eventRewardMapping.getEventReward(); + EventRandom eventRandom = eventRewardMapping.getEventRandom(); + + List probabilityPoints = objectMapper.readValue(eventRandom.getProbabilityPointList(), + new TypeReference>() { + } + ); + + return calculateRewardValue(probabilityPoints, eventReward.getMinRewardValue(), + eventReward.getMaxRewardValue()); + } + + private long calculateRewardValue(List points, long minRewardValue, long maxRewardValue) { + // 무작위 x 값을 생성합니다. + double x = new Random().nextDouble(); + + // x 값이 속하는 구간을 찾습니다. + for (int i = 0; i < points.size() - 1; i++) { + if (x >= points.get(i).getX() && x < points.get(i + 1).getX()) { + // x 값이 속하는 구간을 찾았으므로 해당 구간에서 무작위 보상값을 생성합니다. + return randomRewardValue(points.get(i).getY(), points.get(i + 1).getY(), minRewardValue, + maxRewardValue); + } + } + + // x 값이 마지막 구간에 속하는 경우 + if (x >= points.get(points.size() - 1).getX()) { + return randomRewardValue(points.get(points.size() - 1).getY(), 1.0, minRewardValue, maxRewardValue); + } + + // 이 부분은 도달할 수 없지만 혹시 모를 오류를 대비해 예외를 던집니다. + throw new IllegalArgumentException("Could not find a matching range for x: " + x); + } + + private long randomRewardValue(double y1, double y2, long minRewardValue, long maxRewardValue) { + // y1과 y2 사이의 무작위 y 값을 생성합니다. + double y = y1 + new Random().nextDouble() * (y2 - y1); + + // 보상값을 계산합니다. + return (long) ((maxRewardValue - minRewardValue) * y + minRewardValue); + } + + @Getter + private static class ProbabilityPoint { + + private double x; + private double y; } } diff --git a/src/main/java/com/yello/server/global/common/ErrorCode.java b/src/main/java/com/yello/server/global/common/ErrorCode.java index 9fbe2dbb..ca5f4adf 100644 --- a/src/main/java/com/yello/server/global/common/ErrorCode.java +++ b/src/main/java/com/yello/server/global/common/ErrorCode.java @@ -106,6 +106,7 @@ public enum ErrorCode { EVENT_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 Event가 존재하지 않습니다."), EVENT_TIME_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 EventTime가 존재하지 않습니다."), IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION(NOT_FOUND, "멱등키의 이벤트가 존재하지 않습니다. 이벤트 참여를 먼저 해주세요"), + EVENT_RANDOM_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 EventRandom가 존재하지 않습니다."), /** * 409 CONFLICT diff --git a/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java b/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java index 8c5e9404..e6933092 100644 --- a/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java +++ b/src/main/java/com/yello/server/global/exception/ControllerExceptionAdvice.java @@ -21,6 +21,7 @@ import com.yello.server.domain.authorization.exception.NotValidTokenForbiddenException; import com.yello.server.domain.authorization.exception.OAuthException; import com.yello.server.domain.event.exception.EventBadRequestException; +import com.yello.server.domain.event.exception.EventNotFoundException; import com.yello.server.domain.friend.exception.FriendException; import com.yello.server.domain.friend.exception.FriendNotFoundException; import com.yello.server.domain.group.exception.GroupNotFoundException; @@ -192,7 +193,8 @@ public ResponseEntity ForbiddenException(CustomException exception GoogleTokenNotFoundException.class, UserAdminNotFoundException.class, NoticeNotFoundException.class, - AdminConfigurationNotFoundException.class + AdminConfigurationNotFoundException.class, + EventNotFoundException.class }) public ResponseEntity NotFoundException(CustomException exception) { return ResponseEntity.status(NOT_FOUND) From d49e5797bf5f44b2f5306ff0ec931be55a7c51c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 7 Feb 2024 23:58:49 +0900 Subject: [PATCH 088/112] =?UTF-8?q?YEL-211=20[feat]=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/reward-event.adoc | 6 + .../static/docs/create-event-history.html | 14 ++- .../resources/static/docs/reward-event.html | 43 ++++++- .../event/medium/EventControllerTest.java | 112 ++++++++++++++++-- 4 files changed, 157 insertions(+), 18 deletions(-) diff --git a/src/docs/asciidoc/reward-event.adoc b/src/docs/asciidoc/reward-event.adoc index 40113680..52310784 100644 --- a/src/docs/asciidoc/reward-event.adoc +++ b/src/docs/asciidoc/reward-event.adoc @@ -8,9 +8,15 @@ include::{snippets}/api/v1/event/reward/1/http-request.adoc[] === 응답 include::{snippets}/api/v1/event/reward/1/http-response.adoc[] +include::{snippets}/api/v1/event/reward/2/http-response.adoc[] === 주의 +- "rewardTag": "TICKET" | "POINT" +- "rewardValue": Long +- "rewardTitle": String +- "rewardImage": String + === NOTE - Header에 이벤트 참여에 입력했던 멱등키를 넣어주세요. diff --git a/src/main/resources/static/docs/create-event-history.html b/src/main/resources/static/docs/create-event-history.html index a66be8a2..e4935724 100644 --- a/src/main/resources/static/docs/create-event-history.html +++ b/src/main/resources/static/docs/create-event-history.html @@ -458,8 +458,18 @@

    요청

    }
    -
    -

    Unresolved directive in create-event-history.adoc - include::/Users/euije/Github/YELLO-Server/build/generated-snippets/api/v1/event/join/2/http-request.adoc[]

    +
    +
    +
    POST /api/v1/event HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer your-access-token
    +IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87
    +Content-Length: 21
    +
    +{
    +  "tag" : "ADMOB"
    +}
    +
    diff --git a/src/main/resources/static/docs/reward-event.html b/src/main/resources/static/docs/reward-event.html index f8bf3973..1e2959e4 100644 --- a/src/main/resources/static/docs/reward-event.html +++ b/src/main/resources/static/docs/reward-event.html @@ -469,9 +469,29 @@

    응답

    "message" : "이벤트 보상에 성공하였습니다.", "data" : { "rewardTag" : "TICKET", - "rewardValue" : 200, - "rewardTitle" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg", - "rewardImage" : "200 포인트를 얻었어요!" + "rewardValue" : 1, + "rewardTitle" : "1 열람권를 얻었어요!", + "rewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg" + } +}
    +
    +
    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 200,
    +  "message" : "이벤트 보상에 성공하였습니다.",
    +  "data" : {
    +    "rewardTag" : "POINT",
    +    "rewardValue" : 100,
    +    "rewardTitle" : "200 포인트를 얻었어요!",
    +    "rewardImage" : "https://storage.googleapis.com/yelloworld/image/coin-stack.svg"
       }
     }
    @@ -479,7 +499,22 @@

    응답

    주의

    - +
    +
      +
    • +

      "rewardTag": "TICKET" | "POINT"

      +
    • +
    • +

      "rewardValue": Long

      +
    • +
    • +

      "rewardTitle": String

      +
    • +
    • +

      "rewardImage": String

      +
    • +
    +

    NOTE

    diff --git a/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java index 4d88c3d9..edcc5a0c 100644 --- a/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java +++ b/src/test/java/com/yello/server/domain/event/medium/EventControllerTest.java @@ -1,7 +1,5 @@ package com.yello.server.domain.event.medium; -import static com.yello.server.domain.event.entity.EventRewardRandomType.FIXED; -import static com.yello.server.domain.event.entity.EventRewardRandomType.RANDOM; import static com.yello.server.domain.event.entity.EventType.ADMOB; import static com.yello.server.domain.event.entity.EventType.LUNCH_EVENT; import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; @@ -24,6 +22,7 @@ import com.yello.server.domain.event.dto.response.EventResponse; import com.yello.server.domain.event.dto.response.EventRewardResponse; import com.yello.server.domain.event.entity.Event; +import com.yello.server.domain.event.entity.EventRandom; import com.yello.server.domain.event.entity.EventReward; import com.yello.server.domain.event.entity.EventRewardMapping; import com.yello.server.domain.event.entity.EventTime; @@ -135,18 +134,27 @@ class EventControllerTest { .endTime(OffsetTime.of(14, 0, 0, 0, ZoneOffset.of("+09:00"))) .build(); + final EventRandom eventRandom1 = EventRandom.builder() + .randomTag("FIXED") + .probabilityPointList("[{\"x\": 0, \"y\": 1},{ \"x\": 1, \"y\": 1}]") + .build(); + final EventRandom eventRandom2 = EventRandom.builder() + .randomTag("RANDOM") + .probabilityPointList("[{\"x\": 0, \"y\": 0},{ \"x\": 0.8, \"y\": 0.55 }, { \"x\": 1, \"y\": 1 }]") + .build(); + final List rewardList1 = List.of( EventRewardMapping.builder() .eventTime(eventTime1) .eventReward(ticket) .eventRewardProbability(10) - .randomTag(FIXED) + .eventRandom(eventRandom1) .build(), EventRewardMapping.builder() .eventTime(eventTime1) .eventReward(point) .eventRewardProbability(90) - .randomTag(RANDOM) + .eventRandom(eventRandom2) .build() ); @@ -176,7 +184,7 @@ class EventControllerTest { .eventTime(eventTime3) .eventReward(admobPoint) .eventRewardProbability(100) - .randomTag(FIXED) + .eventRandom(eventRandom1) .build() ); @@ -237,18 +245,27 @@ class EventControllerTest { .endTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) .build(); + final EventRandom eventRandom1 = EventRandom.builder() + .randomTag("FIXED") + .probabilityPointList("[{\"x\": 0, \"y\": 1},{ \"x\": 1, \"y\": 1}]") + .build(); + final EventRandom eventRandom2 = EventRandom.builder() + .randomTag("RANDOM") + .probabilityPointList("[{\"x\": 0, \"y\": 0},{ \"x\": 0.8, \"y\": 0.55 }, { \"x\": 1, \"y\": 1 }]") + .build(); + final List rewardList2 = List.of( EventRewardMapping.builder() .eventTime(eventTime2) .eventReward(ticket) .eventRewardProbability(40) - .randomTag(FIXED) + .eventRandom(eventRandom1) .build(), EventRewardMapping.builder() .eventTime(eventTime2) .eventReward(point) .eventRewardProbability(60) - .randomTag(RANDOM) + .eventRandom(eventRandom2) .build() ); @@ -278,7 +295,7 @@ class EventControllerTest { .eventTime(eventTime3) .eventReward(admobPoint) .eventRewardProbability(100) - .randomTag(FIXED) + .eventRandom(eventRandom1) .build() ); @@ -338,12 +355,17 @@ class EventControllerTest { .startTime(OffsetTime.of(0, 0, 0, 0, ZoneOffset.of("+09:00"))) .endTime(OffsetTime.of(23, 59, 59, 999999, ZoneOffset.of("+09:00"))) .build(); + + final EventRandom eventRandom1 = EventRandom.builder() + .randomTag("FIXED") + .probabilityPointList("[{\"x\": 0, \"y\": 1},{ \"x\": 1, \"y\": 1}]") + .build(); final List rewardList3 = List.of( EventRewardMapping.builder() .eventTime(eventTime3) .eventReward(admobPoint) .eventRewardProbability(100) - .randomTag(FIXED) + .eventRandom(eventRandom1) .build() ); @@ -403,13 +425,46 @@ class EventControllerTest { @Test void 이벤트_참여에_성공합니다2() throws Exception { + // given + final String idempotencyKey = "0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87"; + final EventJoinRequest request = EventJoinRequest.builder() + .tag(ADMOB.getInitial()) + .build(); + + doNothing() + .when(eventService) + .joinEvent(any(Long.class), any(UUID.class), eq(request)); + + // when + + // then + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/event") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .header(ConstantUtil.IdempotencyKeyHeader, idempotencyKey) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("api/v1/event/join/2", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @Test + void 이벤트_보상에_성공합니다1() throws Exception { // given final String idempotencyKey = "87552f7c-9b62-4b12-b567-1bd062b09288"; + /** + * TODO EventRewardResponse.of로 변경할 것 + */ EventRewardResponse response = EventRewardResponse.builder() .rewardTag("TICKET") - .rewardValue(200L) - .rewardImage("200 포인트를 얻었어요!") - .rewardTitle("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .rewardValue(1L) + .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .rewardTitle("1 열람권를 얻었어요!") .build(); given(eventService.rewardEvent(any(Long.class), any(UUID.class))) @@ -430,4 +485,37 @@ class EventControllerTest { )) .andExpect(MockMvcResultMatchers.status().isOk()); } + + @Test + void 이벤트_보상에_성공합니다2() throws Exception { + // given + final String idempotencyKey = "87552f7c-9b62-4b12-b567-1bd062b09288"; + /** + * TODO EventRewardResponse.of로 변경할 것 + */ + EventRewardResponse response = EventRewardResponse.builder() + .rewardTag("POINT") + .rewardValue(100L) + .rewardImage("https://storage.googleapis.com/yelloworld/image/coin-stack.svg") + .rewardTitle("200 포인트를 얻었어요!") + .build(); + + given(eventService.rewardEvent(any(Long.class), any(UUID.class))) + .willReturn(response); + // when + + // then + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/event/reward") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .header(ConstantUtil.IdempotencyKeyHeader, idempotencyKey) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("api/v1/event/reward/2", + Preprocessors.preprocessRequest(excludeRequestHeaders), + Preprocessors.preprocessResponse(excludeResponseHeaders) + )) + .andExpect(MockMvcResultMatchers.status().isOk()); + } } From b49d0442b2d98e4ade11490f5bb9dbaf5f2c4e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Thu, 8 Feb 2024 12:55:48 +0900 Subject: [PATCH 089/112] =?UTF-8?q?develop=20[develop]=20cors=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/SecurityConfiguration.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/yello/server/domain/authorization/configuration/SecurityConfiguration.java b/src/main/java/com/yello/server/domain/authorization/configuration/SecurityConfiguration.java index 14bc887d..058e9b07 100644 --- a/src/main/java/com/yello/server/domain/authorization/configuration/SecurityConfiguration.java +++ b/src/main/java/com/yello/server/domain/authorization/configuration/SecurityConfiguration.java @@ -8,6 +8,7 @@ import com.yello.server.domain.authorization.service.TokenProvider; import com.yello.server.domain.user.repository.UserRepository; import com.yello.server.global.exception.ExceptionHandlerFilter; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,6 +17,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity @@ -27,6 +31,17 @@ public class SecurityConfiguration { private final TokenProvider tokenProvider; private final UserRepository userRepository; + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList("*")); + configuration.setAllowedMethods(Arrays.asList("*")); + configuration.setAllowedHeaders(Arrays.asList("*")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity @@ -37,7 +52,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws httpSecurityCsrfConfigurer.disable(); }) .cors(httpSecurityCorsConfigurer -> { - httpSecurityCorsConfigurer.disable(); + httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource()); }) .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> { authorizationManagerRequestMatcherRegistry From 5119fb25acaa3940e05eafb81f9297e3c1389e46 Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Fri, 9 Feb 2024 22:36:36 +0900 Subject: [PATCH 090/112] YEL-213 [feat] admob verify --- build.gradle | 4 + .../event/controller/EventController.java | 23 ++- .../event/dto/request/AdmobSsvRequest.java | 45 ++++ .../domain/event/service/EventService.java | 22 ++ .../yello/server/global/common/ErrorCode.java | 1 + .../server/global/common/SuccessCode.java | 1 + .../resources/static/docs/add-friend.html | 161 ++++++++------- src/main/resources/static/docs/apple.html | 158 +++++++------- .../resources/static/docs/check-keyword.html | 158 +++++++------- .../static/docs/check-user-by-id.html | 158 +++++++------- .../resources/static/docs/check-user-v2.html | 158 +++++++------- .../resources/static/docs/check-user.html | 158 +++++++------- .../static/docs/check-vote-available.html | 158 +++++++------- .../resources/static/docs/create-vote.html | 158 +++++++------- .../resources/static/docs/delete-friend.html | 158 +++++++------- .../resources/static/docs/delete-user-v2.html | 158 +++++++------- .../resources/static/docs/delete-user.html | 158 +++++++------- .../resources/static/docs/device-token.html | 158 +++++++------- src/main/resources/static/docs/edit-user.html | 158 +++++++------- .../static/docs/find-friend-votes-v2.html | 158 +++++++------- .../static/docs/find-friend-votes.html | 158 +++++++------- .../resources/static/docs/find-friends.html | 158 +++++++------- .../static/docs/find-group-friends.html | 158 +++++++------- .../static/docs/find-kakao-friends.html | 158 +++++++------- .../resources/static/docs/find-notice.html | 158 +++++++------- .../static/docs/find-onboarding-friends.html | 158 +++++++------- .../resources/static/docs/find-question.html | 158 +++++++------- src/main/resources/static/docs/find-vote.html | 158 +++++++------- .../resources/static/docs/find-votes.html | 158 +++++++------- .../static/docs/get-unread-vote.html | 158 +++++++------- src/main/resources/static/docs/google.html | 158 +++++++------- src/main/resources/static/docs/index.html | 188 ++++++++--------- src/main/resources/static/docs/login.html | 158 +++++++------- src/main/resources/static/docs/overview.html | 158 +++++++------- src/main/resources/static/docs/pay.html | 158 +++++++------- .../resources/static/docs/purchase-check.html | 158 +++++++------- .../resources/static/docs/purchase-info.html | 158 +++++++------- .../resources/static/docs/reissue-token.html | 192 ++++++++++-------- .../static/docs/reveal-full-name.html | 158 +++++++------- .../resources/static/docs/reveal-name.html | 158 +++++++------- .../static/docs/search-department.html | 158 +++++++------- .../resources/static/docs/search-friend.html | 158 +++++++------- .../static/docs/search-high-class.html | 158 +++++++------- .../static/docs/search-high-name.html | 158 +++++++------- .../resources/static/docs/search-school.html | 158 +++++++------- .../static/docs/shuffle-friends.html | 158 +++++++------- src/main/resources/static/docs/signup.html | 158 +++++++------- src/main/resources/static/docs/sub-check.html | 158 +++++++------- .../resources/static/docs/user-data-get.html | 158 +++++++------- .../resources/static/docs/user-data-post.html | 158 +++++++------- .../static/docs/validate-yelloid.html | 158 +++++++------- 51 files changed, 3687 insertions(+), 3586 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/event/dto/request/AdmobSsvRequest.java diff --git a/build.gradle b/build.gradle index 67629786..c9a03190 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/src/main/java/com/yello/server/domain/event/controller/EventController.java b/src/main/java/com/yello/server/domain/event/controller/EventController.java index 4fe61519..1005edca 100644 --- a/src/main/java/com/yello/server/domain/event/controller/EventController.java +++ b/src/main/java/com/yello/server/domain/event/controller/EventController.java @@ -1,10 +1,12 @@ package com.yello.server.domain.event.controller; +import static com.yello.server.global.common.ErrorCode.ADMOB_URI_BAD_REQUEST_EXCEPTION; 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.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.VERIFY_ADMOB_SSV_SUCCESS; import static com.yello.server.global.common.util.ConstantUtil.IdempotencyKeyHeader; import com.fasterxml.jackson.core.JsonProcessingException; @@ -17,6 +19,8 @@ import com.yello.server.global.common.annotation.AccessTokenUser; import com.yello.server.global.common.dto.BaseResponse; import jakarta.servlet.http.HttpServletRequest; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -36,7 +40,8 @@ public class EventController { private final EventService eventService; @GetMapping("/v1/event") - public BaseResponse> getEvents(@AccessTokenUser User user) throws JsonProcessingException { + public BaseResponse> getEvents(@AccessTokenUser User user) + throws JsonProcessingException { val data = eventService.getEvents(user.getId()); return BaseResponse.success(EVENT_NOTICE_SUCCESS, data); } @@ -78,4 +83,20 @@ public BaseResponse rewardEvent(@AccessTokenUser User user, val data = eventService.rewardEvent(user.getId(), uuidIdempotencyKey); return BaseResponse.success(EVENT_REWARD_SUCCESS, data); } + + @GetMapping("/v1/admob/verify") + public BaseResponse verifyAdmobReward(HttpServletRequest request) { + URI uri; + try { + 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); + + return BaseResponse.success(VERIFY_ADMOB_SSV_SUCCESS); + } } diff --git a/src/main/java/com/yello/server/domain/event/dto/request/AdmobSsvRequest.java b/src/main/java/com/yello/server/domain/event/dto/request/AdmobSsvRequest.java new file mode 100644 index 00000000..52720afb --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/dto/request/AdmobSsvRequest.java @@ -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 parameters ) { + Function 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("rewardAmount")) + .flatMap(arr -> Arrays.stream(arr).findFirst()) + .map(Integer::parseInt) + .orElse(0); + + return AdmobSsvRequest.builder() + .customData(getParameter.apply("customData")) + .signature(getParameter.apply("signature")) + .keyId(keyId) + .transactionId(getParameter.apply("transaction_id")) + .rewardItem(getParameter.apply("reward_item")) + .rewardAmount(rewardAmount) + .build(); + } + +} diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index 3ea52c66..710be7d4 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -10,6 +10,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.crypto.tink.apps.rewardedads.RewardedAdsVerifier; +import com.yello.server.domain.event.dto.request.AdmobSsvRequest; 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; @@ -27,18 +29,22 @@ import com.yello.server.domain.event.repository.EventRepository; import com.yello.server.domain.user.entity.User; import com.yello.server.domain.user.repository.UserRepository; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.net.URI; import java.time.Duration; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.UUID; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -203,6 +209,22 @@ public EventRewardResponse rewardEvent(Long userId, UUID uuidIdempotencyKey) thr return EventRewardResponse.of(eventInstanceReward); } + @SneakyThrows + public void verifyAdmobReward(URI uri, HttpServletRequest request) { + // admob 검증하기 + RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() + .fetchVerifyingPublicKeysWith( + RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD) + .build(); + verifier.verify(uri.toString()); + + // request 정보 가져오기 + Map parameters = request.getParameterMap(); + AdmobSsvRequest admobRequest = AdmobSsvRequest.of(parameters); + + // 보상하기 + } + private @NotNull EventRewardMapping selectRandomly(@NotEmpty List eventRewardMappingList) { // 전체 확률 합계를 계산합니다. int totalProbability = eventRewardMappingList.stream() diff --git a/src/main/java/com/yello/server/global/common/ErrorCode.java b/src/main/java/com/yello/server/global/common/ErrorCode.java index ca5f4adf..4b7dba2d 100644 --- a/src/main/java/com/yello/server/global/common/ErrorCode.java +++ b/src/main/java/com/yello/server/global/common/ErrorCode.java @@ -45,6 +45,7 @@ public enum ErrorCode { EVENT_DATE_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 날짜가 아닙니다."), EVENT_TIME_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 현재 유효한 시간이 아닙니다."), EVENT_COUNT_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "해당 이벤트는 보상 횟수가 전부 소진되었습니다."), + ADMOB_URI_BAD_REQUEST_EXCEPTION(BAD_REQUEST, "URI의 값이 올바르지 않습니다."), /** * 401 UNAUTHORIZED diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 587db04e..cbc4d011 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -62,6 +62,7 @@ public enum SuccessCode { EVENT_REWARD_CREATE_ADMIN_SUCCESS(OK, "어드민 권한으로 이벤트 보상 생성에 성공하였습니다."), EVENT_JOIN_SUCCESS(OK, "이벤트 참여에 성공하였습니다."), EVENT_REWARD_SUCCESS(OK, "이벤트 보상에 성공하였습니다."), + VERIFY_ADMOB_SSV_SUCCESS(OK, "Admob ssv 검증에 성공하였습니다."), /** * 201 CREATED diff --git a/src/main/resources/static/docs/add-friend.html b/src/main/resources/static/docs/add-friend.html index 64bb0f9c..70ee9041 100644 --- a/src/main/resources/static/docs/add-friend.html +++ b/src/main/resources/static/docs/add-friend.html @@ -4,23 +4,25 @@ - + 친구 추가하기 -
    -
    - - + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/login.html b/src/main/resources/static/docs/login.html index 79046842..fd84b612 100644 --- a/src/main/resources/static/docs/login.html +++ b/src/main/resources/static/docs/login.html @@ -4,23 +4,25 @@ - + 소셜 로그인 + + + +
    +
    +

    이벤트 참여

    +
    +
    +

    요청

    +
    +
    +
    POST /api/v1/admob/reward HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer your-access-token
    +IdempotencyKey: 87552f7c-9b62-4b12-b567-1bd062b09288
    +Content-Length: 134
    +
    +{
    +  "rewardType" : "ADMOB_POINT",
    +  "randomType" : "FIXED",
    +  "uuid" : "87552f7c-9b62-4b12-b567-1bd062b09288",
    +  "rewardNumber" : 10
    +}
    +
    +
    +
    +
    +

    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배 이벤트인 경우 현재 투표 후 받은 포인트 보내줘야함

      +
    • +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +
    +{
    +  "status" : 201,
    +  "message" : "Admob 광고 보고 보상받기에 성공했습니다.",
    +  "data" : {
    +    "rewardTag" : "ADMOB_POINT",
    +    "rewardValue" : 10,
    +    "rewardTitle" : "10 포인트를 얻었어요!",
    +    "rewardImage" : "https://storage.googleapis.com/yelloworld/image/ticket-reward.svg"
    +  }
    +}
    +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      Header에 무작위한 UUID4 값을 넣어주세요

      +
      +
        +
      • +

        예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87

        +
      • +
      +
      +
    • +
    • +

      주의사항

      +
      +
        +
      • +

        같은 멱등성키를 2번 요청하면, 400번 에러.

        +
      • +
      +
      +
    • +
    • +

      ADMOB

      +
      +
        +
      • +

        ADMOB 서버에 SSV(ServerSideVerification) Options의 customData에 입력한 것과 동일한 멱등성 키를 넘겨주세요.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.02.11 릴리즈

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file From 9e23914aafb705eda63c5fe875f64863b26d5ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Wed, 14 Feb 2024 00:25:29 +0900 Subject: [PATCH 098/112] =?UTF-8?q?develop=20[develop]=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8=EC=84=9C=20=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/find-event.adoc | 3 +- .../resources/static/docs/add-friend.html | 161 +++++++-------- src/main/resources/static/docs/apple.html | 158 +++++++------- .../resources/static/docs/check-keyword.html | 158 +++++++------- .../static/docs/check-user-by-id.html | 158 +++++++------- .../resources/static/docs/check-user-v2.html | 158 +++++++------- .../resources/static/docs/check-user.html | 158 +++++++------- .../static/docs/check-vote-available.html | 158 +++++++------- .../static/docs/create-event-history.html | 19 +- .../resources/static/docs/create-vote.html | 158 +++++++------- .../resources/static/docs/delete-friend.html | 158 +++++++------- .../resources/static/docs/delete-user-v2.html | 158 +++++++------- .../resources/static/docs/delete-user.html | 158 +++++++------- .../resources/static/docs/device-token.html | 158 +++++++------- src/main/resources/static/docs/edit-user.html | 158 +++++++------- .../resources/static/docs/find-event.html | 15 +- .../static/docs/find-friend-votes-v2.html | 158 +++++++------- .../static/docs/find-friend-votes.html | 158 +++++++------- .../resources/static/docs/find-friends.html | 158 +++++++------- .../static/docs/find-group-friends.html | 158 +++++++------- .../static/docs/find-kakao-friends.html | 158 +++++++------- .../resources/static/docs/find-notice.html | 158 +++++++------- .../static/docs/find-onboarding-friends.html | 158 +++++++------- .../resources/static/docs/find-question.html | 158 +++++++------- src/main/resources/static/docs/find-vote.html | 158 +++++++------- .../resources/static/docs/find-votes.html | 158 +++++++------- .../static/docs/get-unread-vote.html | 158 +++++++------- src/main/resources/static/docs/google.html | 158 +++++++------- src/main/resources/static/docs/index.html | 191 +++++++++-------- src/main/resources/static/docs/login.html | 158 +++++++------- src/main/resources/static/docs/overview.html | 158 +++++++------- src/main/resources/static/docs/pay.html | 158 +++++++------- .../resources/static/docs/purchase-check.html | 158 +++++++------- .../resources/static/docs/purchase-info.html | 158 +++++++------- .../resources/static/docs/reissue-token.html | 192 ++++++++---------- .../static/docs/reveal-full-name.html | 158 +++++++------- .../resources/static/docs/reveal-name.html | 158 +++++++------- .../static/docs/search-department.html | 158 +++++++------- .../resources/static/docs/search-friend.html | 158 +++++++------- .../static/docs/search-high-class.html | 158 +++++++------- .../static/docs/search-high-name.html | 158 +++++++------- .../resources/static/docs/search-school.html | 158 +++++++------- .../static/docs/shuffle-friends.html | 158 +++++++------- src/main/resources/static/docs/signup.html | 158 +++++++------- src/main/resources/static/docs/sub-check.html | 158 +++++++------- .../resources/static/docs/user-data-get.html | 182 ++++++++++------- .../resources/static/docs/user-data-post.html | 158 +++++++------- .../static/docs/validate-yelloid.html | 158 +++++++------- 48 files changed, 3628 insertions(+), 3613 deletions(-) diff --git a/src/docs/asciidoc/find-event.adoc b/src/docs/asciidoc/find-event.adoc index 4b486a4a..f8969cc0 100644 --- a/src/docs/asciidoc/find-event.adoc +++ b/src/docs/asciidoc/find-event.adoc @@ -23,7 +23,8 @@ include::{snippets}/api/v1/event/3/http-response.adoc[] - endDate : "2024-12-31T00:00:00+09:00" - title : "점심 시간 깜짝 선물!" - subTitle : "평일 12-14시 최대 1회까지 참여 가능" -- animationList : [{...json1...}, {...json2...}] +- animationList : string[] +* URL이 들어감. - eventReward: *EventReward* | null * 해당 필드가 **null**일 시, 이벤트 보여주지 않도록 해주세요. diff --git a/src/main/resources/static/docs/add-friend.html b/src/main/resources/static/docs/add-friend.html index 70ee9041..64bb0f9c 100644 --- a/src/main/resources/static/docs/add-friend.html +++ b/src/main/resources/static/docs/add-friend.html @@ -4,25 +4,23 @@ - + 친구 추가하기 +
    +
    - - - + + \ No newline at end of file diff --git a/src/main/resources/static/docs/login.html b/src/main/resources/static/docs/login.html index fd84b612..79046842 100644 --- a/src/main/resources/static/docs/login.html +++ b/src/main/resources/static/docs/login.html @@ -4,25 +4,23 @@ - + 소셜 로그인 + + + +
    +
    +

    상점에서 보상형 광고 가능한지 여부 (명세)

    +
    +
    +

    요청

    +
    +
    +
    GET /api/v1/admob/{tag} HTTP/1.1
    +Authorization: Bearer your-access-token
    +Content-Type: application/json
    +
    +
    +
    +
    +

    요청 파라미터

    +
    +
    +
    tag -> shop
    +
    +
    +
    +
      +
    • +

      보상형 광고 다른곳에서 사용할 수도 있으므로 tag로 어떤 곳에서 사용하고 있는곳인지 tag로 명시

      +
    • +
    • +

      현재는 상점 → shop에서만 보상형 광고 진행

      +
    • +
    +
    +
    +
    +

    응답

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json
    +{
    +  "status" : 200,
    +  "message" : "광고 보고 포인트 얻기 가능 여부 조회에 성공했습니다.",
    +  "data": {
    +    "createdAt": "2024-01-01 12:00:00",
    +    "isPossible" : true,
    +  }
    +}
    +
    +
    +
    +
    +

    NOTE

    +
    +
      +
    • +

      Header에 무작위한 UUID4 값을 넣어주세요

      +
      +
        +
      • +

        예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87

        +
      • +
      +
      +
    • +
    • +

      주의사항

      +
      +
        +
      • +

        같은 멱등성키를 2번 요청하면, 400번 에러.

        +
      • +
      +
      +
    • +
    • +

      ADMOB

      +
      +
        +
      • +

        ADMOB 서버에 SSV(ServerSideVerification) Options의 customData에 입력한 것과 동일한 멱등성 키를 넘겨주세요.

        +
      • +
      +
      +
    • +
    +
    +
    +
    +

    CHANGELOG

    +
    +
      +
    • +

      2024.02.16 명세서 작성

      +
    • +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/find-event.html b/src/main/resources/static/docs/find-event.html index 0e5d5edc..292228db 100644 --- a/src/main/resources/static/docs/find-event.html +++ b/src/main/resources/static/docs/find-event.html @@ -499,7 +499,7 @@

    응답

    } ] } }, { - "tag" : "ADMOB", + "tag" : "ADMOB_POINT", "startDate" : "2024-01-01T00:00:00+09:00", "endDate" : "2024-12-31T00:00:00+09:00", "title" : "ADMOB 광고입니다.", @@ -568,7 +568,7 @@

    응답

    } ] } }, { - "tag" : "ADMOB", + "tag" : "ADMOB_POINT", "startDate" : "2024-01-01T00:00:00+09:00", "endDate" : "2024-12-31T00:00:00+09:00", "title" : "ADMOB 광고입니다.", @@ -616,7 +616,7 @@

    응답

    } ], "eventReward" : null }, { - "tag" : "ADMOB", + "tag" : "ADMOB_POINT", "startDate" : "2024-01-01T00:00:00+09:00", "endDate" : "2024-12-31T00:00:00+09:00", "title" : "ADMOB 광고입니다.", From 11b655af70f0db04104fa21e91af159905369dfd Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Fri, 16 Feb 2024 15:36:38 +0900 Subject: [PATCH 101/112] =?UTF-8?q?=ED=95=99=EA=B5=90=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9C=20=EA=B2=80=EC=83=89=EC=8B=9C=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=ED=95=99=EA=B5=90=EC=9D=B8=20=EC=B9=9C=EA=B5=AC=EB=93=A4=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/friend/service/FriendService.java | 83 ++++++------ .../user/repository/UserJpaRepository.java | 124 ++++++++++-------- .../user/repository/UserRepository.java | 2 + .../user/repository/UserRepositoryImpl.java | 5 + .../server/global/common/SuccessCode.java | 2 +- .../domain/user/FakeUserRepository.java | 9 ++ 6 files changed, 122 insertions(+), 103 deletions(-) diff --git a/src/main/java/com/yello/server/domain/friend/service/FriendService.java b/src/main/java/com/yello/server/domain/friend/service/FriendService.java index 9f907c75..4450a430 100644 --- a/src/main/java/com/yello/server/domain/friend/service/FriendService.java +++ b/src/main/java/com/yello/server/domain/friend/service/FriendService.java @@ -1,16 +1,7 @@ package com.yello.server.domain.friend.service; -import static com.yello.server.global.common.ErrorCode.EXIST_FRIEND_EXCEPTION; -import static com.yello.server.global.common.util.ConstantUtil.YELLO_FEMALE; -import static com.yello.server.global.common.util.ConstantUtil.YELLO_MALE; - import com.yello.server.domain.friend.dto.request.KakaoRecommendRequest; -import com.yello.server.domain.friend.dto.response.FriendResponse; -import com.yello.server.domain.friend.dto.response.FriendShuffleResponse; -import com.yello.server.domain.friend.dto.response.FriendsResponse; -import com.yello.server.domain.friend.dto.response.RecommendFriendResponse; -import com.yello.server.domain.friend.dto.response.SearchFriendResponse; -import com.yello.server.domain.friend.dto.response.SearchFriendVO; +import com.yello.server.domain.friend.dto.response.*; import com.yello.server.domain.friend.entity.Friend; import com.yello.server.domain.friend.exception.FriendException; import com.yello.server.domain.friend.repository.FriendRepository; @@ -20,12 +11,6 @@ import com.yello.server.domain.vote.repository.VoteRepository; import com.yello.server.domain.vote.service.VoteManager; import com.yello.server.global.common.factory.PaginationFactory; -import java.lang.Character.UnicodeBlock; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; import lombok.Builder; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -33,6 +18,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.lang.Character.UnicodeBlock; +import java.util.*; + +import static com.yello.server.global.common.ErrorCode.EXIST_FRIEND_EXCEPTION; +import static com.yello.server.global.common.util.ConstantUtil.YELLO_FEMALE; +import static com.yello.server.global.common.util.ConstantUtil.YELLO_MALE; + @Service @Builder @RequiredArgsConstructor @@ -47,13 +39,13 @@ public class FriendService { public FriendsResponse findAllFriends(Pageable pageable, Long userId) { final Page friendsData = friendRepository.findAllFriendsByUserId(pageable, userId); List friends = friendsData.stream() - .map(friend -> { - User targetUser = friend.getTarget(); - Integer friendCount = friendRepository.countAllByUserId(targetUser.getId()); - Integer yelloCount = voteRepository.countAllByReceiverUserId(targetUser.getId()); - return UserResponse.of(targetUser, yelloCount, friendCount); - }) - .toList(); + .map(friend -> { + User targetUser = friend.getTarget(); + Integer friendCount = friendRepository.countAllByUserId(targetUser.getId()); + Integer yelloCount = voteRepository.countAllByReceiverUserId(targetUser.getId()); + return UserResponse.of(targetUser, yelloCount, friendCount); + }) + .toList(); return FriendsResponse.of(friendsData.getTotalElements(), friends); } @@ -85,10 +77,10 @@ public RecommendFriendResponse findAllRecommendSchoolFriends(Pageable pageable, Integer size = userRepository.countAllByGroupNameFilteredByNotFriend(userId, user.getGroup().getGroupName()); List recommendFriends = - userRepository.findAllByGroupNameFilteredByNotFriend(userId, user.getGroup().getGroupName(), pageable) - .stream() - .map(FriendResponse::of) - .toList(); + userRepository.findAllByGroupNameFilteredByNotFriend(userId, user.getGroup().getGroupName(), pageable) + .stream() + .map(FriendResponse::of) + .toList(); return RecommendFriendResponse.of(size, recommendFriends); } @@ -106,25 +98,25 @@ public void deleteFriend(Long userId, Long targetId) { } public RecommendFriendResponse findAllRecommendKakaoFriends(Pageable pageable, Long userId, - KakaoRecommendRequest request) { + KakaoRecommendRequest request) { final User user = userRepository.getById(userId); List kakaoFriends = Arrays.stream(request.friendKakaoId()) - .filter(userRepository::existsByUuid) - .map(userRepository::getByUuid) - .filter(friend -> !friendRepository.existsByUserAndTarget(user.getId(), friend.getId())) - .toList(); + .filter(userRepository::existsByUuid) + .map(userRepository::getByUuid) + .filter(friend -> !friendRepository.existsByUserAndTarget(user.getId(), friend.getId())) + .toList(); List pageList = PaginationFactory.getPage(kakaoFriends, pageable) - .stream() - .map(FriendResponse::of) - .toList(); + .stream() + .map(FriendResponse::of) + .toList(); return RecommendFriendResponse.of(kakaoFriends.size(), pageList); } public SearchFriendResponse searchFriend(Long userId, Pageable pageable, - String keyword) { + String keyword) { final User user = userRepository.getById(userId); final String groupName = user.getGroup().getGroupName(); List uuidList = Arrays.asList(YELLO_FEMALE, YELLO_MALE); @@ -137,23 +129,24 @@ public SearchFriendResponse searchFriend(Long userId, Pageable pageable, if (!isEnglish(keyword)) { friendList.addAll( - userRepository.findAllByGroupContainingName(groupName, keyword, uuidList)); + userRepository.findAllByGroupContainingName(groupName, keyword, uuidList)); friendList.addAll( - userRepository.findAllByOtherGroupContainingName(groupName, keyword, uuidList)); + userRepository.findAllByOtherGroupContainingName(groupName, keyword, uuidList)); } else { friendList.addAll( - userRepository.findAllByGroupContainingYelloId(groupName, keyword, uuidList)); + userRepository.findAllByGroupContainingYelloId(groupName, keyword, uuidList)); friendList.addAll( - userRepository.findAllByOtherGroupContainingYelloId(groupName, keyword, uuidList)); + userRepository.findAllByOtherGroupContainingYelloId(groupName, keyword, uuidList)); + friendList.addAll(userRepository.findAllByGroupNameContainingAndFriendListNotContaining(keyword, uuidList, friendList)); } List pageList = PaginationFactory.getPage(friendList, pageable) - .stream() - .filter(friend -> !userId.equals(friend.getId())) - .map(friend -> SearchFriendVO.of(friend, - friendRepository.existsByUserAndTarget(userId, friend.getId()))) - .toList(); + .stream() + .filter(friend -> !userId.equals(friend.getId())) + .map(friend -> SearchFriendVO.of(friend, + friendRepository.existsByUserAndTarget(userId, friend.getId()))) + .toList(); return SearchFriendResponse.of(friendList.size(), pageList); } diff --git a/src/main/java/com/yello/server/domain/user/repository/UserJpaRepository.java b/src/main/java/com/yello/server/domain/user/repository/UserJpaRepository.java index 1d5eaab1..01d64b09 100644 --- a/src/main/java/com/yello/server/domain/user/repository/UserJpaRepository.java +++ b/src/main/java/com/yello/server/domain/user/repository/UserJpaRepository.java @@ -1,115 +1,125 @@ package com.yello.server.domain.user.repository; import com.yello.server.domain.user.entity.User; -import java.util.List; -import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; +import java.util.Optional; + public interface UserJpaRepository extends JpaRepository { @Query("select u from User u " + - "where u.id = :id " + - "and u.deletedAt is null") + "where u.id = :id " + + "and u.deletedAt is null") Optional findById(@Param("id") Long id); @Query("select u from User u " + - "where u.id = :id") + "where u.id = :id") Optional findByIdNotFiltered(@Param("id") Long id); @Query("select u from User u " + - "where u.uuid = :uuid") + "where u.uuid = :uuid") Optional findByUuid(@Param("uuid") String uuid); @Query("select u from User u " + - "where u.uuid = :uuid") + "where u.uuid = :uuid") Optional findByUuidNotFiltered(@Param("uuid") String uuid); @Query("select case when count(u) > 0 then true else false end from User u " + - "where u.uuid = :uuid " + - "and u.deletedAt is null") + "where u.uuid = :uuid " + + "and u.deletedAt is null") boolean existsByUuid(@Param("uuid") String uuid); @Query("select u from User u " + - "where u.yelloId = :yelloId " + - "and u.deletedAt is null") + "where u.yelloId = :yelloId " + + "and u.deletedAt is null") Optional findByYelloId(@Param("yelloId") String yelloId); @Query("select u from User u " + - "where u.yelloId = :yelloId") + "where u.yelloId = :yelloId") Optional findByYelloIdNotFiltered(@Param("yelloId") String yelloId); @Query("select u from User u, UserGroup g " + - "where u.group.id = g.id " + - "and g.id = :groupId " + - "and u.deletedAt is null") + "where u.group.id = g.id " + + "and g.id = :groupId " + + "and u.deletedAt is null") List findAllByGroupId(@Param("groupId") Long groupId); @Query("select count (u) from User u, UserGroup g " + - "where u.group.id = g.id " + - "and g.groupName = :groupName " + - "and u.id <> :userId " + - "and u.id not in (select f.target.id from Friend f where :userId = f.user.id and f.target.deletedAt is null) " - + - "and u.deletedAt is null") + "where u.group.id = g.id " + + "and g.groupName = :groupName " + + "and u.id <> :userId " + + "and u.id not in (select f.target.id from Friend f where :userId = f.user.id and f.target.deletedAt is null) " + + + "and u.deletedAt is null") Integer countAllByGroupNameFilteredByNotFriend(@Param("userId") Long userId, @Param("groupName") String groupName); @Query("select u from User u, UserGroup g " + - "where u.group.id = g.id " + - "and g.groupName = :groupName " + - "and u.id <> :userId " + - "and u.id not in (select f.target.id from Friend f where :userId = f.user.id and f.target.deletedAt is null) " - + - "and u.deletedAt is null") + "where u.group.id = g.id " + + "and g.groupName = :groupName " + + "and u.id <> :userId " + + "and u.id not in (select f.target.id from Friend f where :userId = f.user.id and f.target.deletedAt is null) " + + + "and u.deletedAt is null") List findAllByGroupNameFilteredByNotFriend(@Param("userId") Long userId, - @Param("groupName") String groupName, Pageable pageable); + @Param("groupName") String groupName, Pageable pageable); @Query("select u from User u " - + "where u.group.groupName = :groupName " - + "and u.uuid not in :uuidList " - + "and u.name like CONCAT('%', :keyword, '%') " - + "and u.deletedAt is null " - + "order by u.name ASC ") + + "where u.group.groupName = :groupName " + + "and u.uuid not in :uuidList " + + "and u.name like CONCAT('%', :keyword, '%') " + + "and u.deletedAt is null " + + "order by u.name ASC ") List findAllByGroupContainingName(@Param("groupName") String groupName, - @Param("keyword") String keyword, @Param("uuidList") List uuidList); + @Param("keyword") String keyword, @Param("uuidList") List uuidList); @Query("select u from User u " - + "where u.group.groupName <> :groupName " - + "and u.uuid not in :uuidList " - + "and u.name like CONCAT('%', :keyword, '%') " - + "and u.deletedAt is null " - + "order by u.groupAdmissionYear DESC ") + + "where u.group.groupName <> :groupName " + + "and u.uuid not in :uuidList " + + "and u.name like CONCAT('%', :keyword, '%') " + + "and u.deletedAt is null " + + "order by u.groupAdmissionYear DESC ") List findAllByOtherGroupContainingName(@Param("groupName") String groupName, - @Param("keyword") String keyword, @Param("uuidList") List uuidList); + @Param("keyword") String keyword, @Param("uuidList") List uuidList); + + @Query("select u from User u " + + "where u.group.groupName like CONCAT('%', :keyword, '%') " + + "and u.uuid not in :uuidList " + + "and u not in :friendList " + + "and u.deletedAt is null " + + "order by u.name ASC ") + List findAllByGroupContaining(@Param("keyword") String keyword, @Param("uuidList") List uuidList, @Param("friendList") List friendList); + @Query("select u from User u " - + "where u.group.groupName = :groupName " - + "and u.uuid not in :uuidList " - + "and LOWER(u.yelloId) like LOWER(CONCAT('%', :keyword, '%')) " - + "and u.deletedAt is null " - + "order by u.yelloId ASC ") + + "where u.group.groupName = :groupName " + + "and u.uuid not in :uuidList " + + "and LOWER(u.yelloId) like LOWER(CONCAT('%', :keyword, '%')) " + + "and u.deletedAt is null " + + "order by u.yelloId ASC ") List findAllByGroupContainingYelloId(@Param("groupName") String groupName, - @Param("keyword") String keyword, @Param("uuidList") List uuidList); + @Param("keyword") String keyword, @Param("uuidList") List uuidList); @Query("select u from User u " - + "where u.group.groupName <> :groupName " - + "and u.uuid not in :uuidList " - + "and LOWER(u.yelloId) like LOWER(CONCAT('%', :keyword, '%')) " - + "and u.deletedAt is null " - + "order by u.groupAdmissionYear DESC ") + + "where u.group.groupName <> :groupName " + + "and u.uuid not in :uuidList " + + "and LOWER(u.yelloId) like LOWER(CONCAT('%', :keyword, '%')) " + + "and u.deletedAt is null " + + "order by u.groupAdmissionYear DESC ") List findAllByOtherGroupContainingYelloId(@Param("groupName") String groupName, - @Param("keyword") String keyword, @Param("uuidList") List uuidList); + @Param("keyword") String keyword, @Param("uuidList") List uuidList); @Query("select u from User u " - + "where u.deviceToken = :deviceToken " - + "and u.deletedAt is null") + + "where u.deviceToken = :deviceToken " + + "and u.deletedAt is null") Optional findByDeviceToken(@Param("deviceToken") String deviceToken); @Query("select u from User u " + - "where u.deviceToken = :deviceToken") + "where u.deviceToken = :deviceToken") Optional findByDeviceTokenNotFiltered(@Param("deviceToken") String deviceToken); Long countAllByYelloIdContaining(String yelloId); @@ -117,10 +127,10 @@ List findAllByOtherGroupContainingYelloId(@Param("groupName") String group Long countAllByNameContaining(String name); @Query("select u from User u " - + "where LOWER(u.yelloId) like LOWER(CONCAT('%', :yelloId, '%'))") + + "where LOWER(u.yelloId) like LOWER(CONCAT('%', :yelloId, '%'))") Page findAllByYelloIdContaining(Pageable pageable, @Param("yelloId") String yelloId); @Query("select u from User u " - + "where LOWER(u.name) like LOWER(CONCAT('%', :name, '%'))") + + "where LOWER(u.name) like LOWER(CONCAT('%', :name, '%'))") Page findAllByNameContaining(Pageable pageable, @Param("name") String name); } diff --git a/src/main/java/com/yello/server/domain/user/repository/UserRepository.java b/src/main/java/com/yello/server/domain/user/repository/UserRepository.java index 66df3633..2ef67768 100644 --- a/src/main/java/com/yello/server/domain/user/repository/UserRepository.java +++ b/src/main/java/com/yello/server/domain/user/repository/UserRepository.java @@ -60,6 +60,8 @@ List findAllByGroupContainingYelloId(String groupName, String keyword, List findAllByOtherGroupContainingYelloId(String groupName, String keyword, List uuidList); + List findAllByGroupNameContainingAndFriendListNotContaining(String keyword, List uuidList, List friendList); + Long count(); Long countAllByYelloIdContaining(String yelloId); diff --git a/src/main/java/com/yello/server/domain/user/repository/UserRepositoryImpl.java b/src/main/java/com/yello/server/domain/user/repository/UserRepositoryImpl.java index a56bc042..aa1e8df4 100644 --- a/src/main/java/com/yello/server/domain/user/repository/UserRepositoryImpl.java +++ b/src/main/java/com/yello/server/domain/user/repository/UserRepositoryImpl.java @@ -153,6 +153,11 @@ public List findAllByOtherGroupContainingYelloId(String groupName, String return userJpaRepository.findAllByOtherGroupContainingYelloId(groupName, keyword, uuidList); } + @Override + public List findAllByGroupNameContainingAndFriendListNotContaining(String keyword, List uuidList, List friendList) { + return userJpaRepository.findAllByGroupContaining(keyword, uuidList, friendList); + } + @Override public Long count() { return userJpaRepository.count(); diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 524ecb24..10025da4 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -72,7 +72,7 @@ public enum SuccessCode { private final HttpStatus httpStatus; - private final String message; + String message; public int getHttpStatusCode() { return httpStatus.value(); diff --git a/src/test/java/com/yello/server/domain/user/FakeUserRepository.java b/src/test/java/com/yello/server/domain/user/FakeUserRepository.java index 908bb1ff..569a3e48 100644 --- a/src/test/java/com/yello/server/domain/user/FakeUserRepository.java +++ b/src/test/java/com/yello/server/domain/user/FakeUserRepository.java @@ -258,6 +258,15 @@ public List findAllByOtherGroupContainingYelloId(String groupName, String .toList(); } + @Override + public List findAllByGroupNameContainingAndFriendListNotContaining(String keyword, List uuidList, List friendList) { + return data.stream() + .filter(user -> user.getGroup().getGroupName().contains(keyword)) + .filter(user -> !user.getId().equals(1L)) + .filter(user -> !uuidList.contains(user.getUuid())) + .toList(); + } + @Override public Long count() { return (long) data.size(); From 978bd4658906e030dcb584ac4f600847ce813bac Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 17 Feb 2024 00:13:07 +0900 Subject: [PATCH 102/112] =?UTF-8?q?YEL-220=20[feat]=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yello/server/domain/friend/service/FriendService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/yello/server/domain/friend/service/FriendService.java b/src/main/java/com/yello/server/domain/friend/service/FriendService.java index 4450a430..db988fec 100644 --- a/src/main/java/com/yello/server/domain/friend/service/FriendService.java +++ b/src/main/java/com/yello/server/domain/friend/service/FriendService.java @@ -132,13 +132,12 @@ public SearchFriendResponse searchFriend(Long userId, Pageable pageable, userRepository.findAllByGroupContainingName(groupName, keyword, uuidList)); friendList.addAll( userRepository.findAllByOtherGroupContainingName(groupName, keyword, uuidList)); - + friendList.addAll(userRepository.findAllByGroupNameContainingAndFriendListNotContaining(keyword, uuidList, friendList)); } else { friendList.addAll( userRepository.findAllByGroupContainingYelloId(groupName, keyword, uuidList)); friendList.addAll( userRepository.findAllByOtherGroupContainingYelloId(groupName, keyword, uuidList)); - friendList.addAll(userRepository.findAllByGroupNameContainingAndFriendListNotContaining(keyword, uuidList, friendList)); } List pageList = PaginationFactory.getPage(friendList, pageable) From 3626450113d905cf9004316b7a4902c5499076b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?user=20=EC=9D=B4=EB=A6=84?= Date: Sat, 17 Feb 2024 01:24:44 +0900 Subject: [PATCH 103/112] =?UTF-8?q?YEL-214=20[feat]=20@PathVariable,=20@Re?= =?UTF-8?q?questParam=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/yello/server/domain/admin/controller/AdminController.java b/src/main/java/com/yello/server/domain/admin/controller/AdminController.java index 631f5899..eb8de5e2 100644 --- a/src/main/java/com/yello/server/domain/admin/controller/AdminController.java +++ b/src/main/java/com/yello/server/domain/admin/controller/AdminController.java @@ -47,7 +47,6 @@ import com.yello.server.infrastructure.firebase.dto.request.NotificationCustomMessage; import com.yello.server.infrastructure.firebase.service.NotificationService; import java.util.List; -import javax.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.val; import org.springframework.web.bind.annotation.DeleteMapping; @@ -75,9 +74,9 @@ public BaseResponse postAdminLogin(@RequestBody AdminLoginRe @GetMapping("/user") public BaseResponse getUserAdmin(@AccessTokenUser User user, - @RequestParam Integer page, - @Nullable @RequestParam String field, - @Nullable @RequestParam String value) { + @RequestParam(value = "page") Integer page, + @RequestParam(value = "field", required = false) String field, + @RequestParam(value = "value", required = false) String value) { val data = (field == null && value == null) ? adminService.findUser(user.getId(), createPageableByNameSortDescLimitTen(page)) : adminService.findUserContaining(user.getId(), @@ -88,28 +87,28 @@ public BaseResponse getUserAdmin(@AccessTokenUser User user, @GetMapping("/user/{id}") public BaseResponse getUserDetailAdmin(@AccessTokenUser User user, - @PathVariable Long id) { + @PathVariable(value = "id") Long id) { val data = adminService.findUserDetail(user.getId(), id); return BaseResponse.success(READ_USER_DETAIL_ADMIN_SUCCESS, data); } @PostMapping("/user/{id}") public BaseResponse postUserDetailAdmin(@AccessTokenUser User user, - @PathVariable Long id, @RequestBody AdminUserDetailRequest request) { + @PathVariable(value = "id") Long id, @RequestBody AdminUserDetailRequest request) { val data = adminService.updateUserDetail(user.getId(), id, request); return BaseResponse.success(UPDATE_USER_DETAIL_ADMIN_SUCCESS); } @DeleteMapping("/user") - public BaseResponse deleteUser(@AccessTokenUser User user, @RequestParam Long userId) { + public BaseResponse deleteUser(@AccessTokenUser User user, @RequestParam(value = "userId") Long userId) { adminService.deleteUser(user.getId(), userId); return BaseResponse.success(DELETE_USER_ADMIN_SUCCESS); } @GetMapping("/cooldown") public BaseResponse getCooldownAdmin(@AccessTokenUser User user, - @RequestParam Integer page, - @Nullable @RequestParam String yelloId) { + @RequestParam(value = "page") Integer page, + @RequestParam(value = "yelloId", required = false) String yelloId) { val data = yelloId == null ? adminService.findCooldown(user.getId(), createPageableLimitTen(page)) : adminService.findCooldownContaining(user.getId(), createPageableLimitTen(page), @@ -118,14 +117,15 @@ public BaseResponse getCooldownAdmin(@AccessTokenUser Use } @DeleteMapping("/cooldown") - public BaseResponse deleteCooldown(@AccessTokenUser User user, @RequestParam Long cooldownId) { + public BaseResponse deleteCooldown(@AccessTokenUser User user, + @RequestParam(value = "cooldownId") Long cooldownId) { adminService.deleteCooldown(user.getId(), cooldownId); return BaseResponse.success(DELETE_COOLDOWN_ADMIN_SUCCESS); } @GetMapping("/question") public BaseResponse getQuestionAdmin(@AccessTokenUser User user, - @RequestParam Integer page) { + @RequestParam(value = "page") Integer page) { val data = adminService.findQuestion(user.getId(), createPageable(page, 20)); return BaseResponse.success(READ_QUESTION_ADMIN_SUCCESS, data); } @@ -133,14 +133,14 @@ public BaseResponse getQuestionAdmin(@AccessTokenUser Use @GetMapping("/question/{id}") public BaseResponse getQuestionDetailAdmin( @AccessTokenUser User user, - @PathVariable Long id) { + @PathVariable(value = "id") Long id) { val data = adminService.findQuestionDetail(user.getId(), id); return BaseResponse.success(READ_QUESTION_DETAIL_ADMIN_SUCCESS, data); } @PostMapping("/question/{id}") public BaseResponse postQuestionSendAdmin(@AccessTokenUser User user, - @PathVariable Long id, @RequestBody AdminQuestionVoteRequest request) { + @PathVariable(value = "id") Long id, @RequestBody AdminQuestionVoteRequest request) { val data = adminService.createVote(user.getId(), id, request); data.forEach(notificationService::sendYelloNotification); @@ -148,7 +148,8 @@ public BaseResponse postQuestionSendAdmin(@AccessTokenUser User use } @DeleteMapping("/question") - public BaseResponse deleteQuestion(@AccessTokenUser User user, @RequestParam Long questionId) { + public BaseResponse deleteQuestion(@AccessTokenUser User user, + @RequestParam(value = "questionId") Long questionId) { adminService.deleteQuestion(user.getId(), questionId); return BaseResponse.success(DELETE_QUESTION_ADMIN_SUCCESS); } @@ -162,7 +163,7 @@ public BaseResponse postCustomNotificationSendAdmin(@AccessTokenUse } @GetMapping("/configuration") - public BaseResponse getConfigurations(@RequestParam("tag") String tag, + public BaseResponse getConfigurations(@RequestParam(value = "tag") String tag, @AccessTokenUser User user) { final AdminConfigurationType configurationType = AdminConfigurationType.fromCode(tag); val data = adminService.getConfigurations(user.getId(), configurationType); @@ -194,7 +195,7 @@ public BaseResponse createNotice(@AccessTokenUser User user, } @PostMapping("/notice/{id}") - public BaseResponse updateNotice(@AccessTokenUser User user, @PathVariable("id") Long noticeId, + public BaseResponse updateNotice(@AccessTokenUser User user, @PathVariable(value = "id") Long noticeId, @RequestBody AdminNoticeCreateRequest request) { val data = adminService.updateNotice(user.getId(), noticeId, request); return BaseResponse.success(NOTICE_UPDATE_DETAIL_ADMIN_SUCCESS, data); From 4b2bc7d46616e675a4adaf8fd18a2589f5fef1d2 Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 17 Feb 2024 01:47:41 +0900 Subject: [PATCH 104/112] =?UTF-8?q?YEL-220=20[feat]=20=EA=B4=91=EA=B3=A0?= =?UTF-8?q?=20=EB=B3=B4=EA=B3=A0=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=96=BB?= =?UTF-8?q?=EA=B8=B0=20=EC=97=AC=EB=B6=80=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 10 ++++++++ .../dto/response/GetIsPossibleAdmob.java | 19 ++++++++++++++ .../domain/event/service/EventService.java | 25 +++++++++++++++++++ .../server/domain/user/entity/UserData.java | 11 ++++++++ .../domain/user/entity/UserDataType.java | 4 ++- .../server/global/common/SuccessCode.java | 1 + .../global/common/util/ConstantUtil.java | 4 +-- 7 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/event/dto/response/GetIsPossibleAdmob.java diff --git a/src/main/java/com/yello/server/domain/event/controller/EventController.java b/src/main/java/com/yello/server/domain/event/controller/EventController.java index 17300e89..65a3d2fc 100644 --- a/src/main/java/com/yello/server/domain/event/controller/EventController.java +++ b/src/main/java/com/yello/server/domain/event/controller/EventController.java @@ -4,6 +4,7 @@ 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.GET_IS_POSSIBLE_ADMOB_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; @@ -14,6 +15,7 @@ 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; +import com.yello.server.domain.event.dto.response.GetIsPossibleAdmob; import com.yello.server.domain.event.exception.EventBadRequestException; import com.yello.server.domain.event.service.EventService; import com.yello.server.domain.user.entity.User; @@ -30,6 +32,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -89,4 +92,11 @@ public BaseResponse rewardAdmob(@AccessTokenUser User user, val data = eventService.rewardAdmob(user.getId(), request); return BaseResponse.success(REWARD_ADMOB_SUCCESS, data); } + + @GetMapping("/v1/admob/possible/{tag}") + public BaseResponse getIsPossibleAdmob(@AccessTokenUser User user, @PathVariable("tag") String tag) { + System.out.println(" asfsaasdfasdf"); + val data = eventService.getIsPossibleAdmob(user.getId(), tag); + return BaseResponse.success(GET_IS_POSSIBLE_ADMOB_SUCCESS, data); + } } diff --git a/src/main/java/com/yello/server/domain/event/dto/response/GetIsPossibleAdmob.java b/src/main/java/com/yello/server/domain/event/dto/response/GetIsPossibleAdmob.java new file mode 100644 index 00000000..dd5a07d4 --- /dev/null +++ b/src/main/java/com/yello/server/domain/event/dto/response/GetIsPossibleAdmob.java @@ -0,0 +1,19 @@ +package com.yello.server.domain.event.dto.response; + +import com.yello.server.domain.user.entity.UserData; +import com.yello.server.global.common.factory.TimeFactory; +import lombok.Builder; + +@Builder +public record GetIsPossibleAdmob( + String createdAt, + Boolean isPossible +) { + public static GetIsPossibleAdmob of(UserData userAdmob) { + return GetIsPossibleAdmob.builder() + .createdAt(userAdmob.getValue()) + .isPossible(userAdmob.isPossible()) + .build(); + } + +} diff --git a/src/main/java/com/yello/server/domain/event/service/EventService.java b/src/main/java/com/yello/server/domain/event/service/EventService.java index a9da86f5..09ac1eeb 100644 --- a/src/main/java/com/yello/server/domain/event/service/EventService.java +++ b/src/main/java/com/yello/server/domain/event/service/EventService.java @@ -1,11 +1,15 @@ package com.yello.server.domain.event.service; +import static com.yello.server.domain.user.entity.UserDataType.fromCode; import static com.yello.server.global.common.ErrorCode.DUPLICATE_ADMOB_REWARD_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_COUNT_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_DATE_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.EVENT_TIME_BAD_REQUEST_EXCEPTION; import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_CONFLICT_EXCEPTION; import static com.yello.server.global.common.ErrorCode.IDEMPOTENCY_KEY_NOT_FOUND_EXCEPTION; +import static com.yello.server.global.common.factory.TimeFactory.minusTime; +import static com.yello.server.global.common.factory.TimeFactory.toDateFormattedString; +import static com.yello.server.global.common.util.ConstantUtil.ADMOB_SHOP_TIME; import static com.yello.server.global.common.util.ConstantUtil.GlobalZoneId; import com.fasterxml.jackson.core.JsonProcessingException; @@ -17,6 +21,7 @@ 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; +import com.yello.server.domain.event.dto.response.GetIsPossibleAdmob; import com.yello.server.domain.event.entity.Event; import com.yello.server.domain.event.entity.EventHistory; import com.yello.server.domain.event.entity.EventInstance; @@ -33,6 +38,8 @@ import com.yello.server.domain.event.exception.EventNotFoundException; import com.yello.server.domain.event.repository.EventRepository; import com.yello.server.domain.user.entity.User; +import com.yello.server.domain.user.entity.UserData; +import com.yello.server.domain.user.repository.UserDataRepository; import com.yello.server.domain.user.repository.UserRepository; import com.yello.server.global.common.factory.UuidFactory; import jakarta.servlet.http.HttpServletRequest; @@ -40,6 +47,7 @@ import jakarta.validation.constraints.NotNull; import java.net.URI; import java.time.Duration; +import java.time.LocalDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -61,6 +69,7 @@ public class EventService { private final EventRepository eventRepository; private final ObjectMapper objectMapper; private final UserRepository userRepository; + private final UserDataRepository userDataRepository; public List getEvents(Long userId) throws JsonProcessingException { // exception @@ -290,9 +299,25 @@ public EventRewardResponse rewardAdmob(Long userId, AdmobRewardRequest request) EventInstanceReward rewardInstance = eventRepository.save(EventInstanceReward.of(eventInstance, eventReward)); + // user-data cooldown 추가 + UserData userAdmob = + userDataRepository.findByUserIdAndTag(userId, fromCode(request.rewardType())) + .orElseGet(() -> userDataRepository.save(UserData.of(fromCode(request.rewardType()), + toDateFormattedString(LocalDateTime.now()), user))); + + userAdmob.setValue(toDateFormattedString(LocalDateTime.now())); + return EventRewardResponse.of(rewardInstance); } + public GetIsPossibleAdmob getIsPossibleAdmob(Long userId, String tag) { + final User user = userRepository.getById(userId); + UserData userAdmob = userDataRepository.findByUserIdAndTag(userId, fromCode(tag)) + .orElse(UserData.of(fromCode(tag), + toDateFormattedString(minusTime(LocalDateTime.now(), ADMOB_SHOP_TIME)), user)); + return GetIsPossibleAdmob.of(userAdmob); + } + private EventReward handleRewardByType(AdmobRewardRequest request, User user) { switch (RewardType.fromCode(request.rewardType())) { case ADMOB_POINT -> { diff --git a/src/main/java/com/yello/server/domain/user/entity/UserData.java b/src/main/java/com/yello/server/domain/user/entity/UserData.java index 75a3a04c..4802b2cb 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserData.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserData.java @@ -1,5 +1,9 @@ package com.yello.server.domain.user.entity; +import static com.yello.server.global.common.factory.TimeFactory.getSecondsBetween; +import static com.yello.server.global.common.util.ConstantUtil.ADMOB_TIMER_TIME; + +import com.yello.server.global.common.factory.TimeFactory; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; @@ -10,6 +14,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -53,4 +59,9 @@ public static UserData of(UserDataType tag, String value, User user) { public void setValue(String value) { this.value = value; } + + public Boolean isPossible(){ + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return TimeFactory.getSecondsBetween(LocalDateTime.now(), LocalDateTime.parse(this.value, formatter)) >= ADMOB_TIMER_TIME; + } } diff --git a/src/main/java/com/yello/server/domain/user/entity/UserDataType.java b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java index 3edcb9f9..65390e43 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserDataType.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserDataType.java @@ -3,6 +3,7 @@ import static com.yello.server.global.common.ErrorCode.ENUM_BAD_REQUEST_EXCEPTION; import com.yello.server.global.exception.EnumIllegalArgumentException; +import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.Arrays; import lombok.Getter; @@ -16,7 +17,8 @@ public enum UserDataType { */ WITHDRAW_REASON(String.class, "withdraw-reason"), ACCOUNT_UPDATED_AT(ZonedDateTime.class, "account-updated-at"), - RECOMMENDED(ZonedDateTime.class, "recommended"); + RECOMMENDED(ZonedDateTime.class, "recommended"), + ADMOB_POINT(LocalDateTime.class, "ADMOB_POINT"); private final Class classType; private final String initial; diff --git a/src/main/java/com/yello/server/global/common/SuccessCode.java b/src/main/java/com/yello/server/global/common/SuccessCode.java index 7c305d39..975c0249 100644 --- a/src/main/java/com/yello/server/global/common/SuccessCode.java +++ b/src/main/java/com/yello/server/global/common/SuccessCode.java @@ -63,6 +63,7 @@ public enum SuccessCode { EVENT_JOIN_SUCCESS(OK, "이벤트 참여에 성공하였습니다."), EVENT_REWARD_SUCCESS(OK, "이벤트 보상에 성공하였습니다."), VERIFY_ADMOB_SSV_SUCCESS(OK, "Admob ssv 검증에 성공하였습니다."), + GET_IS_POSSIBLE_ADMOB_SUCCESS(OK, "광고 보고 포인트 얻기 가능 여부 조회에 성공했습니다."), /** * 201 CREATED 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 d16758c3..1aa8819d 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 @@ -73,8 +73,8 @@ public class ConstantUtil { public static final int NO_FRIEND_COUNT = 0; public static final int SUBSCRIBE_DAYS = 7; public static final int PLUS_BASIC_TIME = 0; - public static final String USER_VOTE_TYPE = "send"; - public static final String ALL_VOTE_TYPE = "all"; + public static final int ADMOB_SHOP_TIME = 60; + public static final long ADMOB_TIMER_TIME = 3600L; private ConstantUtil() { throw new IllegalStateException(); From 7a9309f884bf2429bd8aa9bad4f18aeed7f795d4 Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 17 Feb 2024 02:20:24 +0900 Subject: [PATCH 105/112] =?UTF-8?q?YEL-220=20[feat]=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asciidoc/check-is-possible-admob.adoc | 40 ++------- src/docs/asciidoc/index.adoc | 2 +- .../static/docs/check-is-possible-admob.html | 82 ++++++++----------- src/main/resources/static/docs/index.html | 3 + .../event/medium/EventControllerTest.java | 44 +++++++++- 5 files changed, 91 insertions(+), 80 deletions(-) diff --git a/src/docs/asciidoc/check-is-possible-admob.adoc b/src/docs/asciidoc/check-is-possible-admob.adoc index 9d508af8..6620b0cc 100644 --- a/src/docs/asciidoc/check-is-possible-admob.adoc +++ b/src/docs/asciidoc/check-is-possible-admob.adoc @@ -1,52 +1,28 @@ :reproducible: -== 상점에서 보상형 광고 가능한지 여부 (명세) +== 상점에서 보상형 광고 가능한지 여부 === 요청 -[http,json] ----- -GET /api/v1/admob/{tag} HTTP/1.1 -Authorization: Bearer your-access-token -Content-Type: application/json ----- +include::{snippets}/api/v1/admob/possible/http-request.adoc[] === 요청 파라미터 +include::{snippets}/api/v1/admob/possible/path-parameters.adoc[] + ---- -tag -> shop +tag -> ADMOB_POINT ---- * 보상형 광고 다른곳에서 사용할 수도 있으므로 tag로 어떤 곳에서 사용하고 있는곳인지 tag로 명시 -* 현재는 상점 -> shop에서만 보상형 광고 진행 +* 현재는 상점에 있는 보상형 광고 (ADMOB_POINT를 tag에 요청) === 응답 -[http,json] ----- -HTTP/1.1 200 OK -Vary: Origin -Vary: Access-Control-Request-Method -Vary: Access-Control-Request-Headers -Content-Type: application/json -{ - "status" : 200, - "message" : "광고 보고 포인트 얻기 가능 여부 조회에 성공했습니다.", - "data": { - "createdAt": "2024-01-01 12:00:00", - "isPossible" : true, - } -} ----- - +include::{snippets}/api/v1/admob/possible/http-response.adoc[] === NOTE -- Header에 무작위한 UUID4 값을 넣어주세요 -* 예시) IdempotencyKey: 0397b5f3-ecdc-47d6-b5d7-2b1afcf00e87 -- 주의사항 -* 같은 멱등성키를 2번 요청하면, 400번 에러. -- ADMOB -* ADMOB 서버에 SSV(ServerSideVerification) Options의 customData에 입력한 것과 동일한 멱등성 키를 넘겨주세요. === CHANGELOG +- 2024.02.17 API 릴리즈 - 2024.02.16 명세서 작성 \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 20168055..dd03887e 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -118,4 +118,4 @@ * 🆕 link:reward-admob.html[광고보고 보상 얻기, 2024-02-11] -* 🆕 link:check-is-possible-admob.adoc[광고보고 보상 얻기 가능 여부 조회, 2024-02-16 (명세)] \ No newline at end of file +* 🆕 link:check-is-possible-admob.adoc[광고보고 보상 얻기 가능 여부 조회, 2024-02-17] \ No newline at end of file diff --git a/src/main/resources/static/docs/check-is-possible-admob.html b/src/main/resources/static/docs/check-is-possible-admob.html index dd8b368b..b11287cf 100644 --- a/src/main/resources/static/docs/check-is-possible-admob.html +++ b/src/main/resources/static/docs/check-is-possible-admob.html @@ -5,7 +5,7 @@ -상점에서 보상형 광고 가능한지 여부 (명세) +상점에서 보상형 광고 가능한지 여부