-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 알림 토큰 생성, 삭제 API 구현 #296
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
Changes from 21 commits
0146241
8d45b9c
887c07e
c0e3f7d
1073813
2afc1cc
4f5f7ff
8ddd568
aa4fcd5
b7c8b39
82642b7
4b61f34
bb95894
8890a5f
412629b
ae39fe3
df6033f
711cc76
99acb17
ee5b4d7
f568b5e
a6a5df2
17bafe1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.blackcompany.eeos.notification.application.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| @Builder(toBuilder = true) | ||
| public class CreateMemberPushTokenRequest { | ||
| @NotBlank private String pushToken; | ||
| @NotBlank private String provider; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.blackcompany.eeos.notification.application.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| @Builder(toBuilder = true) | ||
| public class DeleteMemberPushTokenRequest { | ||
| @NotBlank private String pushToken; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.blackcompany.eeos.notification.application.exception; | ||
|
|
||
| import com.blackcompany.eeos.common.exception.BusinessException; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public class DeniedDeletePushTokenException extends BusinessException { | ||
| private static final String FAIL_CODE = "5008"; | ||
|
|
||
| public DeniedDeletePushTokenException() { | ||
| super(FAIL_CODE, HttpStatus.FORBIDDEN); | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return "토큰 수정 권한이 없습니다."; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.blackcompany.eeos.notification.application.exception; | ||
|
|
||
| import com.blackcompany.eeos.common.exception.BusinessException; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public class NotFoundNotificationProviderException extends BusinessException { | ||
|
|
||
| private static final String FAIL_CODE = "5006"; | ||
| private final String provider; | ||
|
|
||
| public NotFoundNotificationProviderException(String provider) { | ||
| super(FAIL_CODE, HttpStatus.NOT_FOUND); | ||
| this.provider = provider; | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return String.format("%s 는 존재하지 않는 외부 제공자 입니다.", provider); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.blackcompany.eeos.notification.application.exception; | ||
|
|
||
| import com.blackcompany.eeos.common.exception.BusinessException; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public class NotFoundPushTokenException extends BusinessException { | ||
|
|
||
| private static final String FAIL_CODE = "5007"; | ||
|
|
||
| public NotFoundPushTokenException() { | ||
|
|
||
| super(FAIL_CODE, HttpStatus.NOT_FOUND); | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return "존재하지 않는 알림토큰 입니다."; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.blackcompany.eeos.notification.application.model; | ||
|
|
||
| import com.blackcompany.eeos.common.support.AbstractModel; | ||
| import java.time.LocalDateTime; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @Builder(toBuilder = true) | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| public class MemberPushTokenModel implements AbstractModel { | ||
| private Long id; | ||
| private Long memberId; | ||
| private NotificationProvider notificationProvider; | ||
| private String pushToken; | ||
| private LocalDateTime lastActiveAt; | ||
|
|
||
| public MemberPushTokenModel renew(Long memberId) { | ||
| return this.toBuilder().memberId(memberId).lastActiveAt(LocalDateTime.now()).build(); | ||
| } | ||
| } | ||
|
Comment on lines
+14
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말이 길어졌습니다..ㅎ 알림 토큰의 경우 생성시 타임스탬프를 기록해서 신선도 관리가 필요합니다. 이 경우에 마지막으로 사용한 날짜를 update_date 로 하면 jpa dirty checking 에 걸리지 않을 수도 있겠다 생각했습니다 (기존 저장된 토큰과 비교해서 변경사항이 없기 때문) 이때문에 lastActiveAt 이라는 필드를 만들게 되었고, 사용자가 로그인 후 기존 알림 토큰을 사용할때 lastActiveAt 값이 현재시간으로 업데이트되어 마지막으로 토큰을 사용한 시간을 기록하도록 구현했습니다! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.blackcompany.eeos.notification.application.model; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.exception.NotFoundNotificationProviderException; | ||
| import java.util.Arrays; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public enum NotificationProvider { | ||
| FCM("fcm"), | ||
| ETC("etc"); | ||
|
|
||
| private final String provider; | ||
|
|
||
| NotificationProvider(String provider) { | ||
| this.provider = provider; | ||
| } | ||
|
|
||
| public static NotificationProvider find(String name) { | ||
| return Arrays.stream(NotificationProvider.values()) | ||
| .filter(provider -> provider.name().equalsIgnoreCase(name)) | ||
| .findFirst() | ||
| .orElseThrow(() -> new NotFoundNotificationProviderException(name)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.blackcompany.eeos.notification.application.model.converter; | ||
|
|
||
| import com.blackcompany.eeos.common.support.converter.AbstractEntityConverter; | ||
| import com.blackcompany.eeos.notification.application.model.MemberPushTokenModel; | ||
| import com.blackcompany.eeos.notification.persistence.MemberPushTokenEntity; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class MemberPushTokenEntityConverter | ||
| implements AbstractEntityConverter<MemberPushTokenEntity, MemberPushTokenModel> { | ||
|
|
||
| @Override | ||
| public MemberPushTokenModel from(MemberPushTokenEntity source) { | ||
| return MemberPushTokenModel.builder() | ||
| .id(source.getId()) | ||
| .memberId(source.getMemberId()) | ||
| .notificationProvider(source.getProvider()) | ||
| .pushToken(source.getPushToken()) | ||
| .lastActiveAt(source.getLastActiveAt()) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public MemberPushTokenEntity toEntity(MemberPushTokenModel source) { | ||
| return MemberPushTokenEntity.builder() | ||
| .id(source.getId()) | ||
| .memberId(source.getMemberId()) | ||
| .pushToken(source.getPushToken()) | ||
| .provider(source.getNotificationProvider()) | ||
| .lastActiveAt(source.getLastActiveAt()) | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.blackcompany.eeos.notification.application.repository; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.model.MemberPushTokenModel; | ||
| import com.blackcompany.eeos.notification.application.model.NotificationProvider; | ||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface MemberPushTokenRepository { | ||
| Optional<MemberPushTokenModel> findByPushToken(String pushToken); | ||
|
|
||
| List<MemberPushTokenModel> findByMemberId(Long memberId); | ||
|
|
||
| List<MemberPushTokenModel> findByMemberIdAndProvider( | ||
| Long memberId, NotificationProvider provider); | ||
|
|
||
| void deleteByPushToken(String pushToken); | ||
|
|
||
| void deleteByMemberId(Long memberId); | ||
|
|
||
| int deleteByUpdatedDateBefore(LocalDateTime updatedDate); | ||
|
|
||
| MemberPushTokenModel save(MemberPushTokenModel memberPushToken); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.blackcompany.eeos.notification.application.service; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.dto.CreateMemberPushTokenRequest; | ||
| import com.blackcompany.eeos.notification.application.dto.DeleteMemberPushTokenRequest; | ||
| import com.blackcompany.eeos.notification.application.exception.DeniedDeletePushTokenException; | ||
| import com.blackcompany.eeos.notification.application.exception.NotFoundPushTokenException; | ||
| import com.blackcompany.eeos.notification.application.model.MemberPushTokenModel; | ||
| import com.blackcompany.eeos.notification.application.model.NotificationProvider; | ||
| import com.blackcompany.eeos.notification.application.repository.MemberPushTokenRepository; | ||
| import com.blackcompany.eeos.notification.application.usecase.CreateMemberPushTokenUsecase; | ||
| import com.blackcompany.eeos.notification.application.usecase.DeleteAllMemberPushTokensUsecase; | ||
| import com.blackcompany.eeos.notification.application.usecase.DeleteMemberPushTokenUsecase; | ||
| import java.time.LocalDateTime; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class NotificationTokenService | ||
| implements CreateMemberPushTokenUsecase, | ||
| DeleteAllMemberPushTokensUsecase, | ||
| DeleteMemberPushTokenUsecase { | ||
|
|
||
| private final MemberPushTokenRepository memberPushTokenRepository; | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void create(Long memberId, CreateMemberPushTokenRequest request) { | ||
| NotificationProvider provider = NotificationProvider.find(request.getProvider()); | ||
|
|
||
| MemberPushTokenModel model = | ||
| memberPushTokenRepository | ||
| .findByPushToken(request.getPushToken()) | ||
| .map(existingModel -> existingModel.renew(memberId)) | ||
| .orElseGet( | ||
| () -> | ||
| MemberPushTokenModel.builder() | ||
| .memberId(memberId) | ||
| .pushToken(request.getPushToken()) | ||
| .notificationProvider(provider) | ||
| .lastActiveAt(LocalDateTime.now()) | ||
| .build()); | ||
|
|
||
| memberPushTokenRepository.save(model); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| @Transactional | ||
| public void deleteAllMemberPushTokens(Long memberId) { | ||
| memberPushTokenRepository.deleteByMemberId(memberId); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void delete(Long memberId, DeleteMemberPushTokenRequest request) { | ||
| MemberPushTokenModel model = | ||
| memberPushTokenRepository | ||
| .findByPushToken(request.getPushToken()) | ||
| .orElseThrow(NotFoundPushTokenException::new); | ||
| if (!model.getMemberId().equals(memberId)) { | ||
| throw new DeniedDeletePushTokenException(); | ||
| } | ||
| memberPushTokenRepository.deleteByPushToken(request.getPushToken()); | ||
| } | ||
Daae-Kim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.blackcompany.eeos.notification.application.usecase; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.dto.CreateMemberPushTokenRequest; | ||
|
|
||
| public interface CreateMemberPushTokenUsecase { | ||
| void create(Long memberId, CreateMemberPushTokenRequest request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.blackcompany.eeos.notification.application.usecase; | ||
|
|
||
| public interface DeleteAllMemberPushTokensUsecase { | ||
| void deleteAllMemberPushTokens(Long memberId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.blackcompany.eeos.notification.application.usecase; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.dto.DeleteMemberPushTokenRequest; | ||
|
|
||
| public interface DeleteMemberPushTokenUsecase { | ||
| void delete(Long memberId, DeleteMemberPushTokenRequest request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.blackcompany.eeos.notification.persistence; | ||
|
|
||
| import com.blackcompany.eeos.notification.application.model.NotificationProvider; | ||
| import java.sql.Timestamp; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| public interface JpaMemberPushTokenRepository extends JpaRepository<MemberPushTokenEntity, Long> { | ||
|
|
||
| Optional<MemberPushTokenEntity> findByPushToken(String token); | ||
|
|
||
| List<MemberPushTokenEntity> findByMemberId(Long memberId); | ||
|
|
||
| List<MemberPushTokenEntity> findByMemberIdAndProvider( | ||
| Long memberId, NotificationProvider provider); | ||
|
|
||
| @Modifying | ||
| @Query("DELETE FROM MemberPushTokenEntity t WHERE t.memberId = :memberId") | ||
| void deleteByMemberId(@Param("memberId") Long memberId); | ||
|
|
||
| void deleteByPushToken(String token); | ||
|
|
||
| int deleteByUpdatedDateBefore(Timestamp updatedDate); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.