-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 안읽은 알림 여부 조회 api 구현 #318
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
Changes from all commits
2377a42
d28b95e
2bc5f6e
c2dee7b
c6b5e7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package konkuk.thip.notification.adapter.in.web.response; | ||
|
|
||
| public record NotificationUncheckedExistsResponse( | ||
| boolean exists | ||
| ) { | ||
| public static NotificationUncheckedExistsResponse of(boolean exists) { | ||
| return new NotificationUncheckedExistsResponse(exists); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,17 @@ public List<NotificationQueryDto> findFeedAndRoomNotificationsOrderByCreatedAtDe | |
| return getNotificationQueryDtos(pageSize, notification, where); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean existsByUserIdAndIsCheckedFalse(Long userId) { | ||
| Integer result = queryFactory.selectOne() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 단순 궁금인데 간단한 쿼리같은데 JPQL대신 queryDsl을 사용한 이유가 따로 있을까요??
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 역시 예리하시네요. 첨부한 블로그 내용 확인해주시면 될 것 같습니다!!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 jpql에서는 count 함수밖에 사용하지 못하는데 이는 모든 테이블을 스캔하는 것이고, exists 함수 또는 현재 성준님이 구현하신 것처럼 select 1 limit 1을 사용하면 특정 매치되는 row가 하나만 있으면 바로 스캔이 중단되어서 성능이 더 좋은 것 같네요! 꼼꼼하십니다 굿굿 |
||
| .from(notification) | ||
| .where(notification.userJpaEntity.userId.eq(userId) | ||
| .and(notification.isChecked.eq(false))) | ||
| .fetchFirst(); | ||
|
|
||
| return result != null; | ||
| } | ||
|
|
||
| private static BooleanExpression applyCursor(Long lastNotificationId, BooleanExpression where, QNotificationJpaEntity notification) { | ||
| if (lastNotificationId != null) { | ||
| where = where.and(notification.notificationId.lt(lastNotificationId)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package konkuk.thip.notification.application.port.in; | ||
|
|
||
| public interface NotificationExistsUncheckedUseCase { | ||
|
|
||
| boolean existsUnchecked(Long userId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package konkuk.thip.notification.application.service; | ||
|
|
||
| import konkuk.thip.notification.application.port.in.NotificationExistsUncheckedUseCase; | ||
| import konkuk.thip.notification.application.port.out.NotificationQueryPort; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class NotificationExistsUncheckedService implements NotificationExistsUncheckedUseCase { | ||
|
|
||
| private final NotificationQueryPort notificationQueryPort; | ||
|
|
||
| @Override | ||
| @Transactional(readOnly = true) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM |
||
| public boolean existsUnchecked(Long userId) { | ||
| return notificationQueryPort.existsUnchecked(userId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package konkuk.thip.notification.adapter.in.web; | ||
|
|
||
| import konkuk.thip.common.util.TestEntityFactory; | ||
| import konkuk.thip.notification.adapter.out.jpa.NotificationJpaEntity; | ||
| import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; | ||
| import konkuk.thip.notification.domain.value.NotificationCategory; | ||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||
| import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; | ||
| import konkuk.thip.user.domain.value.Alias; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
| import org.springframework.boot.test.context.SpringBootTest; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.test.context.ActiveProfiles; | ||
| import org.springframework.test.web.servlet.MockMvc; | ||
| import org.springframework.test.web.servlet.ResultActions; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
|
||
| @SpringBootTest | ||
| @ActiveProfiles("test") | ||
| @Transactional | ||
| @AutoConfigureMockMvc(addFilters = false) | ||
| @DisplayName("[통합] 안읽은 알림 존재 여부 확인 api 통합 테스트") | ||
| class NotificationExistsUncheckedApiTest { | ||
|
|
||
| @Autowired private MockMvc mockMvc; | ||
| @Autowired private UserJpaRepository userJpaRepository; | ||
| @Autowired private NotificationJpaRepository notificationJpaRepository; | ||
| @Autowired private JdbcTemplate jdbcTemplate; | ||
|
|
||
| @Test | ||
| @DisplayName("유저가 읽지 않은 알림이 있을 경우, true 를 반환한다.") | ||
| void notification_exists_unchecked_true() throws Exception { | ||
| //given | ||
| UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER)); | ||
| NotificationJpaEntity n1 = notificationJpaRepository.save(TestEntityFactory.createNotification(user, "알림1", NotificationCategory.FEED)); | ||
|
|
||
| //when | ||
| ResultActions result = mockMvc.perform(get("/notifications/exists-unchecked") | ||
| .requestAttr("userId", user.getUserId())); | ||
|
|
||
| //then | ||
| result.andExpect(status().isOk()) | ||
| .andExpect(jsonPath("$.data.exists").value(true)); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("유저가 읽지 않은 알림이 없을 경우, false 를 반환한다.") | ||
| void notification_exists_unchecked_false() throws Exception { | ||
| //given | ||
| UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER)); | ||
| NotificationJpaEntity n1 = notificationJpaRepository.save(TestEntityFactory.createNotification(user, "알림1", NotificationCategory.FEED)); | ||
| jdbcTemplate.update( | ||
| "UPDATE notifications SET is_checked = TRUE WHERE notification_id = ?", | ||
| n1.getNotificationId() | ||
| ); | ||
|
|
||
| //when | ||
| ResultActions result = mockMvc.perform(get("/notifications/exists-unchecked") | ||
| .requestAttr("userId", user.getUserId())); | ||
|
|
||
| //then | ||
| result.andExpect(status().isOk()) | ||
| .andExpect(jsonPath("$.data.exists").value(false)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Swagger 문서에 예외 처리 설명이 누락되었습니다.
Lines 38, 51에서 다른 엔드포인트들은
@ExceptionDescription어노테이션으로 예외 케이스를 문서화하고 있으나, 이 엔드포인트에는 누락되었습니다. API 문서의 일관성을 위해 추가를 권장합니다.다음과 같이 추가하세요:
@Operation( summary = "유저의 안읽은 알림 존재 여부 확인", description = "유저가 읽지 않은 알림이 존재하는지 여부를 확인합니다." ) + @ExceptionDescription(NOTIFICATION_EXISTS_UNCHECKED) @GetMapping("/notifications/exists-unchecked") public BaseResponse<NotificationUncheckedExistsResponse> existsUnchecked(@Parameter(hidden = true) @UserId final Long userId) {그리고
SwaggerResponseDescription클래스에 상수를 추가하세요:다음 스크립트로
SwaggerResponseDescription클래스 위치 및 패턴을 확인하세요:🏁 Script executed:
Length of output: 72
🏁 Script executed:
Length of output: 12502
🏁 Script executed:
rg -n 'NOTIFICATION_' src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.javaLength of output: 312
existsUnchecked 엔드포인트에 @ExceptionDescription 및 SwaggerResponseDescription enum constant 추가
@GetMapping("/notifications/exists-unchecked")바로 위에기존 알림 처리 enum(NOTIFICATION_MARK_TO_CHECKED) 다음에
🤖 Prompt for AI Agents