Skip to content

Commit eae7fe6

Browse files
authored
Merge pull request #277 from Modic-2025/feature/276-refactor-alaram
[FEAT] : 알림 리팩토링
2 parents 6bb2165 + 1c6a89a commit eae7fe6

6 files changed

Lines changed: 84 additions & 14 deletions

File tree

src/main/java/hanium/modic/backend/domain/notification/dto/GetNotificationsResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public record GetNotificationsResponse(
1313
String title,
1414
String body,
1515
Long postId,
16+
Long senderId,
1617
LocalDateTime createdAt
1718
) {
1819

@@ -24,6 +25,7 @@ public static GetNotificationsResponse of(NotificationEntity entity, Notificatio
2425
entity.getTitle(),
2526
entity.getBody(),
2627
payload.postId(),
28+
payload.senderId(),
2729
entity.getCreateAt()
2830
);
2931
}

src/main/java/hanium/modic/backend/domain/notification/enums/NotificationType.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public String generateBody(NotificationPayload payload) {
4949
POST_REVIEWED {
5050
@Override
5151
public String generateTitle(NotificationPayload payload) {
52-
return "'" + payload.postTitle() + "' 게시글에 새로운 후기가 달렸습니다";
52+
return "'" + payload.postTitle() + "' 게시글에 새로운 후기가 작성되었습니다.";
5353
}
5454

5555
@Override
@@ -61,7 +61,7 @@ public String generateBody(NotificationPayload payload) {
6161
FOLLOWED {
6262
@Override
6363
public String generateTitle(NotificationPayload payload) {
64-
return sender(payload) + "님이 회원님을 팔로우했습니다";
64+
return sender(payload) + "님이 회원님을 팔로우하기 시작했습니다.";
6565
}
6666

6767
@Override
@@ -73,15 +73,28 @@ public String generateBody(NotificationPayload payload) {
7373
DERIVED_POST_CREATED {
7474
@Override
7575
public String generateTitle(NotificationPayload payload) {
76-
return "'" + payload.postTitle() + "' 게시글로 파생 포스트가 생성되었습니다";
76+
return "'" + payload.postTitle() + "' 을 이용하여 2차 창작물이 등록되었습니다.";
7777
}
7878

7979
@Override
8080
public String generateBody(NotificationPayload payload) {
8181
return sender(payload)
82-
+ "님이 '" + payload.postTitle() + "'을(를) 기반으로 파생 포스트를 만들었습니다.";
82+
+ "님이 '" + payload.postTitle() + "'을 이용하여 2차 창작물을 등록했습니다.";
8383
}
84-
};
84+
},
85+
86+
LIKED {
87+
@Override
88+
public String generateTitle(NotificationPayload payload) {
89+
return sender(payload) + "님이 '" + payload.postTitle() + "' 게시글에 좋아요를 눌렀습니다.";
90+
}
91+
92+
@Override
93+
public String generateBody(NotificationPayload payload) {
94+
return sender(payload) + "님이 '" + payload.postTitle() + "' 게시글에 좋아요를 눌렀습니다.";
95+
}
96+
}
97+
;
8598

8699
public abstract String generateTitle(NotificationPayload payload);
87100

src/main/java/hanium/modic/backend/domain/post/service/AiDerivedPostService.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@
1212
import hanium.modic.backend.domain.ai.aiServer.entity.AiChatImageEntity;
1313
import hanium.modic.backend.domain.ai.aiServer.repository.AiChatImageRepository;
1414
import hanium.modic.backend.domain.image.domain.ImagePrefix;
15+
import hanium.modic.backend.domain.notification.dto.NotificationPayload;
16+
import hanium.modic.backend.domain.notification.enums.NotificationType;
17+
import hanium.modic.backend.domain.notification.service.NotificationService;
1518
import hanium.modic.backend.domain.post.entity.PostEntity;
1619
import hanium.modic.backend.domain.post.entity.PostImageEntity;
1720
import hanium.modic.backend.domain.post.enums.PostStatus;
1821
import hanium.modic.backend.domain.post.repository.PostEntityRepository;
1922
import hanium.modic.backend.domain.post.repository.PostImageEntityRepository;
2023
import hanium.modic.backend.domain.postLike.service.AsyncPostStatisticsService;
24+
import hanium.modic.backend.domain.user.entity.UserEntity;
25+
import hanium.modic.backend.domain.user.repository.UserEntityRepository;
2126
import hanium.modic.backend.domain.vote.entity.SimilarityVoteEntity;
2227
import hanium.modic.backend.domain.vote.entity.SimilarityVoteSummaryEntity;
2328
import hanium.modic.backend.domain.vote.enums.VoteDecision;
2429
import hanium.modic.backend.domain.vote.enums.VoteStatus;
2530
import hanium.modic.backend.domain.vote.enums.VoteType;
2631
import hanium.modic.backend.domain.vote.repository.SimilarityVoteRepository;
2732
import hanium.modic.backend.domain.vote.repository.SimilarityVoteSummaryRepository;
33+
import hanium.modic.backend.domain.vote.service.AiSimilarityRequestService;
2834
import hanium.modic.backend.web.post.dto.response.CreatePostResponse;
2935
import lombok.RequiredArgsConstructor;
3036

@@ -42,8 +48,14 @@ public class AiDerivedPostService {
4248
private final SimilarityVoteRepository similarityVoteRepository;
4349
private final SimilarityVoteSummaryRepository voteSummaryRepository;
4450

51+
// 알림관련
52+
private final NotificationService notificationService;
53+
4554
// AI 유사도 검사 요청 서비스
46-
private final hanium.modic.backend.domain.vote.service.AiSimilarityRequestService aiSimilarityRequestService;
55+
private final AiSimilarityRequestService aiSimilarityRequestService;
56+
57+
// 유저 관련
58+
private final UserEntityRepository userEntityRepository;
4759

4860
/**
4961
* AI 파생 포스트 생성 (투표 시스템 연동)
@@ -66,6 +78,9 @@ public CreatePostResponse createAiDerivedPost(
6678
Long nonCommercialPrice,
6779
Long ticketPrice
6880
) {
81+
UserEntity user = userEntityRepository.findById(userId)
82+
.orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND_EXCEPTION));
83+
6984
// 1. 생성된 AI 이미지 조회
7085
AiChatImageEntity createdAiImage = AiChatImageRepository.findById(createdAiImageId)
7186
.orElseThrow(() -> new AppException(ErrorCode.AI_IMAGE_NOT_FOUND_EXCEPTION));
@@ -153,6 +168,16 @@ public void afterCommit() {
153168
// 게시글 통계 초기화 (비동기)
154169
asyncPostStatisticsService.initializeStatistics(savedPost.getId());
155170

171+
// 12. 알람 생성
172+
notificationService.createNotification(
173+
originalPost.getUserId(),
174+
NotificationType.DERIVED_POST_CREATED,
175+
NotificationPayload.builder(user.getId(), user.getName(), user.getEmail())
176+
.postId(aiDerivedPost.getId())
177+
.postTitle(aiDerivedPost.getTitle())
178+
.build()
179+
);
180+
156181
return CreatePostResponse.of(savedPost.getId());
157182
}
158183

src/main/java/hanium/modic/backend/domain/postLike/service/PostLikeService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package hanium.modic.backend.domain.postLike.service;
22

33
import static hanium.modic.backend.common.error.ErrorCode.*;
4+
import static hanium.modic.backend.domain.notification.enums.NotificationType.*;
45

56
import java.util.Collections;
67
import java.util.List;
@@ -12,6 +13,10 @@
1213

1314
import hanium.modic.backend.common.error.exception.AppException;
1415
import hanium.modic.backend.common.error.exception.LockException;
16+
import hanium.modic.backend.domain.notification.dto.NotificationPayload;
17+
import hanium.modic.backend.domain.notification.service.NotificationService;
18+
import hanium.modic.backend.domain.user.entity.UserEntity;
19+
import hanium.modic.backend.domain.user.repository.UserEntityRepository;
1520
import hanium.modic.backend.infra.redis.distributedLock.LockManager;
1621
import hanium.modic.backend.domain.post.entity.PostEntity;
1722
import hanium.modic.backend.domain.post.repository.PostEntityRepository;
@@ -37,6 +42,8 @@ public class PostLikeService {
3742
private final PostEntityRepository postRepository;
3843
private final AsyncPostStatisticsService asyncPostStatisticsService;
3944
private final LockManager lockManager;
45+
private final NotificationService notificationService;
46+
private final UserEntityRepository userRepository;
4047

4148
/**
4249
* 게시글 하트 토글 (추가/삭제)
@@ -47,6 +54,9 @@ public class PostLikeService {
4754
* @throws AppException 락 획득 실패 시
4855
*/
4956
public void toggleLike(Long userId, Long postId) {
57+
UserEntity user = userRepository.findById(userId)
58+
.orElseThrow(() -> new AppException(USER_NOT_FOUND_EXCEPTION));
59+
5060
try {
5161
lockManager.postLikeLock(userId, postId, () -> {
5262
// 1. 게시글 존재 및 권한 확인
@@ -67,6 +77,16 @@ public void toggleLike(Long userId, Long postId) {
6777
postLikeRepository.save(postLike);
6878
log.debug("하트 추가: userId={}, postId={}", userId, postId);
6979
asyncPostStatisticsService.incrementLikeCount(postId);
80+
81+
// 알림 전송
82+
notificationService.createNotification(
83+
post.getUserId(),
84+
LIKED,
85+
NotificationPayload.builder(user.getId(), user.getName(), user.getEmail())
86+
.postId(post.getId())
87+
.postTitle(post.getTitle())
88+
.build()
89+
);
7090
} else {
7191
// 4. 삭제 성공 시, 통계 감소
7292
log.debug("하트 삭제: userId={}, postId={}", userId, postId);

src/test/java/hanium/modic/backend/domain/follow/service/FollowMockingServiceTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import hanium.modic.backend.domain.notification.service.NotificationService;
2626
import hanium.modic.backend.domain.user.entity.UserEntity;
2727
import hanium.modic.backend.domain.user.repository.UserEntityRepository;
28-
import hanium.modic.backend.domain.user.repository.UserImageEntityRepository;
2928
import hanium.modic.backend.domain.user.service.UserImageService;
3029
import hanium.modic.backend.web.follow.dto.response.GetFollowersResponse;
3130
import hanium.modic.backend.web.follow.dto.response.GetFollowingsResponse;
@@ -48,7 +47,6 @@ class FollowMockingServiceTest {
4847
@Mock
4948
private NotificationService notificationService;
5049

51-
5250
@Test
5351
@DisplayName("TEST1: 존재하지 않는 유저의 팔로워 목록 조회 시 예외 발생")
5452
void getFollowersThrowsIfUserNotExists() {

src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import java.util.Optional;
1010

11-
import org.junit.jupiter.api.BeforeEach;
1211
import org.junit.jupiter.api.DisplayName;
1312
import org.junit.jupiter.api.Test;
1413
import org.junit.jupiter.api.extension.ExtendWith;
@@ -26,18 +25,19 @@
2625
import hanium.modic.backend.domain.ai.aiServer.repository.AiChatImageRepository;
2726
import hanium.modic.backend.domain.image.domain.ImagePrefix;
2827
import hanium.modic.backend.domain.image.util.ImageUtil;
28+
import hanium.modic.backend.domain.notification.service.NotificationService;
2929
import hanium.modic.backend.domain.post.entity.PostEntity;
3030
import hanium.modic.backend.domain.post.entity.PostImageEntity;
3131
import hanium.modic.backend.domain.post.repository.PostEntityRepository;
3232
import hanium.modic.backend.domain.post.repository.PostImageEntityRepository;
3333
import hanium.modic.backend.domain.postLike.service.AsyncPostStatisticsService;
34+
import hanium.modic.backend.domain.user.entity.UserEntity;
35+
import hanium.modic.backend.domain.user.factory.UserFactory;
36+
import hanium.modic.backend.domain.user.repository.UserEntityRepository;
3437
import hanium.modic.backend.domain.vote.entity.SimilarityVoteEntity;
35-
import hanium.modic.backend.domain.vote.enums.VoteStatus;
36-
import hanium.modic.backend.domain.vote.enums.VoteType;
3738
import hanium.modic.backend.domain.vote.repository.SimilarityVoteRepository;
3839
import hanium.modic.backend.domain.vote.repository.SimilarityVoteSummaryRepository;
39-
import hanium.modic.backend.domain.user.entity.UserEntity;
40-
import hanium.modic.backend.domain.user.factory.UserFactory;
40+
import hanium.modic.backend.domain.vote.service.AiSimilarityRequestService;
4141
import hanium.modic.backend.web.post.dto.response.CreatePostResponse;
4242

4343
@ExtendWith(MockitoExtension.class)
@@ -68,7 +68,13 @@ class AiDerivedPostServiceTest {
6868
private SimilarityVoteSummaryRepository voteSummaryRepository;
6969

7070
@Mock
71-
private hanium.modic.backend.domain.vote.service.AiSimilarityRequestService aiSimilarityRequestService;
71+
private AiSimilarityRequestService aiSimilarityRequestService;
72+
73+
@Mock
74+
private NotificationService notificationService;
75+
76+
@Mock
77+
private UserEntityRepository userEntityRepository;
7278

7379
@InjectMocks
7480
private AiDerivedPostService aiDerivedPostService;
@@ -105,13 +111,15 @@ void createAiDerivedPost_Success() {
105111
.build();
106112
PostEntity mockSavedPost = createMockPostWithId(newPostId, mockUser);
107113

114+
when(userEntityRepository.findById(userId)).thenReturn(Optional.of(mockUser));
108115
when(createdAiImageRepository.findById(createdAiImageId)).thenReturn(Optional.of(mockAiImage));
109116
when(postEntityRepository.findById(originalPostId)).thenReturn(Optional.of(mockOriginalPost));
110117
when(postImageEntityRepository.findById(originalImageId)).thenReturn(Optional.of(mockOriginalImage));
111118
when(postEntityRepository.save(any(PostEntity.class))).thenReturn(mockSavedPost);
112119
when(postImageEntityRepository.save(any(PostImageEntity.class))).thenReturn(mockOriginalImage);
113120
when(voteSummaryRepository.save(any())).thenReturn(null);
114121
doNothing().when(asyncPostStatisticsService).initializeStatistics(anyLong());
122+
doNothing().when(notificationService).createNotification(anyLong(), any(), any());
115123

116124
// Create a mock vote entity with ID
117125
SimilarityVoteEntity mockSavedVote = mock(SimilarityVoteEntity.class);
@@ -183,7 +191,9 @@ void createAiDerivedPost_AiImageNotFound_ShouldThrowException() {
183191
Long commercialPrice = 2000L;
184192
Long nonCommercialPrice = 1000L;
185193
Long ticketPrice = 300L;
194+
UserEntity mockUser = UserFactory.createMockUser(userId);
186195

196+
when(userEntityRepository.findById(userId)).thenReturn(Optional.of(mockUser));
187197
when(createdAiImageRepository.findById(nonExistentAiImageId)).thenReturn(Optional.empty());
188198

189199
// when & then
@@ -211,11 +221,13 @@ void createAiDerivedPost_AccessDenied_ShouldThrowException() {
211221
Long commercialPrice = 2000L;
212222
Long nonCommercialPrice = 1000L;
213223
Long ticketPrice = 300L;
224+
UserEntity mockUser = UserFactory.createMockUser(userId);
214225

215226
// 다른 사용자의 AI 이미지
216227
AiChatImageEntity mockAiImage = createMockCreatedAiImageWithId(
217228
createdAiImageId, otherUserId, 1L, "request-123");
218229

230+
when(userEntityRepository.findById(userId)).thenReturn(Optional.of(mockUser));
219231
when(createdAiImageRepository.findById(createdAiImageId)).thenReturn(Optional.of(mockAiImage));
220232

221233
// when & then

0 commit comments

Comments
 (0)