Skip to content

Conversation

@david-parkk
Copy link
Contributor

@david-parkk david-parkk commented Dec 29, 2025

#️⃣ 연관된 이슈

closes #375

📝작업 내용

  • 알림 비활성화/활성화 API 변경

작업 상세 내용

  • 배열로 변경
  • DB 벌크 save, delete

💬리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 알람 비활성화를 여러 알람 타입에 대해 일괄 처리할 수 있도록 개선되었습니다.
    • 요청/응답 형식이 목록 기반으로 변경되어 한 번에 여러 알람의 상태(isDisabled)를 반환/조정합니다.
  • 테스트

    • 관련 단위/통합 테스트가 목록 기반 입력·출력에 맞게 업데이트되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Walkthrough

알람 비활성화 API가 단일 AlarmType에서 여러 AlarmType의 배치 처리로 변경되었습니다. 요청이 리스트로 바뀌고, 서비스는 기존 비활성화 항목을 일괄 조회하여 생성/삭제 대상을 집합으로 계산한 뒤 saveAll/deleteAll로 배치 적용하고, 응답은 여러 알람 결과 리스트로 반환합니다.

Changes

코호트 / 파일(s) 변경 사항 요약
서비스 계층
src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java
disableAlarm()이 단일 타입 토글에서 여러 AlarmType을 받아 기존 비활성화 항목을 일괄 조회(findByUserAndAlarmTypeIn)하고 생성/삭제 목록을 계산한 뒤 saveAll()/deleteAll()로 배치 처리하도록 리팩토링. 결과를 집계해 리스트 형태의 응답으로 반환
저장소 계층
src/main/java/ku_rum/backend/domain/alarm/domain/repository/UserDisabledAlarmRepository.java
새로운 쿼리 메서드 추가: findByUserAndAlarmTypeIn(User user, List<AlarmType> alarmTypes) — 여러 타입에 대한 일괄 조회 지원
요청/응답 DTO
src/main/java/ku_rum/backend/domain/alarm/dto/request/PatchDisableAlarmRequest.java
src/main/java/ku_rum/backend/domain/alarm/dto/response/DisableAlarmDto.java
src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java
요청 레코드가 alarmTypealarmTypes: List<AlarmType>로 변경. 새로운 DisableAlarmDto(AlarmType, Boolean) 추가. 응답 레코드가 단일 알람에서 List<DisableAlarmDto>를 포함하는 구조로 변경됨
테스트 / 프레젠테이션
src/test/java/ku_rum/backend/domain/alarm/presentation/AlarmControllerTest.java
테스트 및 REST Docs가 요청/응답 스키마 변경에 따라 "alarmTypes": [...] 입력과 data.alarms[] 출력 구조로 업데이트됨

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as Controller
    participant Service as AlarmService
    participant Repo as UserDisabledAlarmRepository
    participant DB as Database

    Client->>Controller: PATCH /alarms/disable (alarmTypes: [A,B,C])
    Controller->>Service: disableAlarm(user, [A,B,C])
    Service->>Repo: findByUserAndAlarmTypeIn(user, [A,B,C])
    Repo->>DB: SELECT existing disabled WHERE user & alarmType IN (...)
    DB-->>Repo: [existing records]
    Repo-->>Service: [existing disabled alarms]
    Note over Service: 계산 — toCreate = requested - existing\ntoDelete = existing ∩ requested(remove flags)
    Service->>Repo: saveAll(toCreate)
    Repo->>DB: INSERT ... (batch)
    DB-->>Repo: insert results
    Repo-->>Service: created records
    Service->>Repo: deleteAll(toDelete)
    Repo->>DB: DELETE ... (batch)
    DB-->>Repo: delete results
    Repo-->>Service: deleted records
    Service-->>Controller: PatchDisableAlarmResponse(userId, [DisableAlarmDto...])
    Controller-->>Client: 200 OK { data: { userId, alarms: [...] } }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • backend#368: 동일한 disableAlarm 흐름과 DTO/저장소 변경을 다루는 PR로, 본 PR의 배치 처리 확장과 직접적인 연관이 있음.
  • backend#342: AlarmService 관련 변경을 포함한 PR으로, 서비스 로직 및 API 변경과 교차되는 부분이 있음.

Suggested reviewers

  • kmw10693

Poem

🐰 여러 알람이 춤을 추네
리스트로 모여 한 번에 정리해
깔끔히 만들고 배치로 보낸다
당근 한 입 축하의 박수 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항(알림 API를 배열로 변경)을 명확히 요약하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(연관 이슈, 작업 내용, 작업 상세)을 포함하고 있습니다.
Linked Issues check ✅ Passed 코드 변경이 #375 이슈의 요구사항(배열로 변경, 벌크 DB 작업)을 모두 충족하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #375 이슈 범위 내에 있으며, 범위를 벗어난 변경사항은 없습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#375

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java (2)

159-165: 루프 내 스트림 검색은 O(n*m) 복잡도를 유발합니다.

매 반복마다 existingDisabledAlarms 스트림을 순회하여 일치하는 항목을 찾고 있습니다. existingAlarmTypes를 Set으로 만들었듯이, existingDisabledAlarms도 Map으로 미리 변환하면 O(n) 복잡도로 개선할 수 있습니다.

🔎 개선 제안
         Set<AlarmType> existingAlarmTypes = existingDisabledAlarms.stream()
                 .map(UserDisabledAlarm::getAlarmType)
                 .collect(Collectors.toSet());
+        Map<AlarmType, UserDisabledAlarm> existingAlarmMap = existingDisabledAlarms.stream()
+                .collect(Collectors.toMap(UserDisabledAlarm::getAlarmType, alarm -> alarm));

         List<UserDisabledAlarm> toCreate = new ArrayList<>();
         List<UserDisabledAlarm> toDelete = new ArrayList<>();

         for (AlarmType alarmType : alarmTypes) {
             if (existingAlarmTypes.contains(alarmType)) {
-                existingDisabledAlarms.stream()
-                        .filter(alarm -> alarm.getAlarmType().equals(alarmType))
-                        .findFirst()
-                        .ifPresent(toDelete::add);
+                toDelete.add(existingAlarmMap.get(alarmType));
                 continue;
             }

147-149: alarmTypes가 null 또는 빈 리스트인 경우에 대한 검증이 필요합니다.

request.alarmTypes()가 null이거나 빈 리스트일 경우, 현재 코드는 정상적으로 동작하지만 불필요한 DB 조회가 발생합니다. 요청 유효성 검증을 추가하는 것이 좋습니다.

🔎 개선 제안
     public PatchDisableAlarmResponse disableAlarm(CustomUserDetails userDetails, PatchDisableAlarmRequest request) {
         User user = userService.getUser();
         List<AlarmType> alarmTypes = request.alarmTypes();
+        if (alarmTypes == null || alarmTypes.isEmpty()) {
+            return new PatchDisableAlarmResponse(user.getId(), List.of());
+        }
         List<UserDisabledAlarm> existingDisabledAlarms = userDisabledAlarmRepository.findByUserAndAlarmTypeIn(user,
                 alarmTypes);
src/main/java/ku_rum/backend/domain/alarm/dto/request/PatchDisableAlarmRequest.java (1)

6-7: 요청 DTO에 유효성 검증 어노테이션 추가를 권장합니다.

alarmTypes가 null이거나 빈 리스트인 경우를 방지하기 위해 Jakarta Validation 어노테이션을 추가하는 것이 좋습니다.

🔎 개선 제안
 package ku_rum.backend.domain.alarm.dto.request;

 import java.util.List;
+import jakarta.validation.constraints.NotEmpty;
 import ku_rum.backend.domain.alarm.domain.AlarmType;

-public record PatchDisableAlarmRequest(List<AlarmType> alarmTypes) {
+public record PatchDisableAlarmRequest(@NotEmpty List<AlarmType> alarmTypes) {
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d7c9c6 and e520f9d.

📒 Files selected for processing (6)
  • src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java
  • src/main/java/ku_rum/backend/domain/alarm/domain/repository/UserDisabledAlarmRepository.java
  • src/main/java/ku_rum/backend/domain/alarm/dto/request/PatchDisableAlarmRequest.java
  • src/main/java/ku_rum/backend/domain/alarm/dto/response/DisableAlarmDto.java
  • src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java
  • src/test/java/ku_rum/backend/domain/alarm/presentation/AlarmControllerTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
src/main/java/ku_rum/backend/domain/alarm/domain/repository/UserDisabledAlarmRepository.java (1)

16-17: LGTM!

Spring Data JPA의 네이밍 컨벤션을 올바르게 사용하여 IN 절 쿼리를 생성합니다.

src/main/java/ku_rum/backend/domain/alarm/dto/response/DisableAlarmDto.java (1)

6-9: LGTM!

새로운 DTO가 깔끔하게 구현되었습니다. 팩토리 메서드가 엔티티에서 필요한 데이터를 올바르게 추출합니다.

src/test/java/ku_rum/backend/domain/alarm/presentation/AlarmControllerTest.java (1)

205-227: LGTM!

테스트가 배열 기반 API 구조를 올바르게 반영하고 있으며, REST Docs 필드 문서화도 적절하게 업데이트되었습니다.

Comment on lines 9 to 14
Long userId = null;
if (!toCreate.isEmpty()) {
userId = toCreate.get(0).getUser().getId();
} else if (!toDelete.isEmpty()) {
userId = toDelete.get(0).getUser().getId();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

toCreatetoDelete 모두 빈 리스트인 경우 userId가 null이 됩니다.

빈 리스트로 요청이 오거나 요청된 모든 알림 타입이 이미 현재 상태와 동일한 경우, userId가 null로 반환될 수 있습니다. 서비스 레이어에서 사용자 정보를 직접 전달하는 것을 고려해 주세요.

🔎 대안 제안

팩토리 메서드 시그니처를 변경하여 userId를 직접 받도록 수정:

-    public static PatchDisableAlarmResponse of(List<UserDisabledAlarm> toCreate, List<UserDisabledAlarm> toDelete) {
-        Long userId = null;
-        if (!toCreate.isEmpty()) {
-            userId = toCreate.get(0).getUser().getId();
-        } else if (!toDelete.isEmpty()) {
-            userId = toDelete.get(0).getUser().getId();
-        }
+    public static PatchDisableAlarmResponse of(Long userId, List<UserDisabledAlarm> toCreate, List<UserDisabledAlarm> toDelete) {

그리고 AlarmService에서 호출 시:

-        return PatchDisableAlarmResponse.of(toCreate, toDelete);
+        return PatchDisableAlarmResponse.of(user.getId(), toCreate, toDelete);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java
around lines 9-14, the current logic can leave userId null when both toCreate
and toDelete are empty; change the factory/constructor to accept a non-null Long
userId (remove nullable deduction from lists), update all callers (AlarmService)
to pass the current user's id explicitly, and validate/assert userId is present
(or throw IllegalArgumentException) so the response never contains a null
userId.

@github-actions
Copy link

github-actions bot commented Dec 29, 2025

Test Results

 38 files   38 suites   11s ⏱️
156 tests 156 ✅ 0 💤 0 ❌
157 runs  157 ✅ 0 💤 0 ❌

Results for commit c859abd.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@kmw10693 kmw10693 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 래빗만 반영하면 될것 같습니다~!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java (1)

159-172: 토글 로직은 정확하지만, 성능 최적화가 가능합니다.

alarmType에 대해 existingDisabledAlarms를 반복적으로 필터링하는 것은 O(n×m) 복잡도를 가집니다.

🔎 성능 개선을 위한 제안

먼저 Map을 생성하여 O(1) 조회가 가능하도록 개선할 수 있습니다:

         Set<AlarmType> existingAlarmTypes = existingDisabledAlarms.stream()
                 .map(UserDisabledAlarm::getAlarmType)
                 .collect(Collectors.toSet());
+        Map<AlarmType, UserDisabledAlarm> existingAlarmsMap = existingDisabledAlarms.stream()
+                .collect(Collectors.toMap(UserDisabledAlarm::getAlarmType, alarm -> alarm));

         List<UserDisabledAlarm> toCreate = new ArrayList<>();
         List<UserDisabledAlarm> toDelete = new ArrayList<>();

         for (AlarmType alarmType : alarmTypes) {
             if (existingAlarmTypes.contains(alarmType)) {
-                existingDisabledAlarms.stream()
-                        .filter(alarm -> alarm.getAlarmType().equals(alarmType))
-                        .findFirst()
-                        .ifPresent(toDelete::add);
+                toDelete.add(existingAlarmsMap.get(alarmType));
                 continue;
             }
src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java (1)

19-23: 리스트 결합 방식을 Stream.concat으로 개선할 수 있습니다.

현재 방식도 정확하지만, 더 함수형 스타일로 개선할 수 있습니다.

🔎 함수형 스타일 리팩토링 제안
-        List<DisableAlarmDto> allAlarms = new ArrayList<>();
-        allAlarms.addAll(createdAlarms);
-        allAlarms.addAll(deletedAlarms);
+        List<DisableAlarmDto> allAlarms = Stream.concat(
+                createdAlarms.stream(),
+                deletedAlarms.stream()
+        ).toList();

이 경우 java.util.stream.Stream import도 추가해야 합니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e520f9d and c859abd.

📒 Files selected for processing (2)
  • src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java
  • src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
src/main/java/ku_rum/backend/domain/alarm/application/AlarmService.java (3)

4-4: 배치 처리를 위한 import 추가가 적절합니다.

ArrayList, Set, Collectors import가 배치 연산 구현에 올바르게 사용되고 있습니다.

Also applies to: 9-10


174-182: 조건부 배치 연산과 응답 생성이 올바릅니다.

빈 리스트에 대한 불필요한 DB 호출을 방지하는 조건 검사가 적절하고, Line 181에서 user.getId()를 명시적으로 전달하여 이전 리뷰에서 지적된 userId null 이슈를 해결했습니다.


147-158: 배치 처리 구조가 올바르게 구현되었습니다.

여러 AlarmType을 한 번에 처리하기 위한 구조가 적절하며, UserDisabledAlarmRepository.findByUserAndAlarmTypeIn() 메서드가 List 파라미터로 IN 절을 올바르게 처리합니다. 기존 비활성화된 알림을 조회하고 Set으로 변환하여 효율적인 조회가 가능합니다.

src/main/java/ku_rum/backend/domain/alarm/dto/response/PatchDisableAlarmResponse.java (2)

7-9: 이전 리뷰의 userId null 이슈가 해결되었습니다.

팩토리 메서드 시그니처가 userId를 파라미터로 직접 받도록 변경되어, toCreatetoDelete가 모두 비어있을 때 userId가 null이 되는 문제가 해결되었습니다.


11-17: isDisabled 플래그 로직이 올바르게 수정되었습니다.

이전 리뷰에서 지적된 isDisabled 플래그 반전 이슈가 해결되었습니다:

  • toCreate (새로 비활성화) → isDisabled=true
  • toDelete (재활성화) → isDisabled=false

@david-parkk david-parkk merged commit dfc32aa into develop Dec 29, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

알림 활성화 API 수정

3 participants