Skip to content

fix: 외래키로 인해 물리적 탈퇴가 되지 않는 문제 수정 - #848

Merged
whqtker merged 6 commits into
developfrom
whqtker/investigate-withdrawal-logic
Oct 18, 2026
Merged

whqtker merged 6 commits into
developfrom
whqtker/investigate-withdrawal-logic

Conversation

@whqtker

@whqtker whqtker commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

관련 이슈

작업 내용

탈퇴 후 30일이 지난 사용자를 삭제하는 UserRemovalScheduler가 외래키 제약으로 인해 매일 밤 롤백되고 있었습니다.

채팅

  • 1:1 채팅방은 참여자 한 명이 사라지면 유지될 수 없으므로 방 전체를 삭제합니다.
    메시지 → 읽음 상태 → 참여자(양쪽) → 방 순으로 정리합니다.
  • 그룹 채팅방은 남은 멤버들의 대화가 유지되어야 하므로, 탈퇴자의 메시지·읽음 상태·참여자 행만
    삭제하고 방은 남깁니다. 그룹방을 만드는 경로는 아직 없지만, 도입 시 한 명의 탈퇴로 남은 멤버의
    대화가 사라지는 회귀를 막기 위해 분기해 두었습니다.
  • 채팅방이 멘토링을 참조하므로 멘토링보다 먼저 삭제하도록 순서를 바꿨습니다.

멘토링

  • 기존에는 멘티로 참여한 멘토링만 삭제했습니다. 멘토로 참여한 멘토링도 함께 삭제합니다.

뉴스

  • 탈퇴자가 쓴 뉴스에 다른 사용자가 누른 좋아요를 함께 삭제합니다.

게시글·채팅 메시지의 소프트 삭제

  • 신고로 가려진(is_deleted = true) 게시글과 메시지는 엔티티 조회 필터에 걸려
    삭제되지 않았습니다. 플래그를 되돌린 뒤 삭제해 JPA cascade가 그대로 동작하게 했습니다.

정지 기록

  • 탈퇴자를 대상으로 한 기록은 삭제합니다.
  • 탈퇴자가 집행한 기록은 다른 사용자의 징계 이력이므로, 집행자 참조만 해제하고 행은 보존합니다.
    이를 위해 user_ban.banned_by를 nullable로 변경했습니다. (V61)

신고 기록

  • 기존에는 탈퇴자가 접수한 신고를 삭제하고, 탈퇴자가 신고당한 기록은 남겼습니다. 방향이 반대였습니다.
  • 탈퇴자를 대상으로 한 신고는 탈퇴자의 이력이므로 삭제합니다.
  • 탈퇴자가 접수한 신고는 신고당한 사용자의 이력이므로, 신고자 참조만 해제하고 행은 보존합니다.
    정지 기록과 같은 기준입니다. 이를 위해 report.reporter_id를 nullable로 변경했습니다. (V62)
  • 기존 동작에서는 누군가를 신고한 뒤 탈퇴하면 그 신고가 관리자 화면의 피신고 이력에서 함께
    사라졌습니다.

프로필 이미지

  • S3Service.deleteExProfile이 빈 key로 S3를 호출해 예외를 던지던 문제를 막았습니다.
    profile_image_url이 null이거나 빈 문자열인 계정에서 발생합니다.

특이 사항

  • 마이그레이션이 두 건 추가됐습니다. V61 (user_ban.banned_by nullable),
    V62 (report.reporter_id nullable).
  • 탈퇴자가 참여한 1:1 채팅방은 상대방의 대화 기록도 함께 삭제됩니다. 참여자가 사라진 방을 남기면
    상대방의 채팅방 목록 조회가 CHAT_PARTNER_NOT_FOUND로 전체 실패하기 때문에, 방을 남기는
    선택지가 사실상 없었습니다.
  • 이 수정에 대한 통합 테스트는 없습니다. 테스트 환경은 ddl-auto=create로 스키마를 만들어
    운영 외래키가 생성되지 않으므로 이 결함을 재현할 수 없습니다. 검증은 실제 마이그레이션을 적용한
    로컬 MySQL에서 수동으로 했습니다. (1:1·그룹 채팅방 분기, 신고 기록 양방향 처리 포함)

리뷰 요구사항 (선택)

  • 채팅방 전체 삭제가 맞는 방향인지 봐주세요. 대안은 chat_message.sender_id를 nullable로 바꿔
    익명화하는 것인데, 조회 로직 재작업이 필요하고 탈퇴자의 메시지를 영구 보관하게 됩니다.
    이 논의는 feat: 탈퇴자의 채팅 메시지를 삭제 대신 "(알 수 없음)"으로 표기 #849 로 분리했습니다.
  • user_ban과 report의 집행자·신고자 참조를 NULL로 두는 방식이 적절한지 봐주세요.
    외래키를 제거해 원래 id를 남기는 선택지도 있습니다.
  • 이번 PR 범위 밖으로 남긴 것들입니다.
    • 사용자 단위 트랜잭션 분리 (지금은 한 명이 실패하면 그날 전체가 롤백됩니다)
    • 스케줄러 실패 알림 (예외가 로그로만 남아 이번 결함이 오래 드러나지 않았습니다)
    • 테스트 환경 스키마를 Flyway 기준으로 정비
    • 첨부·이미지의 S3 객체 정리 → fix: 탈퇴자 삭제 시 채팅 첨부와 게시글 이미지의 S3 객체가 고아로 남음 #850
    • report.reported_id 의 엔티티-스키마 불일치 (엔티티는 nullable = false, V40은 nullable).
      기존 NULL 행 백필 판단이 필요해 이번 범위에서 제외했습니다.

탈퇴 후 30일이 지난 사용자를 삭제하는 스케줄러가 외래키 제약에 걸려
매일 롤백되고 있었다. 삭제 목록에서 빠져 있던 데이터를 채우고 순서를 바로잡는다.

- 1:1 채팅방은 참여자가 사라지면 유지될 수 없으므로 방 전체를 삭제한다
- 채팅방이 멘토링을 참조하므로 멘토링보다 먼저 삭제한다
- 멘토로 참여한 멘토링과 탈퇴자가 쓴 뉴스의 좋아요를 함께 삭제한다
- 신고로 가려진 게시글과 메시지는 조회 필터에 걸리므로 플래그를 되돌린 뒤 삭제한다
- 정지 기록은 대상 행만 삭제하고, 집행자 참조는 해제해 다른 사용자의 이력을 보존한다
- 프로필 이미지 key 가 비어 있으면 S3 호출을 건너뛴다

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@whqtker whqtker self-assigned this Sep 22, 2026
@whqtker whqtker added the 버그 Something isn't working label Sep 22, 2026
@whqtker whqtker added the 최종 리뷰 최소 1명 필수 label Sep 22, 2026
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cfddbda8-abc9-4985-9edd-e1caa9fac2e2
📥 Commits

Reviewing files that changed from the base of the PR and between b93fe6f and 87b90f7.

📒 Files selected for processing: 1
  • src/main/java/com/example/solidconnection/siteuser/domain/UserBan.java

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

  1. 연관 데이터 정리 계약 변경
    뉴스, 게시물, 채팅, 멘토링, 제재 데이터의 조회·삭제 메서드를 추가하거나 변경했습니다.

  2. 탈퇴 사용자 삭제 흐름 변경
    UserRemovalScheduler가 뉴스 좋아요, 게시물, 채팅, 멘토링, 제재 기록을 정리하도록 변경했습니다.

  3. 제재 참조와 스키마 변경
    banned_by 컬럼이 NULL을 허용하도록 변경했습니다. 탈퇴자가 집행한 제재 및 해제 기록의 집행자 참조를 NULL로 변경합니다.

  4. 프로필 이미지 삭제 가드 추가
    키가 null이거나 공백이면 S3Service.deleteExProfile이 deleteFile을 호출하지 않습니다.

Priority: ⬆️ High

Estimated code review effort: 3 (Moderate) | ~25 minutes

Severity of issue fixed: High

Merge Risk: ⚪ Minimal · up to 87b90

This change allows the scheduled removal flow to clear ban references without violating the database contract. The checked cleanup paths show no unresolved merge-blocking risk.

Security architecture risk: 🔵 Low · up to 87b90

The expanded cleanup is scoped to departing users and their related records. Other users’ sanctions are preserved when the issuer account is removed. No new authorization bypass was identified, but concurrent updates and recovery behavior remain incompletely established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — Erasure affects shared data: both sides’ messages in the departing user’s one-to-one rooms, other users’ likes on that user’s news, and mentor-side mentoring records. Group-message and read-status deletion is limited to the departing user’s participant IDs. The scheduled batch may include multiple eligible accounts.

Trust Boundaries and Controls

  • observed — Destructive room scope is derived from persisted participation for the selected account and the room’s group flag, rather than from a caller-supplied room list. Message sender IDs refer to chat-participant IDs, matching the group-cleanup selector.
  • inferred — Clearing moderation flags and subsequently deleting posts or messages occurs within the scheduler transaction. Under normal transactional isolation, the temporary unhidden state is not committed independently; a rollback should restore the preceding database state.

Resilience and Maintainability Implications

  • inferred — Ban cleanup has transactional rollback and retained foreign-key constraints as counterevidence against partial committed detachment. However, the selection query has no explicit lock or version predicate, and administrative ban writes use separate transactions. Exact concurrent-write and recovery outcomes remain unproven without effective isolation and deployment configuration; this is not a verified bypass.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 19.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 41 functions across 12 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed [#844]
1. 채팅 정리: UserRemovalScheduler는 1:1 채팅방의 메시지, 읽음 상태, 양쪽 참여자, 방을 순서대로 삭제합니다. 그룹 채팅방에서는 탈퇴자의 참여자와 해당 메시지 및 읽음 상태만 삭제합니다. 채팅 정리는 멘토링 정리보다 먼저 실행됩니다.
2. 멘토링 및 뉴스 정리: 멘티와 멘토 기준의 멘토링을 삭제합니다. 탈퇴자가 작성…
Out of Scope Changes check Passed 변경은 [#844]의 탈퇴 사용자 물리 삭제 실패와 프로필 이미지 삭제 예외를 해결하는 범위에 있습니다. 삭제 순서 조정, 관련 리포지토리 메서드, 소프트 삭제 플래그 복구, 정지 참조 해제, V60 마이그레이션은 해당 목적을 지원합니다. 관련 없는 변경은 확인되지 않았습니다.
Title check Passed 제목은 외래키로 인해 사용자 물리 삭제가 실패하는 문제를 수정한다는 PR의 핵심 변경을 명확하고 간결하게 설명합니다.
Description check Passed PR 설명은 이슈, 작업 내용, 특이 사항, 선택 리뷰 요구사항을 모두 포함합니다. 채팅, 멘토링, 뉴스, 소프트 삭제, 정지 기록, 신고 기록, 프로필 이미지 변경과 검증 한계를 구체적으로 설명합니다.
Full details: Linked Issues check

Explanation

[#844]

  1. 채팅 정리: UserRemovalScheduler는 1:1 채팅방의 메시지, 읽음 상태, 양쪽 참여자, 방을 순서대로 삭제합니다. 그룹 채팅방에서는 탈퇴자의 참여자와 해당 메시지 및 읽음 상태만 삭제합니다. 채팅 정리는 멘토링 정리보다 먼저 실행됩니다.
  2. 멘토링 및 뉴스 정리: 멘티와 멘토 기준의 멘토링을 삭제합니다. 탈퇴자가 작성한 뉴스의 좋아요를 뉴스 삭제 전에 삭제합니다.
  3. 소프트 삭제 및 정지 기록: 게시글과 채팅 메시지의 is_deleted를 false로 변경한 뒤 삭제합니다. 탈퇴자를 대상으로 한 user_ban 행은 삭제합니다. 탈퇴자가 집행한 기록은 bannedBy와 unbannedBy 참조를 해제하고 보존합니다. V60은 user_ban.banned_by를 nullable로 변경합니다.
  4. 프로필 이미지: S3Service.deleteExProfile은 프로필 키가 null 또는 공백이면 S3 삭제를 호출하지 않습니다.
    위 구현은 #844의 직접적인 코딩 요구사항을 충족합니다.
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a430c8465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/com/example/solidconnection/siteuser/domain/UserBan.java Outdated

@Hexeong Hexeong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

고생하십니다~ 의견 남게두겠습니다!

Comment thread src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java Outdated
Comment thread src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java Outdated
기존에는 탈퇴자가 속한 채팅방이면 1:1 과 그룹을 구분하지 않고 방 전체를 삭제했다.
그룹 채팅방을 만드는 경로가 아직 없어 영향은 없었지만, 그룹 채팅이 도입되면
멤버 한 명의 탈퇴로 남은 멤버들의 대화가 통째로 사라진다.

- 1:1 채팅방은 참여자가 사라지면 유지될 수 없으므로 방 전체를 삭제한다
- 그룹 채팅방은 탈퇴자의 메시지, 읽음 상태, 참여자 행만 삭제하고 방은 유지한다

그룹방을 삭제 대상에서 제외하기만 하면 탈퇴자의 참여자 행이 남아
site_user 삭제가 다시 외래키에 막히므로, 참여 기록은 반드시 정리해야 한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T06:56:13.557490Z adc9977 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

banned_by 를 nullable 로 바꾸는 변경이므로, 애노테이션 기본값에 기대지 않고
매핑에 계약을 드러낸다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87b90f7c07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

whqtker and others added 3 commits October 18, 2026 15:25
기존에는 탈퇴자가 접수한 신고를 삭제하고, 탈퇴자가 신고당한 기록은 남겼다.
신고 한 건의 주인은 신고당한 사람이므로 방향이 반대였다.

- 탈퇴자를 대상으로 한 신고는 탈퇴자의 이력이므로 삭제한다
- 탈퇴자가 접수한 신고는 신고당한 사용자의 이력이므로 신고자 참조만 해제하고 보존한다

기존 동작에서는 누군가를 신고한 뒤 탈퇴하면 그 신고가 관리자 화면의 피신고 이력에서
함께 사라졌다. 정지 기록과 같은 기준으로 맞춘다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
develop 에 V60 이 추가되어 번호가 겹치므로 한 칸씩 밀어준다.

- V60__alter_user_ban_banned_by_nullable.sql -> V61
- V61__alter_report_reporter_id_nullable.sql -> V62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@whqtker
whqtker merged commit 8aea6ce into develop Oct 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

버그 Something isn't working 최종 리뷰 최소 1명 필수

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: 탈퇴 사용자 물리 삭제가 외래키 제약으로 전면 실패하는 문제를 수정한다.

2 participants