Skip to content

feat: 프론트 미연동 API 구현 - #119

Merged
KoungQ merged 1 commit into
productionfrom
feat/api-missing-features
Jul 31, 2026
Merged

feat: 프론트 미연동 API 구현#119
KoungQ merged 1 commit into
productionfrom
feat/api-missing-features

Conversation

@KoungQ

@KoungQ KoungQ commented Jul 31, 2026

Copy link
Copy Markdown
Member

📝 Pull Request Template

📌 제목

프론트 미연동 기능 API 구현


📢 요약

변경 사항 및 관련 이슈를 간단하게 설명해주세요.

  • dorumdorum-fe에 화면은 있지만 백엔드 API가 없어 실제 연동하지 못했던 기능 API를 추가했습니다.
  • 알림 설정, 회원 탈퇴, 고객 문의, 방/공지 상세 조회, 방 검색 키워드, 룸메이트 기록 조회 API를 구현했습니다.
  • 신규 테이블 notification_settings, support_inquiries를 추가하고 관련 컨트롤러 단위 테스트를 보강했습니다.

🔗 연관 이슈: Resolves #이슈번호


🚀 PR 유형

해당하는 항목에 체크해주세요.

  • ✨ 새로운 기능 추가
  • 🐛 버그 수정
  • 🎨 CSS/UI 디자인 변경
  • 🔧 코드에 영향 없는 변경(오타 수정, 탭 사이즈 변경, 변수명 변경 등)
  • 🔨 코드 리팩토링
  • 📝 주석 추가 및 수정
  • 📄 문서 수정
  • 🧪 테스트 추가 또는 리팩토링
  • 🏗️ 빌드 및 패키지 매니저 수정
  • 📂 파일 또는 폴더명 수정
  • 🗑️ 파일 또는 폴더 삭제

✅ PR 체크리스트

PR이 다음 요구 사항을 충족하는지 확인해주세요.

  • 🔹 커밋 메시지 컨벤션을 준수했습니다. (Commit message convention 참고)
  • 🔹 변경 사항에 대한 테스트를 수행했습니다. (버그 수정/기능 테스트)
  • 🔹 관련 문서를 업데이트했습니다. (필요한 경우)

📜 기타

리뷰어가 알면 좋을 추가 사항을 적어주세요.

  • 추가 API
    • GET /api/users/me/notification-settings
    • PUT /api/users/me/notification-settings
    • DELETE /api/users/me
    • POST /api/support/inquiries
    • GET /api/support/inquiries
    • GET /api/support/inquiries/{inquiryNo}
    • GET /api/rooms/{roomNo}
    • GET /api/notices/{noticeNo}
    • GET /api/users/me/roommate-history
  • 변경 API
    • POST /api/rooms/search 요청에 keyword 필드를 추가했습니다.
    • keyword는 방 제목과 방장 닉네임을 대상으로 검색합니다.
  • 회원 탈퇴는 방장으로 운영 중인 방이 있으면 409 ACTIVE_ROOM_OWNER로 막고, 탈퇴 가능 시 사용자 soft delete 및 익명화를 수행합니다.
  • 검증
    • ./gradlew test

Summary by CodeRabbit

  • 새로운 기능

    • 공지사항 상세 조회 기능을 추가했습니다.
    • 방 제목과 호스트 닉네임으로 방을 검색할 수 있습니다.
    • 방 상세 정보와 좋아요·신청 상태를 확인할 수 있습니다.
    • 알림 설정을 조회하고 항목별로 변경할 수 있습니다.
    • 룸메이트 현재·과거 이력을 조회할 수 있습니다.
    • 고객 문의를 접수하고 목록 및 상세 내용을 확인할 수 있습니다.
    • 회원 탈퇴와 탈퇴 사유 입력을 지원합니다.
  • 개선 사항

    • 운영 중인 방이 있는 경우 회원 탈퇴를 제한합니다.
    • 회원 탈퇴 시 개인정보를 안전하게 익명화합니다.
    • 관련 기능의 요청 검증과 응답 안정성을 강화했습니다.

@KoungQ KoungQ self-assigned this Jul 31, 2026
@KoungQ KoungQ added the feature label Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

공지사항, 알림 설정, 방 검색·상세 조회, 룸메이트 이력, 고객 문의, 회원 탈퇴 기능을 추가했습니다. 각 기능에 필요한 도메인 모델, 저장소, 유스케이스, REST API, 스키마와 컨트롤러 테스트를 함께 구성했습니다.

Changes

공지사항 상세 조회

Layer / File(s) Summary
공지사항 상세 조회 흐름
src/main/java/com/project/dorumdorum/domain/notice/...
GET /api/notices/{noticeNo} API와 조회 유스케이스를 추가했습니다. 공지가 없으면 _NOT_FOUND 예외를 발생시킵니다.

알림 설정

Layer / File(s) Summary
알림 설정 계약 및 저장 모델
src/main/java/com/project/dorumdorum/domain/notification/application/dto/..., src/main/java/com/project/dorumdorum/domain/notification/domain/..., src/main/resources/schema.sql
사용자별 알림 설정 엔티티, 요청·응답 DTO, 저장소와 notification_settings 테이블을 추가했습니다.
알림 설정 조회 및 갱신
src/main/java/com/project/dorumdorum/domain/notification/application/usecase/...
설정이 없으면 기본 설정을 생성하고, 요청 값으로 설정을 갱신합니다.
알림 설정 API 및 검증
src/main/java/com/project/dorumdorum/domain/notification/ui/..., src/test/java/com/project/dorumdorum/domain/notification/ui/...
알림 설정 조회·수정 API와 컨트롤러 단위 테스트를 추가했습니다.

방 조회 기능

Layer / File(s) Summary
방 검색 키워드 필터
src/main/java/com/project/dorumdorum/domain/room/application/dto/request/ChecklistFilterRequest.java, src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java, src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java
방 제목과 호스트 닉네임에 대한 키워드 부분 검색을 일반 및 네이티브 조회 경로에 적용했습니다.
방 상세 응답 및 상태 계산
src/main/java/com/project/dorumdorum/domain/room/application/dto/response/RoomDetailResponse.java, src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java
방 상세 정보와 호스트·좋아요·신청 관계를 조합한 응답을 추가했습니다.
방 상세 조회 API
src/main/java/com/project/dorumdorum/domain/room/ui/...
GET /api/rooms/{roomNo} API와 컨트롤러를 추가했습니다.

룸메이트 이력

Layer / File(s) Summary
룸메이트 이력 조회 계약
src/main/java/com/project/dorumdorum/domain/roommate/application/dto/response/RoommateHistoryResponse.java, src/main/java/com/project/dorumdorum/domain/roommate/domain/...
현재·과거 관계를 포함하는 응답 타입과 조회 계약을 추가했습니다.
룸메이트 이력 네이티브 조회
src/main/java/com/project/dorumdorum/domain/roommate/infra/repository/RoommateRepositoryImpl.java
네이티브 SQL 결과를 RoommateHistoryResponse로 매핑하고 관계·날짜 변환을 처리합니다.
룸메이트 이력 API
src/main/java/com/project/dorumdorum/domain/roommate/application/usecase/..., src/main/java/com/project/dorumdorum/domain/roommate/ui/...
GET /api/users/me/roommate-history API와 읽기 전용 조회 유스케이스를 추가했습니다.

고객 문의

Layer / File(s) Summary
문의 데이터 계약 및 저장 모델
src/main/java/com/project/dorumdorum/domain/support/..., src/main/resources/schema.sql
문의 요청·응답, 엔티티, 카테고리·상태, 저장소와 support_inquiries 테이블을 추가했습니다.
문의 생성 및 조회 유스케이스
src/main/java/com/project/dorumdorum/domain/support/application/usecase/...
문의 생성, 본인 목록 조회, 상세 조회 유스케이스를 추가했습니다.
문의 API 및 컨트롤러 검증
src/main/java/com/project/dorumdorum/domain/support/ui/..., src/test/java/com/project/dorumdorum/domain/support/ui/...
문의 생성·목록·상세 조회 API와 컨트롤러 테스트를 추가했습니다.

회원 탈퇴

Layer / File(s) Summary
탈퇴 관련 데이터 정리 계약
src/main/java/com/project/dorumdorum/domain/room/domain/repository/..., src/main/java/com/project/dorumdorum/domain/roommate/domain/repository/..., src/main/java/com/project/dorumdorum/domain/notification/domain/repository/...
사용자 관련 좋아요, 방 요청, 룸메이트, 알림, 디바이스의 일괄 soft delete 쿼리를 추가했습니다.
탈퇴 처리 및 개인정보 익명화
src/main/java/com/project/dorumdorum/domain/user/application/..., src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java, src/main/java/com/project/dorumdorum/global/exception/...
활성 방 소유자의 탈퇴를 차단하고, 관련 데이터를 정리한 뒤 사용자 개인정보를 익명화하고 삭제합니다.
회원 탈퇴 API 및 인증 폐기
src/main/java/com/project/dorumdorum/domain/user/ui/..., src/test/java/com/project/dorumdorum/domain/user/unit/ui/...
선택적 탈퇴 요청, 로그아웃, 인증 쿠키 만료와 204 응답을 구현했습니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: ydking0911

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 프론트엔드에 필요한 여러 백엔드 API를 구현한 변경 사항을 간결하게 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-missing-features

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.

@KoungQ
KoungQ merged commit 13f5f1f into production Jul 31, 2026
3 of 4 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/project/dorumdorum/domain/notification/application/usecase/UpdateNotificationSettingUseCase.java`:
- Around line 17-24: Update UpdateNotificationSettingUseCase.execute to handle
concurrent first-time creation: retain the existing lookup and update flow, but
catch the unique-constraint save failure, re-fetch the setting by userNo,
reapply the request, and save the existing entity before creating the response.
Ensure unrelated persistence exceptions still propagate.

In
`@src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java`:
- Around line 15-17: DeleteAccountUseCase의 회원 탈퇴 흐름에서 notification_settings의 처리
정책을 명시하세요. NotificationSettingRepository에 사용자별 삭제 또는 soft delete 메서드를 추가해
DeleteAccountUseCase에서 호출하고, 해당 정책을 검증하는 테스트를 추가하거나, 설정을 보존하는 정책이라면 그 계약과 테스트를
명확히 반영하세요.

In
`@src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java`:
- Around line 36-53: Update LoadRoomDetailUseCase.execute() so room details
remain available when the host is soft-deleted and anonymized. Map the
anonymized host fields from UserEntity.anonymizeForWithdrawal() consistently,
and replace the direct host.getStudentNo().substring(...) usage with a
withdrawal-safe fallback or guarded transformation. Preserve normal host details
for active users and avoid 404/500 responses.
- Around line 62-68: Update resolveAppliedStatus in LoadRoomDetailUseCase to
consider only non-deleted RoomRequest records when determining WAITING status.
Apply the isDeleted/deletedAt condition through the repository query used by
findByUserNoAndRoomAndDirection, while preserving APPROVED for isMyRoom and NONE
when no active request exists.

In
`@src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java`:
- Around line 22-24: Update the DeleteAccountUseCase.execute() call flow so bulk
cleanup runs before the first clearAutomatically operation, or reload/re-persist
User before applying anonymizeForWithdrawal() and delete(). Apply the ordering
or persistence fix across RoomRequestRepository.java lines 22-24,
NotificationDeviceRepository.java lines 20-22, and NotificationRepository.java
lines 15-17; preserve the existing cleanup behavior while ensuring User changes
are synchronized afterward.

In
`@src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java`:
- Around line 116-117: Update countByFilter to join the users entity before
applying keywordContains(request), adding the same
leftJoin(user).on(user.userNo.eq(room.hostUserNo)) used by findByCursor after
from(room). Preserve the existing filters and count behavior.

In
`@src/main/java/com/project/dorumdorum/domain/roommate/infra/repository/RoommateRepositoryImpl.java`:
- Around line 74-120: Update findMyRoommateHistory so my_rm is limited to one
representative roommate row per room_no before joining with rm, selecting the
latest record for the user in each room while preserving the existing history
status, ordering, and response mapping behavior.
- Around line 74-114: findMyRoommateHistory의 조회 대상에서 배정 완료 관계만 포함하도록
rm.confirm_status 조건을 추가하고, PENDING 및 ACCEPTED 상태를 제외하세요. 기존 쿼리 파라미터 바인딩 방식에 맞춰
completedStatuses를 COMPLETED 기준으로 설정·바인딩하며, 현재 이력 정렬과 삭제 상태 처리 로직은 유지하세요.
- Around line 22-37: Update RoommateRepositoryImpl constructors so EntityManager
is always required and never initialized to null, removing the single-argument
constructor or otherwise ensuring every instance receives the dependency;
preserve findMyRoommateHistory(...) so its entityManager.createNativeQuery(...)
call is safe.

In
`@src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java`:
- Around line 32-45: Update DeleteAccountUseCase.execute to handle the
DeleteAccountRequest request instead of leaving the withdrawal reason unused:
persist the reason through the appropriate existing logging or withdrawal-reason
storage mechanism before anonymizing and deleting the user; if the reason is
intentionally ignored, document that decision in the method.
- Around line 33-35: Update the active-room ownership check used by
DeleteAccountUseCase so ACTIVE_ROOM_OWNER applies only to operating rooms,
excluding COMPLETED rooms. In CreateRoomUseCase.execute, lock the same user’s
active-room record while validating room creation, using the corresponding
repository locking query, so concurrent room creation and account deletion
cannot leave an anonymized user as host.

In
`@src/test/java/com/project/dorumdorum/domain/support/ui/SupportInquiryControllerTest.java`:
- Around line 29-65: SupportInquiryControllerTest에 load 엔드포인트 위임 검증이 누락되어 있습니다.
기존 create_ReturnsCreatedInquiry 및 loadAll_ReturnsMyInquiries 패턴을 따라
loadDetailUseCase.execute("user-1", "1")가 응답을 반환하도록 설정하고,
controller.load("user-1", "1") 호출 후 해당 위임과 응답 본문을 검증하는 테스트를 추가하세요.

In
`@src/test/java/com/project/dorumdorum/domain/user/unit/ui/DeleteAccountControllerTest.java`:
- Around line 40-51: 보강된 테스트 커버리지를 위해 DeleteAccountControllerTest의
delete_DeletesAccountAndExpiresAuth 주변에 두 시나리오를 추가하세요. null request를 전달했을 때
deleteAccountUseCase.execute가 new DeleteAccountRequest(null)로 호출되는지 검증하고, 해당
유스케이스가 RestApiException을 던질 때 예외를 확인하면서 logoutUseCase와 authCookieWriter가 호출되지
않았는지 검증하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 583bb07c-6413-48f4-b40e-05487e2fc420

📥 Commits

Reviewing files that changed from the base of the PR and between c26f996 and 12a724c.

📒 Files selected for processing (52)
  • src/main/java/com/project/dorumdorum/domain/notice/application/usecase/LoadNoticeDetailUseCase.java
  • src/main/java/com/project/dorumdorum/domain/notice/ui/LoadNoticeDetailController.java
  • src/main/java/com/project/dorumdorum/domain/notice/ui/spec/LoadNoticeDetailApiSpec.java
  • src/main/java/com/project/dorumdorum/domain/notification/application/dto/request/NotificationSettingRequest.java
  • src/main/java/com/project/dorumdorum/domain/notification/application/dto/response/NotificationSettingResponse.java
  • src/main/java/com/project/dorumdorum/domain/notification/application/usecase/LoadNotificationSettingUseCase.java
  • src/main/java/com/project/dorumdorum/domain/notification/application/usecase/UpdateNotificationSettingUseCase.java
  • src/main/java/com/project/dorumdorum/domain/notification/domain/entity/NotificationSetting.java
  • src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java
  • src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java
  • src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationSettingRepository.java
  • src/main/java/com/project/dorumdorum/domain/notification/ui/NotificationSettingController.java
  • src/main/java/com/project/dorumdorum/domain/notification/ui/spec/NotificationSettingApiSpec.java
  • src/main/java/com/project/dorumdorum/domain/room/application/dto/request/ChecklistFilterRequest.java
  • src/main/java/com/project/dorumdorum/domain/room/application/dto/response/RoomDetailResponse.java
  • src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomLikeRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java
  • src/main/java/com/project/dorumdorum/domain/room/ui/LoadRoomDetailController.java
  • src/main/java/com/project/dorumdorum/domain/room/ui/spec/LoadRoomDetailApiSpec.java
  • src/main/java/com/project/dorumdorum/domain/roommate/application/dto/response/RoommateHistoryResponse.java
  • src/main/java/com/project/dorumdorum/domain/roommate/application/usecase/LoadMyRoommateHistoryUseCase.java
  • src/main/java/com/project/dorumdorum/domain/roommate/domain/repository/RoommateQueryRepository.java
  • src/main/java/com/project/dorumdorum/domain/roommate/domain/repository/RoommateRepository.java
  • src/main/java/com/project/dorumdorum/domain/roommate/domain/service/RoommateService.java
  • src/main/java/com/project/dorumdorum/domain/roommate/infra/repository/RoommateRepositoryImpl.java
  • src/main/java/com/project/dorumdorum/domain/roommate/ui/LoadMyRoommateHistoryController.java
  • src/main/java/com/project/dorumdorum/domain/roommate/ui/spec/LoadMyRoommateHistoryApiSpec.java
  • src/main/java/com/project/dorumdorum/domain/support/application/dto/request/CreateSupportInquiryRequest.java
  • src/main/java/com/project/dorumdorum/domain/support/application/dto/response/SupportInquiryResponse.java
  • src/main/java/com/project/dorumdorum/domain/support/application/usecase/CreateSupportInquiryUseCase.java
  • src/main/java/com/project/dorumdorum/domain/support/application/usecase/LoadMySupportInquiriesUseCase.java
  • src/main/java/com/project/dorumdorum/domain/support/application/usecase/LoadSupportInquiryDetailUseCase.java
  • src/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiry.java
  • src/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiryCategory.java
  • src/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiryStatus.java
  • src/main/java/com/project/dorumdorum/domain/support/domain/repository/SupportInquiryRepository.java
  • src/main/java/com/project/dorumdorum/domain/support/ui/SupportInquiryController.java
  • src/main/java/com/project/dorumdorum/domain/support/ui/spec/SupportInquiryApiSpec.java
  • src/main/java/com/project/dorumdorum/domain/user/application/dto/request/DeleteAccountRequest.java
  • src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java
  • src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java
  • src/main/java/com/project/dorumdorum/domain/user/ui/DeleteAccountController.java
  • src/main/java/com/project/dorumdorum/domain/user/ui/spec/DeleteAccountApiSpec.java
  • src/main/java/com/project/dorumdorum/global/exception/code/status/UserErrorStatus.java
  • src/main/resources/schema.sql
  • src/test/java/com/project/dorumdorum/domain/notification/ui/NotificationSettingControllerTest.java
  • src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java
  • src/test/java/com/project/dorumdorum/domain/support/ui/SupportInquiryControllerTest.java
  • src/test/java/com/project/dorumdorum/domain/user/unit/ui/DeleteAccountControllerTest.java

Comment on lines +17 to +24
@Transactional
public NotificationSettingResponse execute(String userNo, NotificationSettingRequest request) {
NotificationSetting setting = notificationSettingRepository.findByUserNo(userNo)
.orElseGet(() -> NotificationSetting.defaultFor(userNo));
setting.update(request);
NotificationSetting saved = notificationSettingRepository.save(setting);
return NotificationSettingResponse.from(saved);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

최초 설정 저장 시 경합 조건(TOCTOU)이 발생할 수 있습니다.

findByUserNo 조회와 save 사이에 동기화 장치가 없습니다. user_nonotification_settings 테이블에서 UNIQUE 제약을 가집니다.

사용자가 설정을 아직 저장하지 않은 상태에서 두 개의 PUT 요청이 거의 동시에 들어오면, 두 요청 모두 findByUserNo에서 빈 결과를 받습니다. 그 결과 두 요청이 각각 신규 엔티티를 생성해 저장을 시도하고, 유니크 제약 위반으로 한쪽 요청이 예외와 함께 실패합니다.

저장 시 제약 위반 예외를 잡아 재조회 후 재적용하는 방식으로 이 경합을 방지하는 것을 제안합니다.

🔧 제안하는 수정
+import org.springframework.dao.DataIntegrityViolationException;
+
 `@Transactional`
 public NotificationSettingResponse execute(String userNo, NotificationSettingRequest request) {
     NotificationSetting setting = notificationSettingRepository.findByUserNo(userNo)
             .orElseGet(() -> NotificationSetting.defaultFor(userNo));
     setting.update(request);
-    NotificationSetting saved = notificationSettingRepository.save(setting);
-    return NotificationSettingResponse.from(saved);
+    try {
+        NotificationSetting saved = notificationSettingRepository.saveAndFlush(setting);
+        return NotificationSettingResponse.from(saved);
+    } catch (DataIntegrityViolationException e) {
+        NotificationSetting existing = notificationSettingRepository.findByUserNo(userNo)
+                .orElseThrow(() -> e);
+        existing.update(request);
+        return NotificationSettingResponse.from(notificationSettingRepository.save(existing));
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/notification/application/usecase/UpdateNotificationSettingUseCase.java`
around lines 17 - 24, Update UpdateNotificationSettingUseCase.execute to handle
concurrent first-time creation: retain the existing lookup and update flow, but
catch the unique-constraint save failure, re-fetch the setting by userNo,
reapply the request, and save the existing entity before creating the response.
Ensure unrelated persistence exceptions still propagate.

Comment on lines +15 to +17
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Notification n SET n.deletedAt = CURRENT_TIMESTAMP WHERE n.recipientNo = :userNo AND n.deletedAt IS NULL")
void deleteAllByRecipientNo(@Param("userNo") String userNo);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 \
  'NotificationSettingRepository|notification_settings|deleteAllByUserNo|deleteAllByRecipientNo' \
  src/main/java src/main/resources

Repository: DorumDorum/BE

Length of output: 22575


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- schema notification_settings ---\n'
sed -n '63,90p' src/main/resources/schema.sql

printf '\n--- entity NotificationSetting ---\n'
cat -n src/main/java/com/project/dorumdorum/domain/notification/domain/entity/NotificationSetting.java

printf '\n--- repository NotificationSettingRepository ---\n'
cat -n src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationSettingRepository.java

printf '\n--- DeleteAccountUseCase imports and body ---\n'
cat -n src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java | sed -n '1,70p'

printf '\n--- search for foreign key/cascade in JPA config and mapping files ---\n'
rg -n "`@JoinColumn`|`@ManyToOne`|onDelete|foreignKey|cascade" src/main/java src/main/resources --glob '!**/*.jar' | sed -n '1,220p'

Repository: DorumDorum/BE

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- schema notification_settings ---'
sed -n '63,90p' src/main/resources/schema.sql

printf '%s\n' ''
printf '%s\n' '--- entity NotificationSetting ---'
cat -n src/main/java/com/project/dorumdorum/domain/notification/domain/entity/NotificationSetting.java

printf '%s\n' ''
printf '%s\n' '--- repository NotificationSettingRepository ---'
cat -n src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationSettingRepository.java

printf '%s\n' ''
printf '%s\n' '--- DeleteAccountUseCase imports and body ---'
cat -n src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java | sed -n '1,70p'

printf '%s\n' ''
printf '%s\n' '--- relational mapping/cascade clues ---'
rg -n -C 3 '`@JoinColumn`|`@ManyToOne`|onDelete|`@JoinColumnOptions`|cascade|`@FieldDefault`|DEFAULT|notification_settings|user_no' src/main/java src/main/resources --glob '!**/*.jar' | sed -n '1,240p'

printf '%s\n' ''
printf '%s\n' '--- deterministic check: delete handlers for notification user owner column ---'
python3 - <<'PY'
from pathlib import Path
import re
files = {
    'schema': Path('src/main/resources/schema.sql').read_text(),
    'entity': Path('src/main/java/com/project/dorumdorum/domain/notification/domain/entity/NotificationSetting.java').read_text(),
    'repo': Path('src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationSettingRepository.java').read_text(),
    'delete': Path('src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java').read_text(),
}
checks = {
    'notices_table_is_user_owned': bool(re.search(r'CREATE TABLE IF NOT EXISTS notification_settings[\s\S]*?user_no', files['schema'])),
    'no_db_fk_with_on_delete_cascade_in_schema': not re.search(r'FOREIGN KEY[\s\S]*?notification_settings|ON DELETE CASCADE', files['schema']),
    'setting_has_deletion_field_like_deleted_at': bool(re.search(r'(`@Column`|deleted_at|deletedAt)', files['entity'], re.I)),
    'repository_has_soft_delete_or_delete_all_by_userNo': bool(re.search(r'deleteAll|DELETE|DELETE_ALL|deletedAt', files['repo'], re.I)),
    'delete_account_calls_setting_delete': bool(re.search(r'NotificationSettingRepository|notificationSettingRepository|notification_settings', files['delete'], re.I)),
    'jpa_reference_to_notification_setting_entity': bool(re.search(r'Table\(name = "notification_settings"\)|class NotificationSetting', files['entity'])),
}
for name, value in checks.items():
    print(f'{name}: {value}')
PY

Repository: DorumDorum/BE

Length of output: 26625


notification_settings 탈퇴 처리 계약을 명시해 주세요.

notification_settingsuser_no를 가진 사용자 소유 설정처럼 보이지만, 현재 DeleteAccountUseCase는 이 테이블의 soft delete 또는 cascade 처리를 하지 않습니다. NotificationSettingRepository에 삭제/soft delete 메서드를 추가하고 DeleteAccountUseCase에서 호출하거나, 해당 사용자 설정을 보존하도록 정책과 테스트를 명시해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java`
around lines 15 - 17, DeleteAccountUseCase의 회원 탈퇴 흐름에서 notification_settings의 처리
정책을 명시하세요. NotificationSettingRepository에 사용자별 삭제 또는 soft delete 메서드를 추가해
DeleteAccountUseCase에서 호출하고, 해당 정책을 검증하는 테스트를 추가하거나, 설정을 보존하는 정책이라면 그 계약과 테스트를
명확히 반영하세요.

Comment on lines +36 to +53
User host = userService.findById(room.getHostUserNo());
boolean liked = roomLikeRepository.existsByUserNoAndRoom(userNo, room);
boolean isMyRoom = room.isHost(userNo) || roommateService.isUserRoommate(userNo, roomNo);
RoomDetailResponse.AppliedStatus appliedStatus = resolveAppliedStatus(userNo, room, isMyRoom);

return new RoomDetailResponse(
room.getRoomNo(),
room.getRoomType(),
room.getCapacity(),
room.getCurrentMateCount(),
room.getRemaining(),
room.getTitle(),
room.getNotes(),
room.getHostUserNo(),
host.getName(),
host.getNickname(),
host.getMajor(),
host.getStudentNo().substring(2, 4),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 관련 도메인 구조를 먼저 확인합니다.
fd -a -t f '^(User|DeleteAccountUseCase|LoadRoomDetailUseCase)\.java$' src/main/java |
while IFS= read -r file; do
  ast-grep outline "$file" --items all
done

# 탈퇴 익명화 필드와 상세 조회의 호스트 필드 사용을 추적합니다.
rg -n -C 6 --type java \
  'findById\s*\(|studentNo|nickname|major|anonym|soft.?delete|isDeleted|delete' \
  src/main/java/com/project/dorumdorum/domain/user \
  src/main/java/com/project/dorumdorum/domain/room

# 관련 테스트가 탈퇴한 방장의 상세 조회를 검증하는지 확인합니다.
fd -a -t f '.*(RoomDetail|DeleteAccount).*Test.*\.java$' src/test

Repository: DorumDorum/BE

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== User entity soft-delete and anonymization =="
sed -n '1,220p' src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java

echo "== UserRepository behavior around deleted users =="
fd -a -t f '.*User.*Repository.*\.java$' src/main/java src/test | sed -n '1,40p'
for file in $(fd -a -t f '.*User.*Repository.*\.java$' src/main/java); do
  echo "--- $file"
  cat -n "$file"
done

echo "== UserService =="
cat -n src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java

echo "== LoadRoomDetailUseCase full =="
cat -n src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java

echo "== RoomDetailResponse =="
fd -a -t f 'RoomDetailResponse\.java$' src/main/java | xargs -r -I{} sh -c 'echo "--- {}"; cat -n "{}"'

echo "== Test files mentioning deleted/withdrawn soft-deleted host/no_deleted_at or room detail =="
rg -n --type java \
  'deletedAt|deleted_at|anonymize|withdrawn|soft.?delete|hostUserNo|LOAD_ROOM_DETAIL|LoadRoomDetail|RoomDetail' \
  src/test || true

Repository: DorumDorum/BE

Length of output: 18268


탈퇴한 방장이 여전히 참고되는 상세 조회의 응답 규칙을 맞춰주세요.

DeleteAccountUseCase가 비활성 방장인 경우 익명화 후 soft delete를 유지하는 구조입니다. LoadRoomDetailUseCase.execute()userService.findById()를 soft-deleted 사용자도 조회하는 대로 사용하고 host.getStudentNo().substring(...)도 그대로 호출하면 이탈한 방장도 있는 상세 조회에서 404/500이 발생할 수 있습니다. UserEntity.anonymizeForWithdrawal()name, nickname, major, studentNo 처리를 응답 매핑에서 모두 사용하거나, 사용해야 하는 경우 탈퇴 사용자용 fallback 값을 사용하거나, 추후 방장 탈퇴 정책과 같은 동작이 되도록 조정해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java`
around lines 36 - 53, Update LoadRoomDetailUseCase.execute() so room details
remain available when the host is soft-deleted and anonymized. Map the
anonymized host fields from UserEntity.anonymizeForWithdrawal() consistently,
and replace the direct host.getStudentNo().substring(...) usage with a
withdrawal-safe fallback or guarded transformation. Preserve normal host details
for active users and avoid 404/500 responses.

Comment on lines +62 to +68
private RoomDetailResponse.AppliedStatus resolveAppliedStatus(String userNo, Room room, boolean isMyRoom) {
if (isMyRoom) {
return RoomDetailResponse.AppliedStatus.APPROVED;
}
return roomRequestRepository.findByUserNoAndRoomAndDirection(userNo, room, Direction.USER_TO_ROOM).isPresent()
? RoomDetailResponse.AppliedStatus.WAITING
: RoomDetailResponse.AppliedStatus.NONE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# RoomRequest와 저장소 계약의 구조를 확인합니다.
fd -a -t f '^(RoomRequest|RoomRequestRepository)\.java$' src/main/java |
while IFS= read -r file; do
  ast-grep outline "$file" --items all
done

# 신청 상태, 삭제, 그리고 상세 조회에 사용되는 저장소 메서드를 추적합니다.
rg -n -C 6 --type java \
  'findByUserNoAndRoomAndDirection|WAITING|APPROVED|REJECTED|CANCEL|status|delete' \
  src/main/java/com/project/dorumdorum/domain/room

Repository: DorumDorum/BE

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
fd -a -t f '^RoomRequest|^RoomDetailResponse|BaseEntity|Roommate|RoomService|DeleteRoomUseCase|ConfirmRoomAssignmentUseCase|DecideApplicationRequestUseCase|ApplyRoomUseCase' src/main/java

echo
echo "== RoomRequest =="
cat -n src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomRequest.java

echo
echo "== BaseEntity =="
fd -a -t f 'BaseEntity.java' src/main/java | while IFS= read -r f; do echo "--- $f"; cat -n "$f"; done

echo
echo "== RoomRequestRepository =="
cat -n src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java | sed -n '1,220p'
fd -a -t f 'RoomRequestQueryRepository.*\.java$' src/main/java | while IFS= read -r f; do echo "--- $f"; cat -n "$f" | sed -n '1,260p'; done

echo
echo "== detail response =="
fd -a -t f 'RoomDetailResponse.java' src/main/java | while IFS= read -r f; do echo "--- $f"; cat -n "$f"; done

echo
echo "== app state lifecycle =="
for f in \
  src/main/java/com/project/dorumdorum/domain/room/application/usecase/ApplyRoomUseCase.java \
  src/main/java/com/project/dorumdorum/domain/room/application/usecase/DecideApplicationRequestUseCase.java \
  src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomRequestService.java \
  src/main/java/com/project/dorumdorum/domain/room/ui/ApplyRoomController.java \
  src/main/java/com/project/dorumdorum/domain/room/ui/spec/ApplyRoomApiSpec.java \
  src/main/java/com/project/dorumdorum/domain/room/ui/DecideApplicationRequestController.java \
  src/main/java/com/project/dorumdorum/domain/room/ui/spec/DecideApplicationRequestApiSpec.java \
  src/main/java/com/project/dorumdorum/domain/room/ui/LoadRoomDetailController.java \
  src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoommentRepositoryImpl.java \
  src/main/java/com/project/dorumdorum/domain/roommate/domain/entity/Roommate.java
do
  if [ -f "$f" ]; then
    echo "--- $f"
    cat -n "$f" | sed -n '1,260p'
  fi
done

echo
echo "== focused lifecycle searches =="
rg -n -C 4 --type java \
  'APPROVED|WAITING|REJECTED|CANCEL|cancelJoinRequest|save|delete|roomRequestRepository\.save|confirmed|assignment|Roommate' \
  src/main/java/com/project/dorumdorum/domain/room src/main/java/com/project/dorumdorum/domain/roommate

Repository: DorumDorum/BE

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== LoadRoomDetailUseCase =="
cat -n src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java | sed -n '1,120p'

echo
echo "== RoommateRepository =="
cat -n src/main/java/com/project/dorumdorum/domain/roommate/domain/repository/RoommateRepository.java | sed -n '1,160p'
fd -a -t f 'RoommateRepositoryImpl.java' src/main/java | while IFS= read -r f; do echo "--- $f"; cat -n "$f" | sed -n '1,180p'; done

echo
echo "== RoommateService lifecycle =="
cat -n src/main/java/com/project/dorumdorum/domain/roommate/domain/service/RoommateService.java | sed -n '1,240p'

echo
echo "== load detail request specs =="
fd -a -t f 'LoadRoomDetailApiSpec.java' src/main/java | while IFS= read -r f; do echo "--- $f"; cat -n "$f"; done

echo
echo "== all Request status fields and repository query states =="
rg -n -C 3 --type java \
  'enum .*status|status.*enum|requestStatus|Confirmation|RequestStatus|RoomRequestStatus|findByUserNoAndRoomAndDirection|deleteAllByRoom|deleteAllByUserNo|deleteAllBy' \
  src/main/java/com/project/dorumdorum/domain/room || true
rg -n -C 3 --type java \
  'enum ConfirmStatus|RoomStatus|DELETE|remove|kick|ConfirmStatus' \
  src/main/java/com/project/dorumdorum/domain/roommate src/main/java/com/project/dorumdorum/domain/room | sed -n '1,260p'

Repository: DorumDorum/BE

Length of output: 50369


신청 상태를 isDeleted() 단위로 처리해 주세요.

현재 RoomRequest은 승인/거절 시 soft delete만 하고 상태 필드가 없습니다. resolveAppliedStatus()가 soft delete된 요청까지 WAITING으로 반환하므로, 이 조회는 deletedAt IS NULL인 대기 요청만 반환하도록 제한할 필요가 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.java`
around lines 62 - 68, Update resolveAppliedStatus in LoadRoomDetailUseCase to
consider only non-deleted RoomRequest records when determining WAITING status.
Apply the isDeleted/deletedAt condition through the repository query used by
findByUserNoAndRoomAndDirection, while preserving APPROVED for isMyRoom and NONE
when no active request exists.

Comment on lines +22 to +24
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE RoomRequest r SET r.deletedAt = CURRENT_TIMESTAMP WHERE r.userNo = :userNo AND r.deletedAt IS NULL")
void deleteAllByUserNo(@Param("userNo") String userNo);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 10 \
  'deleteAllByUserNo|deleteAllByRecipientNo|userService\.findById|anonymizeForWithdrawal|user\.delete|userRepository\.save' \
  src/main/java

Repository: DorumDorum/BE

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- DeleteAccountUseCase.java ---\n'
cat -n src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java

printf '\n--- Relevant repository methods ---\n'
cat -n src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java | sed -n '1,80p'
rg -n -C 8 '`@Transactional`|findById\(String userNo\)|save\(' src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java
rg -n -C 6 '`@Modifying`\(clearAutomatically = true, flushAutomatically = true\)|`@Modifying`' src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java \
  src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java

Repository: DorumDorum/BE

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- DeleteAccountUseCase.java ---'
cat -n src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java

printf '%s\n' ''
printf '%s\n' '--- Relevant repository methods ---'
cat -n src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java | sed -n '1,90p'

printf '%s\n' ''
printf '%s\n' '--- Repository `@Modifying` annotations ---'
rg -n -C 6 '`@Modifying`\(clearAutomatically = true, flushAutomatically = true\)|`@Modifying`' \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java \
  src/main(java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java \
  src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java || true

Repository: DorumDorum/BE

Length of output: 5727


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Repository `@Modifying` annotations ---'
rg -n -C 6 '`@Modifying`\(clearAutomatically = true, flushAutomatically = true\)|`@Modifying`' \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/repository/NotificationRepository.java \
  src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java || true

printf '%s\n' ''
printf '%s\n' '--- Relevant User methods ---'
rg -n -C 8 'anonymizeForWithdrawal|delete|role|setRole|soft-delete|deletedAt' \
  src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java

Repository: DorumDorum/BE

Length of output: 5112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- NotificationRepository.java ---'
cat -n src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java

printf '%s\n' ''
printf '%s\n' '--- All deleteAllByUserNo/deleteAllByRecipientNo methods ---'
rg -n -C 8 '`@Modifying`\(clearAutomatically = true, flushAutomatically = true\)|`@Modifying`|\bvoid deleteAllByUserNo\b|\bvoid deleteAllByRecipientNo\b' \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java \
  src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java || true

printf '%s\n' ''
printf '%s\n' '--- User.java relevant lines ---'
cat -n src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java | sed -n '1,110p'

printf '%s\n' ''
printf '%s\n' '--- DeleteAccountUseCase line details ---'
cat -n src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java | sed -n '32,45p'

Repository: DorumDorum/BE

Length of output: 10945


🌐 Web query:

Spring Data JPA clearAutomatically true clears persistence context managed entities

💡 Result:

In Spring Data JPA, setting clearAutomatically = true within the @Modifying annotation instructs the framework to clear the underlying persistence context (the EntityManager) immediately after the modifying query (e.g., INSERT, UPDATE, or DELETE) has been executed [1][2][3]. When this attribute is enabled, all entities currently managed by the EntityManager are detached [4]. This effectively ensures that any subsequent operations within the same transaction will reload fresh data from the database, preventing the application from working with potentially outdated entity states that were not updated by the database-level operation [1][5]. However, there is a significant side effect: because EntityManager.clear() is called, any changes made to managed entities that have not yet been flushed to the database will be lost [1][4]. To mitigate this risk, it is common practice to use flushAutomatically = true alongside clearAutomatically = true [5][6][7]. This combined configuration ensures that all pending changes are written to the database before the execution of the modifying query and that the persistence context is subsequently cleared to avoid stale data [8][5][7]. The clearAutomatically attribute defaults to false [2][3][9]. While it was historically set to true by default in some very early versions of Spring Data JPA, this was changed to false to prevent unintended data loss [10][11].

Citations:


User 변경을 clearAutomatically 이후에 항상 리디스크로 반영해 주세요.

DeleteAccountUseCase.execute()에서 첫 RoomLikeRepository.clearAutomatically 이후 User가 detached 상태가 됩니다. 그런 뒤 RoommateRepository.deleteAllByUserNo(), NotificationRepository.clearAutomatically, NotificationDeviceRepository.clearAutomatically가 실행되어 해당 User 인스턴스에 적용된 user.anonymizeForWithdrawal()/user.delete() 값이 persistence context와 동기화되지 않을 수 있습니다. bulk cleanup을 삭제 전으로 옮기거나, 변경 처리 전에 User를 다시 조회/지속시킬 수 있도록 호출 흐름을 수정해 주세요.

📍 Affects 3 files
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java#L22-L24 (this comment)
  • src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.java#L20-L22
  • src/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.java#L15-L17
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java`
around lines 22 - 24, Update the DeleteAccountUseCase.execute() call flow so
bulk cleanup runs before the first clearAutomatically operation, or
reload/re-persist User before applying anonymizeForWithdrawal() and delete().
Apply the ordering or persistence fix across RoomRequestRepository.java lines
22-24, NotificationDeviceRepository.java lines 20-22, and
NotificationRepository.java lines 15-17; preserve the existing cleanup behavior
while ensuring User changes are synchronized afterward.

Comment on lines +74 to +120
@Override
public List<RoommateHistoryResponse> findMyRoommateHistory(String userNo) {
Query nativeQuery = entityManager.createNativeQuery("""
SELECT
rm.roommate_no,
r.room_no,
r.title,
r.room_type,
r.capacity,
rm.user_no,
u.name,
u.nickname,
u.student_no,
u.major,
SUBSTRING(u.student_no FROM 3 FOR 2),
GREATEST(COALESCE(my_rm.created_at, r.created_at), COALESCE(r.created_at, my_rm.created_at)),
CASE
WHEN my_rm.deleted_at IS NULL AND rm.deleted_at IS NULL AND r.deleted_at IS NULL THEN NULL
ELSE COALESCE(rm.deleted_at, my_rm.deleted_at, r.deleted_at)
END,
CASE
WHEN my_rm.deleted_at IS NULL AND rm.deleted_at IS NULL AND r.deleted_at IS NULL THEN 'CURRENT'
ELSE 'PAST'
END
FROM roommate my_rm
JOIN roommate rm
ON rm.room_no = my_rm.room_no
AND rm.user_no <> :userNo
JOIN room r
ON r.room_no = my_rm.room_no
LEFT JOIN users u
ON u.user_no = rm.user_no
WHERE my_rm.user_no = :userNo
ORDER BY
CASE
WHEN my_rm.deleted_at IS NULL AND rm.deleted_at IS NULL AND r.deleted_at IS NULL THEN 0
ELSE 1
END,
COALESCE(rm.deleted_at, my_rm.deleted_at, r.deleted_at, rm.created_at) DESC,
rm.roommate_no DESC
""");
nativeQuery.setParameter("userNo", userNo);

@SuppressWarnings("unchecked")
List<Object[]> rows = nativeQuery.getResultList();
return rows.stream().map(this::toRoommateHistoryResponse).toList();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

같은 방 재입주 시 중복 이력 행이 발생할 수 있어요.

my_rmWHERE my_rm.user_no = :userNo 조건만 가지고 있고, deleted_at 필터가 없습니다. RoommateService.create(같은 파일 Line 24-33)는 재입주를 막지 않으므로, 사용자가 방을 나갔다가 다시 들어오면 같은 room_no에 대해 my_rm row가 여러 개 생길 수 있습니다.

이 상태에서 Line 99-101의 JOIN roommate rm ON rm.room_no = my_rm.room_no가 각 my_rm row마다 실행되므로, 동일한 rm 파트너에 대해 여러 행이 반환됩니다. historyNo(rm.roommate_no)는 같지만 startedAtmy_rm.created_at에 따라 달라져, 클라이언트에 같은 룸메이트가 중복으로 표시될 수 있습니다.

my_rm을 방별로 하나의 대표 row(예: 가장 최근 row 또는 관련 시점을 집계한 값)로 제한하는 방향을 검토해 주세요.

-- 예시: my_rm을 room_no별 최신 1건으로 제한
JOIN (
    SELECT DISTINCT ON (room_no) *
    FROM roommate
    WHERE user_no = :userNo
    ORDER BY room_no, created_at DESC
) my_rm ON true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/roommate/infra/repository/RoommateRepositoryImpl.java`
around lines 74 - 120, Update findMyRoommateHistory so my_rm is limited to one
representative roommate row per room_no before joining with rm, selecting the
latest record for the user in each room while preserving the existing history
status, ordering, and response mapping behavior.

Comment on lines +32 to +45
public void execute(String userNo, DeleteAccountRequest request) {
if (roomRepository.existsByHostUserNoAndDeletedAtIsNull(userNo)) {
throw new RestApiException(ACTIVE_ROOM_OWNER);
}

User user = userService.findById(userNo);
roomLikeRepository.deleteAllByUserNo(userNo);
roomRequestRepository.deleteAllByUserNo(userNo);
roommateRepository.deleteAllByUserNo(userNo);
notificationRepository.deleteAllByRecipientNo(userNo);
notificationDeviceRepository.deleteAllByUserNo(userNo);
user.anonymizeForWithdrawal();
user.delete();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

탈퇴 사유(request)가 어디에도 저장되지 않습니다.

execute 메서드는 DeleteAccountRequest request를 파라미터로 받지만, 메서드 본문에서 한 번도 사용하지 않습니다. 프론트엔드가 탈퇴 사유를 전송하도록 화면을 구성했다면, 이 값을 로그나 별도 테이블에 저장하는 로직이 필요할 것으로 보입니다. 의도적으로 무시하는 것이라면 주석으로 그 이유를 남겨 주시기 바랍니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java`
around lines 32 - 45, Update DeleteAccountUseCase.execute to handle the
DeleteAccountRequest request instead of leaving the withdrawal reason unused:
persist the reason through the appropriate existing logging or withdrawal-reason
storage mechanism before anonymizing and deleting the user; if the reason is
intentionally ignored, document that decision in the method.

Comment on lines +33 to +35
if (roomRepository.existsByHostUserNoAndDeletedAtIsNull(userNo)) {
throw new RestApiException(ACTIVE_ROOM_OWNER);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Room 엔티티의 상태 필드와 방 생성 시 사용자 검증 로직 확인
fd Room.java --type f --exec cat -n {} \;
rg -n -C 5 'class Room\b' --type=java
rg -n -A 10 'CreateRoom|RoomStatus' --type=java

Repository: DorumDorum/BE

Length of output: 7036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd 'DeleteAccountUseCase.java|Create.*Room.*|Room.*Repository.java|RoomStatus.*|User.*Repository.java|User.*Application.*UseCase' . --type f -X echo {} \; -H

echo "== DeleteAccountUseCase =="
fd 'DeleteAccountUseCase.java' . --type f -exec cat -n {} \;

echo "== RoomStatus enum =="
fd 'RoomStatus.java' . --type f -exec cat -n {} \;

echo "== room repository methods =="
fd 'Room.*Repository.*\.(java)' . --type f -exec echo '--- {} ---' \; -exec cat -n {} \;

echo "== user delete/account use cases =="
fd '.*Account.*UseCase.*\.java|.*User.*UseCase.*\.java' com 2>/dev/null || fd '.*Account.*UseCase.*\.java' . --type f -X echo {} \;

echo "== create room / room status changes =="
rg -n -C 4 'CreateRoom|createRoom|roomStatus|COMPLETED|CONFIRM_PENDING|ACTIVE_ROOM_OWNER|existsByHostUserNoAndDeletedAtIsNull|DeleteAccount|delete.*Account|soft.*delete|deletedAt|deleted_at' --type=java .

Repository: DorumDorum/BE

Length of output: 3247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files to inspect =="
for f in \
  src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java \
  src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomStatus.java \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRepository.java \
  src/main/java/com/project/dorumdorum/domain/room/application/usecase/CreateRoomUseCase.java \
  src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java \
  src/main/java/com/project/dorumdorum/global/common/BaseEntity.java
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  else
    echo "== missing $f =="
  fi
done

echo "== relevant search =="
rg -n -C 4 'DeleteAccountUseCase|CreateRoomUseCase|RoomStatus|existsByHostUserNoAndDeletedAtIsNull|ACTIVE_ROOM_OWNER|deletedAt|deleted_at|updateStatus|save\(\)|findById|findByUserNo' --type=java src/main/java || true

Repository: DorumDorum/BE

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RoomService and Room-related methods around create/status =="
for f in \
  src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomQueryRepository.java \
  src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java \
  src/main/java/com/project/dorumdorum/domain/room/infrastructure/persistence/RoomQueryRepositoryImpl.java
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f" | sed -n '1,220p'
  else
    echo "== missing $f =="
  fi
done

echo "== targeted status/save queries =="
rg -n 'RoomStatus\.\(CONFIRM_PENDING|COMPLETED|IN_PROGRESS\)|updateStatus|roomStatus|created_at|insert|save|deleteByRoomNo|existsByHostUserNoAndDeletedAtIsNull|findAllActive|findByRoom' src/main/java/com/project/dorumdorum/domain/room/com project/dorumdorum/domain/user src/main/java/com/project/dorumdorum/domain/checklist --type=java

Repository: DorumDorum/BE

Length of output: 9824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files containing RoomQueryRepositoryImpl =="
fd 'RoomQueryRepositoryImpl' . --type f -X sh -c 'cat -n "$1"|sed -n "1,220p"; echo "--- $1 ---"' sh {}

echo "== files containing DecidedApplicationRequestUsecase =="
cat -n src/main/java/com/project/dorumdorum/domain/room/application/usecase/DecideApplicationRequestUseCase.java \
  | sed -n '1,140p'

echo "== user validation services =="
for f in \
  src/main/java/com/project/dorumdorum/domain/user/domain/service/UserService.java \
  src/main/java/com/project/dorumdorum/domain/user/application/validator/UserValidator.java
do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,180p'
done

echo "== targeted status/save/lock queries after split path =="
rg -n 'RoomStatus\.\(CONFIRM_PENDING|COMPLETED|IN_PROGRESS\)|updateStatus|roomStatus|created_at|insert|save|deleteByRoomNo|existsByHostUserNoAndDeletedAtIsNull|findAllActive|findByRoom|findByCursor|countByFilter|findMyRoom' src/main/java/com/project/dorumdorum/domain/room src/main/java/com/project/dorumdorum/domain/roommate src/main/java/com/project/dorumdorum/domain/checklist src/main/java/com/project/dorumdorum/domain/user --type=java -C 2

echo "== RoomQueryRepositoryImpl path candidates =="
git ls-files '*RoomQueryRepository*'

Repository: DorumDorum/BE

Length of output: 7371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== status change usages =="
rg -n -C 3 'RoomStatus\.|updateStatus|CONFIRM_PENDING|IN_PROGRESS|COMPLETED' src/main/java/com/project/dorumdorum/domain --type=java

echo "== room query implementation files =="
git ls-files 'src/main/java/**/RoomQueryRepository*.java'
git ls-files 'src/main/java/**/*RoomQuery*.java'
git ls-files 'src/main/java/**/*QueryRepository*.java'

echo "== repository implementation snippets that join rooms =="
rg -n -C 2 'join r\.room|r\.roomStatus|r\.roomStatus|idx_room_status|findMyRoom|findLikedRooms|findAppliedRooms|countByFilter|findByCursor' src/main/java/com/project/dorumdorum --type=java

echo "== targeted files =="
for f in $(git ls-files 'src/main/java/**/Room*.java' | grep -E 'Repository(Impl|)?\.java|Service\.java|UseCase\.java$'); do
  if rg -q 'RoomStatus|updateStatus|save\(room|roomRepository\.save|existsByHostUserNoAndDeletedAtIsNull|validateExistsById|userNo' "$f"; then
    echo "--- $f ---"
    cat -n "$f" | sed -n '1,220p'
  fi
done

echo "== user validator files =="
git ls-files 'src/main/java/**/User*Validator*.java' 'src/main/java/**/*Validator*.java' | sed -n '1,120p'

Repository: DorumDorum/BE

Length of output: 50369


운영 중인 방의 차단을 COMPLETED 방까지 확장하지 않도록 상태를 확인해 주세요.

RoomStatusCOMPLETED가 있고, 모든 룸메이트 승인 시 room.updateStatus(RoomStatus.COMPLETED)가 설정되므로 완료된 방도 탈퇴를 막는다면 과거 방 개설 사용자도 영구적으로 탈퇴할 수 없습니다. ACTIVE_ROOM_OWNERroomStatus.eq(RoomStatus.OPERATING_STATUS) 또는 !roomStatus.eq(RoomStatus.COMPLETED)와 같은 활성 상태 조건에서만 사용하도록 조정해 주세요.

동시에 방 생성 시 방장 사용자의 탈퇴 상태를 검증하지 않아, existsByHostUserNoAndDeletedAtIsNull 통과 후 다른 요청에서 방이 생성되어 익명화된 사용자가 방장으로 남을 수 있습니다. CreateRoomUseCase.execute(...)에서 같은 userNo의 활성 방을 락하여 동시 생성 및 탈퇴 시 orphan 방장 상태를 막아 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.java`
around lines 33 - 35, Update the active-room ownership check used by
DeleteAccountUseCase so ACTIVE_ROOM_OWNER applies only to operating rooms,
excluding COMPLETED rooms. In CreateRoomUseCase.execute, lock the same user’s
active-room record while validating room creation, using the corresponding
repository locking query, so concurrent room creation and account deletion
cannot leave an anonymized user as host.

Comment on lines +29 to +65
@Mock
private CreateSupportInquiryUseCase createUseCase;

@Mock
private LoadMySupportInquiriesUseCase loadAllUseCase;

@Mock
private LoadSupportInquiryDetailUseCase loadDetailUseCase;

@InjectMocks
private SupportInquiryController controller;

@Test
@DisplayName("create는 문의 접수 결과를 반환한다")
void create_ReturnsCreatedInquiry() {
CreateSupportInquiryRequest request = new CreateSupportInquiryRequest(SupportInquiryCategory.APP_USAGE, "문의합니다");
SupportInquiryResponse response = new SupportInquiryResponse("1", SupportInquiryCategory.APP_USAGE, "문의합니다", SupportInquiryStatus.RECEIVED, LocalDateTime.now());
when(createUseCase.execute("user-1", request)).thenReturn(response);

ResponseEntity<SupportInquiryResponse> result = controller.create("user-1", request);

verify(createUseCase).execute("user-1", request);
assertThat(result.getBody()).isEqualTo(response);
}

@Test
@DisplayName("loadAll은 내 문의 목록을 반환한다")
void loadAll_ReturnsMyInquiries() {
List<SupportInquiryResponse> responses = List.of();
when(loadAllUseCase.execute("user-1")).thenReturn(responses);

ResponseEntity<List<SupportInquiryResponse>> result = controller.loadAll("user-1");

verify(loadAllUseCase).execute("user-1");
assertThat(result.getBody()).isEqualTo(responses);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

load() 엔드포인트에 대한 위임 테스트가 누락되었습니다.

loadDetailUseCase 목이 35-36행에 선언되어 있지만, 어떤 테스트에서도 사용되지 않습니다. SupportInquiryController.load()loadSupportInquiryDetailUseCase.execute(userNo, inquiryNo)로 올바르게 위임하는지 검증하는 테스트가 없습니다.

create_ReturnsCreatedInquiryloadAll_ReturnsMyInquiries와 동일한 패턴으로 테스트를 추가해 주세요. PR 목표에 "관련 컨트롤러 단위 테스트 보강"이 명시되어 있으므로, 세 엔드포인트 모두에 대한 커버리지를 갖추는 것이 좋습니다.

✅ 추가 제안 테스트
`@Test`
`@DisplayName`("load는 문의 상세 정보를 반환한다")
void load_ReturnsInquiryDetail() {
    SupportInquiryResponse response = new SupportInquiryResponse("1", SupportInquiryCategory.APP_USAGE, "문의합니다", SupportInquiryStatus.RECEIVED, LocalDateTime.now());
    when(loadDetailUseCase.execute("user-1", "1")).thenReturn(response);

    ResponseEntity<SupportInquiryResponse> result = controller.load("user-1", "1");

    verify(loadDetailUseCase).execute("user-1", "1");
    assertThat(result.getBody()).isEqualTo(response);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/project/dorumdorum/domain/support/ui/SupportInquiryControllerTest.java`
around lines 29 - 65, SupportInquiryControllerTest에 load 엔드포인트 위임 검증이 누락되어 있습니다.
기존 create_ReturnsCreatedInquiry 및 loadAll_ReturnsMyInquiries 패턴을 따라
loadDetailUseCase.execute("user-1", "1")가 응답을 반환하도록 설정하고,
controller.load("user-1", "1") 호출 후 해당 위임과 응답 본문을 검증하는 테스트를 추가하세요.

Comment on lines +40 to +51
@Test
@DisplayName("delete는 탈퇴 처리 후 토큰과 쿠키를 정리하고 204를 반환한다")
void delete_DeletesAccountAndExpiresAuth() {
DeleteAccountRequest request = new DeleteAccountRequest("서비스를 자주 쓰지 않아요");

ResponseEntity<Void> response = controller.delete(servletResponse, "user-1", "access-token", request);

verify(deleteAccountUseCase).execute("user-1", request);
verify(logoutUseCase).execute("access-token");
verify(authCookieWriter).expireAuthCookies(servletResponse);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

테스트 커버리지를 보강해 주세요.

현재 테스트는 request가 채워진 정상 케이스만 검증합니다. 다음 두 가지 시나리오를 추가하면 컨트롤러의 핵심 분기를 모두 검증할 수 있습니다.

  1. request가 null일 때 deleteAccountUseCase.executenew DeleteAccountRequest(null)로 호출되는지 검증하는 테스트.
  2. deleteAccountUseCase.execute가 예외를 던질 때 logoutUseCaseauthCookieWriter가 호출되지 않는지 검증하는 테스트.
🧪 추가 테스트 제안
`@Test`
`@DisplayName`("delete는 request가 null이면 기본 DeleteAccountRequest로 위임한다")
void delete_WithNullRequest_UsesDefaultRequest() {
    controller.delete(servletResponse, "user-1", "access-token", null);

    verify(deleteAccountUseCase).execute("user-1", new DeleteAccountRequest(null));
}

`@Test`
`@DisplayName`("delete는 탈퇴 처리 실패 시 로그아웃과 쿠키 정리를 수행하지 않는다")
void delete_WhenUseCaseThrows_DoesNotLogoutOrExpireCookies() {
    DeleteAccountRequest request = new DeleteAccountRequest(null);
    doThrow(new RestApiException(ACTIVE_ROOM_OWNER))
            .when(deleteAccountUseCase).execute("user-1", request);

    assertThatThrownBy(() -> controller.delete(servletResponse, "user-1", "access-token", request))
            .isInstanceOf(RestApiException.class);

    verifyNoInteractions(logoutUseCase, authCookieWriter);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/project/dorumdorum/domain/user/unit/ui/DeleteAccountControllerTest.java`
around lines 40 - 51, 보강된 테스트 커버리지를 위해 DeleteAccountControllerTest의
delete_DeletesAccountAndExpiresAuth 주변에 두 시나리오를 추가하세요. null request를 전달했을 때
deleteAccountUseCase.execute가 new DeleteAccountRequest(null)로 호출되는지 검증하고, 해당
유스케이스가 RestApiException을 던질 때 예외를 확인하면서 logoutUseCase와 authCookieWriter가 호출되지
않았는지 검증하세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant