Skip to content

Commit

Permalink
release: 0.3.7
Browse files Browse the repository at this point in the history
* feat: 리뷰 등록시 회원 매너 온도 반영 기능 추가 및 리뷰 등록 리팩토링 (#158)

* refactor: 회원 리뷰 등록 API 리팩토링 (#157)

* feat: 회원 리뷰 등록시, 온도 반영 및 리뷰 피드백 반영 (#157)

* feat: User 엔티티 메소드 추가 (#157)

* feat: Review enum 필드 추가 (#157)

* test: 리뷰 등록시 온도 업데이트에 대한 단위 테스트 (#157)

* test: 리뷰 등록 기능 통합 테스트 (#157)

* refactor: 회원 리뷰 등록 메소드 수정 (#157)

* test: 불필요한 테스트 제거 및 CI 오류 수정 (#157)

* test: MeetingRepository 테스트에서 시간과 id비교 비활서화

* refactor: 회원 탈퇴 URI 변경 (#162)

* refactor: 회원 탈퇴 URI 수정 (#161)

* test: 회원 탈퇴 URI 수정에 대한 테스트 수정 (#161)

* fix: CI 에러 수정 (#161)

* fix: 필드 값 비교를 위해 deprecated 된 메소드을 대체 (#161)

* fix: 필드 값 비교를 위해 deprecated 된 메소드을 대체 (#161)

* feat: fcm token 업데이트 api 추가 (#166)

* fix: user_alert ddl의 pk에 auto_increment를 추가한다

* feat: token 업데이트 api를 추가한다

* fix: 모임 수정 시 이미지가 추가되는 버그 수정 (#169)

* perf: upgrade GPT-3.5 to GPT-4 (#174)

* refactor: 위치 기반 데이터 값 유효기간 변경

* refactor: 위치 기반 데이터 값 유효기간 변경

* refactor: 위치 기반 데이터 값 유효기간 변경

* feat: 모임 종료, 사용자 추천 알림을 추가 (#182)

* feat: Alert Entity를 추가하고, 알림 전송 기능이 Entity를 파라미터로 받아 동작하게 수정한다

* refactor: MeetingAlerted를 BeforeMeetingAlerted로 직관적으로 변경한다

* feat: 모임 종료 알림을 추가한다

* feat: 유저가 추천받았을때 알림 전송 기능을 추가한다

* feat: 유저가 한달 동안 받은 알람을 조회하는 기능 개발 (#185)

* fix: flyway schema에 _이 하나밖에 없는 버그를 수정한다

* feat: 유저가 한달동안 받은 알람을 조회하는 기능을 개발한다

* refactor: AlertRepository의 finalAll 메소드를 findAllByCreatedAt으로 변경한다

* feat: 북마크 추가, 취소 기능 구현 (#184)

* chore: 북마크 스키마 추가

* feat: 북마크 추가, 삭제 기능

* test: 북마크 추가, 삭제 테스트 작성

- 북마크 추가 케이스
- 북마크 취소 케이스
- 모임 단일 응답에 북마크 여부 추

* feat: bookmarked -> isBookmarked, V1__ 언더바 누락 수정

---------

Co-authored-by: ChoiDongKuen <[email protected]>
Co-authored-by: ddingmin <[email protected]>
  • Loading branch information
3 people authored Feb 10, 2024
1 parent f23cb5e commit d8eb41a
Show file tree
Hide file tree
Showing 32 changed files with 541 additions and 148 deletions.
84 changes: 84 additions & 0 deletions src/main/java/net/teumteum/alert/app/AlertHandler.java
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())
);
}
}

This file was deleted.

15 changes: 13 additions & 2 deletions src/main/java/net/teumteum/alert/controller/AlertController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import net.teumteum.alert.domain.AlertService;
import net.teumteum.alert.domain.UserAlertService;
import net.teumteum.alert.domain.request.RegisterAlertRequest;
import net.teumteum.alert.domain.request.UpdateAlertTokenRequest;
import net.teumteum.alert.domain.response.AlertsResponse;
import net.teumteum.core.error.ErrorResponse;
import net.teumteum.core.security.service.SecurityService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -21,20 +24,28 @@
public class AlertController {

private final AlertService alertService;
private final UserAlertService userAlertService;
private final SecurityService securityService;

@PostMapping("/alerts")
@ResponseStatus(HttpStatus.OK)
public void registerAlert(@Valid @RequestBody RegisterAlertRequest registerAlertRequest) {
var loginUserId = securityService.getCurrentUserId();
alertService.registerAlert(loginUserId, registerAlertRequest);
userAlertService.registerAlert(loginUserId, registerAlertRequest);
}

@PatchMapping("/alerts")
@ResponseStatus(HttpStatus.OK)
public void updateAlert(@Valid @RequestBody UpdateAlertTokenRequest updateAlertTokenRequest) {
var loginUserId = securityService.getCurrentUserId();
alertService.updateAlertToken(loginUserId, updateAlertTokenRequest);
userAlertService.updateAlertToken(loginUserId, updateAlertTokenRequest);
}

@GetMapping("/alerts")
@ResponseStatus(HttpStatus.OK)
public AlertsResponse getAlerts() {
var loginUserId = securityService.getCurrentUserId();
return alertService.findAllByUserId(loginUserId);
}

@ExceptionHandler(IllegalArgumentException.class)
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/net/teumteum/alert/domain/Alert.java
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;
}
6 changes: 4 additions & 2 deletions src/main/java/net/teumteum/alert/domain/AlertPublisher.java
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: 5 additions & 12 deletions src/main/java/net/teumteum/alert/domain/AlertRepository.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package net.teumteum.alert.domain;

import jakarta.persistence.LockModeType;
import java.time.Instant;
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> {
public interface AlertRepository extends JpaRepository<Alert, 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);
List<Alert> findAllByUserId(Long userId);

@Query("select a from alert as a where a.createdAt <= :createdAt")
List<Alert> findAllByCreatedAt(@Param("createdAt") Instant createdAt);
}
35 changes: 15 additions & 20 deletions src/main/java/net/teumteum/alert/domain/AlertService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package net.teumteum.alert.domain;

import java.util.List;
import java.util.Set;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import lombok.RequiredArgsConstructor;
import net.teumteum.alert.domain.request.RegisterAlertRequest;
import net.teumteum.alert.domain.request.UpdateAlertTokenRequest;
import net.teumteum.alert.domain.response.AlertsResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -13,28 +13,23 @@
@Transactional(readOnly = true)
public class AlertService {

private static final String EVERY_12AM = "0 0 0 * * *";

private final AlertRepository 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);
});
public Alert save(Alert alert) {
return 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 AlertsResponse findAllByUserId(Long userId) {
return AlertsResponse.of(alertRepository.findAllByUserId(userId));
}

public List<UserAlert> findAllByUserId(Set<Long> userIds) {
return alertRepository.findAllByUserId(userIds);
@Transactional
@Scheduled(cron = EVERY_12AM)
public void deleteOneMonthBeforeAlert() {
var deleteTargets = alertRepository.findAllByCreatedAt(Instant.now().minus(1, ChronoUnit.MONTHS));
alertRepository.deleteAllInBatch(deleteTargets);
}
}
9 changes: 9 additions & 0 deletions src/main/java/net/teumteum/alert/domain/AlertType.java
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,
;
}
12 changes: 0 additions & 12 deletions src/main/java/net/teumteum/alert/domain/Alertable.java

This file was deleted.

25 changes: 0 additions & 25 deletions src/main/java/net/teumteum/alert/domain/BeforeMeetingAlert.java

This file was deleted.

22 changes: 22 additions & 0 deletions src/main/java/net/teumteum/alert/domain/UserAlertRepository.java
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);

}
Loading

0 comments on commit d8eb41a

Please sign in to comment.