Skip to content

Commit af7709e

Browse files
authored
Merge pull request #318 from THIP-TextHip/feat/#317-notification-check-unread-one
[feat] 안읽은 알림 여부 조회 api 구현
2 parents 73a16e3 + c6b5e7b commit af7709e

9 files changed

Lines changed: 140 additions & 0 deletions

File tree

src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import konkuk.thip.common.swagger.annotation.ExceptionDescription;
99
import konkuk.thip.notification.adapter.in.web.response.NotificationShowEnableStateResponse;
1010
import konkuk.thip.notification.adapter.in.web.response.NotificationShowResponse;
11+
import konkuk.thip.notification.adapter.in.web.response.NotificationUncheckedExistsResponse;
12+
import konkuk.thip.notification.application.port.in.NotificationExistsUncheckedUseCase;
1113
import konkuk.thip.notification.application.port.in.NotificationShowEnableStateUseCase;
1214
import konkuk.thip.notification.application.port.in.NotificationShowUseCase;
1315
import konkuk.thip.notification.application.port.in.dto.NotificationType;
@@ -26,6 +28,7 @@ public class NotificationQueryController {
2628

2729
private final NotificationShowEnableStateUseCase notificationShowEnableStateUseCase;
2830
private final NotificationShowUseCase notificationShowUseCase;
31+
private final NotificationExistsUncheckedUseCase notificationExistsUncheckedUseCase;
2932

3033
@Operation(
3134
summary = "사용자 푸시알림 수신여부 조회 (마이페이지 -> 알림설정)",
@@ -56,4 +59,14 @@ public BaseResponse<NotificationShowResponse> showNotifications(
5659
) {
5760
return BaseResponse.ok(notificationShowUseCase.showNotifications(userId, cursor, NotificationType.from(type)));
5861
}
62+
63+
@Operation(
64+
summary = "유저의 안읽은 알림 존재 여부 확인",
65+
description = "유저가 읽지 않은 알림이 존재하는지 여부를 확인합니다."
66+
)
67+
@GetMapping("/notifications/exists-unchecked")
68+
public BaseResponse<NotificationUncheckedExistsResponse> existsUnchecked(@Parameter(hidden = true) @UserId final Long userId) {
69+
return BaseResponse.ok(NotificationUncheckedExistsResponse.of(
70+
notificationExistsUncheckedUseCase.existsUnchecked(userId)));
71+
}
5972
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package konkuk.thip.notification.adapter.in.web.response;
2+
3+
public record NotificationUncheckedExistsResponse(
4+
boolean exists
5+
) {
6+
public static NotificationUncheckedExistsResponse of(boolean exists) {
7+
return new NotificationUncheckedExistsResponse(exists);
8+
}
9+
}

src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public CursorBasedList<NotificationQueryDto> findFeedAndRoomNotificationsByUserI
4040
));
4141
}
4242

43+
@Override
44+
public boolean existsUnchecked(Long userId) {
45+
return notificationJpaRepository.existsByUserIdAndIsCheckedFalse(userId);
46+
}
47+
4348
private CursorBasedList<NotificationQueryDto> findNotificationsByPrimaryKeyCursor(Cursor cursor, PrimaryKeyNotificationQueryFunction queryFunction) {
4449
Long lastNotificationId = cursor.isFirstRequest() ? null : cursor.getLong(0);
4550
int pageSize = cursor.getPageSize();

src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ public interface NotificationQueryRepository {
1111
List<NotificationQueryDto> findRoomNotificationsOrderByCreatedAtDesc(Long userId, Long lastNotificationId, int pageSize);
1212

1313
List<NotificationQueryDto> findFeedAndRoomNotificationsOrderByCreatedAtDesc(Long userId, Long lastNotificationId, int pageSize);
14+
15+
boolean existsByUserIdAndIsCheckedFalse(Long userId);
1416
}

src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepositoryImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ public List<NotificationQueryDto> findFeedAndRoomNotificationsOrderByCreatedAtDe
4747
return getNotificationQueryDtos(pageSize, notification, where);
4848
}
4949

50+
@Override
51+
public boolean existsByUserIdAndIsCheckedFalse(Long userId) {
52+
Integer result = queryFactory.selectOne()
53+
.from(notification)
54+
.where(notification.userJpaEntity.userId.eq(userId)
55+
.and(notification.isChecked.eq(false)))
56+
.fetchFirst();
57+
58+
return result != null;
59+
}
60+
5061
private static BooleanExpression applyCursor(Long lastNotificationId, BooleanExpression where, QNotificationJpaEntity notification) {
5162
if (lastNotificationId != null) {
5263
where = where.and(notification.notificationId.lt(lastNotificationId));
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package konkuk.thip.notification.application.port.in;
2+
3+
public interface NotificationExistsUncheckedUseCase {
4+
5+
boolean existsUnchecked(Long userId);
6+
}

src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ public interface NotificationQueryPort {
1111
CursorBasedList<NotificationQueryDto> findRoomNotificationsByUserId(Long userId, Cursor cursor);
1212

1313
CursorBasedList<NotificationQueryDto> findFeedAndRoomNotificationsByUserId(Long userId, Cursor cursor);
14+
15+
boolean existsUnchecked(Long userId);
1416
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package konkuk.thip.notification.application.service;
2+
3+
import konkuk.thip.notification.application.port.in.NotificationExistsUncheckedUseCase;
4+
import konkuk.thip.notification.application.port.out.NotificationQueryPort;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Service;
7+
import org.springframework.transaction.annotation.Transactional;
8+
9+
@Service
10+
@RequiredArgsConstructor
11+
public class NotificationExistsUncheckedService implements NotificationExistsUncheckedUseCase {
12+
13+
private final NotificationQueryPort notificationQueryPort;
14+
15+
@Override
16+
@Transactional(readOnly = true)
17+
public boolean existsUnchecked(Long userId) {
18+
return notificationQueryPort.existsUnchecked(userId);
19+
}
20+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package konkuk.thip.notification.adapter.in.web;
2+
3+
import konkuk.thip.common.util.TestEntityFactory;
4+
import konkuk.thip.notification.adapter.out.jpa.NotificationJpaEntity;
5+
import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository;
6+
import konkuk.thip.notification.domain.value.NotificationCategory;
7+
import konkuk.thip.user.adapter.out.jpa.UserJpaEntity;
8+
import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository;
9+
import konkuk.thip.user.domain.value.Alias;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
14+
import org.springframework.boot.test.context.SpringBootTest;
15+
import org.springframework.jdbc.core.JdbcTemplate;
16+
import org.springframework.test.context.ActiveProfiles;
17+
import org.springframework.test.web.servlet.MockMvc;
18+
import org.springframework.test.web.servlet.ResultActions;
19+
import org.springframework.transaction.annotation.Transactional;
20+
21+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
23+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
24+
25+
@SpringBootTest
26+
@ActiveProfiles("test")
27+
@Transactional
28+
@AutoConfigureMockMvc(addFilters = false)
29+
@DisplayName("[통합] 안읽은 알림 존재 여부 확인 api 통합 테스트")
30+
class NotificationExistsUncheckedApiTest {
31+
32+
@Autowired private MockMvc mockMvc;
33+
@Autowired private UserJpaRepository userJpaRepository;
34+
@Autowired private NotificationJpaRepository notificationJpaRepository;
35+
@Autowired private JdbcTemplate jdbcTemplate;
36+
37+
@Test
38+
@DisplayName("유저가 읽지 않은 알림이 있을 경우, true 를 반환한다.")
39+
void notification_exists_unchecked_true() throws Exception {
40+
//given
41+
UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER));
42+
NotificationJpaEntity n1 = notificationJpaRepository.save(TestEntityFactory.createNotification(user, "알림1", NotificationCategory.FEED));
43+
44+
//when
45+
ResultActions result = mockMvc.perform(get("/notifications/exists-unchecked")
46+
.requestAttr("userId", user.getUserId()));
47+
48+
//then
49+
result.andExpect(status().isOk())
50+
.andExpect(jsonPath("$.data.exists").value(true));
51+
}
52+
53+
@Test
54+
@DisplayName("유저가 읽지 않은 알림이 없을 경우, false 를 반환한다.")
55+
void notification_exists_unchecked_false() throws Exception {
56+
//given
57+
UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER));
58+
NotificationJpaEntity n1 = notificationJpaRepository.save(TestEntityFactory.createNotification(user, "알림1", NotificationCategory.FEED));
59+
jdbcTemplate.update(
60+
"UPDATE notifications SET is_checked = TRUE WHERE notification_id = ?",
61+
n1.getNotificationId()
62+
);
63+
64+
//when
65+
ResultActions result = mockMvc.perform(get("/notifications/exists-unchecked")
66+
.requestAttr("userId", user.getUserId()));
67+
68+
//then
69+
result.andExpect(status().isOk())
70+
.andExpect(jsonPath("$.data.exists").value(false));
71+
}
72+
}

0 commit comments

Comments
 (0)