-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WALWAL-173] fcm alarm list 구현 (#142)
* feature: FCM 알림 리스트 API 및 읽음 처리 구현 * fix: 테스트코드에 FcmNotificationService 빈 추가 * fix: FcmNotificationResponse CreatedAt 응답값 추가 * test: FcmNotification 테스트 코드 추가 * fix: Contants 분리 및 FcmService 수정 * fix: sonarCloud issue 수정 및 @MockBean 추가 * fix: 메서드 분리 및 수정 * fix: notificationImageUrl 제거 후 서비스로직에서 imageUrl제공 * fix: test코드 Optional추가로인한 코드 수정 * feature: 알림 리스트 조회 시 cursor & limit 사용 * fix: missionRecord불러올때 N+1문제 해결 * fix: test코드수정
- Loading branch information
Showing
20 changed files
with
665 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
src/main/java/com/depromeet/stonebed/domain/fcm/application/FcmNotificationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package com.depromeet.stonebed.domain.fcm.application; | ||
|
||
import com.depromeet.stonebed.domain.fcm.dao.FcmNotificationRepository; | ||
import com.depromeet.stonebed.domain.fcm.dao.FcmRepository; | ||
import com.depromeet.stonebed.domain.fcm.domain.FcmNotification; | ||
import com.depromeet.stonebed.domain.fcm.domain.FcmNotificationType; | ||
import com.depromeet.stonebed.domain.fcm.domain.FcmToken; | ||
import com.depromeet.stonebed.domain.fcm.dto.response.FcmNotificationDto; | ||
import com.depromeet.stonebed.domain.fcm.dto.response.FcmNotificationResponse; | ||
import com.depromeet.stonebed.domain.member.domain.Member; | ||
import com.depromeet.stonebed.domain.missionRecord.dao.MissionRecordBoostRepository; | ||
import com.depromeet.stonebed.domain.missionRecord.dao.MissionRecordRepository; | ||
import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecord; | ||
import com.depromeet.stonebed.global.common.constants.FcmNotificationConstants; | ||
import com.depromeet.stonebed.global.error.ErrorCode; | ||
import com.depromeet.stonebed.global.error.exception.CustomException; | ||
import com.depromeet.stonebed.global.util.FcmNotificationUtil; | ||
import com.depromeet.stonebed.global.util.MemberUtil; | ||
import com.google.firebase.messaging.Notification; | ||
import java.time.LocalDateTime; | ||
import java.time.format.DateTimeFormatter; | ||
import java.time.format.DateTimeParseException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.domain.PageRequest; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional | ||
public class FcmNotificationService { | ||
private final FcmService fcmService; | ||
private final FcmNotificationRepository notificationRepository; | ||
private final MissionRecordBoostRepository missionRecordBoostRepository; | ||
private final MissionRecordRepository missionRecordRepository; | ||
private final FcmRepository fcmRepository; | ||
private final MemberUtil memberUtil; | ||
|
||
private static final DateTimeFormatter DATE_FORMATTER = | ||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); | ||
|
||
private static final long POPULAR_THRESHOLD = 500; | ||
private static final long SUPER_POPULAR_THRESHOLD = 5000; | ||
|
||
public void saveNotification( | ||
FcmNotificationType type, String title, String message, Long targetId, Boolean isRead) { | ||
final Member member = memberUtil.getCurrentMember(); | ||
|
||
FcmNotification notification = | ||
FcmNotification.create(type, title, message, member, targetId, isRead); | ||
notificationRepository.save(notification); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public FcmNotificationResponse getNotificationsForCurrentMember(String cursor, int limit) { | ||
Member member = memberUtil.getCurrentMember(); | ||
|
||
Pageable pageable = createPageable(limit); | ||
List<FcmNotification> notifications = getNotifications(cursor, member.getId(), pageable); | ||
List<FcmNotificationDto> notificationData = convertToNotificationDto(notifications); | ||
String nextCursor = getNextCursor(notifications); | ||
|
||
return FcmNotificationResponse.from(notificationData, nextCursor); | ||
} | ||
|
||
private Pageable createPageable(int limit) { | ||
return PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "createdAt")); | ||
} | ||
|
||
private List<FcmNotificationDto> convertToNotificationDto(List<FcmNotification> notifications) { | ||
List<Long> targetIds = | ||
notifications.stream() | ||
.filter( | ||
notification -> | ||
notification.getType() == FcmNotificationType.BOOSTER) | ||
.map(FcmNotification::getTargetId) | ||
.toList(); | ||
|
||
Map<Long, MissionRecord> missionRecordMap = | ||
missionRecordRepository.findByIdIn(targetIds).stream() | ||
.collect( | ||
Collectors.toMap( | ||
MissionRecord::getId, missionRecord -> missionRecord)); | ||
|
||
return notifications.stream() | ||
.map( | ||
notification -> { | ||
MissionRecord missionRecord = | ||
missionRecordMap.get(notification.getTargetId()); | ||
return FcmNotificationDto.from(notification, missionRecord); | ||
}) | ||
.toList(); | ||
} | ||
|
||
private List<FcmNotification> getNotifications( | ||
String cursor, Long memberId, Pageable pageable) { | ||
if (cursor == null) { | ||
return notificationRepository.findByMemberId(memberId, pageable); | ||
} | ||
|
||
try { | ||
LocalDateTime cursorDate = LocalDateTime.parse(cursor, DATE_FORMATTER); | ||
return notificationRepository.findByMemberIdAndCreatedAtLessThanEqual( | ||
memberId, cursorDate, pageable); | ||
} catch (DateTimeParseException e) { | ||
throw new CustomException(ErrorCode.INVALID_CURSOR_DATE_FORMAT); | ||
} | ||
} | ||
|
||
private String getNextCursor(List<FcmNotification> notifications) { | ||
if (notifications.isEmpty()) { | ||
return null; | ||
} | ||
|
||
FcmNotification lastNotification = notifications.get(notifications.size() - 1); | ||
return lastNotification.getCreatedAt().format(DATE_FORMATTER); | ||
} | ||
|
||
public void checkAndSendBoostNotification(MissionRecord missionRecord) { | ||
Long totalBoostCount = | ||
missionRecordBoostRepository.sumBoostCountByMissionRecord(missionRecord.getId()); | ||
|
||
if (totalBoostCount != null) { | ||
FcmNotificationConstants notificationConstants = | ||
determineNotificationType(totalBoostCount); | ||
|
||
if (notificationConstants != null) { | ||
sendBoostNotification(missionRecord, notificationConstants); | ||
} | ||
} | ||
} | ||
|
||
private FcmNotificationConstants determineNotificationType(Long totalBoostCount) { | ||
if (totalBoostCount == POPULAR_THRESHOLD) { | ||
return FcmNotificationConstants.POPULAR; | ||
} else if (totalBoostCount == SUPER_POPULAR_THRESHOLD) { | ||
return FcmNotificationConstants.SUPER_POPULAR; | ||
} | ||
return null; | ||
} | ||
|
||
private void sendBoostNotification( | ||
MissionRecord missionRecord, FcmNotificationConstants notificationConstants) { | ||
Notification notification = | ||
FcmNotificationUtil.buildNotification( | ||
notificationConstants.getTitle(), notificationConstants.getMessage()); | ||
|
||
String token = | ||
fcmRepository | ||
.findByMember(missionRecord.getMember()) | ||
.map(FcmToken::getToken) | ||
.orElseThrow(() -> new CustomException(ErrorCode.FAILED_TO_FIND_FCM_TOKEN)); | ||
|
||
fcmService.sendSingleMessage(notification, token); | ||
|
||
saveNotification( | ||
FcmNotificationType.BOOSTER, | ||
notificationConstants.getTitle(), | ||
notificationConstants.getMessage(), | ||
missionRecord.getId(), | ||
false); | ||
} | ||
|
||
public void markNotificationAsRead(Long notificationId) { | ||
final Member member = memberUtil.getCurrentMember(); | ||
FcmNotification notification = | ||
notificationRepository | ||
.findByIdAndMember(notificationId, member) | ||
.orElseThrow(() -> new CustomException(ErrorCode.NOTIFICATION_NOT_FOUND)); | ||
|
||
notification.markAsRead(); | ||
notificationRepository.save(notification); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
src/main/java/com/depromeet/stonebed/domain/fcm/dao/FcmNotificationRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.depromeet.stonebed.domain.fcm.dao; | ||
|
||
import com.depromeet.stonebed.domain.fcm.domain.FcmNotification; | ||
import com.depromeet.stonebed.domain.member.domain.Member; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface FcmNotificationRepository extends JpaRepository<FcmNotification, Long> { | ||
List<FcmNotification> findByMemberId(Long memberId, Pageable pageable); | ||
|
||
List<FcmNotification> findByMemberIdAndCreatedAtLessThanEqual( | ||
Long memberId, LocalDateTime cursorDate, Pageable pageable); | ||
|
||
Optional<FcmNotification> findByIdAndMember(Long id, Member member); | ||
} |
Oops, something went wrong.