Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
543820f
[chore] notifications table 구조 변경을 위한 sql 파일 추가 (#308)
seongjunnoh Sep 22, 2025
c9165c1
[feat] NotificationJpaEntity 에 추가할 data 및 컨버터 추가 (#308)
seongjunnoh Sep 22, 2025
f51edcc
[refactor] 수정된 notification 구조에 따라 jpa, 도메인 엔티티 및 mapper 코드 수정 (#308)
seongjunnoh Sep 22, 2025
75d058a
[rename] 피드 관련 알림 처리하는 메서드 네이밍 수정 (#308)
seongjunnoh Sep 22, 2025
100eb32
[refactor] fcm 푸시 알림을 위한 이벤트 퍼블리시 시에 notificationId 추가 및 리다이렉트를 위한 다른…
seongjunnoh Sep 22, 2025
c352794
[refactor] 알림 동기 처리 executor 코드 수정 (#308)
seongjunnoh Sep 22, 2025
22ecd82
[refactor] FeedNotificationOrchestratorSyncImpl 코드 수정 (#308)
seongjunnoh Sep 22, 2025
3480f73
[refactor] FeedNotificationOrchestratorSyncImpl 의 테스트 코드 수정 (#308)
seongjunnoh Sep 22, 2025
698c5c7
[refactor] RoomNotificationOrchestratorSyncImpl 코드 수정 (#308)
seongjunnoh Sep 22, 2025
cd275df
[refactor] RoomNotificationOrchestratorSyncImpl 의 테스트 코드 수정 (#308)
seongjunnoh Sep 22, 2025
6929522
[refactor] fcm 푸시알림을 위한 이벤트 퍼블리시 관련 코드 수정 (#308)
seongjunnoh Sep 22, 2025
b8b456c
[refactor] fcm 푸시알림을 위한 이벤트 리스너 관련 코드 수정 (#308)
seongjunnoh Sep 22, 2025
9b22d5a
[refactor] fcm 푸시알림을 위한 이벤트 리스너 관련 테스트 코드 수정 (#308)
seongjunnoh Sep 22, 2025
d957988
[feat] 알림 읽음 처리 api controller 구현 (#308)
seongjunnoh Sep 22, 2025
b04aebb
[feat] 알림 읽음 처리 api use case 구현 (#308)
seongjunnoh Sep 22, 2025
7359995
[feat] 알림 읽음 처리 api 영속성 코드 구현 (#308)
seongjunnoh Sep 22, 2025
dc5c3b1
[feat] 알림 관련 error code 추가 (#308)
seongjunnoh Sep 22, 2025
d81ed04
[feat] 알림 읽음 처리 api 관련 스웨거 ResponseDescription 추가 (#308)
seongjunnoh Sep 22, 2025
792edd2
[test] 알림 읽음 처리 api 통합 테스트 코드 추가 (#308)
seongjunnoh Sep 22, 2025
c1ef1b6
[test] Notification 도메인 엔티티 단위 테스트 코드 추가 (#308)
seongjunnoh Sep 22, 2025
91f312a
[move] PlatformType enum 에 정적 팩토리 메서드 추가 및 패키지 구조 이동 (#308)
seongjunnoh Sep 22, 2025
af90d6b
[refactor] 기존 fcm 토큰 등록 api 의 request body 유효성 검증 강화 (#308)
seongjunnoh Sep 22, 2025
4c2778a
[refactor] PlatformType 패키지 구조 이동에 따른 import 문 수정 (#308)
seongjunnoh Sep 22, 2025
1f061a3
[refactor] MessageRoute 수정 및 패키지 이동 (#308)
seongjunnoh Sep 22, 2025
4b6e9c5
Merge remote-tracking branch 'origin' into feat/#308-notification-red…
seongjunnoh Sep 22, 2025
7459b5f
[rename] 네이밍 수정 (#308)
seongjunnoh Sep 23, 2025
48394c0
[refactor] request dto bean validation error message 추가 (#308)
seongjunnoh Sep 23, 2025
8d4ba69
[move] PlatformType enum 패키지 이동 (#308)
seongjunnoh Sep 23, 2025
57f9970
[docs] api 명세 추가 (#308)
seongjunnoh Sep 23, 2025
50c7cee
Merge pull request #309 from THIP-TextHip/feat/#308-notification-redi…
seongjunnoh Sep 23, 2025
6cb40fe
[fix] 알림 읽음 처리 api 비즈니스 로직 수정 (#312)
seongjunnoh Sep 24, 2025
0913cfb
Merge pull request #313 from THIP-TextHip/hotfix/#312-notification-ch…
seongjunnoh Sep 24, 2025
47426c4
[refactor] RoomNotificationOrchestrator 메서드 시그니처 수정 (#314)
seongjunnoh Sep 25, 2025
a7f7262
[refactor] RoomNotificationOrchestrator 메서드 시그니처 수정에 따른 호출 코드 수정 (#314)
seongjunnoh Sep 25, 2025
6c5a62a
[refactor] RoomNotificationOrchestrator 구현체 코드 수정 (#314)
seongjunnoh Sep 25, 2025
c997276
[refactor] RoomNotificationOrchestrator 메서드 시그니처 수정에 따른 테스트 코드 수정 (#314)
seongjunnoh Sep 25, 2025
f33f4c0
[refactor] 기록장 조회시 기본 페이징 크기 20으로 수정 (#314)
seongjunnoh Sep 25, 2025
a9145c7
[refactor] 불필요한 import 문 삭제 (#314)
seongjunnoh Sep 25, 2025
b3f59af
Merge pull request #315 from THIP-TextHip/refactor/#314-notification-…
seongjunnoh Sep 25, 2025
7b50425
[chore] 프로메테우스 의존성 추가
seongjunnoh Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {

// Spring Boot Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

// AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +93,34 @@ public CommentCreateResponse createComment(CommentCreateCommand command) {
private void sendNotificationsToPostWriter(PostQueryDto postQueryDto, User actorUser) {
if (postQueryDto.creatorId().equals(actorUser.getId())) return; // 자신이 작성한 게시글 제외

if (postQueryDto.postType().equals(FEED.getType())) {
// 피드 댓글 알림 이벤트 발행
feedNotificationOrchestrator.notifyFeedCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
} else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) {
// 모임방 게시글 댓글 알림 이벤트 발행
roomNotificationOrchestrator.notifyRoomPostCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
PostType postType = PostType.from(postQueryDto.postType());
switch (postType) {
case FEED -> // 피드 댓글 알림 이벤트 발행
feedNotificationOrchestrator.notifyFeedCommented(
postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()
);
case RECORD, VOTE -> // 모임방 게시글 댓글 알림 이벤트 발행
roomNotificationOrchestrator.notifyRoomPostCommented(
postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(),
postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postType
);
}
}

private void sendNotificationsToParentCommentWriter(PostQueryDto postQueryDto, CommentQueryDto parentCommentDto, User actorUser) {
if (parentCommentDto.creatorId().equals(actorUser.getId())) return; // 자신이 작성한 댓글 제외

if (postQueryDto.postType().equals(FEED.getType())) {
// 피드 답글 알림 이벤트 발행
feedNotificationOrchestrator.notifyFeedReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
} else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) {
// 모임방 게시글 답글 알림 이벤트 발행
roomNotificationOrchestrator.notifyRoomPostCommentReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
PostType postType = PostType.from(postQueryDto.postType());
switch (postType) {
case FEED -> // 피드 답글 알림 이벤트 발행
feedNotificationOrchestrator.notifyFeedReplied(
parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()
);
case RECORD, VOTE -> // 모임방 게시글 답글 알림 이벤트 발행
roomNotificationOrchestrator.notifyRoomPostCommentReplied(
parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(),
postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postType
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ private void sendNotifications(CommentIsLikeCommand command, Comment comment) {
if (command.userId().equals(comment.getCreatorId())) return; // 자신의 댓글에 좋아요 누르는 경우 제외

User actorUser = userCommandPort.findById(command.userId());
// 좋아요 푸쉬알림 전송
if (comment.getPostType() == PostType.FEED) {
feedNotificationOrchestrator.notifyFeedCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), comment.getTargetPostId());
}
if (comment.getPostType() == PostType.RECORD || comment.getPostType() == PostType.VOTE) {
PostQueryDto postQueryDto = postHandler.getPostQueryDto(comment.getPostType(), comment.getTargetPostId());
roomNotificationOrchestrator.notifyRoomCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
PostType postType = comment.getPostType();

switch (postType) {
case FEED ->
feedNotificationOrchestrator.notifyFeedCommentLiked(
comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(),comment.getTargetPostId()
);
case RECORD, VOTE -> {
PostQueryDto postQueryDto = postHandler.getPostQueryDto(comment.getPostType(), comment.getTargetPostId());
roomNotificationOrchestrator.notifyRoomCommentLiked(
comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postType
);
}
}
}
}
11 changes: 11 additions & 0 deletions src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,17 @@ public enum ErrorCode implements ResponseCode {
* 205000 : notification error
*/
INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, 205000, "유효하지 않은 알림 타입입니다."),
NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 205001, "알림 리다이렉트 데이터 직렬화에 실패했습니다."),
NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 205002, "알림 리다이렉트 데이터 역직렬화에 실패했습니다."),
NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, 205003, "존재하지 않는 NOTIFICATION 입니다."),
NOTIFICATION_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 205004, "알림 접근 권한이 없습니다."),
NOTIFICATION_ALREADY_CHECKED(HttpStatus.BAD_REQUEST, 205005, "이미 읽음 처리된 알림입니다."),


/**
* 300000 : util error
*/
INVALID_FE_PLATFORM(HttpStatus.BAD_REQUEST, 300000, "유효하지 않은 FE 플랫폼입니다."),

;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public enum SecurityWhitelist {
OAUTH2_AUTHORIZATION("/oauth2/authorization/**"),
LOGIN_OAUTH2_CODE("/login/oauth2/code/**"),
ACTUATOR_HEALTH("/actuator/health"),
ACTUATOR_PROMETHEUS("/actuator/prometheus"),
AUTH_USERS("/auth/users"),
AUTH_TOKEN("/auth/token"),
API_TEST("/api/test/**"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ public enum SwaggerResponseDescription {
// Notiification
FCM_TOKEN_REGISTER(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND,
FCM_TOKEN_NOT_FOUND
FCM_TOKEN_NOT_FOUND,
INVALID_FE_PLATFORM
))),
FCM_TOKEN_ENABLE_STATE_CHANGE(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND,
Expand All @@ -376,6 +377,10 @@ public enum SwaggerResponseDescription {
NOTIFICATION_SHOW(new LinkedHashSet<>(Set.of(
INVALID_NOTIFICATION_TYPE
))),
NOTIFICATION_MARK_TO_CHECKED(new LinkedHashSet<>(Set.of(
NOTIFICATION_NOT_FOUND,
NOTIFICATION_ACCESS_FORBIDDEN
))),

;
private final Set<ErrorCode> errorCodeList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private void sendNotifications(FeedCreateCommand command, Long savedFeedId) {
List<User> targetUsers = userQueryPort.getAllFollowersByUserId(command.userId());
User actorUser = userCommandPort.findById(command.userId());
for (User targetUser : targetUsers) {
feedNotificationOrchestrator.notifyFolloweeNewPost(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId);
feedNotificationOrchestrator.notifyFolloweeNewFeed(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public void onFeedCommentReplied(FeedEvents.FeedCommentRepliedEvent e) {

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onFolloweeNewPost(FeedEvents.FolloweeNewPostEvent e) {
feedUseCase.handleFolloweeNewPost(e);
public void onFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e) {
feedUseCase.handleFolloweeNewFeed(e);
}

@Async
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,89 +14,73 @@ public class FeedEventPublisherAdapter implements FeedEventCommandPort {

@Override
public void publishFollowEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername) {
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FollowerEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.build());
}

@Override
public void publishFeedCommentedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long feedId) {
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FeedCommentedEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.feedId(feedId)
.build());
}

@Override
public void publishFeedRepliedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long feedId) {
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FeedCommentRepliedEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.feedId(feedId)
.build());
}

@Override
public void publishFolloweeNewPostEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long feedId) {
publisher.publishEvent(FeedEvents.FolloweeNewPostEvent.builder()
public void publishFolloweeNewFeedEvent(
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FolloweeNewFeedEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.feedId(feedId)
.build());
}

@Override
public void publishFeedLikedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long feedId) {
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FeedLikedEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.feedId(feedId)
.build());
}

@Override
public void publishFeedCommentLikedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long feedId) {
String title, String content, Long notificationId,
Long targetUserId) {
publisher.publishEvent(FeedEvents.FeedCommentLikedEvent.builder()
.title(title)
.content(content)
.notificationId(notificationId)
.targetUserId(targetUserId)
.actorUserId(actorUserId)
.actorUsername(actorUsername)
.feedId(feedId)
.build());
}
}
}
Loading