Skip to content

Commit

Permalink
feat: ✨ 미확인 푸시 알림 존재 여부 확인 API (#139)
Browse files Browse the repository at this point in the history
* test: repository unit test

* feat: 사용자가 읽지 않은 알림 존재 확인을 위한 repository 메서드 추가

* feat: notification service has_unread_notification 메서드 추가

* feat: usecase 미확인 알림 존재 여부 체크 분기 메서드 추가

* feat: controller api 추가

* test: query join 제거가 가능하도록 test 수정

* fix: jpa method 제거 후 query dsl로 수정

* docs: swagger 문서 작성

* rename: read unread notification() -> is_exists_unread_notification()
  • Loading branch information
psychology50 authored Jul 25, 2024
1 parent 3d0b5d1 commit ab0de1e
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ ResponseEntity<?> getNotifications(
@AuthenticationPrincipal SecurityUserDetails user
);

@Operation(summary = "수신한 알림 중 미확인 알림 존재 여부 조회")
@ApiResponse(responseCode = "200", description = "미확인 알림 존재 여부 조회 성공", content = @Content(schemaProperties = @SchemaProperty(name = "hasUnread", schema = @Schema(type = "boolean"))))
ResponseEntity<?> getUnreadNotifications(@AuthenticationPrincipal SecurityUserDetails user);

@Operation(summary = "수신한 알림 읽음 처리", description = "사용자가 수신한 알림을 읽음처리 합니다. 단, 읽음 처리할 알림의 pk는 사용자가 receiver여야 하며, 미확인 알림만 포함되어 있어야 합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "알림 읽음 처리 성공"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@RequiredArgsConstructor
@RequestMapping("/v2/notifications")
public class NotificationController implements NotificationApi {
private static final String HAS_UNREAD = "hasUnread";
private static final String NOTIFICATIONS = "notifications";

private final NotificationUseCase notificationUseCase;
Expand All @@ -36,6 +37,13 @@ public ResponseEntity<?> getNotifications(
return ResponseEntity.ok(SuccessResponse.from(NOTIFICATIONS, notificationUseCase.getNotifications(user.getUserId(), pageable)));
}

@Override
@GetMapping("/unread")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> getUnreadNotifications(@AuthenticationPrincipal SecurityUserDetails user) {
return ResponseEntity.ok(SuccessResponse.from(HAS_UNREAD, notificationUseCase.hasUnreadNotification(user.getUserId())));
}

@Override
@PatchMapping("")
@PreAuthorize("isAuthenticated() and @notificationManager.hasPermission(principal.userId, #readReq.notificationIds())")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ public class NotificationSearchService {
public Slice<Notification> getNotifications(Long userId, Pageable pageable) {
return notificationService.readNotificationsSlice(userId, pageable);
}

@Transactional(readOnly = true)
public boolean isExistsUnreadNotification(Long userId) {
return notificationService.isExistsUnreadNotification(userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public NotificationDto.SliceRes getNotifications(Long userId, Pageable pageable)
return NotificationMapper.toSliceRes(notifications, pageable);
}

public boolean hasUnreadNotification(Long userId) {
return notificationSearchService.isExistsUnreadNotification(userId);
}

public void updateNotificationsToRead(List<Long> notificationIds) {
notificationSaveService.updateNotificationsToRead(notificationIds);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.util.List;

public interface NotificationCustomRepository {
boolean existsUnreadNotification(Long userId);

/**
* 사용자들에게 정기 지출 등록 알림을 저장한다. (발송이 아님)
* 만약 이미 전송하려는 데이터가 년-월-일에 해당하는 생성일을 가지고 있고, 그 알림의 announcement 타입까지 같다면 저장하지 않는다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.co.pennyway.domain.domains.notification.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import kr.co.pennyway.domain.domains.notification.domain.QNotification;
import kr.co.pennyway.domain.domains.notification.type.Announcement;
import kr.co.pennyway.domain.domains.notification.type.NoticeType;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,10 +19,23 @@
@Repository
@RequiredArgsConstructor
public class NotificationCustomRepositoryImpl implements NotificationCustomRepository {
private final JPAQueryFactory queryFactory;
private final JdbcTemplate jdbcTemplate;

private final QNotification notification = QNotification.notification;

private final int BATCH_SIZE = 1000;

@Override
public boolean existsUnreadNotification(Long userId) {
return queryFactory
.select(notification.id)
.from(notification)
.where(notification.receiver.id.eq(userId)
.and(notification.readAt.isNull()))
.fetchFirst() != null;
}

@Override
public void saveDailySpendingAnnounceInBulk(List<Long> userIds, Announcement announcement) {
int batchCount = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public Slice<Notification> readNotificationsSlice(Long userId, Pageable pageable
return SliceUtil.toSlice(notificationRepository.findList(predicate, queryHandler, sort), pageable);
}

@Transactional(readOnly = true)
public boolean isExistsUnreadNotification(Long userId) {
return notificationRepository.existsUnreadNotification(userId);
}

@Transactional(readOnly = true)
public long countUnreadNotifications(Long userId, List<Long> notificationIds) {
return notificationRepository.countUnreadNotificationsByIds(userId, notificationIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertNotNull;
import static org.springframework.test.util.AssertionErrors.*;

@Slf4j
@DataJpaTest(properties = {"spring.jpa.hibernate.ddl-auto=create"})
Expand Down Expand Up @@ -130,6 +131,51 @@ void countUnreadNotificationsByIds() {
assertEquals("읽지 않은 알림 개수가 2개여야 한다.", 2L, count);
}

@Test
@DisplayName("사용자의 읽지 않은 알림이 존재하면 true를 반환한다.")
void existsTopByReceiver_IdAndReadAtIsNull() {
// given
User user = userRepository.save(createUser("jayang"));

Notification notification1 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();
Notification notification2 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();
Notification notification3 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();

ReflectionTestUtils.setField(notification1, "readAt", LocalDateTime.now());
ReflectionTestUtils.setField(notification2, "readAt", LocalDateTime.now());

notificationRepository.saveAll(List.of(notification1, notification2, notification3));

// when
boolean exists = notificationRepository.existsUnreadNotification(user.getId());

// then
assertTrue("읽지 않은 알림이 존재하면 true를 반환해야 한다.", exists);
}

@Test
@DisplayName("사용자의 읽지 않은 알림이 존재하지 않으면 false를 반환한다.")
void notExistsTopByReceiver_IdAndReadAtIsNull() {
// given
User user = userRepository.save(createUser("jayang"));

Notification notification1 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();
Notification notification2 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();
Notification notification3 = new Notification.Builder(NoticeType.ANNOUNCEMENT, Announcement.DAILY_SPENDING, user).build();

ReflectionTestUtils.setField(notification1, "readAt", LocalDateTime.now());
ReflectionTestUtils.setField(notification2, "readAt", LocalDateTime.now());
ReflectionTestUtils.setField(notification3, "readAt", LocalDateTime.now());

notificationRepository.saveAll(List.of(notification1, notification2, notification3));

// when
boolean exists = notificationRepository.existsUnreadNotification(user.getId());

// then
assertFalse("읽지 않은 알림이 존재하지 않으면 false를 반환해야 한다.", exists);
}

private User createUser(String name) {
return User.builder()
.username("test")
Expand Down

0 comments on commit ab0de1e

Please sign in to comment.