feat: 프론트 미연동 API 구현 - #119
Conversation
Walkthrough공지사항, 알림 설정, 방 검색·상세 조회, 룸메이트 이력, 고객 문의, 회원 탈퇴 기능을 추가했습니다. 각 기능에 필요한 도메인 모델, 저장소, 유스케이스, REST API, 스키마와 컨트롤러 테스트를 함께 구성했습니다. Changes공지사항 상세 조회
알림 설정
방 조회 기능
룸메이트 이력
고객 문의
회원 탈퇴
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (52)
src/main/java/com/project/dorumdorum/domain/notice/application/usecase/LoadNoticeDetailUseCase.javasrc/main/java/com/project/dorumdorum/domain/notice/ui/LoadNoticeDetailController.javasrc/main/java/com/project/dorumdorum/domain/notice/ui/spec/LoadNoticeDetailApiSpec.javasrc/main/java/com/project/dorumdorum/domain/notification/application/dto/request/NotificationSettingRequest.javasrc/main/java/com/project/dorumdorum/domain/notification/application/dto/response/NotificationSettingResponse.javasrc/main/java/com/project/dorumdorum/domain/notification/application/usecase/LoadNotificationSettingUseCase.javasrc/main/java/com/project/dorumdorum/domain/notification/application/usecase/UpdateNotificationSettingUseCase.javasrc/main/java/com/project/dorumdorum/domain/notification/domain/entity/NotificationSetting.javasrc/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationDeviceRepository.javasrc/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationRepository.javasrc/main/java/com/project/dorumdorum/domain/notification/domain/repository/NotificationSettingRepository.javasrc/main/java/com/project/dorumdorum/domain/notification/ui/NotificationSettingController.javasrc/main/java/com/project/dorumdorum/domain/notification/ui/spec/NotificationSettingApiSpec.javasrc/main/java/com/project/dorumdorum/domain/room/application/dto/request/ChecklistFilterRequest.javasrc/main/java/com/project/dorumdorum/domain/room/application/dto/response/RoomDetailResponse.javasrc/main/java/com/project/dorumdorum/domain/room/application/usecase/LoadRoomDetailUseCase.javasrc/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomLikeRepository.javasrc/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRepository.javasrc/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.javasrc/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.javasrc/main/java/com/project/dorumdorum/domain/room/ui/LoadRoomDetailController.javasrc/main/java/com/project/dorumdorum/domain/room/ui/spec/LoadRoomDetailApiSpec.javasrc/main/java/com/project/dorumdorum/domain/roommate/application/dto/response/RoommateHistoryResponse.javasrc/main/java/com/project/dorumdorum/domain/roommate/application/usecase/LoadMyRoommateHistoryUseCase.javasrc/main/java/com/project/dorumdorum/domain/roommate/domain/repository/RoommateQueryRepository.javasrc/main/java/com/project/dorumdorum/domain/roommate/domain/repository/RoommateRepository.javasrc/main/java/com/project/dorumdorum/domain/roommate/domain/service/RoommateService.javasrc/main/java/com/project/dorumdorum/domain/roommate/infra/repository/RoommateRepositoryImpl.javasrc/main/java/com/project/dorumdorum/domain/roommate/ui/LoadMyRoommateHistoryController.javasrc/main/java/com/project/dorumdorum/domain/roommate/ui/spec/LoadMyRoommateHistoryApiSpec.javasrc/main/java/com/project/dorumdorum/domain/support/application/dto/request/CreateSupportInquiryRequest.javasrc/main/java/com/project/dorumdorum/domain/support/application/dto/response/SupportInquiryResponse.javasrc/main/java/com/project/dorumdorum/domain/support/application/usecase/CreateSupportInquiryUseCase.javasrc/main/java/com/project/dorumdorum/domain/support/application/usecase/LoadMySupportInquiriesUseCase.javasrc/main/java/com/project/dorumdorum/domain/support/application/usecase/LoadSupportInquiryDetailUseCase.javasrc/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiry.javasrc/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiryCategory.javasrc/main/java/com/project/dorumdorum/domain/support/domain/entity/SupportInquiryStatus.javasrc/main/java/com/project/dorumdorum/domain/support/domain/repository/SupportInquiryRepository.javasrc/main/java/com/project/dorumdorum/domain/support/ui/SupportInquiryController.javasrc/main/java/com/project/dorumdorum/domain/support/ui/spec/SupportInquiryApiSpec.javasrc/main/java/com/project/dorumdorum/domain/user/application/dto/request/DeleteAccountRequest.javasrc/main/java/com/project/dorumdorum/domain/user/application/usecase/DeleteAccountUseCase.javasrc/main/java/com/project/dorumdorum/domain/user/domain/entity/User.javasrc/main/java/com/project/dorumdorum/domain/user/ui/DeleteAccountController.javasrc/main/java/com/project/dorumdorum/domain/user/ui/spec/DeleteAccountApiSpec.javasrc/main/java/com/project/dorumdorum/global/exception/code/status/UserErrorStatus.javasrc/main/resources/schema.sqlsrc/test/java/com/project/dorumdorum/domain/notification/ui/NotificationSettingControllerTest.javasrc/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.javasrc/test/java/com/project/dorumdorum/domain/support/ui/SupportInquiryControllerTest.javasrc/test/java/com/project/dorumdorum/domain/user/unit/ui/DeleteAccountControllerTest.java
| @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); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
최초 설정 저장 시 경합 조건(TOCTOU)이 발생할 수 있습니다.
findByUserNo 조회와 save 사이에 동기화 장치가 없습니다. user_no는 notification_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.
| @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); |
There was a problem hiding this comment.
🔒 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/resourcesRepository: 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}')
PYRepository: DorumDorum/BE
Length of output: 26625
notification_settings 탈퇴 처리 계약을 명시해 주세요.
notification_settings는 user_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에서 호출하고, 해당 정책을 검증하는 테스트를 추가하거나, 설정을 보존하는 정책이라면 그 계약과 테스트를
명확히 반영하세요.
| 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), |
There was a problem hiding this comment.
🩺 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/testRepository: 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 || trueRepository: 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.
| 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; |
There was a problem hiding this comment.
🎯 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/roomRepository: 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/roommateRepository: 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.
| @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); |
There was a problem hiding this comment.
🗄️ 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/javaRepository: 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.javaRepository: 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 || trueRepository: 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.javaRepository: 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:
- 1: https://docs.spring.io/spring-data/data-jpa/docs/current-SNAPSHOT/reference/html/
- 2: https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/Modifying.html
- 3: https://docs.spring.io/spring-data/jpa/reference/3.5/api/java/org/springframework/data/jpa/repository/Modifying.html
- 4: https://jakarta.ee/specifications/platform/11/apidocs/jakarta/persistence/entitymanager
- 5: https://stackoverflow.com/questions/43665090/why-do-we-have-to-use-modifying-annotation-for-queries-in-data-jpa
- 6: https://stackoverflow.com/questions/68464200/can-i-skip-writing-flushautomatically-true-in-modifying-annotation
- 7: https://stackoverflow.com/questions/32258857/spring-boot-data-jpa-modifying-update-query-refresh-persistence-context
- 8: add flushAutomatically attribute to @Modifying annotation [DATAJPA-806] spring-projects/spring-data-jpa#1167
- 9: https://docs.spring.io/spring-data/data-jpa/reference/api/java/org/springframework/data/jpa/repository/Modifying.html
- 10: Documentation of @Modifying states old behaviour for clearAutomatically [DATAJPA-305] spring-projects/spring-data-jpa#715
- 11: https://stackoverflow.com/questions/14745032/how-to-use-spring-jpa-clearautomatically-correctly
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-L22src/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.
| @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(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
같은 방 재입주 시 중복 이력 행이 발생할 수 있어요.
my_rm은 WHERE 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)는 같지만 startedAt은 my_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.
| 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(); | ||
| } |
There was a problem hiding this comment.
📐 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.
| if (roomRepository.existsByHostUserNoAndDeletedAtIsNull(userNo)) { | ||
| throw new RestApiException(ACTIVE_ROOM_OWNER); | ||
| } |
There was a problem hiding this comment.
🗄️ 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=javaRepository: 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 || trueRepository: 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=javaRepository: 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 방까지 확장하지 않도록 상태를 확인해 주세요.
RoomStatus에 COMPLETED가 있고, 모든 룸메이트 승인 시 room.updateStatus(RoomStatus.COMPLETED)가 설정되므로 완료된 방도 탈퇴를 막는다면 과거 방 개설 사용자도 영구적으로 탈퇴할 수 없습니다. ACTIVE_ROOM_OWNER는 roomStatus.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.
| @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); | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
load() 엔드포인트에 대한 위임 테스트가 누락되었습니다.
loadDetailUseCase 목이 35-36행에 선언되어 있지만, 어떤 테스트에서도 사용되지 않습니다. SupportInquiryController.load()가 loadSupportInquiryDetailUseCase.execute(userNo, inquiryNo)로 올바르게 위임하는지 검증하는 테스트가 없습니다.
create_ReturnsCreatedInquiry와 loadAll_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") 호출 후 해당 위임과 응답 본문을 검증하는 테스트를 추가하세요.
| @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); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
테스트 커버리지를 보강해 주세요.
현재 테스트는 request가 채워진 정상 케이스만 검증합니다. 다음 두 가지 시나리오를 추가하면 컨트롤러의 핵심 분기를 모두 검증할 수 있습니다.
request가 null일 때deleteAccountUseCase.execute가new DeleteAccountRequest(null)로 호출되는지 검증하는 테스트.deleteAccountUseCase.execute가 예외를 던질 때logoutUseCase와authCookieWriter가 호출되지 않는지 검증하는 테스트.
🧪 추가 테스트 제안
`@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가 호출되지
않았는지 검증하세요.
📝 Pull Request Template
📌 제목
📢 요약
🔗 연관 이슈: Resolves #이슈번호
🚀 PR 유형
✅ PR 체크리스트
📜 기타
Summary by CodeRabbit
새로운 기능
개선 사항