Skip to content

Commit

Permalink
Merge pull request #69 from YAPP-Github/feature/PC-684-fcm-device-token
Browse files Browse the repository at this point in the history
[PC-684] FCM 토큰 관련 기능 구현
  • Loading branch information
devchlee12 authored Mar 5, 2025
2 parents 9a0d58a + bab9813 commit 0d4e074
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.yapp.core.auth.token.RefreshTokenService;
import org.yapp.domain.user.application.UserService;

@Service
@RequiredArgsConstructor
public class LogoutService {

private final RefreshTokenService refreshTokenService;
private final UserService userService;

public void logout(Long userId) {
userService.deleteFcmToken(userId);
refreshTokenService.deleteRefreshToken(userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.yapp.domain.user.application;

import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.yapp.core.auth.AuthToken;
import org.yapp.core.auth.AuthTokenGenerator;
import org.yapp.core.domain.fcm.FcmToken;
import org.yapp.core.domain.profile.Profile;
import org.yapp.core.domain.user.RoleStatus;
import org.yapp.core.domain.user.User;
Expand All @@ -13,20 +15,23 @@
import org.yapp.core.exception.ApplicationException;
import org.yapp.core.exception.error.code.UserErrorCode;
import org.yapp.domain.auth.presentation.dto.response.OauthLoginResponse;
import org.yapp.domain.user.dao.FcmTokenRepository;
import org.yapp.domain.user.dao.UserDeleteReasonRepository;
import org.yapp.domain.user.dao.UserRejectHistoryRepository;
import org.yapp.domain.user.dao.UserRepository;
import org.yapp.domain.user.presentation.dto.request.FcmTokenSaveRequest;
import org.yapp.domain.user.presentation.dto.response.UserBasicInfoResponse;
import org.yapp.domain.user.presentation.dto.response.UserRejectHistoryResponse;

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final UserRejectHistoryRepository userRejectHistoryRepository;
private final UserDeleteReasonRepository userDeleteReasonRepository;
private final AuthTokenGenerator authTokenGenerator;
private final UserRepository userRepository;
private final UserRejectHistoryRepository userRejectHistoryRepository;
private final UserDeleteReasonRepository userDeleteReasonRepository;
private final AuthTokenGenerator authTokenGenerator;
private final FcmTokenRepository fcmTokenRepository;

/**
* Role을 USER로 바꾸고 변경된 토큰을 반환한다.
Expand Down Expand Up @@ -98,10 +103,26 @@ public UserBasicInfoResponse getUserBasicInfo(Long userId) {
User user = this.getUserById(userId);

Profile profile = user.getProfile();

String profileStatus =
profile != null ? profile.getProfileStatus().toString() : null;

return new UserBasicInfoResponse(userId, user.getRole(), profileStatus);
}

@Transactional
public void saveFcmToken(Long userId, FcmTokenSaveRequest request) {
Optional<FcmToken> fcmTokenOptional = fcmTokenRepository.findByUserId(userId);
if (fcmTokenOptional.isPresent()) {
FcmToken fcmToken = fcmTokenOptional.get();
fcmToken.updateToken(request.getToken());
} else {
FcmToken fcmToken = new FcmToken(userId, request.getToken());
fcmTokenRepository.save(fcmToken);
}
}

@Transactional
public void deleteFcmToken(Long userId) {
fcmTokenRepository.deleteByUserId(userId);
}
}
12 changes: 12 additions & 0 deletions api/src/main/java/org/yapp/domain/user/dao/FcmTokenRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.yapp.domain.user.dao;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.yapp.core.domain.fcm.FcmToken;

public interface FcmTokenRepository extends JpaRepository<FcmToken, Long> {

Optional<FcmToken> findByUserId(Long userId);

void deleteByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
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;
import org.yapp.domain.auth.application.oauth.service.OauthService;
import org.yapp.domain.user.application.UserService;
import org.yapp.domain.user.presentation.dto.request.FcmTokenSaveRequest;
import org.yapp.domain.user.presentation.dto.request.OauthUserDeleteRequest;
import org.yapp.domain.user.presentation.dto.request.UserDeleteRequest;
import org.yapp.domain.user.presentation.dto.response.UserBasicInfoResponse;
Expand Down Expand Up @@ -69,4 +71,14 @@ public ResponseEntity<CommonResponse<UserBasicInfoResponse>> getUserBasicInfo(
UserBasicInfoResponse userBasicInfo = userService.getUserBasicInfo(userId);
return ResponseEntity.ok(CommonResponse.createSuccess(userBasicInfo));
}

@PostMapping("/fcm-token")
@PreAuthorize(value = "isAuthenticated()")
@Operation(summary = "FCM 토큰 등록", description = "FCM 토큰을 등록합니다", tags = {"사용자"})
public ResponseEntity<CommonResponse<Void>> saveFcmToken(
@AuthenticationPrincipal Long userId,
@RequestBody FcmTokenSaveRequest request) {
userService.saveFcmToken(userId, request);
return ResponseEntity.ok(CommonResponse.createSuccessWithNoContent());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.yapp.domain.user.presentation.dto.request;

import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Getter
public class FcmTokenSaveRequest {

private String token;
}
35 changes: 29 additions & 6 deletions core/auth/src/main/java/org/yapp/core/auth/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.yapp.core.auth.jwt;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -15,12 +17,17 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.yapp.core.exception.error.code.AuthErrorCode;
import org.yapp.core.exception.error.code.ErrorCode;
import org.yapp.core.exception.error.response.ErrorResponse;

@RequiredArgsConstructor
@Component
public class JwtFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;
private final ObjectMapper objectMapper;


@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
Expand All @@ -37,20 +44,25 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
try {
jwtUtil.isExpired(accessToken);
} catch (ExpiredJwtException e) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

PrintWriter writer = response.getWriter();
writer.print("액세스 토큰이 만료되었습니다.");

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String errorMessage = createErrorMessage(AuthErrorCode.EXPIRED_TOKEN);
writer.print(errorMessage);
return;
}

String category = jwtUtil.getCategory(accessToken);
if (!category.equals("access_token")) {
PrintWriter writer = response.getWriter();
writer.print("토큰의 카테고리가 액세스 토큰이 아닙니다.");

response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

PrintWriter writer = response.getWriter();
String errorMessage = createErrorMessage(AuthErrorCode.INVALID_TOKEN_CATEGORY);
writer.print(errorMessage);
return;
}

Expand All @@ -65,4 +77,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

filterChain.doFilter(request, response);
}

private String createErrorMessage(ErrorCode errorCode) {
ErrorResponse errorResponse = new ErrorResponse(errorCode.name(), errorCode.getMessage(), null);
String errorMessage;
try {
errorMessage = objectMapper.writeValueAsString(errorResponse);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return errorMessage;
}
}
11 changes: 11 additions & 0 deletions core/domain/src/main/java/org/yapp/core/domain/fcm/FcmToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@Table(name = "fcm_token")
public class FcmToken {

Expand All @@ -21,4 +23,13 @@ public class FcmToken {
private Long userId;
@Column(name = "token")
private String token;

public FcmToken(Long userId, String token) {
this.userId = userId;
this.token = token;
}

public void updateToken(String token) {
this.token = token;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
@Getter
@RequiredArgsConstructor
public enum AuthErrorCode implements ErrorCode {
OAUTH_ERROR(HttpStatus.FORBIDDEN, "OAuth 오류"),
ACCESS_DENIED(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),
OAUTH_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "OAuth ID를 찾을 수 없습니다."),
ID_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "ID 토큰을 찾을 수 없습니다.");
OAUTH_ERROR(HttpStatus.FORBIDDEN, "OAuth 오류"),
ACCESS_DENIED(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),
OAUTH_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "OAuth ID를 찾을 수 없습니다."),
ID_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "ID 토큰을 찾을 수 없습니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."),
INVALID_TOKEN_CATEGORY(HttpStatus.UNAUTHORIZED, "토큰의 카테고리가 액세스 토큰이 아닙니다.");

private final HttpStatus httpStatus;
private final String message;

private final HttpStatus httpStatus;
private final String message;
}

0 comments on commit 0d4e074

Please sign in to comment.