Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: FCM 알림 기능 #86

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ out/
### VS Code ###
.vscode/

### SECRETS ###
application.yml
application-local.yml
application-dev.yml
application-prod.yml
application.properties
/src/main/resources/firebase-private-key.json

### not used ###
spy.log


### just memo
memo.md
Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ dependencies {
//p6spy
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.1'

// FCM
implementation("com.google.firebase:firebase-admin:6.8.1")
implementation("com.squareup.okhttp3:okhttp:4.9.1")

runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.shy_polarbear.server.domain.comment.repository.CommentRepository;
import com.shy_polarbear.server.domain.feed.model.Feed;
import com.shy_polarbear.server.domain.feed.service.FeedService;
import com.shy_polarbear.server.domain.notification.service.NotificationService;
import com.shy_polarbear.server.domain.notification.vo.NotificationParams;
import com.shy_polarbear.server.domain.user.model.User;
import com.shy_polarbear.server.domain.user.service.UserService;
import com.shy_polarbear.server.global.common.dto.NoCountPageResponse;
Expand All @@ -31,6 +33,7 @@
public class CommentService {
private final UserService userService;
private final FeedService feedService;
private final NotificationService notificationService;
private final CommentRepository commentRepository;
private final CommentLikeRepository commentLikeRepository;

Expand All @@ -53,10 +56,16 @@ public CommentCreateResponse createComment(Long currentUserId, Long feedId, Comm
Long parentId = request.getParentId();
if (Objects.isNull(parentId)) { // 부모 댓글
Comment comment = commentRepository.save(Comment.createComment(user, request.getContent(), feed));
if (checkIsFeedAuthorNotificationReceiver(feed.getAuthor(), user)) { // FCM 알림
notificationService.pushMessage(NotificationParams.ofNewFeedComment(feed.getAuthor(), feed, comment));
}
return CommentCreateResponse.ofParent(comment);
} else { // 자식 댓글
Comment parent = commentRepository.findById(parentId).orElseThrow(() -> new CommentException(ExceptionStatus.NOT_FOUND_COMMENT));
Comment comment = commentRepository.save(Comment.createChildComment(user, request.getContent(), feed, parent));
if (checkIsCommentAuthorNotificationReceiver(parent.getAuthor(), user)) {
notificationService.pushMessage(NotificationParams.ofNewChildComment(feed.getAuthor(), feed, comment));
}
return CommentCreateResponse.ofChild(comment);
}
}
Expand Down Expand Up @@ -116,4 +125,12 @@ private static void checkIsAuthor(User user, Comment comment) {
if (!comment.isAuthor(user.getId()))
throw new CommentException(ExceptionStatus.NOT_MY_COMMENT);
}

private static boolean checkIsFeedAuthorNotificationReceiver(User feedAuthor, User commentAuthor) {
return !feedAuthor.getId().equals(commentAuthor.getId());
}

private static boolean checkIsCommentAuthorNotificationReceiver(User parentCommentAuthor, User childCommentAuthor) {
return !parentCommentAuthor.getId().equals(childCommentAuthor.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.shy_polarbear.server.domain.notification.controller;

import com.shy_polarbear.server.domain.notification.dto.NotificationReadResponse;
import com.shy_polarbear.server.domain.notification.dto.NotificationResponse;
import com.shy_polarbear.server.domain.notification.service.NotificationService;
import com.shy_polarbear.server.global.auth.security.PrincipalDetails;
import com.shy_polarbear.server.global.common.dto.ApiResponse;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/notifications")
public class NotificationController {
private final NotificationService notificationService;

@GetMapping("/users/me")
public ApiResponse<List<NotificationResponse>> getMyNotifications(
@AuthenticationPrincipal PrincipalDetails principalDetails) {
return ApiResponse.success(notificationService.getMyNotifications(principalDetails.getUser().getId()));
}

@PutMapping("/{notificationId}/read")
public ApiResponse<NotificationReadResponse> readNotification(
@PathVariable Long notificationId,
@AuthenticationPrincipal PrincipalDetails principalDetails) {
return ApiResponse.success(
notificationService.readNotification(notificationId, principalDetails.getUser().getId())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.shy_polarbear.server.domain.notification.dto;

import com.shy_polarbear.server.domain.notification.model.Notification;
import lombok.Builder;

@Builder
public record NotificationReadResponse (
Long notificationId
){
public static NotificationReadResponse of(Notification notification) {
return NotificationReadResponse.builder()
.notificationId(notification.getId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.shy_polarbear.server.domain.notification.dto;

import com.shy_polarbear.server.domain.notification.model.Notification;
import com.shy_polarbear.server.domain.notification.model.NotificationType;
import lombok.Builder;

@Builder
public record NotificationResponse (
Long notificationId,
NotificationType notificationType,
String redirectTarget,
Long redirectTargetId,
String title,
String content,
String createdDate,
boolean isRead
){
public static NotificationResponse of(Notification notification) {
NotificationType notificationType = notification.getNotificationType();
String target = notificationType.getRedirectTargetClass().getSimpleName();

return NotificationResponse.builder()
.notificationId(notification.getId())
.notificationType(notificationType)
.redirectTarget(target)
.redirectTargetId(notification.getRedirectTargetId())
.title(notification.getTitle())
.content(notification.getContent())
.createdDate(notification.getCreatedDate())
.isRead(notification.isRead())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shy_polarbear.server.domain.notification.exception;

import com.shy_polarbear.server.global.exception.CustomException;
import com.shy_polarbear.server.global.exception.ExceptionStatus;

public class NotificationException extends CustomException {
public NotificationException(ExceptionStatus exceptionStatus) {
super(exceptionStatus);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.shy_polarbear.server.domain.notification.model;

import com.shy_polarbear.server.domain.comment.model.Comment;
import com.shy_polarbear.server.domain.user.model.User;
import com.shy_polarbear.server.global.common.model.BaseEntity;
import lombok.AccessLevel;
Expand All @@ -17,47 +16,48 @@ public class Notification extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "notofocation_id")
@Column(name = "notification_id")
private Long id;

@Column(nullable = false, updatable = false)
private String title;
@Column(nullable = false, updatable = false)
private String content;
@Column(nullable = false)
private boolean isRead;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comment_id")
private Comment comment;
@JoinColumn(name = "user_id", nullable = false)
private User receiver;

@Enumerated(EnumType.STRING)
@Column(nullable = false, updatable = false)
private NotificationType notificationType;

@Column(nullable = false, updatable = false)
private Long redirectTargetId;


@Builder
private Notification(String title, String content, User user, NotificationType notificationType, Comment comment) {
private Notification(String title, String content, User receiver, NotificationType notificationType, Long redirectTargetId) {
this.title = title;
this.content = content;
this.user = user;
this.comment = comment;
this.receiver = receiver;
this.notificationType = notificationType;
this.redirectTargetId = redirectTargetId;
}

//알림타입이 댓글/대댓글일 경우
public static Notification createCommentNotification(String title, String content, User user, NotificationType notificationType, Comment comment) {
public static Notification createNotification(User receiver, String title, String content, NotificationType notificationType, Long redirectTargetId) {
return Notification.builder()
.receiver(receiver)
.title(title)
.content(content)
.user(user)
.notificationType(notificationType)
.comment(comment)
.redirectTargetId(redirectTargetId)
.build();
}

//알림 타입이 제한일 경우
public static Notification createLimitNotification(String title, String content, User user) {
return Notification.builder()
.title(title)
.content(content)
.user(user)
.notificationType(NotificationType.LIMIT)
.build();
public void read() {
this.isRead = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.shy_polarbear.server.domain.notification.model;

enum NotificationReceiverType {
COMMON, AUTHOR
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알림 받는 사람이 왜 받는지 쉽게 알게 하려고 enum 만드신거 맞죠!?

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package com.shy_polarbear.server.domain.notification.model;

import com.shy_polarbear.server.domain.comment.model.Comment;
import com.shy_polarbear.server.domain.feed.model.Feed;
import com.shy_polarbear.server.global.common.model.BaseEntity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum NotificationType {
COMMENT, CHILD_COMMENT, LIMIT
NEW_FEED_COMMENT(NotificationReceiverType.AUTHOR, Feed.class),
NEW_COMMENT_CHILD_COMMENT(NotificationReceiverType.AUTHOR, Feed.class);
// LIMIT(NotificationReceiverType.COMMON, User.class);

private final NotificationReceiverType notificationReceiverType;
private final Class<? extends BaseEntity> redirectTargetClass;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.shy_polarbear.server.domain.notification.repository;

import com.shy_polarbear.server.domain.notification.model.Notification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NotificationRepository extends JpaRepository<Notification, Long>, NotificationRepositoryCustom {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.shy_polarbear.server.domain.notification.repository;

import com.shy_polarbear.server.domain.notification.model.Notification;
import java.util.List;
import java.util.Optional;

public interface NotificationRepositoryCustom {
// 특정 알림 조회
Optional<Notification> findByIdAndReceiverId(Long notificationId, Long receiverId);

// 내 알림 조회
List<Notification> findAllByReceiverId(Long userId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.shy_polarbear.server.domain.notification.repository;

import static com.shy_polarbear.server.domain.notification.model.QNotification.notification;

import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.shy_polarbear.server.domain.notification.model.Notification;
import com.shy_polarbear.server.global.common.constants.BusinessLogicConstants;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class NotificationRepositoryImpl implements NotificationRepositoryCustom {
private final JPAQueryFactory queryFactory;

@Override
public List<Notification> findAllByReceiverId(Long userId) {
JPAQuery<Notification> query = queryFactory.selectFrom(notification)
.where(notification.receiver.id.eq(userId))
.orderBy(notification.id.desc())
.limit(BusinessLogicConstants.RECENT_NOTIFICATION_LIMIT);

return query.fetch();
}

@Override
public Optional<Notification> findByIdAndReceiverId(Long notificationId, Long receiverId) {
JPAQuery<Notification> query = queryFactory.selectFrom(notification)
.where(notification.id.eq(notificationId)
.and(notification.receiver.id.eq(receiverId)));
return Optional.ofNullable(query.fetchOne());
}

}
Loading