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(notification): add push notification feature #167

Merged
merged 13 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ dependencies {

// Component that test code convert to Swagger file and include restDocs Api
testImplementation('com.epages:restdocs-api-spec-mockmvc:0.18.2') //2.2


// Json Serializer
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0'
implementation 'com.fasterxml.jackson.core:jackson-core:2.15.0'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.0'

// expo push notification
implementation('io.github.jav:expo-server-sdk:1.1.0')
}

// Querydsl
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/e2i/wemeet/config/common/SchedulerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.e2i.wemeet.config.common;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulerConfig {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.e2i.wemeet.config.notification;

import io.github.jav.exposerversdk.PushClient;
import io.github.jav.exposerversdk.PushClientException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class NotificationConfig {

@Bean
public PushClient pushClient() throws PushClientException {
return new PushClient();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public interface MeetingRepository extends JpaRepository<Meeting, Long>, Meeting
join MeetingRequest mr on mr.partnerTeam = pt and mr.team = t
where mr.meetingRequestId = :meetingRequestId
""")
List<LocalDateTime> findCreatedAtByMeetingRequestId(@Param("meetingRequestId") final Long meetingRequestId);
List<LocalDateTime> findCreatedAtByMeetingRequestId(
@Param("meetingRequestId") final Long meetingRequestId);

/*
* 팀장의 pushToken 조회
* */
@Query("""
select p.token
from PushToken p
join Team t on t.teamLeader.memberId = p.member.memberId and t.deletedAt is null
where t.teamId = :teamId
""")
Optional<String> findLeaderPushTokenById(@Param("teamId") final Long teamId);

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

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -10,4 +11,15 @@ public interface PushTokenRepository extends JpaRepository<PushToken, String> {
@Query("select p from PushToken p where p.token = :token")
Optional<PushToken> findByToken(@Param("token") String token);

}
@Query("select p.token from PushToken p where p.member.memberId is not null")
List<String> findAllMemberTokens();

@Query("""
select p.token
from PushToken p
join Member m on m.memberId = p.member.memberId
where p.member.memberId is not null
and m.role = 'USER'
""")
List<String> findTokensOfMemberWithoutTeam();
}
11 changes: 8 additions & 3 deletions src/main/java/com/e2i/wemeet/service/meeting/MeetingEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import com.e2i.wemeet.domain.cost.Spent;
import com.e2i.wemeet.service.cost.SpendEvent;
import com.e2i.wemeet.service.notification.event.NotificationEvent;
import com.e2i.wemeet.service.sns.SnsEvent;

public record MeetingEvent(
SnsEvent snsEvent,
SpendEvent spendEvent
SpendEvent spendEvent,
NotificationEvent notificationEvent

) {

public static MeetingEvent of(String receivePhoneNumber, String message, Spent type, Long memberId) {
public static MeetingEvent of(String receivePhoneNumber, String token, String message,
Spent type, Long memberId) {
SnsEvent snsEvent = new SnsEvent(receivePhoneNumber, message);
SpendEvent spendEvent = new SpendEvent(type, memberId);
return new MeetingEvent(snsEvent, spendEvent);
NotificationEvent notificationEvent = new NotificationEvent(token, message);
return new MeetingEvent(snsEvent, spendEvent, notificationEvent);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public Long sendRequest(final SendMeetingRequestDto requestDto, final Long membe
MeetingRequest request = meetingRequestRepository.save(meetingRequest);

// 이벤트 발행
publishMeetingEvent(getMeetingRequestMessage(), memberLeaderId, partnerTeam, MEETING_REQUEST);
publishMeetingEvent(getMeetingRequestMessage(), memberLeaderId, partnerTeam,
MEETING_REQUEST);
return request.getMeetingRequestId();
}

Expand Down Expand Up @@ -110,7 +111,8 @@ public Long acceptRequest(final Long memberLeaderId, final Long meetingRequestId
// 미팅 성사 이벤트 발행
Team myTeam = meetingRequest.getTeam();
String leaderNickname = meetingRequest.getPartnerTeam().getTeamLeader().getNickname();
publishMeetingEvent(getMeetingAcceptMessage(leaderNickname), memberLeaderId, myTeam, MEETING_ACCEPT);
publishMeetingEvent(getMeetingAcceptMessage(leaderNickname), memberLeaderId, myTeam,
MEETING_ACCEPT);

return saveMeeting(meetingRequest).getMeetingId();
}
Expand Down Expand Up @@ -181,8 +183,11 @@ private void publishMeetingEvent(final String message, final Long memberLeaderId
final Team targetTeam, final Spent spent) {
String leaderPhoneNumber = meetingRepository.findLeaderPhoneNumberById(
targetTeam.getTeamId());
String leaderPushToken = meetingRepository.findLeaderPushTokenById(
targetTeam.getTeamId()).orElse(null);

eventPublisher.publishEvent(
MeetingEvent.of(leaderPhoneNumber, message, spent, memberLeaderId)
MeetingEvent.of(leaderPhoneNumber, leaderPushToken, message, spent, memberLeaderId)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.e2i.wemeet.service.notification;


import java.util.List;

public interface NotificationHandleService {

/*
* Body 미포함 푸시 알림 전송
*/
void sendPushNotification(List<String> tokens, String title);

/*
* Body 포함 푸시 알림 전송
*/
void sendPushNotification(List<String> tokens, String title, String body);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.e2i.wemeet.service.notification;

import io.github.jav.exposerversdk.ExpoPushMessage;
import io.github.jav.exposerversdk.PushClient;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;


@RequiredArgsConstructor
@Service
public class NotificationHandleServiceImpl implements NotificationHandleService {

private final PushClient client;

@Override
public void sendPushNotification(List<String> tokens, String title) {
sendPushNotification(tokens, title, null);
}

@Override
public void sendPushNotification(List<String> tokens, String title, String body) {
ExpoPushMessage expoPushMessage = createExpoPushMessage(tokens, title, body);
client.sendPushNotificationsAsync(List.of(expoPushMessage));
}

private ExpoPushMessage createExpoPushMessage(List<String> tokens, String title, String body) {
ExpoPushMessage expoPushMessage = new ExpoPushMessage();
expoPushMessage.setTo(tokens);
expoPushMessage.setTitle(title);

if (body != null && !body.isBlank()) {
expoPushMessage.setBody(body);
}

return expoPushMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.e2i.wemeet.service.notification;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class NotificationScheduler {

private final NotificationService notificationService;
private static final String TITLE = "기다리고 기다리던 11시 11분이야!";
private static final String BODY = "오늘의 추천 친구들을 확인해 봐! 🤩";

// 매일 23시 11분에 실행
@Scheduled(cron = "0 11 23 * * ?")
public void sendPushNotificationForSuggestion() {
notificationService.sendToAllMembers(TITLE, BODY);
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.e2i.wemeet.service.notification;

public interface NotificationService {

/*
* 전체 사용자 푸시 알림 전송
*/
void sendToAllMembers(String title, String body);

/*
* 팀이 없는 사용자 푸시 알림 전송
*/
void sendToMembersWithoutTeam(String title, String body);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.e2i.wemeet.service.notification;

import com.e2i.wemeet.domain.notification.PushTokenRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class NotificationServiceImpl implements NotificationService {

private final NotificationHandleService notificationHandleService;
private final PushTokenRepository pushTokenRepository;

@Override
public void sendToAllMembers(String title, String body) {
List<String> tokens = pushTokenRepository.findAllMemberTokens();
notificationHandleService.sendPushNotification(tokens, title, body);
}

@Override
public void sendToMembersWithoutTeam(String title, String body) {
List<String> tokens = pushTokenRepository.findTokensOfMemberWithoutTeam();
notificationHandleService.sendPushNotification(tokens, title, body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.e2i.wemeet.service.notification.event;

public interface NotificatioEventService {

/*
* 이벤트 전용 푸시 알림 전송
*/
void sendForNotificationEvent(NotificationEvent notificationEvent);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.e2i.wemeet.service.notification.event;

public record NotificationEvent(String token, String title) {

public static NotificationEvent of(String token, String title) {
return new NotificationEvent(token, title);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.e2i.wemeet.service.notification.event;

import com.e2i.wemeet.service.meeting.MeetingEvent;
import com.e2i.wemeet.service.notification.NotificationHandleService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class NotificationEventServiceImpl implements NotificatioEventService {

private final NotificationHandleService notificationHandleService;

@EventListener(classes = MeetingEvent.class)
public void sendForNotificationEvent(final MeetingEvent event) {
sendForNotificationEvent(event.notificationEvent());
}

@EventListener(classes = NotificationEvent.class)
@Override
public void sendForNotificationEvent(final NotificationEvent notificationEvent) {
if (notificationEvent.token() == null) {
return;
}

notificationHandleService.sendPushNotification(List.of(notificationEvent.token()),
notificationEvent.title());
}
}
Loading
Loading