-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Alert Entity를 추가하고, 알림 전송 기능이 Entity를 파라미터로 받아 동작하게 수정한다 * refactor: MeetingAlerted를 BeforeMeetingAlerted로 직관적으로 변경한다 * feat: 모임 종료 알림을 추가한다 * feat: 유저가 추천받았을때 알림 전송 기능을 추가한다
- Loading branch information
Showing
21 changed files
with
293 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package net.teumteum.alert.app; | ||
|
||
import static net.teumteum.alert.app.AlertExecutorConfigurer.ALERT_EXECUTOR; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import lombok.RequiredArgsConstructor; | ||
import net.teumteum.alert.domain.Alert; | ||
import net.teumteum.alert.domain.AlertPublisher; | ||
import net.teumteum.alert.domain.AlertService; | ||
import net.teumteum.alert.domain.AlertType; | ||
import net.teumteum.alert.domain.UserAlertService; | ||
import net.teumteum.meeting.domain.BeforeMeetingAlerted; | ||
import net.teumteum.meeting.domain.EndMeetingAlerted; | ||
import net.teumteum.user.UserRecommended; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.context.event.EventListener; | ||
import org.springframework.data.util.Pair; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@Profile("prod") | ||
@RequiredArgsConstructor | ||
public class AlertHandler { | ||
|
||
private final UserAlertService userAlertService; | ||
private final AlertService alertService; | ||
private final AlertPublisher alertPublisher; | ||
|
||
@Async(ALERT_EXECUTOR) | ||
@EventListener(BeforeMeetingAlerted.class) | ||
public void handleBeforeMeetingAlerts(BeforeMeetingAlerted alerted) { | ||
userAlertService.findAllByUserId(alerted.userIds()) | ||
.stream() | ||
.map(userAlert -> Pair.of(userAlert.getToken(), | ||
new Alert(null, userAlert.getUserId(), "5분 뒤에 모임이 시작돼요!", | ||
"모임 장소로 가서 틈틈 모임을 준비해주세요.", AlertType.BEFORE_MEETING))) | ||
.map(tokenAndAlert -> Pair.of(tokenAndAlert.getFirst(), alertService.save(tokenAndAlert.getSecond()))) | ||
.forEach( | ||
tokenAndAlert -> alertPublisher.publish(tokenAndAlert.getFirst(), tokenAndAlert.getSecond(), Map.of()) | ||
); | ||
} | ||
|
||
@Async(ALERT_EXECUTOR) | ||
@EventListener(EndMeetingAlerted.class) | ||
public void handleStartMeetingAlerts(EndMeetingAlerted alerted) { | ||
userAlertService.findAllByUserId(alerted.userIds()) | ||
.stream() | ||
.map(userAlert -> Pair.of(userAlert.getToken(), | ||
new Alert(null, userAlert.getUserId(), alerted.meetingTitle(), | ||
"모임이 종료되었어요", AlertType.END_MEETING))) | ||
.map(tokenAndAlert -> Pair.of(tokenAndAlert.getFirst(), alertService.save(tokenAndAlert.getSecond()))) | ||
.forEach(tokenAndAlert -> | ||
alertPublisher.publish(tokenAndAlert.getFirst(), tokenAndAlert.getSecond(), | ||
Map.of("meetingId", alerted.meetingId().toString(), "participants", | ||
toCommaString(alerted.userIds().stream().toList()))) | ||
); | ||
} | ||
|
||
private String toCommaString(List<Long> ids) { | ||
var stringBuilder = new StringBuilder(); | ||
for (int i = 0; i < ids.size() - 1; i++) { | ||
stringBuilder.append(ids.get(i)).append(","); | ||
} | ||
stringBuilder.append(ids.getLast()); | ||
return stringBuilder.toString(); | ||
} | ||
|
||
@Async(ALERT_EXECUTOR) | ||
@EventListener(UserRecommended.class) | ||
public void handleUserRecommended(UserRecommended alerted) { | ||
userAlertService.findAllByUserId(Set.of(alerted.userId())) | ||
.stream() | ||
.map(userAlert -> Pair.of(userAlert.getToken(), | ||
new Alert(null, userAlert.getUserId(), "틈 채우기", | ||
alerted.recommenderName() + "님이 당신을 추천했어요!", AlertType.RECOMMEND_USER))) | ||
.map(tokenAndAlert -> Pair.of(tokenAndAlert.getFirst(), alertService.save(tokenAndAlert.getSecond()))) | ||
.forEach(tokenAndAlert -> | ||
alertPublisher.publish(tokenAndAlert.getFirst(), tokenAndAlert.getSecond(), Map.of()) | ||
); | ||
} | ||
} |
33 changes: 0 additions & 33 deletions
33
src/main/java/net/teumteum/alert/app/BeforeMeetingAlertHandler.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import net.teumteum.core.entity.TimeBaseEntity; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
@Table(name = "alert") | ||
@Entity(name = "alert") | ||
public class Alert extends TimeBaseEntity { | ||
|
||
@Id | ||
@Column(name = "id") | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(name = "userId", nullable = false) | ||
private Long userId; | ||
|
||
@Column(name = "title", nullable = false, length = 20) | ||
private String title; | ||
|
||
@Column(name = "body", nullable = false, length = 20) | ||
private String body; | ||
|
||
@Column(name = "type") | ||
@Enumerated(EnumType.STRING) | ||
private AlertType type; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
import java.util.Map; | ||
|
||
@FunctionalInterface | ||
public interface AlertPublisher<T extends Alertable> { | ||
public interface AlertPublisher { | ||
|
||
void publish(T alertable); | ||
void publish(String token, Alert alert, Map<String, String> data); | ||
|
||
} |
17 changes: 1 addition & 16 deletions
17
src/main/java/net/teumteum/alert/domain/AlertRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,7 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
import jakarta.persistence.LockModeType; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Lock; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
|
||
public interface AlertRepository extends JpaRepository<UserAlert, Long> { | ||
|
||
@Query("select u from user_alert as u where u.userId in :userIds") | ||
List<UserAlert> findAllByUserId(@Param("userIds") Iterable<Long> userIds); | ||
|
||
@Lock(LockModeType.PESSIMISTIC_WRITE) | ||
@Query("select u from user_alert as u where u.userId = :userId") | ||
Optional<UserAlert> findByUserIdWithLock(@Param("userId") Long userId); | ||
|
||
Optional<UserAlert> findByUserId(@Param("userId") Long userId); | ||
public interface AlertRepository extends JpaRepository<Alert, Long> { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
public enum AlertType { | ||
|
||
BEFORE_MEETING, | ||
END_MEETING, | ||
RECOMMEND_USER, | ||
; | ||
} |
This file was deleted.
Oops, something went wrong.
25 changes: 0 additions & 25 deletions
25
src/main/java/net/teumteum/alert/domain/BeforeMeetingAlert.java
This file was deleted.
Oops, something went wrong.
22 changes: 22 additions & 0 deletions
22
src/main/java/net/teumteum/alert/domain/UserAlertRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
import jakarta.persistence.LockModeType; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Lock; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
|
||
public interface UserAlertRepository extends JpaRepository<UserAlert, Long> { | ||
|
||
@Query("select u from user_alert as u where u.userId in :userIds") | ||
List<UserAlert> findAllByUserId(@Param("userIds") Iterable<Long> userIds); | ||
|
||
@Lock(LockModeType.PESSIMISTIC_WRITE) | ||
@Query("select u from user_alert as u where u.userId = :userId") | ||
Optional<UserAlert> findByUserIdWithLock(@Param("userId") Long userId); | ||
|
||
Optional<UserAlert> findByUserId(@Param("userId") Long userId); | ||
|
||
} |
40 changes: 40 additions & 0 deletions
40
src/main/java/net/teumteum/alert/domain/UserAlertService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package net.teumteum.alert.domain; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import lombok.RequiredArgsConstructor; | ||
import net.teumteum.alert.domain.request.RegisterAlertRequest; | ||
import net.teumteum.alert.domain.request.UpdateAlertTokenRequest; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class UserAlertService { | ||
|
||
private final UserAlertRepository alertRepository; | ||
|
||
@Transactional | ||
public void registerAlert(Long userId, RegisterAlertRequest registerAlertRequest) { | ||
alertRepository.findByUserId(userId) | ||
.ifPresentOrElse(userAlert -> { | ||
throw new IllegalArgumentException("이미 토큰이 생성된 user입니다. \"" + userId +"\""); | ||
}, () -> { | ||
var alert = new UserAlert(null, userId, registerAlertRequest.token()); | ||
alertRepository.save(alert); | ||
}); | ||
} | ||
|
||
@Transactional | ||
public void updateAlertToken(Long userId, UpdateAlertTokenRequest updateAlertTokenRequest) { | ||
var userAlert = alertRepository.findByUserIdWithLock(userId) | ||
.orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 토큰을 찾을 수 없습니다.")); | ||
|
||
userAlert.updateToken(updateAlertTokenRequest.token()); | ||
} | ||
|
||
public List<UserAlert> findAllByUserId(Set<Long> userIds) { | ||
return alertRepository.findAllByUserId(userIds); | ||
} | ||
} |
Oops, something went wrong.