diff --git a/src/main/java/com/campus/campus/domain/manager/application/dto/request/RewardGrantedEvent.java b/src/main/java/com/campus/campus/domain/manager/application/dto/request/RewardGrantedEvent.java new file mode 100644 index 00000000..63b476b7 --- /dev/null +++ b/src/main/java/com/campus/campus/domain/manager/application/dto/request/RewardGrantedEvent.java @@ -0,0 +1,10 @@ +package com.campus.campus.domain.manager.application.dto.request; + +import lombok.Builder; + +@Builder +public record RewardGrantedEvent( + Long userId, + String rewardName +) { +} diff --git a/src/main/java/com/campus/campus/domain/manager/application/mapper/ManagerMapper.java b/src/main/java/com/campus/campus/domain/manager/application/mapper/ManagerMapper.java index 35491ae7..45a50f91 100644 --- a/src/main/java/com/campus/campus/domain/manager/application/mapper/ManagerMapper.java +++ b/src/main/java/com/campus/campus/domain/manager/application/mapper/ManagerMapper.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Component; import com.campus.campus.domain.council.domain.entity.StudentCouncil; +import com.campus.campus.domain.manager.application.dto.request.RewardGrantedEvent; import com.campus.campus.domain.manager.application.dto.request.RewardRequest; import com.campus.campus.domain.manager.application.dto.response.CertifyRequestCouncilResponse; import com.campus.campus.domain.manager.application.dto.response.CouncilApproveOrDenyResponse; @@ -71,4 +72,11 @@ public Reward createReward(User user, RewardRequest rewardRequest) { .rewardImageUrl(rewardRequest.rewardImageUrl()) .build(); } + + public RewardGrantedEvent createRewardGrantedEvent(Long userId, String rewardName) { + return RewardGrantedEvent.builder() + .userId(userId) + .rewardName(rewardName) + .build(); + } } diff --git a/src/main/java/com/campus/campus/domain/manager/application/service/ManagerService.java b/src/main/java/com/campus/campus/domain/manager/application/service/ManagerService.java index 03f64a32..671173db 100644 --- a/src/main/java/com/campus/campus/domain/manager/application/service/ManagerService.java +++ b/src/main/java/com/campus/campus/domain/manager/application/service/ManagerService.java @@ -3,6 +3,7 @@ import java.util.List; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.security.crypto.password.PasswordEncoder; @@ -48,6 +49,7 @@ public class ManagerService { private final RedisTokenService redisTokenService; private final ManagerMapper managerMapper; private final JavaMailSender javaMailSender; + private final ApplicationEventPublisher eventPublisher; @Value("${jwt.refresh.expiration-seconds}") private long refreshTokenExpirationSeconds; @@ -130,6 +132,8 @@ public void grantRewardToUser(Long userId, RewardRequest rewardRequest) { rewardRepository.save(reward); user.updateRewardNeeded(false); + + eventPublisher.publishEvent(managerMapper.createRewardGrantedEvent(userId, "스탬프 보상")); } private void sendCouncilApprovedMail(String to) { diff --git a/src/main/java/com/campus/campus/domain/manager/application/service/RewardPushListener.java b/src/main/java/com/campus/campus/domain/manager/application/service/RewardPushListener.java new file mode 100644 index 00000000..586879b9 --- /dev/null +++ b/src/main/java/com/campus/campus/domain/manager/application/service/RewardPushListener.java @@ -0,0 +1,48 @@ +package com.campus.campus.domain.manager.application.service; + +import java.util.Map; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import com.campus.campus.domain.manager.application.dto.request.RewardGrantedEvent; +import com.campus.campus.domain.notification.application.service.NotificationService; +import com.campus.campus.global.firebase.application.service.FirebaseCloudMessageService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RewardPushListener { + private static final String DATA_KEY_TYPE = "type"; + private static final String DATA_TYPE_REWARD_GRANTED = "REWARD_GRANTED"; + + private final FirebaseCloudMessageService firebaseCloudMessageService; + private final NotificationService notificationService; + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleRewardGrantedEvent(RewardGrantedEvent rewardGrantedEvent) { + String title = "보상 지급 알림"; + String body = "스탬프 보상이 지급되었습니다. 보상함을 확인해주세요."; + + notificationService.saveRewardGrantedNotification(rewardGrantedEvent.userId(), title, body); + + String userTopic = "user_" + rewardGrantedEvent.userId(); + + log.info("[PUSH] Reward granted. topic={}, userId={}", userTopic, rewardGrantedEvent.userId()); + + firebaseCloudMessageService.sendToTopic( + userTopic, + title, + body, + Map.of( + DATA_KEY_TYPE, DATA_TYPE_REWARD_GRANTED + ) + ); + } +} diff --git a/src/main/java/com/campus/campus/domain/notification/application/service/NotificationService.java b/src/main/java/com/campus/campus/domain/notification/application/service/NotificationService.java index d905e98f..0b8b48df 100644 --- a/src/main/java/com/campus/campus/domain/notification/application/service/NotificationService.java +++ b/src/main/java/com/campus/campus/domain/notification/application/service/NotificationService.java @@ -101,6 +101,17 @@ public void savePostCreatedNotification(CouncilPostCreatedEvent event, String ti notificationRepository.saveAll(notifications); } + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveRewardGrantedNotification(Long userId, String title, String body) { + User user = userRepository.findByIdAndDeletedAtIsNull(userId) + .orElseThrow(UserNotFoundException::new); + + Notification notification = notificationMapper.createNotification(user, NotificationType.REWARD_GRANTED, title, + body, null); + + notificationRepository.save(notification); + } + @Transactional(readOnly = true) public boolean hasUnread(Long userId) { return notificationRepository.existsByUser_IdAndIsReadFalse(userId); diff --git a/src/main/java/com/campus/campus/domain/notification/domain/entity/NotificationType.java b/src/main/java/com/campus/campus/domain/notification/domain/entity/NotificationType.java index cf3a0a50..1759adfb 100644 --- a/src/main/java/com/campus/campus/domain/notification/domain/entity/NotificationType.java +++ b/src/main/java/com/campus/campus/domain/notification/domain/entity/NotificationType.java @@ -2,5 +2,6 @@ public enum NotificationType { COUNCIL_POST_CREATED, - SYSTEM_NOTICE + SYSTEM_NOTICE, + REWARD_GRANTED }