Skip to content

[WTH-415] 파일 삭제 구현#83

Merged
soo0711 merged 19 commits into
devfrom
feat/WTH-415-파일-삭제-구현
Jul 1, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-415-\ud30c\uc77c-\uc0ad\uc81c-\uad6c\ud604"
Merged

[WTH-415] 파일 삭제 구현#83
soo0711 merged 19 commits into
devfrom
feat/WTH-415-파일-삭제-구현

Conversation

@soo0711

@soo0711 soo0711 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

파일 삭제를 예약하고 탈퇴/삭제 시 연관 파일 정리하도록 구현했습니다.

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

File 엔티티에 삭제 예약 메타데이터를 추가했습니다.
게시글/댓글/영수증 파일 삭제 및 교체 시 기존 파일을 즉시 정리 대상으로 삭제 예약하도록 변경했습니다.
동아리 탈퇴/위드 탈퇴 시 작성한 게시글/댓글 파일과 프로필 파일을 30일 보관 삭제 예약하도록 처리했습니다.

Why

파일 교체/명시 삭제와 탈퇴로 인한 삭제는 보관 정책이 달라 즉시 정리 대상과 30일 보관 대상을 분리했습니다.

How

File.immediateHardDeleteAfter, File.retainedHardDeleteAfter로 즉시 정리/30일 보관 정책을 구분했습니다.

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

마이그레이션은 PR 리뷰 후 반영 예정입니다!
마이페이지를 수정할 예정이라서 동아리 탈퇴시엔 프로필 이미지 삭제 X, 위드 탈퇴시엔 프로필 이미지 삭제 예약으로 구현해뒀습니다.
파일 교체/명시 삭제는 삭제예약 = 즉시, 위드 탈퇴, 동아리 탈퇴 = 30일 예약을 해두었습니다.

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

  • New Features

    • 게시글, 댓글, 클럽, 계정 거래, 프로필 이미지, 탈퇴 처리에서 관련 파일을 일괄 정리하는 방식이 개선되었습니다.
    • 여러 멤버의 활동을 한 번에 처리하는 기능이 추가되어 탈퇴/정리 작업이 더 안정적으로 진행됩니다.
  • Bug Fixes

    • 수정·삭제 시 데이터 충돌을 줄이기 위해 일부 조회가 잠금 기반으로 변경되었습니다.
    • 활성 상태의 파일만 정리되도록 변경되어, 삭제되지 않아야 할 파일이 함께 처리되는 문제를 줄였습니다.
    • 좋아요 수와 댓글/게시글 연동 정리가 더 정확해졌습니다.

@soo0711 soo0711 requested a review from hyxklee June 26, 2026 07:20
@soo0711 soo0711 self-assigned this Jun 26, 2026
@soo0711 soo0711 added ✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링 labels Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-415-파일-삭제-구현

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.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

게시글, 댓글, 동아리, 클럽 멤버, 계정 거래, 사용자 탈퇴 유스케이스에서 파일 삭제 방식이 조회 후 즉시 삭제(deleteAll)에서 저장소의 하드 삭제 메서드(hardDeleteActiveByOwnerTypeAndOwnerId 계열) 호출로 변경되었다. 관련 조회 로직이 락 기반으로 전환되고, PostLikeRepository/PostRepository/CommentRepository에 활성 ID 조회 메서드가 추가되었으며, ClubActivityDeletionPolicy가 멤버 단위 벌크 처리로 확장되었다.

Changes

파일 하드 삭제 전환

Layer / File(s) Summary
FileRepository 하드 삭제 메서드
.../file/domain/repository/FileRepository.kt, .../file/domain/repository/FileRepositoryTest.kt
ownerType/ownerId(목록) 기준 활성(UPLOADED) 파일을 JPQL DELETE로 삭제하는 메서드가 추가되고, 삭제 건수/상태별 존재 여부 검증 테스트가 추가됨.
게시글 첨부파일 삭제 및 락 조회
.../board/application/usecase/command/ManagePostUseCase.kt, .../board/application/usecase/query/GetPostQueryServiceTest.kt, .../board/application/usecase/command/ManagePostUseCaseTest.kt
findPost가 락 조회로 바뀌고 파일 삭제가 하드 삭제 호출로 전환됨. 관련 테스트가 갱신됨.
댓글 첨부파일 삭제 및 락 조회
.../comment/application/usecase/command/ManageCommentUseCase.kt, .../comment/domain/repository/CommentRepository.kt, .../comment/application/usecase/query/GetCommentQueryServiceTest.kt, .../comment/application/usecase/command/ManageCommentUseCaseTest.kt
댓글 조회가 findByIdAndPostIdWithLock으로 바뀌고, 파일 삭제가 하드 삭제 호출로 전환되며 신규 조회 메서드가 추가됨.
동아리/멤버 프로필 파일 삭제 및 락 조회
.../club/application/usecase/command/ManageClubUseCase.kt, .../club/application/usecase/command/ManageClubMemberUsecase.kt, 관련 테스트
클럽/멤버 조회가 락 기반으로 변경되고, 프로필·배경 이미지 삭제가 하드 삭제 호출로 전환됨.
동아리 활동 정리 및 탈퇴 벌크 처리
.../club/domain/service/ClubActivityDeletionPolicy.kt, .../board/domain/repository/PostLikeRepository.kt, .../board/domain/repository/PostRepository.kt, .../user/application/usecase/command/LeaveUserUseCase.kt, 관련 테스트
멤버 목록 단위로 게시글/댓글 파일과 좋아요를 벌크 정리하도록 확장되고, 탈퇴 시 프로필 파일 하드 삭제가 추가됨.
계정 거래 영수증 삭제
.../account/application/usecase/command/ManageAccountTransactionUseCase.kt, 관련 테스트
영수증 삭제가 조회 후 즉시 삭제에서 단일 하드 삭제 호출로 변경됨.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LeaveUserUseCase
  participant ClubActivityDeletionPolicy
  participant FileRepository
  participant PostLikeRepository
  participant PostRepository
  LeaveUserUseCase->>ClubActivityDeletionPolicy: markMembersActivitiesDeleted(activeMembers, now)
  ClubActivityDeletionPolicy->>PostRepository: findActiveIdsByClubMemberIdIn(memberIds)
  ClubActivityDeletionPolicy->>FileRepository: hardDeleteActiveByOwnerTypeAndOwnerIdIn(POST/COMMENT, ownerIds)
  ClubActivityDeletionPolicy->>PostLikeRepository: findActivePostIdsByUserIdAndClubIdIn(userId, clubIds)
  ClubActivityDeletionPolicy->>PostRepository: findAllByIdsWithLock(postIds)
  LeaveUserUseCase->>FileRepository: hardDeleteActiveByOwnerTypeAndOwnerId(CLUB_MEMBER_PROFILE, userId)
Loading

Possibly related PRs

  • Team-Weeth/weeth-server#47: 동일 영역의 첨부파일 삭제 로직(deleteAll 기반)을 다뤄 이번 하드 삭제 전환 이전 상태와 직접 연관됨.
  • Team-Weeth/weeth-server#78: 이번 PR이 사용하는 FileOwnerType.ACCOUNT_TRANSACTION 값을 추가한 PR로 직접 연결됨.
  • Team-Weeth/weeth-server#79: ClubActivityDeletionPolicy의 활동 삭제/좋아요 마킹 설계와 동일한 도메인 로직을 다룸.

Suggested reviewers: hyxklee, JIN921

Poem

토끼가 폴더를 살짝 열어보니
낡은 파일들이 조용히 사라지네
락을 걸고, 목록을 모아
하드 삭제로 깔끔히 정리해요
🐇🗂️✨ 코드밭이 다시 반짝반짝!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.93% 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
Title check ✅ Passed PR 제목이 파일 삭제 구현이라는 핵심 변경을 짧고 구체적으로 잘 요약합니다.
Description check ✅ Passed 요약, Changes의 What/Why/How, 참고사항, 체크리스트가 모두 포함되어 템플릿을 대부분 충족합니다.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-415-파일-삭제-구현

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.

@soo0711

soo0711 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 4

🧹 Nitpick comments (2)
src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt (1)

416-422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

hardDeleteAfter 기대값에 삭제 정책 helper를 사용해 주세요.

여기도 production 경로는 File.immediateHardDeleteAfter(now)를 쓰므로, 테스트가 raw LocalDateTime.now(clock)에 결합되지 않게 같은 helper로 기대값을 만들면 정책 변경 시 테스트가 덜 깨집니다.

🧪 제안 수정 패턴
+                val deletedAt = LocalDateTime.now(clock)
                 verify(exactly = 1) {
                     fileRepository.markActiveDeletedByOwnerTypeAndOwnerId(
                         FileOwnerType.CLUB_PROFILE,
                         1L,
-                        LocalDateTime.now(clock),
-                        LocalDateTime.now(clock),
+                        deletedAt,
+                        File.immediateHardDeleteAfter(deletedAt),
                     )
                 }

deleteProfileImage, deleteBackgroundImage 검증도 같은 방식으로 맞추면 됩니다.

Also applies to: 521-527, 580-586

🤖 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/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt`
around lines 416 - 422, The test expectations in ManageClubUseCaseTest are
coupled to raw LocalDateTime.now(clock) values instead of the same delete-policy
helper used in production. Update the verify() assertions for
deleteProfileImage, deleteBackgroundImage, and the related hard delete checks to
build the expected hardDeleteAfter value with File.immediateHardDeleteAfter(now)
(using the same now source as the test), so the assertions match the production
path and stay aligned if the policy changes.
src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt (1)

240-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

테스트 기대값도 삭제 정책 헬퍼를 따라가게 해주세요.

Production 코드는 File.immediateHardDeleteAfter(now)를 전달하는데, 테스트는 hardDeleteAfterLocalDateTime.now(clock)로 고정하고 있습니다. 정책 helper가 바뀌면 테스트가 구현 정책과 어긋나므로 동일 helper를 사용해 검증하는 편이 안전합니다.

🧪 제안 수정
+                val deletedAt = LocalDateTime.now(clock)
                 verify(exactly = 1) {
                     fileRepository.markActiveDeletedByOwnerTypeAndOwnerId(
                         FileOwnerType.POST,
                         post.id,
-                        LocalDateTime.now(clock),
-                        LocalDateTime.now(clock),
+                        deletedAt,
+                        File.immediateHardDeleteAfter(deletedAt),
                     )
                 }
+                val deletedAt = LocalDateTime.now(clock)
                 verify(exactly = 1) {
                     fileRepository.markActiveDeletedByOwnerTypeAndOwnerId(
                         FileOwnerType.POST,
                         post.id,
-                        LocalDateTime.now(clock),
-                        LocalDateTime.now(clock),
+                        deletedAt,
+                        File.immediateHardDeleteAfter(deletedAt),
                     )
                 }

Also applies to: 353-359

🤖 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/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt`
around lines 240 - 246, The test expectation in ManagePostUseCaseTest is
hardcoding the delete policy timestamp with LocalDateTime.now(clock) instead of
following the same helper used by production code. Update the verifications
around markActiveDeletedByOwnerTypeAndOwnerId to use the same deletion-policy
helper as the implementation, specifically File.immediateHardDeleteAfter(now),
so the test stays aligned if the policy changes. Apply this fix to both affected
verification blocks in the test class.
🤖 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/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.kt`:
- Around line 81-98: The like cleanup in ClubActivityDeletionPolicy still drops
deleted Post/Board IDs because it filters through findAllByIdsWithLock() before
markDeleted(now), so inactive posts never get their remaining active PostLike
rows removed. Refactor the flow around findActivePostIdsByUserIdAndClubIdIn(),
findAllByIdsWithLock(), and findAllActiveByUserIdAndPostIds() so like deletion
is based on the originally fetched postIds (including soft-deleted ones), while
only the like count decrement is limited to posts that are still active. Keep
the lock/query for active posts separate from the deletion of PostLike rows so
deleted content does not prevent cleanup.

In `@src/main/kotlin/com/weeth/domain/file/domain/entity/File.kt`:
- Around line 52-61: The File entity now includes isDeleted, deletedAt, and
hardDeleteAfter, but the corresponding database schema change is missing. Add a
Flyway migration under the existing db migration flow that alters the file table
to include these columns so File and its persistence mapping stay aligned with
ddl-auto validate and production startup in application-prod.yml. Use the File
entity field names and the existing migration versioning pattern (for example
the V2/V4 scripts) to locate where the new ALTER TABLE file changes should be
added.

In `@src/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.kt`:
- Around line 49-68: `FileRepository`의 bulk update 메서드인
`markActiveDeletedByOwnerTypeAndOwnerId`가 `@Modifying(flushAutomatically =
true)`만 사용해 영속성 컨텍스트가 남을 수 있습니다. `@Modifying`에 `clearAutomatically = true`를 추가해
업데이트 후 `File` 엔티티가 같은 트랜잭션에서 stale 상태로 보이지 않도록 수정하고, 이 repository 안의 다른 bulk
update 메서드들도 동일한 방식으로 적용해 일관되게 동기화하세요.

In
`@src/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.kt`:
- Around line 275-280: The Map<String, Any?>.booleanBy helper is too permissive
because value.toString().toBoolean() silently turns null, unexpected strings
like "Y", and typos into false. Tighten the JDBC test helper by only accepting
supported boolean representations in booleanBy and failing fast for anything
else, using the existing booleanBy extension in FileRepositoryTest to locate the
change.

---

Nitpick comments:
In
`@src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt`:
- Around line 240-246: The test expectation in ManagePostUseCaseTest is
hardcoding the delete policy timestamp with LocalDateTime.now(clock) instead of
following the same helper used by production code. Update the verifications
around markActiveDeletedByOwnerTypeAndOwnerId to use the same deletion-policy
helper as the implementation, specifically File.immediateHardDeleteAfter(now),
so the test stays aligned if the policy changes. Apply this fix to both affected
verification blocks in the test class.

In
`@src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt`:
- Around line 416-422: The test expectations in ManageClubUseCaseTest are
coupled to raw LocalDateTime.now(clock) values instead of the same delete-policy
helper used in production. Update the verify() assertions for
deleteProfileImage, deleteBackgroundImage, and the related hard delete checks to
build the expected hardDeleteAfter value with File.immediateHardDeleteAfter(now)
(using the same now source as the test), so the assertions match the production
path and stay aligned if the policy changes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81fb317d-1b32-41a4-a1d2-c6e03323f98a

📥 Commits

Reviewing files that changed from the base of the PR and between 5fca1e3 and 9c24a45.

📒 Files selected for processing (23)
  • src/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageReceiptUseCase.kt
  • src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt
  • src/main/kotlin/com/weeth/domain/board/domain/repository/PostLikeRepository.kt
  • src/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.kt
  • src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt
  • src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt
  • src/main/kotlin/com/weeth/domain/file/domain/entity/File.kt
  • src/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt
  • src/test/kotlin/com/weeth/domain/account/application/usecase/command/ManageReceiptUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicyTest.kt
  • src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/file/domain/entity/FileTest.kt
  • src/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCaseTest.kt

Comment on lines +52 to +61
@Column(nullable = false)
var isDeleted: Boolean = isDeleted
private set

@Column(name = "deleted_at", nullable = true)
var deletedAt: LocalDateTime? = deletedAt
private set

@Column(name = "hard_delete_after", nullable = true)
var hardDeleteAfter: LocalDateTime? = hardDeleteAfter

@coderabbitai coderabbitai Bot Jun 26, 2026

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: 삭제 예약 컬럼 DDL이 현재 PR/브랜치에 포함되어 있는지 확인합니다.
# Expect: 배포 가능한 단위라면 file 테이블에 is_deleted, deleted_at, hard_delete_after를 추가/backfill하는 migration이 있어야 합니다.

rg -n "is_deleted|deleted_at|hard_delete_after|ALTER[[:space:]]+TABLE.*file|CREATE[[:space:]]+TABLE.*file" .
rg -n "flyway|liquibase|ddl-auto|hibernate\\.hbm2ddl" -C 2 .

Repository: Team-Weeth/weeth-server

Length of output: 8937


🏁 Script executed:

#!/bin/bash
# Migration 파일 목록 확인
ls -lt src/main/resources/db/migration/

Repository: Team-Weeth/weeth-server

Length of output: 528


시각적 스키마 마이그레이션 스크립트가 누락되었습니다

File 엔티티에 isDeleted, deletedAt, hardDeleteAfter 필드가 추가되었으나, 이를 DB 에 반영하는 Flyway 마이그레이션 스크립트 ( ALTER TABLE file ...) 가 누락되었습니다.

프로덕션 환경 (application-prod.yml) 에서 Flyway 가 활성화되고 ddl-auto: validate로 설정되어 있어, 스키마 변경이 없으면 애플리케이션 시작 시점에 실패하거나, 컬럼 없어서 SQL 실패가 발생합니다.

  • 현재 V2, V4 마이그레이션 파일은 club_member, users 등 다른 테이블만 포함하고 file 테이블 수정 내용은 없습니다.
  • docs/plan 에 초안은 있으나 실제 src/main/resources/db/migration/ 에 추가되어야 합니다.

마이그레이션 스크립트를 추가하거나, 해당 필드의 DB 적용 단계를 명확히 하고 배포 순서를 엄격히 준수해야 합니다.

🤖 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/kotlin/com/weeth/domain/file/domain/entity/File.kt` around lines 52
- 61, The File entity now includes isDeleted, deletedAt, and hardDeleteAfter,
but the corresponding database schema change is missing. Add a Flyway migration
under the existing db migration flow that alters the file table to include these
columns so File and its persistence mapping stay aligned with ddl-auto validate
and production startup in application-prod.yml. Use the File entity field names
and the existing migration versioning pattern (for example the V2/V4 scripts) to
locate where the new ALTER TABLE file changes should be added.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

해당 PR에서 마이그레이션이 진행 되어야 빌드가 정상 동작하지 않을까용??

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyxklee 맞습니다. 제가 걱정한 포인트도 정확히 그 부분이에요.

엄밀히 말하면 CI의 “빌드” 자체는 통과할 수도 있지만, 이 변경이 배포 가능한 단위인가 관점에서는 마이그레이션이 같이 가는 편이 가장 안전합니다.
지금처럼 File 엔티티에 새 컬럼 매핑이 추가된 상태에서 애플리케이션이 먼저 배포되면, 운영 환경에서는 validate나 실제 쿼리 시점에 컬럼 부재로 실패할 수 있습니다.

그래서 제 코멘트의 의도는:

  • 이 PR에 마이그레이션을 같이 포함하거나
  • 정말 후속 PR로 분리할 거라면 “DB 마이그레이션 선반영 → 애플리케이션 배포” 순서가 강제되는지를 명확히 해야 한다

는 의미였습니다.

즉, 질문 주신 표현대로 보면 “정상 동작 보장”을 위해서는 이번 변경과 같은 배포 단위에서 마이그레이션이 필요하다 쪽에 가깝습니다.


🧠 Learnings used
Learnt from: hyxklee
Repo: Team-Weeth/weeth-server PR: 44
File: src/main/kotlin/com/weeth/domain/user/presentation/SocialCallbackController.kt:55-60
Timestamp: 2026-03-31T06:54:23.087Z
Learning: When reviewing Spring code in Kotlin, note that `UriComponentsBuilder.toUriString()` performs URL encoding internally (it effectively calls `encode().build().toUriString()`), including non-ASCII characters such as Korean. Therefore, do not flag `UriComponentsBuilder.toUriString()` as “missing encoding”. Only `UriComponents.toUriString()` is simple string concatenation and does not apply encoding by default.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

해당 PR에서 마이그레이션이 진행 되어야 빌드가 정상 동작하지 않을까용??

아 맞습니다!! 제가 잘못하고 저렇게 적어버렷네용.. 수정해두겟습니당

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread src/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.kt Outdated
fileRepository.flush()
}
val now = LocalDateTime.now(clock)
fileRepository.markActiveDeletedByOwnerTypeAndOwnerId(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

해당 메서드는 실제로 DB에서 삭제를 하진 않는 것으로 이해가 되는데, 위 API를 실제로 호출하게 되면 file의 삭제가 이루어지지 않아서 유니크 제약조건에서 fail이 발생할 수도 있을 것 같아요!
이전에 해당 문제가 발생했어서 명시적으로 flush를 해줬던건데 (delete가 insert 보다 늦게 동작하기 때문) 이 로직에서는 해당 문제는 없을까용??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

기존에는 delete가 insert보다 늦게 실행되면서 문제가 발생했는데 현재 로직에서는 삭제 마킹 update가 먼저 DB에 반영되도록 되어 있습니다!

말씀 주신 것처럼 소프트 딜리트라서 row는 남기 때문에 동일한 storageKey로 다시 insert되면 unique 제약에 걸릴 수는 있을 것 같습니당.. 하지만 현재 이미지 업로드 플로우에서는 UUID 기반으로 매번 새로운 storageKey를 생성하고 있어서 정상 플로우에서는 동일 storageKey가 재사용되지 않아 괜찮다고 판단했습니다!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

만약에 프론트 측에서 3개의 이미지 중 1개를 삭제하고, 2개를 유지하는 경우는 동일한 storageKey가 들어올 것 같아요!

FileStatus.UPLOADED,
)
if (existingFiles.isNotEmpty()) {
fileRepository.deleteAll(existingFiles)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

근데 기존에 하드 딜리트를 하던 파트를 소프트 딜리트 + 즉시 정리 대상으로 표시하는 방향으로 수정된 이유가 있을까요??
어차피 즉시 정리 대상으로 처리할 것이라면 하드 딜리트를 해도 문제가 없지 않나 싶어서요!

@soo0711 soo0711 Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이 부분은 DB와 S3 삭제 시점이 분리되어 있어서 소프트 딜리트 + 즉시 정리 대상으로 바꿨습니다!

하드 딜리트를 바로 해버리면 DB row는 삭제됐는데 S3 삭제가 실패하거나 반대로 S3를 먼저 삭제했는데 이후 트랜잭션이 롤백되면서 DB에는 파일 정보가 남아 있는데 S3는 없는 상태가 생길 수 있다고 생각했습니닷

그래서 API 호출 시점에는 hardDeleteAfter = now로 즉시 정리 대상임을 표시한 뒤 클린업 과정에서 S3 삭제 성공 후 하드 딜리트를 처리하는 것이 데이터 정합성 측면에서 더 적절하다고 생각해서 해당 방식으로 수정했습니다!!

이 방향에 대해서는 어떻게 생각하시나용??

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

사실 S3 삭제를 진행하고 있진 않긴 해서... 요금이 크게 나오고 있지도 않고.. 음 고민이네요

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

트래픽이 늘어난다면 S3 비용도 무시 못할 정도가 되긴 할 것 같은데 지금 함께 고려할 단계인지를 고민해봐야할 것 같아요!

@hyxklee hyxklee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

고생하셨습니다! 궁금한 점이 있어서 리뷰 몇 개 달아뒀어용

그럼 프로필 작업 이후에 스케줄링을 통한 하드 딜리트 작업이 예정되어 있는 거졍??

@soo0711

soo0711 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

넵! 프로필 작업 이후에 스케줄링을 통한 하드 딜리트 작업예정입니다!!

@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.

🧹 Nitpick comments (1)
src/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountTransactionUseCase.kt (1)

108-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

도메인 검증 로직이 UseCase에 위치.

requireManualType, getManualTransaction의 소유권 검사(transaction.account.id != account.id) 및 허용 유형 검사는 도메인 규칙에 해당합니다. 현재 ManageAccountTransactionUseCase에 직접 구현되어 있어, 코딩 가이드라인상 UseCase는 오케스트레이션만 담당하고 비즈니스 규칙은 엔티티/도메인 서비스에 있어야 한다는 원칙과 어긋납니다.

As per coding guidelines, "UseCase classes orchestrate only; business rules belong in entities or domain services."

🤖 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/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountTransactionUseCase.kt`
around lines 108 - 138, Move the domain validation out of
ManageAccountTransactionUseCase into the domain layer: the MANUAL_TYPES check in
requireManualType and the ownership/type checks in getManualTransaction should
live in AccountTransaction or a domain service instead of the UseCase. Refactor
the UseCase to only orchestrate calls, and expose domain methods on
AccountTransaction and/or Account that encapsulate “same account” and “allowed
type” validation so the business rules are enforced centrally.

Source: Path instructions

🤖 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.

Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountTransactionUseCase.kt`:
- Around line 108-138: Move the domain validation out of
ManageAccountTransactionUseCase into the domain layer: the MANUAL_TYPES check in
requireManualType and the ownership/type checks in getManualTransaction should
live in AccountTransaction or a domain service instead of the UseCase. Refactor
the UseCase to only orchestrate calls, and expose domain methods on
AccountTransaction and/or Account that encapsulate “same account” and “allowed
type” validation so the business rules are enforced centrally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8a48774-4a38-4386-95a8-7014c064f90c

📥 Commits

Reviewing files that changed from the base of the PR and between 9c24a45 and cdfab84.

📒 Files selected for processing (17)
  • src/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountTransactionUseCase.kt
  • src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.kt
  • src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt
  • src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt
  • src/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt
  • src/test/kotlin/com/weeth/domain/account/application/usecase/command/ManageAccountTransactionUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicyTest.kt
  • src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCaseTest.kt

@soo0711 soo0711 merged commit 4154607 into dev Jul 1, 2026
2 checks passed
@soo0711 soo0711 deleted the feat/WTH-415-파일-삭제-구현 branch July 1, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants