diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index 10f3bc9d..60c18c19 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -107,6 +107,8 @@ jobs: export CLOTH_INFERENCE_PATH=${{ secrets.CLOTH_INFERENCE_PATH }} export STYLE_INFERENCE_PATH=${{ secrets.STYLE_INFERENCE_PATH }} export CLOTH_DETECT_PATH=${{ secrets.CLOTH_DETECT_PATH }} + export DEFAULT_PROFILE_IMAGE_URL=${{ secrets.DEFAULT_PROFILE_IMAGE_URL }} + export TODAY_TEMPERATURE_IMAGE_URL=${{ secrets.TODAY_TEMPERATURE_IMAGE_URL }} sudo mkdir -p /home/ubuntu/secrets echo "${{ secrets.FIREBASE_SA_JSON_B64 }}" | base64 -d | sudo tee /home/ubuntu/secrets/firebase-sa.json > /dev/null diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml index 495b97a3..20b85f26 100644 --- a/.github/workflows/prod-cd.yml +++ b/.github/workflows/prod-cd.yml @@ -117,6 +117,8 @@ jobs: export CLOTH_INFERENCE_PATH=${{ secrets.CLOTH_INFERENCE_PATH }} export STYLE_INFERENCE_PATH=${{ secrets.STYLE_INFERENCE_PATH }} export CLOTH_DETECT_PATH=${{ secrets.CLOTH_DETECT_PATH }} + export DEFAULT_PROFILE_IMAGE_URL=${{ secrets.DEFAULT_PROFILE_IMAGE_URL }} + export TODAY_TEMPERATURE_IMAGE_URL=${{ secrets.TODAY_TEMPERATURE_IMAGE_URL }} sudo mkdir -p /home/ubuntu/secrets echo "${{ secrets.FIREBASE_SA_JSON_B64 }}" | base64 -d | sudo tee /home/ubuntu/secrets/firebase-sa.json > /dev/null diff --git a/clokey-api/dev-compose.yml b/clokey-api/dev-compose.yml index 46bc5775..220f6bac 100644 --- a/clokey-api/dev-compose.yml +++ b/clokey-api/dev-compose.yml @@ -35,6 +35,8 @@ services: # Firebase FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json + DEFAULT_PROFILE_IMAGE_URL: ${DEFAULT_PROFILE_IMAGE_URL} + TODAY_TEMPERATURE_IMAGE_URL: ${TODAY_TEMPERATURE_IMAGE_URL} # JWT JWT_ACCESS_TOKEN_SECRET: ${JWT_ACCESS_TOKEN_SECRET} diff --git a/clokey-api/prod-compose.yml b/clokey-api/prod-compose.yml index 5b7185de..9d397826 100644 --- a/clokey-api/prod-compose.yml +++ b/clokey-api/prod-compose.yml @@ -35,6 +35,8 @@ services: # Firebase FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json + DEFAULT_PROFILE_IMAGE_URL: ${DEFAULT_PROFILE_IMAGE_URL} + TODAY_TEMPERATURE_IMAGE_URL: ${TODAY_TEMPERATURE_IMAGE_URL} # JWT JWT_ACCESS_TOKEN_SECRET: ${JWT_ACCESS_TOKEN_SECRET} diff --git a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java index c303e6e0..135ecde5 100644 --- a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java +++ b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java @@ -6,13 +6,8 @@ import com.google.firebase.messaging.Notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.clokey.comment.entitiy.Comment; import org.clokey.domain.comment.event.NewCommentEvent; import org.clokey.domain.comment.event.NewReplyEvent; -import org.clokey.domain.comment.exception.CommentErrorCode; -import org.clokey.domain.comment.repository.CommentRepository; -import org.clokey.domain.history.exception.HistoryErrorCode; -import org.clokey.domain.history.repository.HistoryRepository; import org.clokey.domain.like.event.NewLikeEvent; import org.clokey.domain.member.exception.MemberErrorCode; import org.clokey.domain.member.repository.MemberRepository; @@ -22,11 +17,9 @@ import org.clokey.domain.notification.exception.NotificationErrorCode; import org.clokey.domain.notification.repository.CodiveNotificationRepository; import org.clokey.domain.term.enums.TermInfo; -import org.clokey.domain.term.exception.TermErrorCode; import org.clokey.domain.term.repository.MemberTermRepository; import org.clokey.exception.BaseCustomException; import org.clokey.global.util.MemberUtil; -import org.clokey.history.entity.History; import org.clokey.member.entity.Member; import org.clokey.member.enums.MemberStatus; import org.clokey.notification.entity.CodiveNotification; @@ -34,6 +27,8 @@ import org.clokey.notification.enums.ReadStatus; import org.clokey.notification.enums.RedirectType; import org.clokey.response.SliceResponse; +import org.clokey.term.entity.MemberTerm; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -51,8 +46,6 @@ public class CodiveNotificationServiceImpl implements CodiveNotificationService private final MemberRepository memberRepository; private final CodiveNotificationRepository codiveNotificationRepository; private final MemberTermRepository memberTermRepository; - private final HistoryRepository historyRepository; - private final CommentRepository commentRepository; private final FirebaseMessaging firebaseMessaging; private final ApplicationEventPublisher eventPublisher; @@ -66,7 +59,12 @@ public class CodiveNotificationServiceImpl implements CodiveNotificationService private static final String TODAY_TEMPERATURE_NOTIFICATION = "오늘의 기온은 %d도 입니다!\n날씨에 맞는 오늘의 옷차림이 기다리고 있어요👀"; - private static final String TODAY_TEMPERATURE_IMAGE_URL = "https://example.com/temperature.png"; + + @Value("${DEFAULT_PROFILE_IMAGE_URL:}") + private String configuredDefaultProfileImageUrl; + + @Value("${TODAY_TEMPERATURE_IMAGE_URL:}") + private String configuredTemperatureImageUrl; @Override @Transactional @@ -77,10 +75,10 @@ public void sendNewFollowerNotification(Long followFromId, Long followToId) { if (isAbleToSendNotification(followToMember)) { String content = String.format(NEW_FOLLOWER_NOTIFICATION, followFromMember.getNickname()); - String profileImageUrl = followFromMember.getProfileImageUrl(); + String profileImageUrl = + resolveNotificationImageUrl(followFromMember.getProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -115,10 +113,10 @@ public void sendNewPendingFollowerNotification(Long followFromId, Long followToI String content = String.format( NEW_PENDING_FOLLOWER_NOTIFICATION, followFromMember.getNickname()); - String profileImageUrl = followFromMember.getProfileImageUrl(); + String profileImageUrl = + resolveNotificationImageUrl(followFromMember.getProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -155,10 +153,9 @@ public void sendNewCommentNotification(NewCommentEvent event) { NEW_COMMENT_NOTIFICATION, event.commenterNickname(), event.commentContent()); - String profileImageUrl = event.commenterProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.commenterProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -191,10 +188,9 @@ public void sendNewReplyNotification(NewReplyEvent event) { String content = String.format( NEW_REPLY_NOTIFICATION, event.replierNickname(), event.replyContent()); - String profileImageUrl = event.replierProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.replierProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -225,10 +221,9 @@ public void sendNewLikeNotification(NewLikeEvent event) { if (isAbleToSendNotification(receiver)) { String content = String.format(NEW_LIKE_NOTIFICATION, event.likerNickname()); - String profileImageUrl = event.likerProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.likerProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -257,13 +252,10 @@ public void sendNewTemperatureNotification(TemperatureNotificationRequest reques Member receiver = memberUtil.getCurrentMember(); String content = String.format(TODAY_TEMPERATURE_NOTIFICATION, Math.round(request.temperature())); + String temperatureImageUrl = configuredTemperatureImageUrl; if (isAbleToSendNotification(receiver)) { - Notification notification = - Notification.builder() - .setBody(content) - .setImage(TODAY_TEMPERATURE_IMAGE_URL) - .build(); + Notification notification = createPushNotification(content, temperatureImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -274,7 +266,7 @@ public void sendNewTemperatureNotification(TemperatureNotificationRequest reques CodiveNotification.createCodiveNotification( receiver, content, - TODAY_TEMPERATURE_IMAGE_URL, + temperatureImageUrl, "", RedirectType.NONE, NotificationType.TEMPERATURE_DAILY); @@ -327,18 +319,6 @@ private Member getMemberById(Long memberId) { .orElseThrow(() -> new BaseCustomException(MemberErrorCode.MEMBER_NOT_FOUND)); } - private History getHistoryById(Long historyId) { - return historyRepository - .findById(historyId) - .orElseThrow(() -> new BaseCustomException(HistoryErrorCode.HISTORY_NOT_FOUND)); - } - - private Comment getCommentById(Long commentId) { - return commentRepository - .findById(commentId) - .orElseThrow(() -> new BaseCustomException(CommentErrorCode.COMMENT_NOT_FOUND)); - } - private CodiveNotification getNotificationById(Long notificationId) { return codiveNotificationRepository .findById(notificationId) @@ -348,22 +328,45 @@ private CodiveNotification getNotificationById(Long notificationId) { NotificationErrorCode.NOTIFICATION_NOT_FOUND)); } - private boolean isAbleToSendNotification(Member followToMember) { + private boolean isAbleToSendNotification(Member member) { + if (member.getMemberStatus() != MemberStatus.ACTIVE) { + return false; + } - boolean isActive = (followToMember.getMemberStatus() == MemberStatus.ACTIVE); + if (member.getDeviceToken() == null || member.getDeviceToken().isBlank()) { + return false; + } - boolean hasDeviceToken = - (followToMember.getDeviceToken() != null - && !followToMember.getDeviceToken().isBlank()); + return memberTermRepository + .findByMemberIdAndTermId(member.getId(), TermInfo.PUSH_NOTIFICATION_RECEIVE.getId()) + .map(MemberTerm::isAgreed) + .orElse(false); + } - boolean hasAgreed = - memberTermRepository - .findByMemberIdAndTermId( - followToMember.getId(), TermInfo.PUSH_NOTIFICATION_RECEIVE.getId()) - .orElseThrow(() -> new BaseCustomException(TermErrorCode.TERM_NOT_FOUND)) - .isAgreed(); + private String resolveNotificationImageUrl(String profileImageUrl) { + if (profileImageUrl != null && !profileImageUrl.isBlank()) { + return profileImageUrl; + } + + return resolveDefaultImageUrl(); + } + + private String resolveDefaultImageUrl() { + if (configuredDefaultProfileImageUrl != null + && !configuredDefaultProfileImageUrl.isBlank()) { + return configuredDefaultProfileImageUrl; + } + + return ""; + } + + private Notification createPushNotification(String content, String imageUrl) { + Notification.Builder notificationBuilder = Notification.builder().setBody(content); + if (imageUrl != null && !imageUrl.isBlank()) { + notificationBuilder.setImage(imageUrl); + } - return isActive && hasDeviceToken && hasAgreed; + return notificationBuilder.build(); } @Async @@ -373,8 +376,13 @@ public void sendPushAfterCommit(PushSendEvent event) { try { firebaseMessaging.send(event.message()); } catch (FirebaseMessagingException e) { - log.warn("[Notification] Firebase 전송 실패", e); - throw new BaseCustomException(NotificationErrorCode.NOTIFICATION_FIREBASE_ERROR); + log.warn( + "[Notification] Firebase 전송 실패: errorCode={}, message={}", + e.getMessagingErrorCode(), + e.getMessage(), + e); + } catch (Exception e) { + log.error("[Notification] Firebase 전송 중 예기치 않은 오류 발생", e); } } diff --git a/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java b/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java index e81b2db5..7adfda3e 100644 --- a/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java +++ b/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java @@ -69,10 +69,13 @@ public static CodiveNotification createCodiveNotification( String redirectInfo, RedirectType redirectType, NotificationType notificationType) { + String resolvedNotificationImageUrl = + (notificationImageUrl == null) ? "" : notificationImageUrl; + return CodiveNotification.builder() .member(member) .content(content) - .notificationImageUrl(notificationImageUrl) + .notificationImageUrl(resolvedNotificationImageUrl) .redirectInfo(redirectInfo) .redirectType(redirectType) .notificationType(notificationType)