From ce4c09dd3a439850f20c8eceef88ee3900eae227 Mon Sep 17 00:00:00 2001 From: KyungMin Lee Date: Mon, 11 Mar 2024 21:11:02 +0900 Subject: [PATCH 1/2] Feat/#150 matching success notification (#250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] `NotificationMessage` Enum 추가 * [REFACT] `NotificationMessage` Enum 사용에 따른 리펙토링 * [FEAT] `FCMNotificationType` Enum 추가 * [REFACT] `FCMNotificationType` Enum 추가에 따른 리펙토링 * [REFACT] `NotificationMessage` title 필드 추가 * [FEAT] 매칭 요청 수락시 FCM 알림 발송 기능 추가 * [REMOVE] 사용하지 않는 Enum 필드 제거 * [FEAT] 매칭 성공시 PULL 알림 생성 추가 --- .../application/FCMNotificationService.java | 99 +++++++++++-------- .../notification/domain/Notification.java | 26 +++-- .../domain/enums/FCMNotificationType.java | 16 +++ .../domain/enums/NotificationMessage.java | 22 +++++ .../ProjectJoinRequestService.java | 13 ++- 5 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/FCMNotificationType.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/NotificationMessage.java diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/application/FCMNotificationService.java b/src/main/java/io/oeid/mogakgo/domain/notification/application/FCMNotificationService.java index 24655f26..8d2d3ba8 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/application/FCMNotificationService.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/application/FCMNotificationService.java @@ -1,18 +1,23 @@ package io.oeid.mogakgo.domain.notification.application; +import static io.oeid.mogakgo.domain.notification.domain.enums.NotificationMessage.REVIEW_REQUEST_MESSAGE; + import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import com.google.firebase.messaging.WebpushConfig; import com.google.firebase.messaging.WebpushFcmOptions; +import io.oeid.mogakgo.domain.notification.domain.enums.FCMNotificationType; import io.oeid.mogakgo.domain.notification.domain.vo.FCMToken; import io.oeid.mogakgo.domain.notification.infrastructure.FCMTokenJpaRepository; import io.oeid.mogakgo.domain.user.application.UserCommonService; +import io.oeid.mogakgo.domain.user.domain.User; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Slf4j @@ -43,58 +48,74 @@ public void manageToken(Long userId, String fcmToken) { log.info("manageToken End"); } + @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendNotification(Long userId, String title, String body) { - log.info("sendNotification Start"); getFCMToken(userId).ifPresent( fcmToken -> { - Message message = Message.builder() - .setNotification(Notification.builder() - .setTitle(title) - .setBody(body) - .build()) - .setToken(fcmToken) - .build(); - try { - String response = firebaseMessaging.send(message); - log.info("Successfully sent message: " + response); - } catch (FirebaseMessagingException e) { - log.error("Error sending message: " + e.getMessage()); - } + Message message = generateFirebaseMessage(title, body); + sendMessageToFCM(message); } ); - // send notification - log.info("sendNotification End"); } - public void sendNotification(Long userId, String title, String body, String redirectUri) { - log.info("sendNotification Start"); + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void sendNotification(Long userId, String title, String body, + FCMNotificationType notificationType) { getFCMToken(userId).ifPresent( fcmToken -> { - WebpushConfig webpushConfig = WebpushConfig.builder() - .setFcmOptions(WebpushFcmOptions.builder() - .setLink(clientUrl + redirectUri) - .build()) - .build(); - // send notification - Message message = Message.builder() - .setNotification(Notification.builder() - .setTitle(title) - .setBody(body) - .build()) - .setToken(fcmToken) - .setWebpushConfig(webpushConfig) - .build(); - try { - String response = firebaseMessaging.send(message); - log.info("Successfully sent message: " + response); - } catch (FirebaseMessagingException e) { - log.error("Error sending message: " + e.getMessage()); - } + Message message = generateFirebaseMessage(title, body, + notificationType.getRedirectUri()); + sendMessageToFCM(message); } ); - log.info("sendNotification End"); } + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void sendNotification(Long userId, User receiver, Long projectId) { + String redirectUrl = + FCMNotificationType.REVIEW_REQUEST.getRedirectUri() + "?receiverId=" + receiver.getId() + + "&projectId=" + projectId; + getFCMToken(userId).ifPresent( + fcmToken -> { + Message message = generateFirebaseMessage(REVIEW_REQUEST_MESSAGE.getTitle(), + receiver.getUsername()+ REVIEW_REQUEST_MESSAGE.getMessage(), redirectUrl); + sendMessageToFCM(message); + } + ); + } + + private void sendMessageToFCM(Message message) { + log.info("sendNotification Start"); + try { + String response = firebaseMessaging.send(message); + log.info("Successfully sent message: " + response); + } catch (FirebaseMessagingException e) { + log.error("Error sending message: " + e.getMessage()); + } + } + + private Message generateFirebaseMessage(String title, String body) { + return Message.builder() + .setNotification(Notification.builder() + .setTitle(title) + .setBody(body) + .build()) + .build(); + } + + private Message generateFirebaseMessage(String title, String body, String redirectUri) { + return Message.builder() + .setNotification(Notification.builder() + .setTitle(title) + .setBody(body) + .build()) + .setWebpushConfig(WebpushConfig.builder() + .setFcmOptions(WebpushFcmOptions.builder() + .setLink(clientUrl + redirectUri) + .build()) + .build()) + .build(); + } private Optional getFCMToken(Long userId) { return fcmTokenRepository.findById(userId) diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java b/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java index a6d1d87b..33af3013 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java @@ -1,6 +1,7 @@ package io.oeid.mogakgo.domain.notification.domain; import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; +import io.oeid.mogakgo.domain.notification.domain.enums.NotificationMessage; import io.oeid.mogakgo.domain.notification.domain.enums.NotificationTag; import io.oeid.mogakgo.domain.notification.exception.NotificationException; import io.oeid.mogakgo.domain.project.domain.entity.Project; @@ -33,12 +34,6 @@ @EntityListeners(AuditingEntityListener.class) public class Notification { - private static final String REVIEW_REQUEST_MESSAGE = " 님과의 만남 후기를 작성해주세요!"; - private static final String ACHIEVEMENT_MESSAGE = " 업적을 달성했습니다!"; - private static final String REQUEST_ARRIVAL_MESSAGE = "매칭 참여 요청이 도착했습니다!"; - private static final String MATCHING_SUCCESS_MESSAGE = "매칭이 성공적으로 이루어졌습니다!"; - private static final String MATCHING_FAILED_MESSAGE = "매칭 요청이 거절되었어요 ㅠㅠ"; - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -104,26 +99,29 @@ private Notification(NotificationTag notificationTag, String message, User user, public static Notification newReviewRequestNotification(User user, User receiver, Project project) { - return new Notification(receiver.getUsername() + REVIEW_REQUEST_MESSAGE, user, receiver, - project); + return new Notification( + receiver.getUsername() + NotificationMessage.REVIEW_REQUEST_MESSAGE.getMessage(), user, + receiver, project); } public static Notification newAchievementNotification(User user, Achievement achievement) { - return new Notification(achievement.getTitle() + ACHIEVEMENT_MESSAGE, user, achievement); + return new Notification( + achievement.getTitle() + NotificationMessage.ACHIEVEMENT_MESSAGE.getMessage(), user, + achievement); } public static Notification newRequestArrivalNotification(User user) { - return new Notification(REQUEST_ARRIVAL_MESSAGE, user); + return new Notification(NotificationMessage.REQUEST_ARRIVAL_MESSAGE.getMessage(), user); } public static Notification newMatchingSuccessNotification(User user, Project project) { - return new Notification(NotificationTag.MATCHING_SUCCEEDED, MATCHING_SUCCESS_MESSAGE, user, - project); + return new Notification(NotificationTag.MATCHING_SUCCEEDED, + NotificationMessage.MATCHING_SUCCESS_MESSAGE.getMessage(), user, project); } public static Notification newMatchingFailedNotification(User user, Project project) { - return new Notification(NotificationTag.MATCHING_FAILED, MATCHING_FAILED_MESSAGE, user, - project); + return new Notification(NotificationTag.MATCHING_FAILED, + NotificationMessage.MATCHING_FAILED_MESSAGE.getMessage(), user, project); } private NotificationTag validateNotificationTag(NotificationTag notificationTag) { diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/FCMNotificationType.java b/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/FCMNotificationType.java new file mode 100644 index 00000000..ede4313e --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/FCMNotificationType.java @@ -0,0 +1,16 @@ +package io.oeid.mogakgo.domain.notification.domain.enums; + +import lombok.Getter; + +@Getter +public enum FCMNotificationType { + MATCHING_SUCCEEDED("/project"), + REVIEW_REQUEST("/review"), + ; + + private final String redirectUri; + + FCMNotificationType(String redirectUri) { + this.redirectUri = redirectUri; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/NotificationMessage.java b/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/NotificationMessage.java new file mode 100644 index 00000000..775c3e78 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/domain/enums/NotificationMessage.java @@ -0,0 +1,22 @@ +package io.oeid.mogakgo.domain.notification.domain.enums; + +import lombok.Getter; + +@Getter +public enum NotificationMessage { + REVIEW_REQUEST_MESSAGE("리뷰 요청", " 님과의 만남 후기를 작성해주세요!"), + ACHIEVEMENT_MESSAGE("업적 달성"," 업적을 달성했습니다!"), + REQUEST_ARRIVAL_MESSAGE("매칭 참여 요청", "매칭 참여 요청이 도착했습니다!"), + MATCHING_SUCCESS_MESSAGE("매칭 성공","매칭이 성공적으로 이루어졌습니다!"), + MATCHING_FAILED_MESSAGE("매칭 실패","매칭 요청이 거절되었어요 ㅠㅠ"), + ; + + private final String title; + private final String message; + + NotificationMessage(String title, String message) { + this.title = title; + this.message = message; + } +} + diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java index 33c9a83b..38609901 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java @@ -1,5 +1,7 @@ package io.oeid.mogakgo.domain.project_join_req.application; +import static io.oeid.mogakgo.domain.notification.domain.enums.FCMNotificationType.MATCHING_SUCCEEDED; +import static io.oeid.mogakgo.domain.notification.domain.enums.NotificationMessage.MATCHING_SUCCESS_MESSAGE; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_MATCHING_USER_TO_ACCEPT; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_SENDER_TO_ACCEPT; import static io.oeid.mogakgo.exception.code.ErrorCode400.PROJECT_JOIN_REQUEST_SHOULD_BE_ONLY_ONE; @@ -12,6 +14,8 @@ import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.matching.application.MatchingService; import io.oeid.mogakgo.domain.matching.application.UserMatchingService; +import io.oeid.mogakgo.domain.notification.application.FCMNotificationService; +import io.oeid.mogakgo.domain.notification.application.NotificationService; import io.oeid.mogakgo.domain.project.domain.entity.Project; import io.oeid.mogakgo.domain.project.exception.ProjectException; import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository; @@ -38,8 +42,9 @@ public class ProjectJoinRequestService { private final ProjectJpaRepository projectRepository; private final UserMatchingService userMatchingService; private final MatchingService matchingService; - + private final FCMNotificationService fcmNotificationService; private final UserCommonService userCommonService; + private final NotificationService notificationService; @Transactional public Long create(Long userId, ProjectJoinCreateReq request) { @@ -95,7 +100,11 @@ public Long accept(Long userId, Long projectRequestId) { } catch (ProjectJoinRequestException e) { // TODO: 로그 처리 } - + fcmNotificationService.sendNotification(projectJoinRequest.getSender().getId(), + MATCHING_SUCCESS_MESSAGE.getTitle(), MATCHING_SUCCESS_MESSAGE.getMessage(), + MATCHING_SUCCEEDED); + notificationService.createMatchingSuccessNotification(projectJoinRequest.getSender().getId(), + projectJoinRequest.getProject()); return matchingId; } From a9d18d85241e4ecc1c7c431cb94f191d34671284 Mon Sep 17 00:00:00 2001 From: JIN-076 <57834671+JIN-076@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:16:04 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[REFACT]=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#253)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [REFACT] 사용자의 프로젝트 조회 API 응답타입 수정 * [REFACT] 프로젝트 조회 시, 매칭된 프로젝트도 조회할 수 있도록 리팩토링, 응답으로 매칭 아이디 추가 전달 --- .../project/application/ProjectService.java | 3 ++- .../infrastructure/ProjectRepositoryCustom.java | 3 ++- .../ProjectRepositoryCustomImpl.java | 17 ++++++++++++++--- .../project/presentation/ProjectController.java | 2 +- .../dto/res/ProjectDetailInfoAPIRes.java | 5 +++-- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java index 856811c0..8aae55d5 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java @@ -22,6 +22,7 @@ import io.oeid.mogakgo.domain.project.presentation.dto.req.ProjectCreateReq; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDensityRankRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailInfoAPIRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; import io.oeid.mogakgo.domain.project_join_req.infrastructure.ProjectJoinRequestJpaRepository; @@ -179,7 +180,7 @@ public ProjectDensityRankRes getDensityRankProjects() { return new ProjectDensityRankRes(regionRankList); } - public List getLastedProjectByUserId(Long userId, Long id) { + public ProjectDetailInfoAPIRes getLastedProjectByUserId(Long userId, Long id) { User user = getUser(userId); validateProjectCardCreator(user, id); diff --git a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java index 0efc0095..e82be64d 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java @@ -5,6 +5,7 @@ import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailInfoAPIRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import java.util.List; @@ -20,5 +21,5 @@ CursorPaginationResult findByCreatorIdWithPagination( List getDensityRankProjectsByRegion(int limit); - List findLatestProjectByUserId(Long userId); + ProjectDetailInfoAPIRes findLatestProjectByUserId(Long userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java index e59ae5eb..7df3e5c7 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java @@ -2,6 +2,7 @@ import static io.oeid.mogakgo.domain.project.domain.entity.QProject.project; import static io.oeid.mogakgo.domain.user.domain.QUser.user; +import static io.oeid.mogakgo.domain.matching.domain.entity.QMatching.matching; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -13,6 +14,7 @@ import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; import io.oeid.mogakgo.domain.project.presentation.dto.res.MeetingInfoResponse; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailInfoAPIRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.user.domain.UserDevelopLanguageTag; import io.oeid.mogakgo.domain.user.domain.UserWantedJobTag; @@ -126,7 +128,7 @@ public List getDensityRankProjectsByRegion(int limit) { } @Override - public List findLatestProjectByUserId(Long userId) { + public ProjectDetailInfoAPIRes findLatestProjectByUserId(Long userId) { List entity = jpaQueryFactory.selectFrom(project) .innerJoin(project.creator, user) @@ -134,10 +136,17 @@ public List findLatestProjectByUserId(Long userId) { .where( userIdEq(userId), projectStatusEq(ProjectStatus.PENDING) + .or(projectStatusEq(ProjectStatus.MATCHED)), + createdAtEq(LocalDate.now()) ) .fetch(); - return entity.stream().map( + Long matchingId = entity.isEmpty() ? null : jpaQueryFactory.select(matching.id) + .from(matching) + .where(matching.project.id.eq(entity.get(0).getId())) + .fetchOne(); + + List result = entity.stream().map( project -> new ProjectDetailAPIRes( project.getId(), new UserPublicApiResponse( @@ -165,6 +174,8 @@ public List findLatestProjectByUserId(Long userId) { ) ) ).toList(); + + return ProjectDetailInfoAPIRes.of(matchingId, result); } private BooleanExpression cursorIdCondition(Long cursorId) { @@ -180,7 +191,7 @@ private BooleanExpression regionEq(Region region) { } private BooleanExpression projectStatusEq(ProjectStatus projectStatus) { - return projectStatus != null ? project.projectStatus.eq(projectStatus) : null; + return project.projectStatus.eq(projectStatus); } private BooleanExpression createdAtEq(LocalDate today) { diff --git a/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java b/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java index d8cb453e..89cd4f78 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java @@ -99,7 +99,7 @@ public ResponseEntity getById( @GetMapping("/{id}/info") public ResponseEntity getByUserId( @UserId Long userId, @PathVariable Long id) { - return ResponseEntity.ok().body(ProjectDetailInfoAPIRes.of(projectService.getLastedProjectByUserId(userId, id))); + return ResponseEntity.ok().body(projectService.getLastedProjectByUserId(userId, id)); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailInfoAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailInfoAPIRes.java index 10598626..038ea15f 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailInfoAPIRes.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailInfoAPIRes.java @@ -11,9 +11,10 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class ProjectDetailInfoAPIRes { + private final Long matchingId; private final List response; - public static ProjectDetailInfoAPIRes of(List response) { - return new ProjectDetailInfoAPIRes(response); + public static ProjectDetailInfoAPIRes of(Long matchingId, List response) { + return new ProjectDetailInfoAPIRes(matchingId, response); } }