From 25cfe50ea4bb77232a2bae1638fc2aee35b7a428 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 16:29:41 +0900 Subject: [PATCH 01/11] =?UTF-8?q?M3-286=20Feat=20:=20fcm=20token=20entity?= =?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 --- .../groundflip/domain/entity/FcmToken.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/com/m3pro/groundflip/domain/entity/FcmToken.java diff --git a/src/main/java/com/m3pro/groundflip/domain/entity/FcmToken.java b/src/main/java/com/m3pro/groundflip/domain/entity/FcmToken.java new file mode 100644 index 00000000..b0dd472a --- /dev/null +++ b/src/main/java/com/m3pro/groundflip/domain/entity/FcmToken.java @@ -0,0 +1,37 @@ +package com.m3pro.groundflip.domain.entity; + +import com.m3pro.groundflip.domain.entity.global.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "fcm_token") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class FcmToken extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "fcm_token_id") + private Long id; + + private String token; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; +} From 2f658e53259ff431d721111c37e90eb5da17270a Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 16:36:59 +0900 Subject: [PATCH 02/11] =?UTF-8?q?M3-286=20Feat=20:=20FcmTokenRequest=20dto?= =?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 --- .../domain/dto/user/FcmTokenRequest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/m3pro/groundflip/domain/dto/user/FcmTokenRequest.java diff --git a/src/main/java/com/m3pro/groundflip/domain/dto/user/FcmTokenRequest.java b/src/main/java/com/m3pro/groundflip/domain/dto/user/FcmTokenRequest.java new file mode 100644 index 00000000..17dab09f --- /dev/null +++ b/src/main/java/com/m3pro/groundflip/domain/dto/user/FcmTokenRequest.java @@ -0,0 +1,20 @@ +package com.m3pro.groundflip.domain.dto.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(title = "FCM 등록 토큰 저장") +public class FcmTokenRequest { + @Schema(description = "사용자 Id", example = "125") + private Long userId; + + @Schema(description = "사용자 fcm token", example = "sdfghweredasdvasdfq/weqwefs;dvsdghrthwdffevdrer") + private String fcmToken; +} From e9d878a99e1b00fa8245c48faff52b3566168b15 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 16:37:45 +0900 Subject: [PATCH 03/11] =?UTF-8?q?M3-286=20Feat=20:=20FcmToken=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=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 --- .../com/m3pro/groundflip/controller/UserController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/m3pro/groundflip/controller/UserController.java b/src/main/java/com/m3pro/groundflip/controller/UserController.java index e7e7c948..cffb54a6 100644 --- a/src/main/java/com/m3pro/groundflip/controller/UserController.java +++ b/src/main/java/com/m3pro/groundflip/controller/UserController.java @@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile; import com.m3pro.groundflip.domain.dto.Response; +import com.m3pro.groundflip.domain.dto.user.FcmTokenRequest; import com.m3pro.groundflip.domain.dto.user.UserDeleteRequest; import com.m3pro.groundflip.domain.dto.user.UserInfoRequest; import com.m3pro.groundflip.domain.dto.user.UserInfoResponse; @@ -62,4 +63,12 @@ public Response putUserInfo( userService.deleteUser(userId, userDeleteRequest); return Response.createSuccessWithNoData(); } + + @Operation(summary = "FCM 등록 토큰 등록", description = "푸시 알림을 위한 FCM 등록 토큰을 저장한다.") + @PutMapping("/fcm-token") + public Response postFcmToken( + @RequestBody FcmTokenRequest fcmTokenRequest + ) { + return Response.createSuccessWithNoData(); + } } From 3190370dcb16d83846ab33329a0aba38165e6cd2 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:04:16 +0900 Subject: [PATCH 04/11] =?UTF-8?q?M3-286=20Feat=20:=20fcmToken=20=EC=9D=84?= =?UTF-8?q?=20user=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=B0=BE?= =?UTF-8?q?=EB=8A=94=20=EB=A9=94=EC=84=9C=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 --- .../groundflip/repository/FcmTokenRepository.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java diff --git a/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java b/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java new file mode 100644 index 00000000..7c64c778 --- /dev/null +++ b/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java @@ -0,0 +1,12 @@ +package com.m3pro.groundflip.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.m3pro.groundflip.domain.entity.FcmToken; +import com.m3pro.groundflip.domain.entity.User; + +public interface FcmTokenRepository extends JpaRepository { + Optional findByUser(User user); +} From d14886e7d4cb8d2942f3cb4f007bcc5b7d03410b Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:06:21 +0900 Subject: [PATCH 05/11] =?UTF-8?q?M3-286=20Feat=20:=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20=EB=93=B1=EB=A1=9D=ED=95=98=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../m3pro/groundflip/service/FcmService.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/com/m3pro/groundflip/service/FcmService.java diff --git a/src/main/java/com/m3pro/groundflip/service/FcmService.java b/src/main/java/com/m3pro/groundflip/service/FcmService.java new file mode 100644 index 00000000..9bb253c2 --- /dev/null +++ b/src/main/java/com/m3pro/groundflip/service/FcmService.java @@ -0,0 +1,43 @@ +package com.m3pro.groundflip.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.m3pro.groundflip.domain.dto.user.FcmTokenRequest; +import com.m3pro.groundflip.domain.entity.FcmToken; +import com.m3pro.groundflip.domain.entity.User; +import com.m3pro.groundflip.exception.AppException; +import com.m3pro.groundflip.exception.ErrorCode; +import com.m3pro.groundflip.repository.FcmTokenRepository; +import com.m3pro.groundflip.repository.UserRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FcmService { + private final UserRepository userRepository; + private final FcmTokenRepository fcmTokenRepository; + + @Transactional + public void registerFcmToken(FcmTokenRequest fcmTokenRequest) { + Long userId = fcmTokenRequest.getUserId(); + User user = userRepository.findById(userId).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND)); + Optional fcmToken = fcmTokenRepository.findByUser(user); + + if (fcmToken.isPresent()) { + fcmToken.get().updateModifiedAt(); + } else { + fcmTokenRepository.save( + FcmToken.builder() + .user(user) + .token(fcmTokenRequest.getFcmToken()) + .build() + ); + } + } +} From c693f52575daf3c936f36c9d461573376893af3b Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:16:36 +0900 Subject: [PATCH 06/11] =?UTF-8?q?M3-286=20Feat=20:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/m3pro/groundflip/controller/UserController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/m3pro/groundflip/controller/UserController.java b/src/main/java/com/m3pro/groundflip/controller/UserController.java index cffb54a6..b360b08c 100644 --- a/src/main/java/com/m3pro/groundflip/controller/UserController.java +++ b/src/main/java/com/m3pro/groundflip/controller/UserController.java @@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; 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.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,6 +18,7 @@ import com.m3pro.groundflip.domain.dto.user.UserDeleteRequest; import com.m3pro.groundflip.domain.dto.user.UserInfoRequest; import com.m3pro.groundflip.domain.dto.user.UserInfoResponse; +import com.m3pro.groundflip.service.FcmService; import com.m3pro.groundflip.service.UserService; import io.swagger.v3.oas.annotations.Operation; @@ -32,6 +34,7 @@ @SecurityRequirement(name = "Authorization") public class UserController { private final UserService userService; + private final FcmService fcmService; @Operation(summary = "사용자 기본 정보 조회", description = "닉네임, id, 출생년도, 성별, 프로필 사진, 그룹이름, 그룹 id 를 조회 한다.") @GetMapping("/{userId}") @@ -65,10 +68,11 @@ public Response putUserInfo( } @Operation(summary = "FCM 등록 토큰 등록", description = "푸시 알림을 위한 FCM 등록 토큰을 저장한다.") - @PutMapping("/fcm-token") + @PostMapping("/fcm-token") public Response postFcmToken( @RequestBody FcmTokenRequest fcmTokenRequest ) { + fcmService.registerFcmToken(fcmTokenRequest); return Response.createSuccessWithNoData(); } } From ea9f433b2b1433191ebc7ffd301c829dd5965590 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:20:33 +0900 Subject: [PATCH 07/11] =?UTF-8?q?M3-286=20Feat=20:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=EC=8B=9C=20fcm=20=ED=86=A0=ED=81=B0=EB=8F=84?= =?UTF-8?q?=20=EA=B0=99=EC=9D=B4=20=EC=82=AD=EC=A0=9C=20=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/m3pro/groundflip/repository/FcmTokenRepository.java | 2 ++ src/main/java/com/m3pro/groundflip/service/UserService.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java b/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java index 7c64c778..b75cadee 100644 --- a/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java +++ b/src/main/java/com/m3pro/groundflip/repository/FcmTokenRepository.java @@ -9,4 +9,6 @@ public interface FcmTokenRepository extends JpaRepository { Optional findByUser(User user); + + void deleteByUser(User user); } diff --git a/src/main/java/com/m3pro/groundflip/service/UserService.java b/src/main/java/com/m3pro/groundflip/service/UserService.java index 23d7ad3e..a6d88e7e 100644 --- a/src/main/java/com/m3pro/groundflip/service/UserService.java +++ b/src/main/java/com/m3pro/groundflip/service/UserService.java @@ -21,6 +21,7 @@ import com.m3pro.groundflip.exception.ErrorCode; import com.m3pro.groundflip.jwt.JwtProvider; import com.m3pro.groundflip.repository.AppleRefreshTokenRepository; +import com.m3pro.groundflip.repository.FcmTokenRepository; import com.m3pro.groundflip.repository.RankingRedisRepository; import com.m3pro.groundflip.repository.UserCommunityRepository; import com.m3pro.groundflip.repository.UserRepository; @@ -39,6 +40,7 @@ public class UserService { private final UserRepository userRepository; private final AppleRefreshTokenRepository appleRefreshTokenRepository; private final UserCommunityRepository userCommunityRepository; + private final FcmTokenRepository fcmTokenRepository; private final S3Uploader s3Uploader; private final JwtProvider jwtProvider; private final AppleApiClient appleApiClient; @@ -122,6 +124,8 @@ public void deleteUser(Long userId, UserDeleteRequest userDeleteRequest) { if (deletedUser.getProvider() == Provider.APPLE) { revokeAppleToken(deletedUser.getId()); } + fcmTokenRepository.deleteByUser(deletedUser); + deletedUser.updateBirthYear(convertToDate(1900)); deletedUser.updateNickName(null); deletedUser.updateProfileImage(null); From b50da7bc417ac6b0c1436fbf7fa9f73f7937c114 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:46:43 +0900 Subject: [PATCH 08/11] =?UTF-8?q?M3-286=20Test=20:=20registerFcmToken=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../groundflip/service/FcmServiceTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/test/java/com/m3pro/groundflip/service/FcmServiceTest.java diff --git a/src/test/java/com/m3pro/groundflip/service/FcmServiceTest.java b/src/test/java/com/m3pro/groundflip/service/FcmServiceTest.java new file mode 100644 index 00000000..96ce4b4b --- /dev/null +++ b/src/test/java/com/m3pro/groundflip/service/FcmServiceTest.java @@ -0,0 +1,75 @@ +package com.m3pro.groundflip.service; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.m3pro.groundflip.domain.dto.user.FcmTokenRequest; +import com.m3pro.groundflip.domain.entity.FcmToken; +import com.m3pro.groundflip.domain.entity.User; +import com.m3pro.groundflip.exception.AppException; +import com.m3pro.groundflip.exception.ErrorCode; +import com.m3pro.groundflip.repository.FcmTokenRepository; +import com.m3pro.groundflip.repository.UserRepository; + +@ExtendWith(MockitoExtension.class) +class FcmServiceTest { + private static final Long testUserId = 1L; + private static final String testFcmToken = "test token"; + private static FcmTokenRequest fcmTokenRequest; + @Mock + private UserRepository userRepository; + @Mock + private FcmTokenRepository fcmTokenRepository; + @InjectMocks + private FcmService fcmService; + + @BeforeAll + static void beforeAll() { + fcmTokenRequest = new FcmTokenRequest(testUserId, testFcmToken); + } + + @Test + @DisplayName("[registerFcmToken] user 가 없는 경우 에러 발생") + void registerFcmToken_UserNotFound() { + when(userRepository.findById(testUserId)).thenReturn(Optional.empty()); + + AppException exception = assertThrows(AppException.class, () -> fcmService.registerFcmToken(fcmTokenRequest)); + assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.USER_NOT_FOUND); + } + + @Test + @DisplayName("[registerFcmToken] fcm 토큰 새로 등록") + void registerFcmToken_RegisterNewToken() { + User user = User.builder().id(1L).email("test@test.com").build(); + when(userRepository.findById(testUserId)).thenReturn(Optional.of(user)); + when(fcmTokenRepository.findByUser(user)).thenReturn(Optional.empty()); + + fcmService.registerFcmToken(fcmTokenRequest); + + verify(fcmTokenRepository, times(1)).save(any()); + } + + @Test + @DisplayName("[registerFcmToken] fcm 토큰이 이미 등록된 경우 수정 날짜만 변경") + void registerFcmToken_UpdateModifiedDate() { + User user = User.builder().id(1L).email("test@test.com").build(); + FcmToken fcmToken = FcmToken.builder().id(1L).token(testFcmToken).user(user).build(); + when(userRepository.findById(testUserId)).thenReturn(Optional.of(user)); + when(fcmTokenRepository.findByUser(user)).thenReturn(Optional.of(fcmToken)); + + fcmService.registerFcmToken(fcmTokenRequest); + + assertThat(fcmToken.getModifiedAt()).isNotNull(); + } +} \ No newline at end of file From 90d3d4722f848d4fd1b40ec04c78dc443a24aae3 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 17:50:19 +0900 Subject: [PATCH 09/11] =?UTF-8?q?M3-286=20Test=20:=20deleteUser=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20fcm=20token=20=EC=82=AD=EC=A0=9C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=EB=8F=84=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/m3pro/groundflip/service/UserServiceTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/com/m3pro/groundflip/service/UserServiceTest.java b/src/test/java/com/m3pro/groundflip/service/UserServiceTest.java index 4a6ec5d2..10f41aa8 100644 --- a/src/test/java/com/m3pro/groundflip/service/UserServiceTest.java +++ b/src/test/java/com/m3pro/groundflip/service/UserServiceTest.java @@ -36,6 +36,7 @@ import com.m3pro.groundflip.exception.ErrorCode; import com.m3pro.groundflip.jwt.JwtProvider; import com.m3pro.groundflip.repository.AppleRefreshTokenRepository; +import com.m3pro.groundflip.repository.FcmTokenRepository; import com.m3pro.groundflip.repository.RankingRedisRepository; import com.m3pro.groundflip.repository.UserCommunityRepository; import com.m3pro.groundflip.repository.UserRepository; @@ -50,6 +51,9 @@ class UserServiceTest { @Mock private RankingRedisRepository rankingRedisRepository; + @Mock + private FcmTokenRepository fcmTokenRepository; + @Mock private S3Uploader s3Uploader; @@ -207,6 +211,7 @@ void deleteUserTest() { Calendar calendar = Calendar.getInstance(); calendar.setTime(deleteUser.getBirthYear()); + verify(fcmTokenRepository, times(1)).deleteByUser(any()); assertThat(deleteUser.getNickname()).isEqualTo(null); assertThat(deleteUser.getProfileImage()).isEqualTo(null); assertThat(calendar.get(Calendar.YEAR)).isEqualTo(1900); @@ -236,6 +241,7 @@ void deleteUserTestInApple() { Calendar calendar = Calendar.getInstance(); calendar.setTime(deleteUser.getBirthYear()); + verify(fcmTokenRepository, times(1)).deleteByUser(any()); verify(appleApiClient, times(1)).revokeToken(any()); verify(appleRefreshTokenRepository, times(1)).delete(any()); assertThat(deleteUser.getNickname()).isEqualTo(null); From ebd979c7b785f93c05de5a05ddb6530991dbc0a4 Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 18:24:16 +0900 Subject: [PATCH 10/11] =?UTF-8?q?M3-286=20Refactor=20:=20=EB=9E=AD?= =?UTF-8?q?=ED=82=B9=EC=9D=84=20=EC=88=98=EC=A0=95=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20rankingService=20=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../groundflip/service/PixelService.java | 28 +------------------ .../groundflip/service/RankingService.java | 22 +++++++++++++++ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/m3pro/groundflip/service/PixelService.java b/src/main/java/com/m3pro/groundflip/service/PixelService.java index 9923ba74..507add1d 100644 --- a/src/main/java/com/m3pro/groundflip/service/PixelService.java +++ b/src/main/java/com/m3pro/groundflip/service/PixelService.java @@ -150,7 +150,7 @@ private void occupyPixel(PixelOccupyRequest pixelOccupyRequest) { Pixel targetPixel = pixelRepository.findByXAndY(pixelOccupyRequest.getX(), pixelOccupyRequest.getY()) .orElseThrow(() -> new AppException(ErrorCode.PIXEL_NOT_FOUND)); - updateRankingOnCache(targetPixel, occupyingUserId); + rankingService.updateRanking(targetPixel, occupyingUserId); updatePixelOwner(targetPixel, occupyingUserId); updatePixelAddress(targetPixel); @@ -178,32 +178,6 @@ private void updatePixelAddress(Pixel targetPixel) { } } - /** - * 레디스 상에서 랭킹을 조정한다. - * @param targetPixel 랭킹을 조정할 픽셀 - * @param occupyingUserId 현재 픽셀을 방문한 유저 - * @return - * @author 김민욱 - */ - private void updateRankingOnCache(Pixel targetPixel, Long occupyingUserId) { - Long originalOwnerUserId = targetPixel.getUserId(); - LocalDateTime thisWeekStart = DateUtils.getThisWeekStartDate().atTime(0, 0); - LocalDateTime modifiedAt = targetPixel.getModifiedAt(); - - if (Objects.equals(originalOwnerUserId, occupyingUserId)) { - if (modifiedAt.isAfter(thisWeekStart)) { - return; - } - rankingService.increaseCurrentPixelCount(occupyingUserId); - } else { - if (originalOwnerUserId == null || modifiedAt.isBefore(thisWeekStart)) { - rankingService.increaseCurrentPixelCount(occupyingUserId); - } else { - rankingService.updateRankingAfterOccupy(occupyingUserId, originalOwnerUserId); - } - } - } - /** * 개인 기록 모드에서 픽셀 방문 기록을 가져온다 * @param pixelId 기록을 조회할 픽셀 diff --git a/src/main/java/com/m3pro/groundflip/service/RankingService.java b/src/main/java/com/m3pro/groundflip/service/RankingService.java index 4ea38f20..5195211a 100644 --- a/src/main/java/com/m3pro/groundflip/service/RankingService.java +++ b/src/main/java/com/m3pro/groundflip/service/RankingService.java @@ -1,8 +1,10 @@ package com.m3pro.groundflip.service; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -11,6 +13,7 @@ import com.m3pro.groundflip.domain.dto.ranking.Ranking; import com.m3pro.groundflip.domain.dto.ranking.UserRankingResponse; +import com.m3pro.groundflip.domain.entity.Pixel; import com.m3pro.groundflip.domain.entity.RankingHistory; import com.m3pro.groundflip.domain.entity.User; import com.m3pro.groundflip.exception.AppException; @@ -31,6 +34,25 @@ public class RankingService { private final UserRepository userRepository; private final RankingHistoryRepository rankingHistoryRepository; + public void updateRanking(Pixel targetPixel, Long occupyingUserId) { + Long originalOwnerUserId = targetPixel.getUserId(); + LocalDateTime thisWeekStart = DateUtils.getThisWeekStartDate().atTime(0, 0); + LocalDateTime modifiedAt = targetPixel.getModifiedAt(); + + if (Objects.equals(originalOwnerUserId, occupyingUserId)) { + if (modifiedAt.isAfter(thisWeekStart)) { + return; + } + increaseCurrentPixelCount(occupyingUserId); + } else { + if (originalOwnerUserId == null || modifiedAt.isBefore(thisWeekStart)) { + increaseCurrentPixelCount(occupyingUserId); + } else { + updateRankingAfterOccupy(occupyingUserId, originalOwnerUserId); + } + } + } + /** * 현재 픽셀의 수를 1 증가 시킨다. * @param userId 사용자 id From ed082ab2cfe92752368d481344d2f8992d739d5f Mon Sep 17 00:00:00 2001 From: Koo Min Date: Thu, 8 Aug 2024 18:55:28 +0900 Subject: [PATCH 11/11] =?UTF-8?q?M3-286=20Test=20:=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=9E=AD=ED=82=B9=EC=9D=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/entity/global/BaseTimeEntity.java | 6 +- .../m3pro/groundflip/service/FcmService.java | 2 +- .../groundflip/service/PixelService.java | 2 +- .../groundflip/service/RankingService.java | 24 ++----- .../service/RankingServiceTest.java | 64 +++++++++++++++---- 5 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/m3pro/groundflip/domain/entity/global/BaseTimeEntity.java b/src/main/java/com/m3pro/groundflip/domain/entity/global/BaseTimeEntity.java index abac1d16..83e0bf94 100644 --- a/src/main/java/com/m3pro/groundflip/domain/entity/global/BaseTimeEntity.java +++ b/src/main/java/com/m3pro/groundflip/domain/entity/global/BaseTimeEntity.java @@ -22,7 +22,11 @@ public class BaseTimeEntity { @LastModifiedDate private LocalDateTime modifiedAt; - public void updateModifiedAt() { + public void updateModifiedAtToNow() { modifiedAt = LocalDateTime.now(); } + + public void updateModifiedAt(LocalDateTime localDateTime) { + modifiedAt = localDateTime; + } } diff --git a/src/main/java/com/m3pro/groundflip/service/FcmService.java b/src/main/java/com/m3pro/groundflip/service/FcmService.java index 9bb253c2..c47c4aed 100644 --- a/src/main/java/com/m3pro/groundflip/service/FcmService.java +++ b/src/main/java/com/m3pro/groundflip/service/FcmService.java @@ -30,7 +30,7 @@ public void registerFcmToken(FcmTokenRequest fcmTokenRequest) { Optional fcmToken = fcmTokenRepository.findByUser(user); if (fcmToken.isPresent()) { - fcmToken.get().updateModifiedAt(); + fcmToken.get().updateModifiedAtToNow(); } else { fcmTokenRepository.save( FcmToken.builder() diff --git a/src/main/java/com/m3pro/groundflip/service/PixelService.java b/src/main/java/com/m3pro/groundflip/service/PixelService.java index 507add1d..7335af41 100644 --- a/src/main/java/com/m3pro/groundflip/service/PixelService.java +++ b/src/main/java/com/m3pro/groundflip/service/PixelService.java @@ -159,7 +159,7 @@ private void occupyPixel(PixelOccupyRequest pixelOccupyRequest) { private void updatePixelOwner(Pixel targetPixel, Long occupyingUserId) { if (Objects.equals(targetPixel.getUserId(), occupyingUserId)) { - targetPixel.updateModifiedAt(); + targetPixel.updateModifiedAtToNow(); } else { targetPixel.updateUserId(occupyingUserId); } diff --git a/src/main/java/com/m3pro/groundflip/service/RankingService.java b/src/main/java/com/m3pro/groundflip/service/RankingService.java index 5195211a..49822954 100644 --- a/src/main/java/com/m3pro/groundflip/service/RankingService.java +++ b/src/main/java/com/m3pro/groundflip/service/RankingService.java @@ -43,40 +43,24 @@ public void updateRanking(Pixel targetPixel, Long occupyingUserId) { if (modifiedAt.isAfter(thisWeekStart)) { return; } - increaseCurrentPixelCount(occupyingUserId); + rankingRedisRepository.increaseCurrentPixelCount(occupyingUserId); } else { if (originalOwnerUserId == null || modifiedAt.isBefore(thisWeekStart)) { - increaseCurrentPixelCount(occupyingUserId); + rankingRedisRepository.increaseCurrentPixelCount(occupyingUserId); } else { updateRankingAfterOccupy(occupyingUserId, originalOwnerUserId); } } } - /** - * 현재 픽셀의 수를 1 증가 시킨다. - * @param userId 사용자 id - */ - public void increaseCurrentPixelCount(Long userId) { - rankingRedisRepository.increaseCurrentPixelCount(userId); - } - - /** - * 현재 픽셀의 수를 1 감소 시킨다. - * @param userId 사용자 id - */ - public void decreaseCurrentPixelCount(Long userId) { - rankingRedisRepository.decreaseCurrentPixelCount(userId); - } - /** * 픽셀을 새로 차지하는 유저의 점수는 1증가, 빼앗긴 유저의 점수는 1감소 * @param occupyingUserId 픽셀을 새로 차지하는 유저 * @param deprivedUserId 픽셀을 뺴앗긴 유저 */ public void updateRankingAfterOccupy(Long occupyingUserId, Long deprivedUserId) { - increaseCurrentPixelCount(occupyingUserId); - decreaseCurrentPixelCount(deprivedUserId); + rankingRedisRepository.increaseCurrentPixelCount(occupyingUserId); + rankingRedisRepository.decreaseCurrentPixelCount(deprivedUserId); } /** diff --git a/src/test/java/com/m3pro/groundflip/service/RankingServiceTest.java b/src/test/java/com/m3pro/groundflip/service/RankingServiceTest.java index 856a3f47..5113eb7f 100644 --- a/src/test/java/com/m3pro/groundflip/service/RankingServiceTest.java +++ b/src/test/java/com/m3pro/groundflip/service/RankingServiceTest.java @@ -21,6 +21,7 @@ import com.m3pro.groundflip.domain.dto.ranking.Ranking; import com.m3pro.groundflip.domain.dto.ranking.UserRankingResponse; +import com.m3pro.groundflip.domain.entity.Pixel; import com.m3pro.groundflip.domain.entity.RankingHistory; import com.m3pro.groundflip.domain.entity.User; import com.m3pro.groundflip.exception.AppException; @@ -28,6 +29,7 @@ import com.m3pro.groundflip.repository.RankingHistoryRepository; import com.m3pro.groundflip.repository.RankingRedisRepository; import com.m3pro.groundflip.repository.UserRepository; +import com.m3pro.groundflip.util.DateUtils; @ExtendWith(MockitoExtension.class) class RankingServiceTest { @@ -46,27 +48,67 @@ void init() { } @Test - @DisplayName("[increaseCurrentPixelCount] userId 에 해당하는 현재 소유 픽셀의 개수를 1 증가시킨다.") - void increaseCurrentPixelCountTest() { - Long userId = 1L; + @DisplayName("[updateRanking] 이번주에 이미 차지한 땅이라면 랭킹 업데이트 하지 않는다.") + void updateRanking_NoUpdate() { + Long occupyingUserId = 1L; + Pixel targetPixel = Pixel.builder().userId(occupyingUserId).build(); + targetPixel.updateModifiedAt(DateUtils.getThisWeekStartDate().atTime(0, 0, 0).plusDays(1)); - rankingService.increaseCurrentPixelCount(userId); + rankingService.updateRanking(targetPixel, occupyingUserId); - verify(rankingRedisRepository, times(1)).increaseCurrentPixelCount(userId); + verify(rankingRedisRepository, never()).increaseCurrentPixelCount(occupyingUserId); } @Test - @DisplayName("[decreasePixelCount] userId 에 해당하는 현재 소유 픽셀의 개수를 1 감소시킨다.") - void decreasePixelCountTest() { - Long userId = 1L; + @DisplayName("[updateRanking] 저번주에 차지한 땅이고 차지한 사람과 차지하려는 사람이 같다면 랭킹을 업데이트한다.") + void updateRanking_UpdateBeforeThisWeek() { + Long occupyingUserId = 1L; + Pixel targetPixel = Pixel.builder().userId(occupyingUserId).build(); + targetPixel.updateModifiedAt(DateUtils.getThisWeekStartDate().atTime(0, 0, 0).minusDays(3)); + + rankingService.updateRanking(targetPixel, occupyingUserId); + + verify(rankingRedisRepository, times(1)).increaseCurrentPixelCount(occupyingUserId); + } + + @Test + @DisplayName("[updateRanking] 아무도 차지 한 적이 없는 땅이라면 차지하려는 사람의 점수를 추가한다.") + void updateRanking_NeverOccupied() { + Long occupyingUserId = 1L; + Pixel targetPixel = Pixel.builder().userId(null).build(); + + rankingService.updateRanking(targetPixel, occupyingUserId); + + verify(rankingRedisRepository, times(1)).increaseCurrentPixelCount(occupyingUserId); + } - rankingService.decreaseCurrentPixelCount(userId); + @Test + @DisplayName("[updateRanking] 가장 최근에 차지한 시간이 저번주라면 지금 차지하려는 사람의 점수를 추가한다.") + void updateRanking_BeforeThisWeek() { + Long occupyingUserId = 1L; + Pixel targetPixel = Pixel.builder().userId(3L).build(); + targetPixel.updateModifiedAt(DateUtils.getThisWeekStartDate().atTime(0, 0, 0).minusDays(3)); - verify(rankingRedisRepository, times(1)).decreaseCurrentPixelCount(userId); + rankingService.updateRanking(targetPixel, occupyingUserId); + + verify(rankingRedisRepository, times(1)).increaseCurrentPixelCount(occupyingUserId); + } + + @Test + @DisplayName("[updateRanking] 가장 최근에 차지한 시간이 이번주이고 차지하려는 사람과 차지한 사람이 다르면 지금 차지하려는 사람의 점수를 추가하고 차지한 사람의 점수는 감소시킨다.") + void updateRanking_OccupyPixel() { + Long occupyingUserId = 1L; + Pixel targetPixel = Pixel.builder().userId(3L).build(); + targetPixel.updateModifiedAt(DateUtils.getThisWeekStartDate().atTime(0, 0, 0).plusDays(3)); + + rankingService.updateRanking(targetPixel, occupyingUserId); + + verify(rankingRedisRepository, times(1)).increaseCurrentPixelCount(occupyingUserId); + verify(rankingRedisRepository, times(1)).decreaseCurrentPixelCount(3L); } @Test - @DisplayName("[decreasePixelCount] occupyingUserId 에 해당하는 현재 소유 픽셀의 개수를 1 증가시키고 deprivedUserId 에 해당하는 현재 소유 픽셀의 개수를 1 감소시킨다.") + @DisplayName("[updateRankingAfterOccupy] occupyingUserId 에 해당하는 현재 소유 픽셀의 개수를 1 증가시키고 deprivedUserId 에 해당하는 현재 소유 픽셀의 개수를 1 감소시킨다.") void updateRankingAfterOccupyTest() { Long occupyingUserId = 1L; Long deprivedUserId = 2L;