From 22b334f1b65e80a0c4c9a9670ad454411547251f Mon Sep 17 00:00:00 2001 From: gomin0 Date: Wed, 4 Sep 2024 20:27:52 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat:=20FCM=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=84=EC=86=A1=20=EC=B6=94=EA=B0=80=20(#183)=20(#184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/service/BoardService.java | 55 +++++++++++++------ .../firebase/controller/FcmController.java | 27 --------- .../firebase/dto/FCMAlarmRequestDto.java | 7 --- .../exception/FirebaseExceptionHandler.java | 10 ---- .../myteam/service/TeamInviteService.java | 11 ++++ .../myteam/service/TeamMembersService.java | 23 +++++--- .../controller/FcmNotificationController.java | 41 ++++++++++++++ .../dto/FcmNotificationRequestDto.java | 17 ++++++ .../dto/NotificationResponseDto.java | 22 ++++++++ .../notification/entity/FcmNotification.java | 28 ++++++++++ .../FcmNotificationExceptionHandler.java | 10 ++++ .../repository/FcmNotificationRepository.java | 13 +++++ .../service/FcmNotificationQueryService.java | 29 ++++++++++ .../service/FcmNotificationService.java} | 24 +++++--- .../domain/tactic/service/TacticService.java | 36 ++++++++++++ 15 files changed, 276 insertions(+), 77 deletions(-) delete mode 100644 src/main/java/com/capstone/BnagFer/domain/firebase/controller/FcmController.java delete mode 100644 src/main/java/com/capstone/BnagFer/domain/firebase/dto/FCMAlarmRequestDto.java delete mode 100644 src/main/java/com/capstone/BnagFer/domain/firebase/exception/FirebaseExceptionHandler.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/controller/FcmNotificationController.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/dto/FcmNotificationRequestDto.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/dto/NotificationResponseDto.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/entity/FcmNotification.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/exception/FcmNotificationExceptionHandler.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/repository/FcmNotificationRepository.java create mode 100644 src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationQueryService.java rename src/main/java/com/capstone/BnagFer/domain/{firebase/service/FcmAlarmService.java => notification/service/FcmNotificationService.java} (59%) diff --git a/src/main/java/com/capstone/BnagFer/domain/board/service/BoardService.java b/src/main/java/com/capstone/BnagFer/domain/board/service/BoardService.java index feee41dc..4c7620eb 100644 --- a/src/main/java/com/capstone/BnagFer/domain/board/service/BoardService.java +++ b/src/main/java/com/capstone/BnagFer/domain/board/service/BoardService.java @@ -1,4 +1,5 @@ package com.capstone.BnagFer.domain.board.service; + import com.capstone.BnagFer.domain.accounts.entity.User; import com.capstone.BnagFer.domain.accounts.service.account.AccountsCommonService; import com.capstone.BnagFer.domain.board.dto.request.BoardRequestDto; @@ -12,6 +13,8 @@ import com.capstone.BnagFer.domain.board.repository.BoardImageRepository; import com.capstone.BnagFer.domain.board.repository.BoardLikeRepository; import com.capstone.BnagFer.domain.board.repository.BoardRepository; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationService; import com.capstone.BnagFer.global.common.ApiResponse; import com.capstone.BnagFer.global.common.ErrorCode; import com.capstone.BnagFer.global.util.RedisUtil; @@ -38,6 +41,7 @@ public class BoardService { private final BoardImageRepository boardImageRepository; private final RedisUtil redisUtil; private final S3Provider s3Provider; + private final FcmNotificationService fcmNotificationService; public CreateBoardResponseDto createBoard(BoardRequestDto request, User user, List images) { @@ -134,24 +138,6 @@ public ApiResponse likeButton(Long boardId, User user) { } } -// public CommentResponseDto createComment(Long boardId, CreateCommentRequestDto request, User user, Long parentCommentId) { -// Board board = boardRepository.findById(boardId).orElseThrow(() -> new BoardExceptionHandler(ErrorCode.BOARD_NOT_FOUND)); -// long commentCount = redisUtil.boardGetCommentCount(boardId); -// Comment parent = null; -// if (parentCommentId != null) { -// parent = boardCommentRepository.findById(parentCommentId).orElseThrow(() -> new BoardExceptionHandler(ErrorCode.COMMENT_NOT_FOUND)); -// } -//// if(user.getIsBlocked()) { -//// throw new BoardExceptionHandler(ErrorCode.COMMENT_NOT_FOUND); -//// } -// Comment comment = request.toEntity(user, board, parent); -// accountsCommonService.checkUserProfile(user); -// boardCommentRepository.save(comment); -// commentCount++; -// redisUtil.boardSaveCommentCount(boardId, commentCount); -// return CommentResponseDto.from(comment); -// } - public CommentResponseDto createComment(Long boardId, CreateCommentRequestDto request, User user, Long parentCommentId) { Board board = boardRepository.findById(boardId).orElseThrow(() -> new BoardExceptionHandler(ErrorCode.BOARD_NOT_FOUND)); @@ -174,9 +160,42 @@ public CommentResponseDto createComment(Long boardId, CreateCommentRequestDto re commentCount++; redisUtil.boardSaveCommentCount(boardId, commentCount); + // FCM 알림 전송 + if (parentCommentId == null) { + // 일반 댓글인 경우 + sendCommentNotification(board.getUser(), user); + } else { + // 대댓글인 경우 + sendReplyNotification(board.getUser(), parent.getUser(), user); + } + return CommentResponseDto.from(comment); } + private void sendCommentNotification(User boardOwner, User commenter) { + + FcmNotificationRequestDto alarmRequestDto = new FcmNotificationRequestDto( + "새 댓글", + commenter.getProfile().getNickname() + "님이 회원님의 게시글에 댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(alarmRequestDto, boardOwner.getId()); + + } + + private void sendReplyNotification(User boardOwner, User parentCommentOwner, User replier) { + FcmNotificationRequestDto boardOwnerAlarmDto = new FcmNotificationRequestDto( + "새 댓글", + replier.getProfile().getNickname() + "님이 회원님의 게시글에 댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(boardOwnerAlarmDto, boardOwner.getId()); + + FcmNotificationRequestDto parentCommentOwnerAlarmDto = new FcmNotificationRequestDto( + "새 대댓글", + replier.getProfile().getNickname() + "님이 회원님의 댓글에 대댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(parentCommentOwnerAlarmDto, parentCommentOwner.getId()); + } + public CommentResponseDto updateComment(Long commentId, UpdateCommentRequestDto request, User user) { Comment comment = boardCommentRepository.findById(commentId).orElseThrow(() -> new BoardExceptionHandler(ErrorCode.COMMENT_NOT_FOUND)); diff --git a/src/main/java/com/capstone/BnagFer/domain/firebase/controller/FcmController.java b/src/main/java/com/capstone/BnagFer/domain/firebase/controller/FcmController.java deleted file mode 100644 index 70c15f1e..00000000 --- a/src/main/java/com/capstone/BnagFer/domain/firebase/controller/FcmController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.capstone.BnagFer.domain.firebase.controller; - -import com.capstone.BnagFer.domain.firebase.dto.FCMAlarmRequestDto; -import com.capstone.BnagFer.domain.firebase.service.FcmAlarmService; -import com.capstone.BnagFer.global.common.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -@Slf4j -@RestController -@RequiredArgsConstructor -@Tag(name = "푸시 알림 API") -@RequestMapping("/fcm") -public class FcmController { - - private final FcmAlarmService fcmAlarmService; - - @Operation(summary = "푸시 알림 보내기", description = "해당 유저에게 FCM 푸시 알림 전송") - @PostMapping("/send/{userId}") - public ApiResponse sendAlarm(@Valid @RequestBody FCMAlarmRequestDto requestDto, @PathVariable Long userId) { - return ApiResponse.onSuccess(fcmAlarmService.sendAlarm(requestDto, userId)); - } -} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/firebase/dto/FCMAlarmRequestDto.java b/src/main/java/com/capstone/BnagFer/domain/firebase/dto/FCMAlarmRequestDto.java deleted file mode 100644 index cbad19ad..00000000 --- a/src/main/java/com/capstone/BnagFer/domain/firebase/dto/FCMAlarmRequestDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.capstone.BnagFer.domain.firebase.dto; - -public record FCMAlarmRequestDto ( - String title, - String body -) { -} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/firebase/exception/FirebaseExceptionHandler.java b/src/main/java/com/capstone/BnagFer/domain/firebase/exception/FirebaseExceptionHandler.java deleted file mode 100644 index bd0ce5ca..00000000 --- a/src/main/java/com/capstone/BnagFer/domain/firebase/exception/FirebaseExceptionHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.capstone.BnagFer.domain.firebase.exception; - -import com.capstone.BnagFer.global.common.BaseErrorCode; -import com.capstone.BnagFer.global.common.exception.CustomException; - -public class FirebaseExceptionHandler extends CustomException { - public FirebaseExceptionHandler(BaseErrorCode errorCode) { - super(errorCode); - } -} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamInviteService.java b/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamInviteService.java index 08ca9e1e..00412667 100644 --- a/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamInviteService.java +++ b/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamInviteService.java @@ -4,6 +4,8 @@ import com.capstone.BnagFer.domain.accounts.entity.User; import com.capstone.BnagFer.domain.accounts.repository.ProfileJpaRepository; import com.capstone.BnagFer.domain.accounts.service.account.AccountsCommonService; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationService; import com.capstone.BnagFer.domain.myteam.dto.request.TeamInviteRequestDto; import com.capstone.BnagFer.domain.myteam.dto.response.TeamInviteResponseDto; import com.capstone.BnagFer.domain.myteam.dto.response.TeamMembersResponseDto; @@ -27,6 +29,8 @@ public class TeamInviteService { private final TeamRepository teamRepository; private final TeamMembersRepository teamMembersRepository; private final ProfileJpaRepository profileJpaRepository; + private final FcmNotificationService fcmNotificationService; + public TeamInviteResponseDto inviteTeamMembers(TeamInviteRequestDto request, User inviter) { Profile profile = profileJpaRepository.findByNickname(request.nickName()); if(profile==null){ @@ -64,6 +68,13 @@ public TeamInviteResponseDto inviteTeamMembers(TeamInviteRequestDto request, Use TeamInvite teamInvite = request.toEntity(invitedUser, team, inviter); teamInviteRepository.save(teamInvite); + // FCM 알림 전송 + FcmNotificationRequestDto alarmRequestDto = new FcmNotificationRequestDto( + "팀 초대", + inviter.getProfile().getNickname() + "님이 " + team.getTeamName() + " 팀에 초대하였습니다." + ); + fcmNotificationService.sendAlarm(alarmRequestDto, invitedUser.getId()); + // 초대 정보를 응답 DTO로 변환하여 반환한다 return TeamInviteResponseDto.from(teamInvite); } diff --git a/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamMembersService.java b/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamMembersService.java index 2f62f361..b8e691df 100644 --- a/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamMembersService.java +++ b/src/main/java/com/capstone/BnagFer/domain/myteam/service/TeamMembersService.java @@ -1,7 +1,8 @@ package com.capstone.BnagFer.domain.myteam.service; + import com.capstone.BnagFer.domain.accounts.entity.User; -import com.capstone.BnagFer.domain.accounts.repository.UserJpaRepository; -import com.capstone.BnagFer.domain.accounts.service.account.AccountsCommonService; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationService; import com.capstone.BnagFer.domain.myteam.dto.request.TeamMemberPositionRequestDto; import com.capstone.BnagFer.domain.myteam.dto.response.TeamMemberPositionResponseDto; import com.capstone.BnagFer.domain.myteam.entity.Team; @@ -22,12 +23,13 @@ public class TeamMembersService { private final TeamMembersRepository teamMembersRepository; private final TeamRepository teamRepository; + private final FcmNotificationService fcmNotificationService; public TeamMemberPositionResponseDto allocatePosition(TeamMemberPositionRequestDto request, Long teamId, Long memberId, User user) { - Team team = teamRepository.findById(teamId).orElseThrow(() ->new TeamExceptionHandler(ErrorCode.TEAM_NOT_FOUND)); + Team team = teamRepository.findById(teamId).orElseThrow(() -> new TeamExceptionHandler(ErrorCode.TEAM_NOT_FOUND)); TeamMember teamMember = teamMembersRepository.findById(memberId).orElseThrow(() -> new TeamMemberExceptionHandler(ErrorCode.CANNOT_FIND_TEAMMEMBER)); - if(!team.getId().equals(teamMember.getTeam().getId())) { + if (!team.getId().equals(teamMember.getTeam().getId())) { throw new TeamMemberExceptionHandler(ErrorCode.CANNOT_FIND_TEAMMEMBER); } @@ -35,7 +37,7 @@ public TeamMemberPositionResponseDto allocatePosition(TeamMemberPositionRequestD // 요청한 포지션(requestedPosition)이 이미 다른 멤버에게 할당되어 있는지 확인 TeamMember existingMemberWithPosition = teamMembersRepository.findByTeamAndPosition(team, requestedPosition); boolean teamMemberInTeam = teamMembersRepository.existsByTeamAndId(team, memberId); - if(team.getLeader().getId().equals(user.getId())) { + if (team.getLeader().getId().equals(user.getId())) { if (teamMemberInTeam) { if (existingMemberWithPosition == null || !existingMemberWithPosition.equals(teamMember)) { // 이미 다른 멤버가 요청한 포지션을 가지고 있으면 그 멤버의 포지션을 null로 설정 @@ -46,6 +48,13 @@ public TeamMemberPositionResponseDto allocatePosition(TeamMemberPositionRequestD // 요청한 멤버에게 포지션 할당 teamMember.updatePosition(requestedPosition); teamMembersRepository.save(teamMember); + + // FCM 알림 전송 + FcmNotificationRequestDto alarmRequestDto = new FcmNotificationRequestDto( + "포지션 할당", + team.getTeamName() + " 팀에서 " + requestedPosition.name() + " 포지션이 할당되었습니다." + ); + fcmNotificationService.sendAlarm(alarmRequestDto, teamMember.getUser().getId()); } } else throw new TeamMemberExceptionHandler(ErrorCode.CANNOT_FIND_TEAMMEMBER); @@ -55,10 +64,10 @@ public TeamMemberPositionResponseDto allocatePosition(TeamMemberPositionRequestD } public void deallocatePosition(Long teamId, Long memberId, User user) { - Team team = teamRepository.findById(teamId).orElseThrow(() ->new TeamExceptionHandler(ErrorCode.TEAM_NOT_FOUND)); + Team team = teamRepository.findById(teamId).orElseThrow(() -> new TeamExceptionHandler(ErrorCode.TEAM_NOT_FOUND)); TeamMember teamMember = teamMembersRepository.findById(memberId).orElseThrow(() -> new TeamMemberExceptionHandler(ErrorCode.CANNOT_FIND_TEAMMEMBER)); boolean teamMemberInTeam = teamMembersRepository.existsByTeamAndId(team, memberId); - if(team.getLeader().getId().equals(user.getId())) { + if (team.getLeader().getId().equals(user.getId())) { if (teamMemberInTeam) { if (teamMember.getPosition() != null) teamMember.updatePosition(null); diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/controller/FcmNotificationController.java b/src/main/java/com/capstone/BnagFer/domain/notification/controller/FcmNotificationController.java new file mode 100644 index 00000000..41dcb76b --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/controller/FcmNotificationController.java @@ -0,0 +1,41 @@ +package com.capstone.BnagFer.domain.notification.controller; + +import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.dto.NotificationResponseDto; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationQueryService; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationService; +import com.capstone.BnagFer.global.annotation.LoginUser; +import com.capstone.BnagFer.global.common.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@Tag(name = "푸시 알림 API") +@RequestMapping("/fcm") +public class FcmNotificationController { + + private final FcmNotificationService fcmNotificationService; + private final FcmNotificationQueryService fcmNotificationQueryService; + + @Operation(summary = "푸시 알림 보내기", description = "테스트 용. 해당 유저에게 FCM 푸시 알림 전송") + @PostMapping("/send/{userId}") + public ApiResponse sendAlarm(@Valid @RequestBody FcmNotificationRequestDto requestDto, @PathVariable Long userId) { + return ApiResponse.onSuccess(fcmNotificationService.sendAlarm(requestDto, userId)); + } + + @Operation(summary = "알림 조회하기", description = "사용자한테 온 알림 조회") + @PostMapping("/notification") + public ApiResponse> getUserNotifications(@LoginUser User user) { + return ApiResponse.onSuccess(fcmNotificationQueryService.getUserNotifications(user)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/dto/FcmNotificationRequestDto.java b/src/main/java/com/capstone/BnagFer/domain/notification/dto/FcmNotificationRequestDto.java new file mode 100644 index 00000000..102aeb20 --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/dto/FcmNotificationRequestDto.java @@ -0,0 +1,17 @@ +package com.capstone.BnagFer.domain.notification.dto; + +import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.domain.notification.entity.FcmNotification; + +public record FcmNotificationRequestDto( + String title, + String body +) { + public FcmNotification toEntity(User user) { + return FcmNotification.builder() + .user(user) + .title(this.title) + .content(this.body) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/dto/NotificationResponseDto.java b/src/main/java/com/capstone/BnagFer/domain/notification/dto/NotificationResponseDto.java new file mode 100644 index 00000000..e3c39585 --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/dto/NotificationResponseDto.java @@ -0,0 +1,22 @@ +package com.capstone.BnagFer.domain.notification.dto; + +import com.capstone.BnagFer.domain.notification.entity.FcmNotification; +import lombok.Builder; + +import java.time.LocalDateTime; + + +@Builder +public record NotificationResponseDto( + String title, + String body, + LocalDateTime createdAt +) { + public static NotificationResponseDto from(FcmNotification notification) { + return NotificationResponseDto.builder() + .title(notification.getTitle()) + .body(notification.getContent()) + .createdAt(notification.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/entity/FcmNotification.java b/src/main/java/com/capstone/BnagFer/domain/notification/entity/FcmNotification.java new file mode 100644 index 00000000..f7a611b4 --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/entity/FcmNotification.java @@ -0,0 +1,28 @@ +package com.capstone.BnagFer.domain.notification.entity; + +import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "board") +public class FcmNotification extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + private String title; + + private String content; + +} diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/exception/FcmNotificationExceptionHandler.java b/src/main/java/com/capstone/BnagFer/domain/notification/exception/FcmNotificationExceptionHandler.java new file mode 100644 index 00000000..57670536 --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/exception/FcmNotificationExceptionHandler.java @@ -0,0 +1,10 @@ +package com.capstone.BnagFer.domain.notification.exception; + +import com.capstone.BnagFer.global.common.BaseErrorCode; +import com.capstone.BnagFer.global.common.exception.CustomException; + +public class FcmNotificationExceptionHandler extends CustomException { + public FcmNotificationExceptionHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/repository/FcmNotificationRepository.java b/src/main/java/com/capstone/BnagFer/domain/notification/repository/FcmNotificationRepository.java new file mode 100644 index 00000000..beda910f --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/repository/FcmNotificationRepository.java @@ -0,0 +1,13 @@ +package com.capstone.BnagFer.domain.notification.repository; + +import com.capstone.BnagFer.domain.notification.entity.FcmNotification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FcmNotificationRepository extends JpaRepository { + @Query("SELECT n FROM FcmNotification n WHERE n.user.id = :userId ORDER BY n.createdAt DESC") + List findRecentByUserId(@Param("userId") Long userId); +} diff --git a/src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationQueryService.java b/src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationQueryService.java new file mode 100644 index 00000000..8c12092e --- /dev/null +++ b/src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationQueryService.java @@ -0,0 +1,29 @@ +package com.capstone.BnagFer.domain.notification.service; + +import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.domain.notification.dto.NotificationResponseDto; +import com.capstone.BnagFer.domain.notification.entity.FcmNotification; +import com.capstone.BnagFer.domain.notification.repository.FcmNotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class FcmNotificationQueryService { + + private final FcmNotificationRepository fcmNotificationRepository; + + public List getUserNotifications(User user) { + + List notifications = fcmNotificationRepository.findRecentByUserId(user.getId()); + + return notifications.stream() + .map(NotificationResponseDto::from) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/capstone/BnagFer/domain/firebase/service/FcmAlarmService.java b/src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationService.java similarity index 59% rename from src/main/java/com/capstone/BnagFer/domain/firebase/service/FcmAlarmService.java rename to src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationService.java index 2cf80cc5..0cb66e4f 100644 --- a/src/main/java/com/capstone/BnagFer/domain/firebase/service/FcmAlarmService.java +++ b/src/main/java/com/capstone/BnagFer/domain/notification/service/FcmNotificationService.java @@ -1,10 +1,12 @@ -package com.capstone.BnagFer.domain.firebase.service; +package com.capstone.BnagFer.domain.notification.service; import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.domain.notification.entity.FcmNotification; +import com.capstone.BnagFer.domain.notification.repository.FcmNotificationRepository; import com.capstone.BnagFer.global.util.RedisUtil; import com.capstone.BnagFer.domain.accounts.repository.UserJpaRepository; -import com.capstone.BnagFer.domain.firebase.dto.FCMAlarmRequestDto; -import com.capstone.BnagFer.domain.firebase.exception.FirebaseExceptionHandler; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.exception.FcmNotificationExceptionHandler; import com.capstone.BnagFer.global.common.ErrorCode; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessagingException; @@ -12,18 +14,24 @@ import com.google.firebase.messaging.Notification; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service -public class FcmAlarmService { +@Transactional +public class FcmNotificationService { private final FirebaseMessaging firebaseMessaging; private final UserJpaRepository userJpaRepository; private final RedisUtil redisUtil; + private final FcmNotificationRepository fcmNotificationRepository; - public String sendAlarm(FCMAlarmRequestDto requestDto, Long userId) { + public String sendAlarm(FcmNotificationRequestDto requestDto, Long userId) { User user = userJpaRepository.findById(userId) - .orElseThrow(() -> new FirebaseExceptionHandler(ErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new FcmNotificationExceptionHandler(ErrorCode.USER_NOT_FOUND)); + + FcmNotification fcmNotification = requestDto.toEntity(user); + fcmNotificationRepository.save(fcmNotification); String fcmToken = redisUtil.getFCMToken(user.getEmail()); if (fcmToken != null) { @@ -42,10 +50,10 @@ public String sendAlarm(FCMAlarmRequestDto requestDto, Long userId) { return "알림을 성공적으로 전송했습니다. targetUserId = " + userId; } catch (FirebaseMessagingException e) { e.printStackTrace(); - throw new FirebaseExceptionHandler(ErrorCode.FIREBASE_MESSAGING_ERROR); + throw new FcmNotificationExceptionHandler(ErrorCode.FIREBASE_MESSAGING_ERROR); } } else { - throw new FirebaseExceptionHandler(ErrorCode.FIREBASE_TOKEN_NOT_FOUND); + throw new FcmNotificationExceptionHandler(ErrorCode.FIREBASE_TOKEN_NOT_FOUND); } } } \ No newline at end of file diff --git a/src/main/java/com/capstone/BnagFer/domain/tactic/service/TacticService.java b/src/main/java/com/capstone/BnagFer/domain/tactic/service/TacticService.java index dee8e613..d0ebc566 100644 --- a/src/main/java/com/capstone/BnagFer/domain/tactic/service/TacticService.java +++ b/src/main/java/com/capstone/BnagFer/domain/tactic/service/TacticService.java @@ -1,5 +1,7 @@ package com.capstone.BnagFer.domain.tactic.service; import com.capstone.BnagFer.domain.accounts.entity.User; +import com.capstone.BnagFer.domain.notification.dto.FcmNotificationRequestDto; +import com.capstone.BnagFer.domain.notification.service.FcmNotificationService; import com.capstone.BnagFer.global.util.RedisUtil; import com.capstone.BnagFer.domain.accounts.service.account.AccountsCommonService; import com.capstone.BnagFer.domain.tactic.dto.*; @@ -32,6 +34,7 @@ public class TacticService { private final CommentRepository commentRepository; private final TacticPositionDetailRepository tacticPositionDetailRepository; private final LikeRepository likeRepository; + private final FcmNotificationService fcmNotificationService; public TacticResponse createTactic(TacticCreateRequest request, User user){ @@ -125,9 +128,42 @@ public CommentResponse createComment(Long tacticId, CommentCreateRequest request commentCount++; redisUtil.saveCommentCount(tacticId, commentCount); + // FCM 알림 전송 + if (parentCommentId == null) { + // 일반 댓글인 경우 + sendCommentNotification(tactic.getUser(), user); + } else { + // 대댓글인 경우 + sendReplyNotification(tactic.getUser(), parent.getUser(), user); + } + return CommentResponse.from(tacticComment); } + private void sendCommentNotification(User tacticOwner, User commenter) { + + FcmNotificationRequestDto alarmRequestDto = new FcmNotificationRequestDto( + "새 댓글", + commenter.getProfile().getNickname() + "님이 회원님의 전술에 댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(alarmRequestDto, tacticOwner.getId()); + + } + + private void sendReplyNotification(User tacticOwner, User parentCommentOwner, User replier) { + FcmNotificationRequestDto tacticOwnerAlarmDto = new FcmNotificationRequestDto( + "새 댓글", + replier.getProfile().getNickname() + "님이 회원님의 전술에 댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(tacticOwnerAlarmDto, tacticOwner.getId()); + + FcmNotificationRequestDto parentCommentOwnerAlarmDto = new FcmNotificationRequestDto( + "새 대댓글", + replier.getProfile().getNickname() + "님이 회원님의 댓글에 대댓글을 달았습니다." + ); + fcmNotificationService.sendAlarm(parentCommentOwnerAlarmDto, parentCommentOwner.getId()); + } + public CommentResponse updateComment(Long commentId, CommentUpdateRequest request, User user) { TacticComment tacticComment = commentRepository.findById(commentId).orElseThrow(() -> new TacticExceptionHandler(ErrorCode.COMMENT_NOT_FOUND));