Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

M3-286 FCM 토큰 관리기능 서버 구현 #60

Merged
merged 11 commits into from
Aug 8, 2024
Merged
13 changes: 13 additions & 0 deletions src/main/java/com/m3pro/groundflip/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,9 +14,11 @@
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;
import com.m3pro.groundflip.service.FcmService;
import com.m3pro.groundflip.service.UserService;

import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -31,6 +34,7 @@
@SecurityRequirement(name = "Authorization")
public class UserController {
private final UserService userService;
private final FcmService fcmService;

@Operation(summary = "사용자 기본 정보 조회", description = "닉네임, id, 출생년도, 성별, 프로필 사진, 그룹이름, 그룹 id 를 조회 한다.")
@GetMapping("/{userId}")
Expand Down Expand Up @@ -62,4 +66,13 @@ public Response<?> putUserInfo(
userService.deleteUser(userId, userDeleteRequest);
return Response.createSuccessWithNoData();
}

@Operation(summary = "FCM 등록 토큰 등록", description = "푸시 알림을 위한 FCM 등록 토큰을 저장한다.")
@PostMapping("/fcm-token")
public Response<?> postFcmToken(
@RequestBody FcmTokenRequest fcmTokenRequest
) {
fcmService.registerFcmToken(fcmTokenRequest);
return Response.createSuccessWithNoData();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
37 changes: 37 additions & 0 deletions src/main/java/com/m3pro/groundflip/domain/entity/FcmToken.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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<FcmToken, Long> {
Optional<FcmToken> findByUser(User user);

void deleteByUser(User user);
}
43 changes: 43 additions & 0 deletions src/main/java/com/m3pro/groundflip/service/FcmService.java
Original file line number Diff line number Diff line change
@@ -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> fcmToken = fcmTokenRepository.findByUser(user);

if (fcmToken.isPresent()) {
fcmToken.get().updateModifiedAt();
} else {
fcmTokenRepository.save(
FcmToken.builder()
.user(user)
.token(fcmTokenRequest.getFcmToken())
.build()
);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/m3pro/groundflip/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
75 changes: 75 additions & 0 deletions src/test/java/com/m3pro/groundflip/service/FcmServiceTest.java
Original file line number Diff line number Diff line change
@@ -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("[email protected]").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("[email protected]").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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -50,6 +51,9 @@ class UserServiceTest {
@Mock
private RankingRedisRepository rankingRedisRepository;

@Mock
private FcmTokenRepository fcmTokenRepository;

@Mock
private S3Uploader s3Uploader;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading