Skip to content

Commit ac63312

Browse files
authored
Merge pull request #320 from DevKor-github/feat/free-board-noti
feat: 자유게시판 댓글/답글 알림 추가
2 parents f3886a5 + 1d7467a commit ac63312

14 files changed

Lines changed: 375 additions & 6 deletions

File tree

src/main/java/org/devkor/apu/saerok_server/domain/freeboard/application/FreeBoardPostCommentCommandService.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.devkor.apu.saerok_server.domain.freeboard.api.dto.request.UpdateFreeBoardPostCommentRequest;
77
import org.devkor.apu.saerok_server.domain.freeboard.api.dto.response.CreateFreeBoardPostCommentResponse;
88
import org.devkor.apu.saerok_server.domain.freeboard.api.dto.response.UpdateFreeBoardPostCommentResponse;
9+
import org.devkor.apu.saerok_server.domain.freeboard.application.event.FreeBoardNotificationEvent;
910
import org.devkor.apu.saerok_server.domain.freeboard.core.entity.FreeBoardPost;
1011
import org.devkor.apu.saerok_server.domain.freeboard.core.entity.FreeBoardPostComment;
1112
import org.devkor.apu.saerok_server.domain.freeboard.core.repository.FreeBoardPostCommentRepository;
@@ -14,6 +15,7 @@
1415
import org.devkor.apu.saerok_server.domain.user.core.repository.UserRepository;
1516
import org.devkor.apu.saerok_server.global.shared.exception.ForbiddenException;
1617
import org.devkor.apu.saerok_server.global.shared.exception.NotFoundException;
18+
import org.springframework.context.ApplicationEventPublisher;
1719
import org.springframework.stereotype.Service;
1820

1921
@Service
@@ -24,6 +26,7 @@ public class FreeBoardPostCommentCommandService {
2426
private final FreeBoardPostCommentRepository commentRepository;
2527
private final FreeBoardPostRepository postRepository;
2628
private final UserRepository userRepository;
29+
private final ApplicationEventPublisher eventPublisher;
2730

2831
/* 댓글 작성 */
2932
public CreateFreeBoardPostCommentResponse createComment(Long userId, Long postId,
@@ -35,8 +38,9 @@ public CreateFreeBoardPostCommentResponse createComment(Long userId, Long postId
3538
.orElseThrow(() -> new NotFoundException("존재하지 않는 게시글 id예요"));
3639

3740
FreeBoardPostComment comment;
41+
FreeBoardPostComment parentComment = null;
3842
if (req.parentId() != null) {
39-
FreeBoardPostComment parentComment = commentRepository.findById(req.parentId())
43+
parentComment = commentRepository.findById(req.parentId())
4044
.orElseThrow(() -> new NotFoundException("존재하지 않는 댓글 id예요"));
4145

4246
if (!parentComment.getPost().getId().equals(postId)) {
@@ -55,6 +59,15 @@ public CreateFreeBoardPostCommentResponse createComment(Long userId, Long postId
5559
}
5660

5761
commentRepository.save(comment);
62+
63+
eventPublisher.publishEvent(new FreeBoardNotificationEvent.CommentCreated(
64+
userId, user.getNickname(),
65+
postId, post.getUser().getId(),
66+
parentComment != null ? parentComment.getId() : null,
67+
parentComment != null ? parentComment.getUser().getId() : null,
68+
req.content()
69+
));
70+
5871
return new CreateFreeBoardPostCommentResponse(comment.getId());
5972
}
6073

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.devkor.apu.saerok_server.domain.freeboard.application.event;
2+
3+
public sealed interface FreeBoardNotificationEvent {
4+
5+
record CommentCreated(
6+
Long actorId, String actorNickname,
7+
Long postId, Long postOwnerId,
8+
Long parentCommentId, Long parentCommentOwnerId,
9+
String commentContent
10+
) implements FreeBoardNotificationEvent {}
11+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.devkor.apu.saerok_server.domain.freeboard.application.event;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.devkor.apu.saerok_server.domain.notification.application.facade.NotifyActionDsl;
6+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.ActionKind;
7+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.Actor;
8+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.Target;
9+
import org.springframework.scheduling.annotation.Async;
10+
import org.springframework.stereotype.Component;
11+
import org.springframework.transaction.event.TransactionPhase;
12+
import org.springframework.transaction.event.TransactionalEventListener;
13+
14+
@Slf4j
15+
@Component
16+
@RequiredArgsConstructor
17+
public class FreeBoardNotificationWorker {
18+
19+
private final NotifyActionDsl notifyAction;
20+
21+
@Async("pushNotificationExecutor")
22+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
23+
public void handle(FreeBoardNotificationEvent.CommentCreated event) {
24+
try {
25+
Actor actor = Actor.of(event.actorId(), event.actorNickname());
26+
27+
if (event.parentCommentId() != null) {
28+
// 대댓글: 원댓글 작성자에게 REPLY 알림
29+
if (!event.parentCommentOwnerId().equals(event.actorId())) {
30+
notifyAction
31+
.by(actor)
32+
.on(Target.freeBoardComment(event.parentCommentId()))
33+
.did(ActionKind.REPLY)
34+
.comment(event.commentContent())
35+
.to(event.parentCommentOwnerId());
36+
}
37+
// 게시글 소유자에게 COMMENT 알림 (원댓글 작성자와 다른 경우에만)
38+
if (!event.postOwnerId().equals(event.actorId())
39+
&& !event.postOwnerId().equals(event.parentCommentOwnerId())) {
40+
notifyAction
41+
.by(actor)
42+
.on(Target.freeBoardPost(event.postId()))
43+
.did(ActionKind.COMMENT)
44+
.comment(event.commentContent())
45+
.to(event.postOwnerId());
46+
}
47+
} else {
48+
// 원댓글: 게시글 소유자에게 COMMENT 알림
49+
if (!event.postOwnerId().equals(event.actorId())) {
50+
notifyAction
51+
.by(actor)
52+
.on(Target.freeBoardPost(event.postId()))
53+
.did(ActionKind.COMMENT)
54+
.comment(event.commentContent())
55+
.to(event.postOwnerId());
56+
}
57+
}
58+
} catch (Exception e) {
59+
log.error("Failed to send freeboard comment notification: postId={}, actorId={}",
60+
event.postId(), event.actorId(), e);
61+
}
62+
}
63+
}

src/main/java/org/devkor/apu/saerok_server/domain/notification/application/adapter/DelegatingTargetMetadataAdapter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ public class DelegatingTargetMetadataAdapter implements TargetMetadataPort {
1818

1919
private final CollectionTargetMetadataAdapter collectionAdapter;
2020
private final CommentTargetMetadataAdapter commentAdapter;
21+
private final FreeBoardPostTargetMetadataAdapter freeBoardPostAdapter;
22+
private final FreeBoardCommentTargetMetadataAdapter freeBoardCommentAdapter;
2123

2224
@Override
2325
public Map<String, Object> enrich(Target target, Map<String, Object> baseExtras) {
2426
return switch (target.type()) {
2527
case COLLECTION -> collectionAdapter.enrich(target, baseExtras);
2628
case COMMENT -> commentAdapter.enrich(target, baseExtras);
29+
case FREE_BOARD_POST -> freeBoardPostAdapter.enrich(target, baseExtras);
30+
case FREE_BOARD_COMMENT -> freeBoardCommentAdapter.enrich(target, baseExtras);
2731
};
2832
}
2933
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.devkor.apu.saerok_server.domain.notification.application.adapter;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.devkor.apu.saerok_server.domain.freeboard.core.repository.FreeBoardPostCommentRepository;
5+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.Target;
6+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.TargetType;
7+
import org.devkor.apu.saerok_server.domain.notification.application.port.TargetMetadataPort;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
/**
14+
* TargetType.FREE_BOARD_COMMENT 전용 메타데이터 어댑터.<br>
15+
* - extras.freeBoardCommentId<br>
16+
* - extras.freeBoardPostId (댓글이 속한 자유게시판 글)
17+
*/
18+
@Component
19+
@RequiredArgsConstructor
20+
public class FreeBoardCommentTargetMetadataAdapter implements TargetMetadataPort {
21+
22+
private final FreeBoardPostCommentRepository commentRepository;
23+
24+
@Override
25+
public Map<String, Object> enrich(Target target, Map<String, Object> baseExtras) {
26+
if (target.type() != TargetType.FREE_BOARD_COMMENT) {
27+
return baseExtras != null ? baseExtras : Map.of();
28+
}
29+
30+
Map<String, Object> extras = baseExtras != null ? new HashMap<>(baseExtras) : new HashMap<>();
31+
extras.put("freeBoardCommentId", target.id());
32+
33+
commentRepository.findById(target.id())
34+
.ifPresent(comment -> extras.put("freeBoardPostId", comment.getPost().getId()));
35+
36+
return extras;
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.devkor.apu.saerok_server.domain.notification.application.adapter;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.Target;
5+
import org.devkor.apu.saerok_server.domain.notification.application.model.dsl.TargetType;
6+
import org.devkor.apu.saerok_server.domain.notification.application.port.TargetMetadataPort;
7+
import org.springframework.stereotype.Component;
8+
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
12+
/**
13+
* TargetType.FREE_BOARD_POST 전용 메타데이터 어댑터.<br>
14+
* - extras.freeBoardPostId
15+
*/
16+
@Component
17+
@RequiredArgsConstructor
18+
public class FreeBoardPostTargetMetadataAdapter implements TargetMetadataPort {
19+
20+
@Override
21+
public Map<String, Object> enrich(Target target, Map<String, Object> baseExtras) {
22+
if (target.type() != TargetType.FREE_BOARD_POST) {
23+
return baseExtras != null ? baseExtras : Map.of();
24+
}
25+
26+
Map<String, Object> extras = baseExtras != null ? new HashMap<>(baseExtras) : new HashMap<>();
27+
extras.put("freeBoardPostId", target.id());
28+
return extras;
29+
}
30+
}

src/main/java/org/devkor/apu/saerok_server/domain/notification/application/facade/NotifyActionDsl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public void to(Long recipientId) {
103103
var notificationSubject = switch (target.type()) {
104104
case COLLECTION -> NotificationSubject.COLLECTION;
105105
case COMMENT -> NotificationSubject.COMMENT;
106+
case FREE_BOARD_POST -> NotificationSubject.FREE_BOARD_POST;
107+
case FREE_BOARD_COMMENT -> NotificationSubject.FREE_BOARD_COMMENT;
106108
};
107109

108110
var notificationAction = switch (action) {
@@ -112,9 +114,15 @@ public void to(Long recipientId) {
112114
case SUGGEST_BIRD_ID -> NotificationAction.SUGGEST_BIRD_ID;
113115
};
114116

115-
Long relatedId = target.type() == TargetType.COMMENT && extras.containsKey("collectionId")
116-
? (Long) extras.get("collectionId")
117-
: target.id();
117+
Long relatedId = switch (target.type()) {
118+
case COMMENT -> extras.containsKey("collectionId")
119+
? (Long) extras.get("collectionId")
120+
: target.id();
121+
case FREE_BOARD_COMMENT -> extras.containsKey("freeBoardPostId")
122+
? (Long) extras.get("freeBoardPostId")
123+
: target.id();
124+
default -> target.id();
125+
};
118126

119127
publisher.push(
120128
new ActionNotificationPayload(

src/main/java/org/devkor/apu/saerok_server/domain/notification/application/model/dsl/Target.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
public record Target(TargetType type, Long id) {
44
public static Target collection(Long id) { return new Target(TargetType.COLLECTION, id); }
55
public static Target comment(Long id) { return new Target(TargetType.COMMENT, id); }
6+
public static Target freeBoardPost(Long id) { return new Target(TargetType.FREE_BOARD_POST, id); }
7+
public static Target freeBoardComment(Long id) { return new Target(TargetType.FREE_BOARD_COMMENT, id); }
68
}

src/main/java/org/devkor/apu/saerok_server/domain/notification/application/model/dsl/TargetType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
public enum TargetType {
44
COLLECTION,
55
COMMENT,
6+
FREE_BOARD_POST,
7+
FREE_BOARD_COMMENT,
68
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.devkor.apu.saerok_server.domain.notification.core.entity;
22

33
public enum NotificationSubject {
4-
COLLECTION, // "내 컬렉션에 대한 활동"
5-
COMMENT, // "내 댓글에 대한 활동"
4+
COLLECTION, // "내 컬렉션에 대한 활동"
5+
COMMENT, // "내 컬렉션 댓글에 대한 활동"
6+
FREE_BOARD_POST, // "내 자유게시판 글에 대한 활동"
7+
FREE_BOARD_COMMENT, // "내 자유게시판 댓글에 대한 활동"
68
}

0 commit comments

Comments
 (0)