[WTH-415] 파일 삭제 구현#83
Hidden character warning
Conversation
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 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 |
|
Warning Review limit reached
More reviews will be available in 23 minutes and 27 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthrough파일 엔티티와 저장소가 삭제 예약 메타데이터를 지원하도록 확장됐고, 수취·게시글·댓글·클럽·탈퇴 관련 유스케이스와 정책이 즉시 삭제 대신 활성 삭제 마킹과 하드 삭제 시점 기록을 사용하도록 바뀌었다. 관련 테스트와 조회 조건도 함께 갱신됐다. Changes파일 삭제 예약 전환
Sequence Diagram(s)sequenceDiagram
participant ClubActivityDeletionPolicy
participant CommentRepository
participant FileRepository
participant PostLikeRepository
participant PostRepository
ClubActivityDeletionPolicy->>CommentRepository: findActiveIdsByClubMemberIdIn(memberIds)
ClubActivityDeletionPolicy->>FileRepository: markActiveDeletedByOwnerTypeAndOwnerIdIn(...)
ClubActivityDeletionPolicy->>PostLikeRepository: findActivePostIdsByUserIdAndClubIdIn(userId, clubIds)
ClubActivityDeletionPolicy->>PostRepository: findAllByIdsWithLock(postIds)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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)를 쓰므로, 테스트가 rawLocalDateTime.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)를 전달하는데, 테스트는hardDeleteAfter를LocalDateTime.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
📒 Files selected for processing (23)
src/main/kotlin/com/weeth/domain/account/application/usecase/command/ManageReceiptUseCase.ktsrc/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.ktsrc/main/kotlin/com/weeth/domain/board/domain/repository/PostLikeRepository.ktsrc/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.ktsrc/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.ktsrc/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.ktsrc/main/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.ktsrc/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.ktsrc/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.ktsrc/main/kotlin/com/weeth/domain/file/domain/entity/File.ktsrc/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.ktsrc/test/kotlin/com/weeth/domain/account/application/usecase/command/ManageReceiptUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicyTest.ktsrc/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/file/domain/entity/FileTest.ktsrc/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCaseTest.kt
| @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 |
There was a problem hiding this comment.
🗄️ 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.
📌 Summary
파일 삭제를 예약하고 탈퇴/삭제 시 연관 파일 정리하도록 구현했습니다.
📝 Changes
What
File 엔티티에 삭제 예약 메타데이터를 추가했습니다.
게시글/댓글/영수증 파일 삭제 및 교체 시 기존 파일을 즉시 정리 대상으로 삭제 예약하도록 변경했습니다.
동아리 탈퇴/위드 탈퇴 시 작성한 게시글/댓글 파일과 프로필 파일을 30일 보관 삭제 예약하도록 처리했습니다.
Why
파일 교체/명시 삭제와 탈퇴로 인한 삭제는 보관 정책이 달라 즉시 정리 대상과 30일 보관 대상을 분리했습니다.
How
File.immediateHardDeleteAfter,File.retainedHardDeleteAfter로 즉시 정리/30일 보관 정책을 구분했습니다.📸 Screenshots / Logs
💡 Reviewer 참고사항
마이그레이션은 후속 PR에서 반영 예정입니다!
마이페이지를 수정할 예정이라서 동아리 탈퇴시엔 프로필 이미지 삭제 X, 위드 탈퇴시엔 프로필 이미지 삭제 예약으로 구현해뒀습니다.
파일 교체/명시 삭제는 삭제예약 = 즉시, 위드 탈퇴, 동아리 탈퇴 = 30일 예약을 해두었습니다.
✅ Checklist
Summary by CodeRabbit
New Features
Bug Fixes