From aba962293bded9e5f3174f30034413b2880f7fe1 Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 16 Sep 2023 14:28:50 +0900 Subject: [PATCH 01/12] chore: gitignore firebase key file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 616f832..8187ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -36,11 +36,14 @@ 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 + ### just memo memo.md \ No newline at end of file From ce3608df85dcb6bbb4c27803ed84c0075f7012cd Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Tue, 19 Sep 2023 22:56:38 +0900 Subject: [PATCH 02/12] chore: gitignore spy.log --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8187ba6..c0f628b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ application-prod.yml application.properties /src/main/resources/firebase-private-key.json +### not used ### +spy.log + ### just memo memo.md \ No newline at end of file From 0f9f7e67f04ea952e4f7499c5170d8dd462a486a Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Tue, 19 Sep 2023 23:52:05 +0900 Subject: [PATCH 03/12] chore: gitignore spy.log --- spy.log | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 spy.log diff --git a/spy.log b/spy.log deleted file mode 100644 index 368f48a..0000000 --- a/spy.log +++ /dev/null @@ -1,17 +0,0 @@ -1694416365593|1|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists blocked_user CASCADE |drop table if exists blocked_user CASCADE -1694416365596|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists comment CASCADE |drop table if exists comment CASCADE -1694416365600|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists comment_like CASCADE |drop table if exists comment_like CASCADE -1694416365601|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists dormant_user CASCADE |drop table if exists dormant_user CASCADE -1694416365604|2|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists feed CASCADE |drop table if exists feed CASCADE -1694416365604|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists feed_image CASCADE |drop table if exists feed_image CASCADE -1694416365605|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists feed_like CASCADE |drop table if exists feed_like CASCADE -1694416365606|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists multiple_choice CASCADE |drop table if exists multiple_choice CASCADE -1694416365607|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists notification CASCADE |drop table if exists notification CASCADE -1694416365607|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists point CASCADE |drop table if exists point CASCADE -1694416365608|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists prize CASCADE |drop table if exists prize CASCADE -1694416365608|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists quiz CASCADE |drop table if exists quiz CASCADE -1694416365609|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists refresh_token CASCADE |drop table if exists refresh_token CASCADE -1694416365610|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists user_quiz CASCADE |drop table if exists user_quiz CASCADE -1694416365611|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists users CASCADE |drop table if exists users CASCADE -1694416365612|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop table if exists withdrawn_user CASCADE |drop table if exists withdrawn_user CASCADE -1694416365612|0|statement|connection 34|url jdbc:h2:mem:bf1e32b7-b24d-4247-addc-6ec86a7fbc99|drop sequence if exists hibernate_sequence|drop sequence if exists hibernate_sequence From aeeeaa391186ba72055b92b83805dcf1c0880158 Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Wed, 20 Sep 2023 21:42:56 +0900 Subject: [PATCH 04/12] =?UTF-8?q?chore:=20fcm=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index d4b6bfb..6377ec0 100644 --- a/build.gradle +++ b/build.gradle @@ -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' From c9d769399d965ae316e018a20bb3161d20b07c1d Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Wed, 20 Sep 2023 23:57:29 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20fcm=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/NotificationException.java | 11 +++++ .../notification/model/Notification.java | 45 ++++++++++--------- .../model/NotificationReceiverType.java | 5 +++ .../notification/model/NotificationType.java | 15 ++++++- .../user/dto/auth/request/JoinRequest.java | 3 ++ .../dto/auth/request/SocialLoginRequest.java | 6 ++- .../server/domain/user/model/User.java | 20 +++++++++ .../global/common/config/WebConfig.java | 13 ++++++ .../constants/BusinessLogicConstants.java | 5 +++ .../global/exception/ExceptionStatus.java | 6 +++ 10 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/exception/NotificationException.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationReceiverType.java create mode 100644 src/main/java/com/shy_polarbear/server/global/common/config/WebConfig.java diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/exception/NotificationException.java b/src/main/java/com/shy_polarbear/server/domain/notification/exception/NotificationException.java new file mode 100644 index 0000000..ccffdc3 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/exception/NotificationException.java @@ -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); + } + +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java index 3379681..c0c5bdf 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java @@ -19,45 +19,50 @@ public class Notification extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "notofocation_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; +// +// @OneToOne(fetch = FetchType.LAZY) +// @JoinColumn(name = "comment_id") +// private Comment comment; + @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; } } diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationReceiverType.java b/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationReceiverType.java new file mode 100644 index 0000000..9a47ac5 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationReceiverType.java @@ -0,0 +1,5 @@ +package com.shy_polarbear.server.domain.notification.model; + +enum NotificationReceiverType { + COMMON, AUTHOR +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationType.java b/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationType.java index c1926ba..e9444ef 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationType.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/model/NotificationType.java @@ -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 redirectTargetClass; } diff --git a/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/JoinRequest.java b/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/JoinRequest.java index 95c4371..3e2e3a8 100644 --- a/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/JoinRequest.java +++ b/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/JoinRequest.java @@ -19,4 +19,7 @@ public class JoinRequest { @NotBlank private String email; private String profileImage; + + @NotBlank + private String fcmToken; } diff --git a/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/SocialLoginRequest.java b/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/SocialLoginRequest.java index 396a66c..8f4f264 100644 --- a/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/SocialLoginRequest.java +++ b/src/main/java/com/shy_polarbear/server/domain/user/dto/auth/request/SocialLoginRequest.java @@ -14,9 +14,11 @@ public class SocialLoginRequest { private String socialType; @NotBlank private String socialAccessToken; + @NotBlank + private String fcmToken; - public static SocialLoginRequest from(String socialType, String socialAccessToken) { - return new SocialLoginRequest(socialType, socialAccessToken); + public static SocialLoginRequest from(String socialType, String socialAccessToken, String fcmToken) { + return new SocialLoginRequest(socialType, socialAccessToken, fcmToken); } } diff --git a/src/main/java/com/shy_polarbear/server/domain/user/model/User.java b/src/main/java/com/shy_polarbear/server/domain/user/model/User.java index 9c3a12f..3348630 100644 --- a/src/main/java/com/shy_polarbear/server/domain/user/model/User.java +++ b/src/main/java/com/shy_polarbear/server/domain/user/model/User.java @@ -1,6 +1,7 @@ package com.shy_polarbear.server.domain.user.model; +import com.shy_polarbear.server.domain.notification.model.Notification; import com.shy_polarbear.server.domain.quiz.model.UserQuiz; import com.shy_polarbear.server.domain.point.model.Point; import com.shy_polarbear.server.global.common.model.BaseEntity; @@ -55,6 +56,10 @@ public class User extends BaseEntity { private ProviderType provider; @Column(nullable = false, unique = true) private String password; + @Column(unique = true) + private String fcmToken; + @OneToMany(mappedBy = "receiver") + private List notificationList = new ArrayList<>(); public void addUserQuiz(UserQuiz userQuiz) { this.userQuiz.add(userQuiz); @@ -118,6 +123,21 @@ public boolean isSameNickName(String nickName) { return Objects.equals(this.nickName, nickName); } + public void updateFcmToken(String fcmToken) { + this.fcmToken = fcmToken; + } + + public void removeFcmToken() { + this.fcmToken = null; + } + + public void addNotification(Notification notification) { + System.out.println("this.notificationList = " + this.notificationList); + System.out.println("this.notificationList.isEmpty() = " + this.notificationList.isEmpty()); + + this.notificationList.add(notification); + } + // test public void setIdForTest(Long mockId) { this.id = mockId; diff --git a/src/main/java/com/shy_polarbear/server/global/common/config/WebConfig.java b/src/main/java/com/shy_polarbear/server/global/common/config/WebConfig.java new file mode 100644 index 0000000..bc1ae4e --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/global/common/config/WebConfig.java @@ -0,0 +1,13 @@ +package com.shy_polarbear.server.global.common.config; + +import org.json.simple.parser.JSONParser; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WebConfig { + @Bean + public JSONParser jsonParser() { + return new JSONParser(); + } +} diff --git a/src/main/java/com/shy_polarbear/server/global/common/constants/BusinessLogicConstants.java b/src/main/java/com/shy_polarbear/server/global/common/constants/BusinessLogicConstants.java index 41a4e75..a577118 100644 --- a/src/main/java/com/shy_polarbear/server/global/common/constants/BusinessLogicConstants.java +++ b/src/main/java/com/shy_polarbear/server/global/common/constants/BusinessLogicConstants.java @@ -20,4 +20,9 @@ public abstract class BusinessLogicConstants { public static final int COMMENT_CONTENT_MAX_LENGTH = 300; public static final String COMMENT_LIMIT_PARAM_DEFAULT_VALUE = "10"; + /** + * FCM 푸시 알림 + **/ + public static final int RECENT_NOTIFICATION_LIMIT = 30; + } diff --git a/src/main/java/com/shy_polarbear/server/global/exception/ExceptionStatus.java b/src/main/java/com/shy_polarbear/server/global/exception/ExceptionStatus.java index 864a504..e934f33 100644 --- a/src/main/java/com/shy_polarbear/server/global/exception/ExceptionStatus.java +++ b/src/main/java/com/shy_polarbear/server/global/exception/ExceptionStatus.java @@ -44,6 +44,12 @@ public enum ExceptionStatus { NOT_FOUND_CHOICE(404, 3010, "존재하지 않는 선택지입니다."), NO_MORE_DAILY_QUIZ(404,3002,"데일리 퀴즈가 더 이상 존재하지 않습니다."), QUIZ_SUBMISSION_NULL_CLIENT_ERROR(400,3003,"클라이언트 오류: 제출된 선택지가 NULL 입니다."), + + // 푸시 알림 + GET_FCM_ACCESS_TOKEN_ERROR(400, 4000, "FCM 토큰을 받는데 실패했습니다."), + FCM_MESSAGE_JSON_PARSING_ERROR(400,4001,"FCM 토큰 JSON 파싱 과정에서 오류가 발생했습니다."), + SEND_FCM_PUSH_ERROR(400,4002,"FCM 푸시 알림을 전송하는데 실패했습니다."), + NOT_FOUND_NOTIFICATION(404, 4004, "존재하지 않는 알림입니다."); ; From a79d0629055e8b176dde3b33421084bb30c3baee Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 06:12:30 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20Notification=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=BB=AC=EB=9F=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/domain/notification/model/Notification.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java index c0c5bdf..beec974 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java @@ -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; @@ -30,10 +29,6 @@ public class Notification extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User receiver; -// -// @OneToOne(fetch = FetchType.LAZY) -// @JoinColumn(name = "comment_id") -// private Comment comment; @Enumerated(EnumType.STRING) @Column(nullable = false, updatable = false) From 9c034836ded3e2644e9971c371dfa77154329f8b Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 06:17:13 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20FCM=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/NotificationResponse.java | 33 ++++++ .../repository/NotificationRepository.java | 7 ++ .../FirebaseCloudMessagingService.java | 101 ++++++++++++++++++ .../service/NotificationService.java | 39 +++++++ .../domain/notification/vo/FcmMessage.java | 10 ++ .../domain/notification/vo/Message.java | 10 ++ .../domain/notification/vo/MessageData.java | 12 +++ .../notification/vo/NotificationParams.java | 52 +++++++++ .../domain/notification/vo/PushMessage.java | 8 ++ 9 files changed, 272 insertions(+) create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/service/FirebaseCloudMessagingService.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/vo/FcmMessage.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/vo/Message.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/vo/MessageData.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/vo/NotificationParams.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/vo/PushMessage.java diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java new file mode 100644 index 0000000..6910cb0 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java @@ -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 target, + 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) + .target(target) + .redirectTargetId(notification.getRedirectTargetId()) + .title(notification.getTitle()) + .content(notification.getContent()) + .createdDate(notification.getCreatedDate()) // TODO: 포맷 변경 YYYY-MM-DD HH:mm + .isRead(notification.isRead()) + .build(); + } +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java new file mode 100644 index 0000000..cd10ac1 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java @@ -0,0 +1,7 @@ +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 { +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/service/FirebaseCloudMessagingService.java b/src/main/java/com/shy_polarbear/server/domain/notification/service/FirebaseCloudMessagingService.java new file mode 100644 index 0000000..a81c287 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/service/FirebaseCloudMessagingService.java @@ -0,0 +1,101 @@ +package com.shy_polarbear.server.domain.notification.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import com.shy_polarbear.server.domain.notification.exception.NotificationException; +import com.shy_polarbear.server.domain.notification.vo.FcmMessage; +import com.shy_polarbear.server.domain.notification.vo.Message; +import com.shy_polarbear.server.domain.notification.vo.MessageData; +import com.shy_polarbear.server.domain.notification.vo.NotificationParams; +import com.shy_polarbear.server.global.exception.ExceptionStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; + + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class FirebaseCloudMessagingService { + private final ObjectMapper objectMapper; + private final JSONParser jsonParser; + + private static final String FCM_PRIVATE_KEY_PATH = "firebase-private-key.json"; + private static final String FIREBASE_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; + // TODO: 요청할 URL 전달 받기 + private static final String PROJECT_ID_URL = "https://fcm.googleapis.com/v1/projects/shypolarbear-199e5\n/messages:send"; + + private String getAccessToken() { // FCM 토큰 발급 받기 + try { + GoogleCredentials credentials = GoogleCredentials + .fromStream(new ClassPathResource(FCM_PRIVATE_KEY_PATH).getInputStream()) + .createScoped(List.of(FIREBASE_SCOPE)); + credentials.refreshIfExpired(); + return credentials.getAccessToken().getTokenValue(); + } catch (IOException e) { + throw new NotificationException(ExceptionStatus.GET_FCM_ACCESS_TOKEN_ERROR); + } + } + + private String buildMessage(String targetToken, NotificationParams params) { + try { + FcmMessage fcmMessage = FcmMessage.builder() + .validate_only(false) + .message(Message.builder() + .token(targetToken) + .data(MessageData.builder() + .title(params.title()) + .body(params.content()) + .redirectTargetId(String.valueOf(params.redirectTargetId())) + .type(params.notificationType().toString()) + .build()) + .build()) + .build(); + + return objectMapper.writeValueAsString(fcmMessage); + } catch (JsonProcessingException e) { + throw new NotificationException(ExceptionStatus.FCM_MESSAGE_JSON_PARSING_ERROR); + } + } + + // TODO: Async + public CompletableFuture sendPushMessage(String fcmToken, NotificationParams notificationParams) { + String message = buildMessage(fcmToken, notificationParams); + String accessToken = getAccessToken(); + + + OkHttpClient okHttpClient = new OkHttpClient(); + Request request = new Request.Builder() + .url(PROJECT_ID_URL) + .addHeader("Authorization", "Bearer " + accessToken) + .addHeader("Content-Type", "application/json; UTF-8") + .post(RequestBody.create(message, MediaType.parse("application/json; charset=urf-8"))) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + + if (!response.isSuccessful() && response.body() != null) { // 비정상적인 응답일 경우, false 반환 + JSONObject responseBody = (JSONObject) jsonParser.parse(response.body().string()); + String errorMessage = ((JSONObject) responseBody.get("error")).get("message").toString(); + log.warn("FCM [sendPushMessage] okHttp response is not OK : {}", errorMessage); + return CompletableFuture.completedFuture(false); + } + + return CompletableFuture.completedFuture(true); + } catch (Exception e) { + log.warn("FCM [sendPushMessage] I/O Exception : {}", e.getMessage()); + throw new NotificationException(ExceptionStatus.SEND_FCM_PUSH_ERROR); + } + } +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java new file mode 100644 index 0000000..b70fca7 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java @@ -0,0 +1,39 @@ +package com.shy_polarbear.server.domain.notification.service; + +import com.shy_polarbear.server.domain.notification.model.Notification; +import com.shy_polarbear.server.domain.notification.repository.NotificationRepository; +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class NotificationService { + private final FirebaseCloudMessagingService fcmService; + private final NotificationRepository notificationRepository; + private final UserService userService; + + @Transactional + public void pushMessage(NotificationParams params) { + User receiver = userService.getUser(params.receiver().getId()); + + fcmService.sendPushMessage(receiver.getFcmToken(), params); + Notification notification = Notification.createNotification( + receiver, + params.title(), + params.content(), + params.notificationType(), + params.redirectTargetId()); + + notificationRepository.save(notification); + receiver.addNotification(notification); + } + +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/vo/FcmMessage.java b/src/main/java/com/shy_polarbear/server/domain/notification/vo/FcmMessage.java new file mode 100644 index 0000000..9d5b725 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/vo/FcmMessage.java @@ -0,0 +1,10 @@ +package com.shy_polarbear.server.domain.notification.vo; + +import lombok.Builder; + +@Builder +public record FcmMessage ( // 필드명은 FCM 메세지 스펙 따라야 합니다 + Boolean validate_only, + Message message +){ +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/vo/Message.java b/src/main/java/com/shy_polarbear/server/domain/notification/vo/Message.java new file mode 100644 index 0000000..a293818 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/vo/Message.java @@ -0,0 +1,10 @@ +package com.shy_polarbear.server.domain.notification.vo; + +import lombok.Builder; + +@Builder +public record Message( + String token, + MessageData data +) { +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/vo/MessageData.java b/src/main/java/com/shy_polarbear/server/domain/notification/vo/MessageData.java new file mode 100644 index 0000000..a864b8f --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/vo/MessageData.java @@ -0,0 +1,12 @@ +package com.shy_polarbear.server.domain.notification.vo; + +import lombok.Builder; + +@Builder +public record MessageData( + String title, + String body, + String redirectTargetId, + String type +) { +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/vo/NotificationParams.java b/src/main/java/com/shy_polarbear/server/domain/notification/vo/NotificationParams.java new file mode 100644 index 0000000..5f2f0ec --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/vo/NotificationParams.java @@ -0,0 +1,52 @@ +package com.shy_polarbear.server.domain.notification.vo; + +import com.shy_polarbear.server.domain.comment.model.Comment; +import com.shy_polarbear.server.domain.feed.model.Feed; +import com.shy_polarbear.server.domain.notification.model.NotificationType; +import com.shy_polarbear.server.domain.user.model.User; +import lombok.Builder; + +public record NotificationParams( + User receiver, + NotificationType notificationType, + Long redirectTargetId, + String title, + String content +){ + @Builder + public NotificationParams {} + + public static NotificationParams ofNewFeedComment( + User feedAuthor, + Feed feed, + Comment comment + ) { + final String content = """ + %s 글에 댓글이 달렸어요! + %s""".formatted(feed.getTitle(), comment.getContent()); + return NotificationParams.builder() + .receiver(feedAuthor) + .notificationType(NotificationType.NEW_FEED_COMMENT) + .redirectTargetId(feed.getId()) + .title(feed.getTitle()) + .content(content) + .build(); + } + + public static NotificationParams ofNewChildComment( + User parentCommentAuthor, + Feed feed, + Comment childComment + ) { + final String content = """ + %s 글에 대댓글이 달렸어요! + %s""".formatted(feed.getTitle(), childComment.getContent()); + return NotificationParams.builder() + .receiver(parentCommentAuthor) + .notificationType(NotificationType.NEW_COMMENT_CHILD_COMMENT) + .redirectTargetId(feed.getId()) + .title(feed.getTitle()) + .content(content) + .build(); + } +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/vo/PushMessage.java b/src/main/java/com/shy_polarbear/server/domain/notification/vo/PushMessage.java new file mode 100644 index 0000000..bf4b400 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/vo/PushMessage.java @@ -0,0 +1,8 @@ +package com.shy_polarbear.server.domain.notification.vo; + +public record PushMessage( + Long receiverId, + String title, + String body +) { +} From a651897bbea05a3ee9bf5feb61f0a96fdb6825e4 Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 06:19:26 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20FCM=20=EC=95=8C=EB=A6=BC=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/service/CommentService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/shy_polarbear/server/domain/comment/service/CommentService.java b/src/main/java/com/shy_polarbear/server/domain/comment/service/CommentService.java index d11e38f..3331425 100644 --- a/src/main/java/com/shy_polarbear/server/domain/comment/service/CommentService.java +++ b/src/main/java/com/shy_polarbear/server/domain/comment/service/CommentService.java @@ -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; @@ -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; @@ -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); } } @@ -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()); + } } From 7eb193c4bc347df05e319e39315d88af0b0ac489 Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 07:08:54 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20Notification=20id=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/domain/notification/model/Notification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java index beec974..33a5fe5 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/model/Notification.java @@ -16,7 +16,7 @@ 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) From 1f68243f13ded286d0b9039b2a10a5bb500544cc Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 07:17:19 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 26 +++++++++++++ .../dto/NotificationResponse.java | 6 +-- .../repository/NotificationRepository.java | 6 ++- .../NotificationRepositoryCustom.java | 14 +++++++ .../NotificationRepositoryImpl.java | 38 +++++++++++++++++++ .../service/NotificationService.java | 11 ++++++ .../server/domain/user/model/User.java | 3 -- 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryCustom.java create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java b/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java new file mode 100644 index 0000000..6d2301c --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java @@ -0,0 +1,26 @@ +package com.shy_polarbear.server.domain.notification.controller; + +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.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +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> getMyNotifications(@AuthenticationPrincipal PrincipalDetails principalDetails) { + return ApiResponse.success(notificationService.getMyNotifications(principalDetails.getUser().getId())); + } + +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java index 6910cb0..38f0833 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationResponse.java @@ -8,7 +8,7 @@ public record NotificationResponse ( Long notificationId, NotificationType notificationType, - String target, + String redirectTarget, Long redirectTargetId, String title, String content, @@ -22,11 +22,11 @@ public static NotificationResponse of(Notification notification) { return NotificationResponse.builder() .notificationId(notification.getId()) .notificationType(notificationType) - .target(target) + .redirectTarget(target) .redirectTargetId(notification.getRedirectTargetId()) .title(notification.getTitle()) .content(notification.getContent()) - .createdDate(notification.getCreatedDate()) // TODO: 포맷 변경 YYYY-MM-DD HH:mm + .createdDate(notification.getCreatedDate()) .isRead(notification.isRead()) .build(); } diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java index cd10ac1..eaa1a6b 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java @@ -1,7 +1,11 @@ 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; import org.springframework.data.jpa.repository.JpaRepository; -public interface NotificationRepository extends JpaRepository { +public interface NotificationRepository extends JpaRepository, NotificationRepositoryCustom { + + List findTop30ByReceiverIdOrderByIdDesc(Long receiverId); } diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryCustom.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryCustom.java new file mode 100644 index 0000000..2116ec0 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryCustom.java @@ -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 findByIdAndReceiverId(Long notificationId, Long receiverId); + + // 내 알림 조회 + List findAllByReceiverId(Long userId); + +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java new file mode 100644 index 0000000..bfc4f38 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.shy_polarbear.server.domain.notification.repository; + +import static com.shy_polarbear.server.domain.notification.model.QNotification.notification; +import static com.shy_polarbear.server.domain.user.model.QUser.user; + +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.domain.notification.model.QNotification; +import com.shy_polarbear.server.domain.user.model.QUser; +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 findAllByReceiverId(Long userId){ + JPAQuery 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 findByIdAndReceiverId(Long notificationId, Long receiverId) { + JPAQuery query = queryFactory.selectFrom(notification) + .where(notification.id.eq(notificationId) + .and(notification.receiver.id.eq(receiverId))); + return Optional.ofNullable(query.fetchOne()); + } + +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java index b70fca7..5027c0d 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java @@ -1,10 +1,12 @@ package com.shy_polarbear.server.domain.notification.service; +import com.shy_polarbear.server.domain.notification.dto.NotificationResponse; import com.shy_polarbear.server.domain.notification.model.Notification; import com.shy_polarbear.server.domain.notification.repository.NotificationRepository; 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 java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -36,4 +38,13 @@ public void pushMessage(NotificationParams params) { receiver.addNotification(notification); } + // 내 알림 리스트 조회 + public List getMyNotifications(Long userId) { + List notificationList = notificationRepository.findAllByReceiverId(1L); + return notificationList.stream() + .map(NotificationResponse::of) + .toList(); + } + + } diff --git a/src/main/java/com/shy_polarbear/server/domain/user/model/User.java b/src/main/java/com/shy_polarbear/server/domain/user/model/User.java index 3348630..5641707 100644 --- a/src/main/java/com/shy_polarbear/server/domain/user/model/User.java +++ b/src/main/java/com/shy_polarbear/server/domain/user/model/User.java @@ -132,9 +132,6 @@ public void removeFcmToken() { } public void addNotification(Notification notification) { - System.out.println("this.notificationList = " + this.notificationList); - System.out.println("this.notificationList.isEmpty() = " + this.notificationList.isEmpty()); - this.notificationList.add(notification); } From 8b990b0d85dc2515fb43f5007f71b293e38ba07b Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 07:27:20 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 15 +++++++++++++-- .../dto/NotificationReadResponse.java | 15 +++++++++++++++ .../notification/service/NotificationService.java | 13 +++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationReadResponse.java diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java b/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java index 6d2301c..0679bea 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/controller/NotificationController.java @@ -1,5 +1,6 @@ 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; @@ -7,8 +8,9 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.stereotype.Controller; 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; @@ -19,8 +21,17 @@ public class NotificationController { private final NotificationService notificationService; @GetMapping("/users/me") - public ApiResponse> getMyNotifications(@AuthenticationPrincipal PrincipalDetails principalDetails) { + public ApiResponse> getMyNotifications( + @AuthenticationPrincipal PrincipalDetails principalDetails) { return ApiResponse.success(notificationService.getMyNotifications(principalDetails.getUser().getId())); } + @PutMapping("/{notificationId}/read") + public ApiResponse readNotification( + @PathVariable Long notificationId, + @AuthenticationPrincipal PrincipalDetails principalDetails) { + return ApiResponse.success( + notificationService.readNotification(notificationId, principalDetails.getUser().getId()) + ); + } } diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationReadResponse.java b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationReadResponse.java new file mode 100644 index 0000000..7268864 --- /dev/null +++ b/src/main/java/com/shy_polarbear/server/domain/notification/dto/NotificationReadResponse.java @@ -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(); + } +} diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java index 5027c0d..1fce2cd 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java @@ -1,11 +1,15 @@ package com.shy_polarbear.server.domain.notification.service; +import com.google.api.gax.rpc.StatusCode; +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.exception.NotificationException; import com.shy_polarbear.server.domain.notification.model.Notification; import com.shy_polarbear.server.domain.notification.repository.NotificationRepository; 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.exception.ExceptionStatus; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,5 +50,14 @@ public List getMyNotifications(Long userId) { .toList(); } + // 알림 읽음 처리 + @Transactional + public NotificationReadResponse readNotification(Long notificationId, Long userId) { + Notification notification = notificationRepository.findByIdAndReceiverId(notificationId, userId) + .orElseThrow(() -> new NotificationException(ExceptionStatus.NOT_FOUND_NOTIFICATION)); + notification.read(); + + return NotificationReadResponse.of(notification); + } } From cfb688c300159aa8431f930b159bbb48bf659739 Mon Sep 17 00:00:00 2001 From: wonseok2877 Date: Sat, 11 Nov 2023 07:34:07 +0900 Subject: [PATCH 12/12] style: remove unused import statements --- .../notification/repository/NotificationRepository.java | 3 --- .../repository/NotificationRepositoryImpl.java | 9 +++------ .../domain/notification/service/NotificationService.java | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java index eaa1a6b..78634a9 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepository.java @@ -1,11 +1,8 @@ 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; import org.springframework.data.jpa.repository.JpaRepository; public interface NotificationRepository extends JpaRepository, NotificationRepositoryCustom { - List findTop30ByReceiverIdOrderByIdDesc(Long receiverId); } diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java index bfc4f38..a32c7fb 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/repository/NotificationRepositoryImpl.java @@ -1,31 +1,28 @@ package com.shy_polarbear.server.domain.notification.repository; import static com.shy_polarbear.server.domain.notification.model.QNotification.notification; -import static com.shy_polarbear.server.domain.user.model.QUser.user; 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.domain.notification.model.QNotification; -import com.shy_polarbear.server.domain.user.model.QUser; 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{ +public class NotificationRepositoryImpl implements NotificationRepositoryCustom { private final JPAQueryFactory queryFactory; @Override - public List findAllByReceiverId(Long userId){ + public List findAllByReceiverId(Long userId) { JPAQuery 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 findByIdAndReceiverId(Long notificationId, Long receiverId) { diff --git a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java index 1fce2cd..ab2d28b 100644 --- a/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java +++ b/src/main/java/com/shy_polarbear/server/domain/notification/service/NotificationService.java @@ -1,6 +1,5 @@ package com.shy_polarbear.server.domain.notification.service; -import com.google.api.gax.rpc.StatusCode; 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.exception.NotificationException;