Skip to content

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

Open
soo0711 wants to merge 15 commits into
devfrom
feat/WTH-415-파일-삭제-구현

Hidden character warning

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

[WTH-415] 파일 삭제 구현#83
soo0711 wants to merge 15 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

파일 엔티티와 저장소가 삭제 예약 메타데이터를 지원하도록 확장됐고, 수취·게시글·댓글·클럽·탈퇴 관련 유스케이스와 정책이 즉시 삭제 대신 활성 삭제 마킹과 하드 삭제 시점 기록을 사용하도록 바뀌었다. 관련 테스트와 조회 조건도 함께 갱신됐다.

Changes

파일 삭제 예약 전환

Layer / File(s) Summary
File contract and repository
src/main/kotlin/com/weeth/domain/file/domain/entity/File.kt, src/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.kt, src/test/kotlin/com/weeth/domain/file/domain/entity/FileTest.kt, src/test/kotlin/com/weeth/domain/file/domain/repository/FileRepositoryTest.kt
File에 삭제 예약 필드와 메서드가 추가되고, FileRepositoryisDeleted 필터와 벌크 마킹 쿼리를 사용하도록 바뀌었으며, 엔티티·저장소 테스트가 새 컬럼과 상태를 검증한다.
Receipt and comment cleanup
src/main/kotlin/com/weeth/domain/account/.../ManageReceiptUseCase.kt, src/main/kotlin/com/weeth/domain/comment/.../ManageCommentUseCase.kt, src/test/kotlin/com/weeth/domain/account/.../ManageReceiptUseCaseTest.kt, src/test/kotlin/com/weeth/domain/comment/.../ManageCommentUseCaseTest.kt, src/test/kotlin/com/weeth/domain/comment/.../GetCommentQueryServiceTest.kt
수납증명서와 댓글 파일 삭제가 FileRepository.markActiveDeletedByOwnerTypeAndOwnerId(...)Clock 기반 시간으로 바뀌고, 테스트는 deleteAll 대신 마킹 호출과 FileStatus.UPLOADED 조회를 확인한다.
Post and club cleanup
src/main/kotlin/com/weeth/domain/board/.../ManagePostUseCase.kt, src/main/kotlin/com/weeth/domain/club/.../ManageClubUseCase.kt, src/test/kotlin/com/weeth/domain/board/.../GetPostQueryServiceTest.kt, src/test/kotlin/com/weeth/domain/board/.../ManagePostUseCaseTest.kt, src/test/kotlin/com/weeth/domain/club/.../ManageClubUseCaseTest.kt
게시글과 동아리 파일 삭제가 즉시 삭제에서 활성 삭제 마킹으로 바뀌고, 관련 조회·수정·삭제 테스트가 새 시각 인자와 마킹 호출을 검증하도록 갱신됐다.
Club member profile cleanup
src/main/kotlin/com/weeth/domain/club/.../ManageClubMemberUsecase.kt, src/test/kotlin/com/weeth/domain/club/.../ManageClubMemberUseCaseTest.kt
프로필 이미지 변경·삭제가 멤버 락 조회와 파일 삭제 예약 호출로 바뀌고, 테스트는 deleteAll 대신 markActiveDeletedByOwnerTypeAndOwnerId(...)와 새 멤버 조회 메서드를 확인한다.
Club activity and leave flow
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/comment/domain/repository/CommentRepository.kt, src/main/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicy.kt, src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt, src/test/kotlin/com/weeth/domain/club/domain/service/ClubActivityDeletionPolicyTest.kt, src/test/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCaseTest.kt
클럽 탈퇴 활동 정리가 댓글 파일, 게시글 파일, 좋아요, 멤버 탈퇴를 함께 처리하도록 확장되고, 관련 저장소 메서드와 테스트가 멤버 목록·클럽 목록 기준 호출로 바뀌었다.

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)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Team-Weeth/weeth-server#79: 동아리 탈퇴 흐름에서 ClubActivityDeletionPolicy를 확장해 활동 파일을 삭제 예약 마킹하는 구조가 이 PR의 활동 정리 변경과 같은 코드 경로를 다룹니다.
  • Team-Weeth/weeth-server#82: 두 PR 모두 LeaveUserUseCase의 탈퇴 처리 중 clubActivityDeletionPolicy.markMemberActivitiesDeleted/markMembersActivitiesDeleted를 호출하는 흐름과 연결됩니다.

Poem

나는 토끼, 귀를 쫑긋 세웠지 🐰
파일들은 곧바로 사라지지 않고
조용히 삭제 예약을 품었네
댓글도, 게시글도, 동아리도
달빛 아래서 하드 삭제를 기다려요

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.07% 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 제목은 파일 삭제 구현이라는 핵심 변경을 간결하게 담고 있어 변경 내용과 대체로 일치합니다.
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.
Description check ✅ Passed 요구된 섹션(Summary, Changes, Screenshots/Logs, Reviewer 참고사항, Checklist)이 모두 포함되어 있고 What/Why/How도 구분되어 있습니다.
✨ 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
Contributor

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/main/kotlin/com/weeth/domain/file/domain/repository/FileRepository.kt Outdated
fileRepository.flush()
}
val now = LocalDateTime.now(clock)
fileRepository.markActiveDeletedByOwnerTypeAndOwnerId(

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.

해당 메서드는 실제로 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가 재사용되지 않아 괜찮다고 판단했습니다!

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

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.

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

@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 삭제 성공 후 하드 딜리트를 처리하는 것이 데이터 정합성 측면에서 더 적절하다고 생각해서 해당 방식으로 수정했습니다!!

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

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

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

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

@soo0711

soo0711 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

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

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