[Feature] 포스트 썸네일 필수화 및 파생 포스트 상태 개편#192
Conversation
- 게시물 생성 요청 실패 - 잘못된 요청 수정
Walkthrough포스트 도메인 전반을 리팩터링하여 상태 체계(PostStatus)로 통합하고 썸네일 필수/검증 로직을 도입했습니다. AI 파생포스트 생성 플로우와 투표 반영 로직을 이에 맞게 수정했으며, 컨트롤러·서비스·DTO·리포지토리·테스트를 일관되게 업데이트했습니다. 삭제된 API: 파생포스트 삭제. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant PC as PostController
participant PS as PostService
participant PR as PostRepository
participant PIR as PostImageRepository
U->>PC: POST /posts (title, desc, prices, imageIds, thumbnailImageId)
PC->>PS: createPost(..., imageIds, thumbnailImageId)
PS->>PS: validateThumbnailInImages()
alt thumbnail not in list
PS-->>PC: throw THUMBNAIL_IMAGE_NOT_IN_IMAGE_LIST_EXCEPTION
PC-->>U: 400 Bad Request
else valid
PS->>PR: save(Post{postStatus=ORIGINAL, thumbnailImageId})
PS->>PIR: link images to post
PS-->>PC: CreatePostResponse
PC-->>U: 201 Created
end
sequenceDiagram
autonumber
actor U as User
participant ADC as AiDerivedPostController
participant ADS as AiDerivedPostService
participant AIR as AiChatImageRepository
participant PR as PostRepository
participant PIR as PostImageRepository
participant VR as VoteRepositories
U->>ADC: POST /posts/ai-derived (createdAiImageId, title, ...)
ADC->>ADS: createAiDerivedPost(userId, createdAiImageId, ...)
ADS->>AIR: find AI Image by id (with owner)
ADS->>PR: save(Post{postStatus=DERIVED_PENDING, thumbnailImageId=aiImageId})
ADS->>PIR: save(PostImage{postEntity=savedPost, imageId=aiImageId})
ADS->>VR: init similarity vote + summary
ADS-->>ADC: CreatePostResponse
ADC-->>U: 201 Created
sequenceDiagram
autonumber
actor A as Admin/Reviewer
participant VS as VotingService
participant PR as PostRepository
A->>VS: finalize vote for derived post
VS->>PR: findById(derivedPostId)
alt postStatus == ORIGINAL or not found
VS-->>A: error (invalid derived post)
else decision == APPROVE
VS->>PR: update postStatus to DERIVED_APPROVED
VS-->>A: OK
else decision == REJECT
VS->>PR: update postStatus to DERIVED_REJECTED
VS-->>A: OK
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✅ Passed checks (2 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 |
There was a problem hiding this comment.
Pull Request Overview
This PR implements thumbnail image requirements and refactors the post status system from boolean-based to enum-based classification. The changes consolidate original and derived post handling with proper status management through the PostStatus enum.
- Replaced boolean
isAiDerivedPostfield withPostStatusenum supporting ORIGINAL, DERIVED_PENDING, DERIVED_APPROVED, and DERIVED_REJECTED states - Added mandatory thumbnail image validation for post creation/updates with new error handling
- Updated AI derived post creation to use generated images as thumbnails and integrate voting system initialization
Reviewed Changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Test files (multiple) | Updated test cases to use new PostStatus enum and thumbnail requirements |
| DTO files | Added thumbnailImageId fields to request DTOs and updated response DTOs |
| Controller files | Updated API endpoints to handle thumbnail validation and new status structure |
| Service files | Refactored post services to use PostStatus enum and implement thumbnail validation |
| Repository files | Updated queries to filter by PostStatus instead of boolean flags |
| Entity files | Replaced isAiDerivedPost boolean with PostStatus enum and added thumbnailImageId |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| .aiChatRoomId(requestChatMessage.getAiChatRoomId()) | ||
| .messageOrder(messageOrder) | ||
| .senderType(SenderType.AI) | ||
| .textContent("") |
There was a problem hiding this comment.
Duplicate textContent() method calls detected. The first call setting empty string will be overwritten by the second call. Remove the duplicate call on line 138.
| .textContent("") |
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedListener.java
Show resolved
Hide resolved
| .ticketPrice(5L) | ||
| .isAiDerivedPost(true) | ||
| .thumbnailImageId(1L) | ||
| .postStatus(PostStatus.ORIGINAL) |
There was a problem hiding this comment.
AI derived posts should not have PostStatus.ORIGINAL. This should be PostStatus.DERIVED_PENDING or another appropriate derived status.
src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java
Show resolved
Hide resolved
src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/java/hanium/modic/backend/domain/post/enums/PostStatus.java (1)
3-6: 클래스 레벨 Javadoc을 업데이트해주세요.이제
PostStatusenum이 원본 포스트(ORIGINAL)와 파생 포스트 상태를 모두 포함하므로, "AI 파생 게시물의 승인 상태"라는 설명이 부정확합니다. 전체 포스트 상태를 나타낸다는 것을 반영하도록 Javadoc을 수정해주세요.다음과 같이 수정하는 것을 권장합니다:
/** - * AI 파생 게시물의 승인 상태를 나타내는 Enum - * 투표 시스템과 연동하여 사용됩니다. + * 게시물의 상태를 나타내는 Enum + * 원본 게시물(ORIGINAL)과 AI 파생 게시물의 승인 상태(DERIVED_*)를 포함합니다. + * 파생 게시물은 투표 시스템과 연동하여 상태가 결정됩니다. */src/main/java/hanium/modic/backend/domain/post/service/AiDerivedPostService.java (1)
85-99: 파생 포스트 썸네일 ID가 잘못 저장됩니다.
PostEntity.thumbnailImageId에는PostImageEntity의 ID가 들어가야 하는데, 현재 Line 87에서 AI 이미지 ID(createdAiImageId)를 그대로 넣고 있습니다. 아래에서 저장하는PostImageEntity는 새 ID를 발급받기 때문에, 이후postImageService.createImageGetUrl(derivedPost.getThumbnailImageId())호출 시 잘못된 ID로 조회하여 썸네일 URL 생성이 실패합니다. 파생 포스트 생성 직후 저장된PostImageEntity의 ID를 가져와서 썸네일 ID로 갱신해야 합니다.- PostEntity aiDerivedPost = PostEntity.builder() + PostEntity aiDerivedPost = PostEntity.builder() .userId(userId) .title(title) .description(description) .commercialPrice(commercialPrice) .nonCommercialPrice(nonCommercialPrice) .ticketPrice(ticketPrice) .parentPostId(createdAiImage.getPostId()) // 원본 포스트 ID 설정 - .postStatus(PostStatus.DERIVED_PENDING) // 투표 대기 상태로 설정 - .thumbnailImageId(createdAiImageId) // 썸네일은 생성된 AI 이미지로 설정 + .postStatus(PostStatus.DERIVED_PENDING) // 투표 대기 상태로 설정 + .thumbnailImageId(null) // 저장 후 실제 PostImage ID로 갱신 예정 .build(); PostEntity savedPost = postEntityRepository.save(aiDerivedPost); // 파생 포스트 이미지 저장 PostImageEntity postImage = PostImageEntity.builder() .imagePath(createdAiImage.getImagePath()) // 기존 AI 이미지 경로 사용, AI 이미지 Entity 삭제되어도 S3는 삭제 x .fullImageName(createdAiImage.getFullImageName()) .imageName(createdAiImage.getImageName()) .extension(createdAiImage.getExtension()) .imagePurpose(ImagePrefix.POST) .postEntity(savedPost) .build(); - postImageEntityRepository.save(postImage); + PostImageEntity savedPostImage = postImageEntityRepository.save(postImage); + + savedPost.updateThumbnailImageId(savedPostImage.getId()); + postEntityRepository.save(savedPost);
♻️ Duplicate comments (2)
src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java (2)
73-74: 동일한 상태 설정 문제가 여기에도 존재합니다.Line 54-55와 동일한 문제입니다. AI 파생 포스트인데
PostStatus.ORIGINAL로 설정되어 있습니다.
92-93: 동일한 상태 설정 문제가 여기에도 존재합니다.Line 54-55와 동일한 문제입니다. AI 파생 포스트인데
PostStatus.ORIGINAL로 설정되어 있습니다.
🧹 Nitpick comments (5)
src/main/java/hanium/modic/backend/domain/ai/aiChat/dto/ChatMessageRequest.java (1)
12-12: 빈 문자열 허용 문제:@NotBlank사용을 권장합니다.
@NotNull은 null 값만 검증하며 빈 문자열("")은 통과시킵니다. 채팅 메시지처럼 사용자가 입력하는 텍스트 콘텐츠의 경우, 공백만 있거나 빈 문자열인 경우도 거부해야 합니다.
@NotBlank로 변경하면 null, 빈 문자열, 공백만 있는 문자열을 모두 검증할 수 있습니다.다음 diff를 적용하세요:
- @NotNull(message = "채팅 메시지 내용은 필수입니다.") + @NotBlank(message = "채팅 메시지 내용은 필수입니다.")import 문도 함께 변경:
-import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank;src/test/java/hanium/modic/backend/web/post/controller/AiDerivedPostControllerIntegrationTest.java (1)
119-133: AI 파생 포스트 상태도 검증해 주세요신규 상태 체계(PostStatus)가 핵심 변경 사항인데, 성공 시나리오에서 저장된 파생 포스트의 postStatus를 확인하지 않으면 회귀를 놓치기 쉽습니다. 생성 직후 기대하는 상태값(DERIVED_PENDING 등 서비스 정의)에 대한 assert를 추가해 주세요.
src/main/java/hanium/modic/backend/web/post/controller/PostController.java (1)
93-101: API 설명에서 제거된 파라미터 반영 필요정렬 기준(sort) 파라미터는 시그니처에서 빠졌는데 설명 문자열에는 여전히 포함되어 있습니다. 문서와 실제 파라미터가 어긋나 혼선을 줄 수 있으니 설명에서 정렬 기준 언급을 삭제하거나 현재 동작으로 보완해 주세요.
src/test/java/hanium/modic/backend/domain/post/service/PostServiceTest.java (2)
105-120: 썸네일 ID 검증 assertion 추가 제안thumbnailImageId 필수가 이번 변경의 핵심인데, createPostTest에서는 해당 필드가 저장됐는지 확인하지 않아 회귀를 잡기 어렵습니다. 아래처럼 썸네일 ID를 검증하도록 assertion을 추가해 주세요.
assertThat(savedPost.getTitle()).isEqualTo(title); assertThat(savedPost.getDescription()).isEqualTo(description); assertThat(savedPost.getCommercialPrice()).isEqualTo(commercialPrice); assertThat(savedPost.getNonCommercialPrice()).isEqualTo(nonCommercialPrice); + assertThat(savedPost.getThumbnailImageId()).isEqualTo(postImageEntities.get(0).getId());
534-548: updatePost 테스트에서도 썸네일 변경 검증 필요updatePost_Success 역시 썸네일 ID가 새 값으로 교체됐는지 확인하지 않습니다. 썸네일 필수화 요구사항을 테스트가 덮도록 아래와 같이 assert를 추가해 주세요.
assertEquals(newDescription, mockPost.getDescription()); assertEquals(newCommercialPrice, mockPost.getCommercialPrice()); assertEquals(newNonCommercialPrice, mockPost.getNonCommercialPrice()); + assertEquals(anotherPostImageId1, mockPost.getThumbnailImageId());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (28)
src/main/java/hanium/modic/backend/common/error/ErrorCode.java(1 hunks)src/main/java/hanium/modic/backend/domain/ai/aiChat/dto/ChatMessageRequest.java(1 hunks)src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedListener.java(1 hunks)src/main/java/hanium/modic/backend/domain/post/entity/PostEntity.java(1 hunks)src/main/java/hanium/modic/backend/domain/post/enums/PostStatus.java(1 hunks)src/main/java/hanium/modic/backend/domain/post/repository/PostEntityRepository.java(2 hunks)src/main/java/hanium/modic/backend/domain/post/service/AiDerivedPostService.java(4 hunks)src/main/java/hanium/modic/backend/domain/post/service/PostService.java(9 hunks)src/main/java/hanium/modic/backend/domain/vote/service/VotingService.java(2 hunks)src/main/java/hanium/modic/backend/web/post/controller/AiDerivedPostController.java(0 hunks)src/main/java/hanium/modic/backend/web/post/controller/PostController.java(4 hunks)src/main/java/hanium/modic/backend/web/post/dto/request/CreateAiDerivedPostRequest.java(0 hunks)src/main/java/hanium/modic/backend/web/post/dto/request/CreatePostRequest.java(1 hunks)src/main/java/hanium/modic/backend/web/post/dto/request/UpdatePostRequest.java(1 hunks)src/main/java/hanium/modic/backend/web/post/dto/response/GetPostResponse.java(3 hunks)src/main/java/hanium/modic/backend/web/post/dto/response/GetPostTreeResponse.java(1 hunks)src/main/java/hanium/modic/backend/web/post/dto/response/GetPostsResponse.java(1 hunks)src/main/java/hanium/modic/backend/web/post/dto/response/GetSimplePostsResponse.java(1 hunks)src/main/java/hanium/modic/backend/web/profile/controller/ProfileController.java(2 hunks)src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java(6 hunks)src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java(4 hunks)src/test/java/hanium/modic/backend/domain/post/service/PostServiceTest.java(23 hunks)src/test/java/hanium/modic/backend/web/ai/controller/AiImagePermissionControllerIntegrationTest.java(2 hunks)src/test/java/hanium/modic/backend/web/post/controller/AiDerivedPostControllerIntegrationTest.java(2 hunks)src/test/java/hanium/modic/backend/web/post/controller/AiDerivedPostControllerTest.java(3 hunks)src/test/java/hanium/modic/backend/web/post/controller/PostControllerIntegrationTest.java(4 hunks)src/test/java/hanium/modic/backend/web/post/controller/PostControllerTest.java(22 hunks)src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java(1 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/hanium/modic/backend/web/post/dto/request/CreateAiDerivedPostRequest.java
- src/main/java/hanium/modic/backend/web/post/controller/AiDerivedPostController.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/hanium/modic/backend/web/post/dto/response/GetSimplePostsResponse.java (2)
src/main/java/hanium/modic/backend/web/post/dto/response/GetPostResponse.java (2)
Getter(69-74)Getter(76-81)src/main/java/hanium/modic/backend/web/post/dto/response/GetPostsResponse.java (1)
Getter(34-39)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (1)
src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
UserFactory(9-29)
src/test/java/hanium/modic/backend/domain/post/service/PostServiceTest.java (2)
src/test/java/hanium/modic/backend/domain/image/entityfactory/ImageFactory.java (1)
ImageFactory(15-59)src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
UserFactory(9-29)
src/main/java/hanium/modic/backend/web/post/dto/response/GetPostsResponse.java (2)
src/main/java/hanium/modic/backend/web/post/dto/response/GetPostResponse.java (2)
Getter(69-74)Getter(76-81)src/main/java/hanium/modic/backend/web/post/dto/response/GetSimplePostsResponse.java (1)
Getter(18-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (31)
src/main/java/hanium/modic/backend/common/error/ErrorCode.java (1)
47-47: 새로운 에러 코드 추가가 적절합니다.썸네일 이미지 검증을 위한 새로운 에러 코드가 올바르게 추가되었습니다. HTTP 상태 코드, 에러 코드 번호, 메시지 모두 기존 규칙을 잘 따르고 있습니다.
src/main/java/hanium/modic/backend/web/post/dto/response/GetPostTreeResponse.java (1)
30-30: PostStatus 필드로의 전환이 올바르게 적용되었습니다.기존 파생 포스트 관련 필드에서 새로운
postStatus필드를 사용하도록 변경되었으며, 전체 리팩터링 방향과 일치합니다.src/test/java/hanium/modic/backend/web/ai/controller/AiImagePermissionControllerIntegrationTest.java (1)
49-50: 테스트 데이터가 새로운 스키마에 맞게 올바르게 업데이트되었습니다.
PostStatus.ORIGINAL과thumbnailImageId(1L)설정이 적절하며, 모든 테스트 케이스에서 일관되게 사용됩니다.src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java (1)
68-68: 테스트 코드가 새로운 PostStatus 기반 응답 구조에 맞게 올바르게 수정되었습니다.
isAiDerivedPostboolean 필드에서postStatusenum으로의 전환이 적절하게 반영되었습니다.src/main/java/hanium/modic/backend/domain/post/enums/PostStatus.java (2)
9-9: ORIGINAL 상태 추가가 적절합니다.원본 포스트를 명확하게 구분할 수 있는
ORIGINAL상태가 추가되어 포스트 유형 식별이 더 명확해졌습니다.
14-24: 파생 포스트 상태명 변경이 적절합니다.
DERIVED_접두사 추가로 파생 포스트 관련 상태임을 명확하게 표현하고 있으며, 코드 가독성이 향상되었습니다.src/main/java/hanium/modic/backend/domain/vote/service/VotingService.java (2)
192-195: 파생 포스트 검증 로직이 올바르게 구현되었습니다.
PostStatus.ORIGINAL체크를 통해 원본 포스트가 아닌 파생 포스트만 상태 업데이트되도록 적절하게 검증하고 있습니다.
202-204: 투표 결과에 따른 상태 매핑이 올바르게 구현되었습니다.투표 결과가
APPROVE일 때DERIVED_APPROVED, 그 외에는DERIVED_REJECTED로 설정하는 로직이 명확하고 정확합니다.src/main/java/hanium/modic/backend/web/post/dto/request/CreatePostRequest.java (1)
38-39: 서비스 레이어 썸네일 검증 로직 구현 확인
PostService.validateThumbnailInImages메서드에서thumbnailImageId가imageIds에 포함되는지 검증하고THUMBNAIL_IMAGE_NOT_IN_IMAGE_LIST_EXCEPTION을 발생시키는 로직이 정상적으로 구현되어 있습니다.src/test/java/hanium/modic/backend/web/post/controller/PostControllerIntegrationTest.java (4)
82-90: LGTM! 썸네일 필수화 테스트 케이스 업데이트 완료게시물 생성 요청에
thumbnailImageId파라미터가 올바르게 추가되었습니다.image1.getId()를 썸네일로 사용하는 것은imageIds목록에 포함된 이미지를 썸네일로 지정하는 유효한 시나리오입니다.
144-152: LGTM! 게시물 수정 테스트 케이스 업데이트 완료게시물 수정 요청에도
thumbnailImageId가 올바르게 추가되었습니다.postImageIds.get(0)를 사용하여 기존 이미지 중 하나를 썸네일로 지정하는 것은 적절합니다.
187-195: LGTM! 이미지 도용 테스트 케이스 업데이트 완료타인의 이미지를 도용하는 엣지 케이스 테스트에도
thumbnailImageId가 올바르게 추가되었습니다. 도용된 이미지를 썸네일로 지정하는 시나리오를 테스트하므로 유효합니다.
230-238: LGTM! 이미지 삭제 테스트 케이스 업데이트 완료이미지 삭제 시나리오에서
thumbnailImageId가 올바르게 추가되었습니다. 남길 이미지를 썸네일로 지정하는 것은 적절한 테스트 시나리오입니다.src/main/java/hanium/modic/backend/web/profile/controller/ProfileController.java (1)
16-16: LGTM! DTO 통합 리팩터링 완료
GetSimplePostsResponse에서GetPostsResponse로의 변경은 응답 DTO를 통합하는 일관된 리팩터링입니다. 컨트롤러의 모든 메서드가 올바르게 업데이트되었습니다.Also applies to: 59-65, 72-78
src/test/java/hanium/modic/backend/web/post/controller/PostControllerTest.java (5)
74-155: LGTM! 게시물 생성 유효성 검증 테스트 업데이트 완료모든
CreatePostRequest테스트 케이스에thumbnailImageId파라미터가 올바르게 추가되었습니다. 유효성 검증 시나리오가 새로운 필수 필드를 반영하여 잘 구성되어 있습니다.
162-178: LGTM! PostStatus Enum 적용 완료
GetPostResponse생성 시PostStatus.ORIGINAL을 사용하도록 올바르게 업데이트되었습니다. Boolean 기반 식별에서 Enum 기반 상태 관리로의 전환이 잘 반영되었습니다.
202-223: LGTM! 게시물 목록 조회 테스트 업데이트 완료
GetPostsResponse생성 시PostStatus.ORIGINAL을 사용하고, 서비스 호출 시그니처에서sort파라미터가 제거되었습니다. 변경 사항이 일관되게 적용되었습니다.
297-405: LGTM! 게시물 수정 유효성 검증 테스트 업데이트 완료모든
UpdatePostRequest테스트 케이스에thumbnailImageId파라미터가 올바르게 추가되었습니다. 썸네일 누락 케이스(Line 385-389)도 적절히 테스트되고 있습니다.
407-488: LGTM! 포스트 트리 조회 테스트 업데이트 완료포스트 트리 조회 테스트에서
PostStatus.DERIVED_APPROVED,PostStatus.DERIVED_PENDING등의 새로운 상태값이 올바르게 사용되고 있습니다. 상태 기반 계층 구조 표현이 잘 반영되었습니다.src/main/java/hanium/modic/backend/web/post/dto/request/UpdatePostRequest.java (1)
36-40: LGTM! 썸네일 이미지 필수 필드 추가 완료
thumbnailImageId필드가@NotNull검증과 함께 올바르게 추가되었습니다. 검증 메시지("썸네일 이미지는 필수입니다.")도 명확하고 적절합니다.src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (3)
69-134: LGTM! AI 파생 포스트 생성 테스트 업데이트 완료서비스 시그니처 변경에 맞춰
originalImageId파라미터가 제거되었고,isAiDerivedPost플래그 대신PostStatus를 사용하도록 올바르게 업데이트되었습니다. 투표 생성 및 통계 초기화 로직에 대한 검증도 적절합니다.
136-161: LGTM! AI 이미지 미존재 예외 테스트 업데이트 완료서비스 호출 시그니처가 새로운 파라미터 구조에 맞춰 올바르게 업데이트되었습니다. 에러 처리 검증도 적절합니다.
163-194: LGTM! AI 이미지 접근 권한 예외 테스트 업데이트 완료권한 검증 실패 시나리오가 새로운 서비스 시그니처에 맞춰 올바르게 업데이트되었습니다.
src/test/java/hanium/modic/backend/web/post/controller/AiDerivedPostControllerTest.java (3)
42-60: LGTM! 유효성 검증 테스트 업데이트 완료서비스 시그니처 변경에 맞춰
verify호출이 올바르게 업데이트되었습니다.originalImageId파라미터가 제거되고 새로운 구조를 따릅니다.
199-226: LGTM! AI 이미지 미존재 예외 테스트 업데이트 완료Mock 설정이 새로운 서비스 시그니처(
title파라미터 포함)에 맞춰 올바르게 업데이트되었습니다.
228-256: LGTM! AI 이미지 접근 권한 예외 테스트 업데이트 완료권한 검증 실패 시나리오의 Mock 설정이 새로운 서비스 시그니처에 맞춰 올바르게 업데이트되었습니다.
src/main/java/hanium/modic/backend/domain/post/repository/PostEntityRepository.java (2)
19-23: LGTM! PostStatus 기반 메서드로 전환 완료Boolean 기반 메서드(
findAllByIsAiDerivedPost)가PostStatus기반 메서드들로 올바르게 교체되었습니다. 단일 상태 조회(findAllByPostStatus)와 복수 상태 조회(findAllByPostStatusIn)를 모두 지원하여 유연성이 향상되었습니다.
25-51: 비즈니스 요구사항 검증: 재귀 CTE 필터 적용
WHERE p.post_status = 'DERIVED_APPROVED'로DERIVED_PENDING/DERIVED_REJECTED파생 포스트를 제외하는 동작이 의도에 부합하는지 확인- 루트 포스트가
DERIVED_PENDING인 경우에도 최상위 노드로 반환되는 현상이 적절한지 검증해 주세요src/main/java/hanium/modic/backend/web/post/dto/response/GetPostResponse.java (1)
10-10: LGTM! GetPostResponse의 PostStatus 전환 완료Boolean 플래그(
isAiDerivedPost)가PostStatusenum으로 올바르게 교체되었습니다. 이는 상태를 명시적으로 표현하여ORIGINAL,DERIVED_PENDING,DERIVED_APPROVED,DERIVED_REJECTED등을 구분할 수 있게 합니다. 팩토리 메서드도postEntity.getPostStatus()를 사용하도록 적절히 업데이트되었습니다.Also applies to: 28-28, 62-62
src/main/java/hanium/modic/backend/web/post/dto/response/GetSimplePostsResponse.java (1)
10-16: 응답 필드 확장 확인title·postStatus·likeCount 추가로 리스트 응답에서 필요한 메타 정보를 한 번에 전달할 수 있게 되어 흐름이 자연스럽습니다. 기존 ImageDto 구조도 그대로 유지되어 호환성 문제 없겠습니다.
src/main/java/hanium/modic/backend/web/post/dto/response/GetPostsResponse.java (1)
11-31: postStatus/likeCount 포함 구조 반영 좋습니다postStatus와 likeCount를 함께 노출해 클라이언트에서 상태별 분기와 메타 집계를 바로 처리할 수 있어 응답 일관성이 좋아졌습니다. of() 팩토리도 깔끔히 정리되었네요.
| .textContent("") | ||
| .textContent(message.textContext()) |
There was a problem hiding this comment.
NPE 방지 로직이 작동하지 않습니다.
Builder 패턴에서 같은 필드에 대해 두 번 textContent()를 호출하면 마지막 호출만 적용됩니다. Line 138의 빈 문자열 설정은 line 139에 의해 덮어씌워지므로 NPE 방지 효과가 전혀 없습니다.
message.textContext()가 null인 경우 여전히 NPE가 발생할 수 있습니다.
다음과 같이 수정하세요:
AiChatMessageEntity responseChatMessage = AiChatMessageEntity.builder()
.userId(requestChatMessage.getUserId())
.postId(requestChatMessage.getPostId())
.aiChatRoomId(requestChatMessage.getAiChatRoomId())
.messageOrder(messageOrder)
.senderType(SenderType.AI)
- .textContent("")
- .textContent(message.textContext())
+ .textContent(message.textContext() != null ? message.textContext() : "")
.aiChatImageId(null)
.requestId(message.requestId())
.status(AiImageStatus.RESPONSE)
.build();추가 확인 필요: message.textContext() 메서드명이 textContent의 오타인지 확인하세요. 일반적으로 "context"보다 "content"가 적절한 명칭입니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .textContent("") | |
| .textContent(message.textContext()) | |
| AiChatMessageEntity responseChatMessage = AiChatMessageEntity.builder() | |
| .userId(requestChatMessage.getUserId()) | |
| .postId(requestChatMessage.getPostId()) | |
| .aiChatRoomId(requestChatMessage.getAiChatRoomId()) | |
| .messageOrder(messageOrder) | |
| .senderType(SenderType.AI) | |
| .textContent(message.textContext() != null ? message.textContext() : "") | |
| .aiChatImageId(null) | |
| .requestId(message.requestId()) | |
| .status(AiImageStatus.RESPONSE) | |
| .build(); |
🤖 Prompt for AI Agents
In
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedListener.java
around lines 138-139, the current builder calls .textContent("") then
.textContent(message.textContext()), so the empty-string NPE guard is
overwritten and provides no protection; change it to call .textContent(...) only
once with a null-safe value (e.g., .textContent(message.textContext() != null ?
message.textContext() : "" ) or using
Objects.requireNonNullElse/Optional.ofNullable) so a null textContext won’t
cause NPE, and also verify whether the method name message.textContext() is a
typo (should it be textContent()) and correct it if necessary.
| @Column(name = "post_status", nullable = false) | ||
| @Enumerated(EnumType.STRING) | ||
| private PostStatus postStatus; | ||
|
|
||
| @Column(name = "thumbnail_image_id", nullable = false) | ||
| private Long thumbnailImageId; | ||
|
|
||
| @Builder | ||
| public PostEntity( | ||
| Long userId, | ||
| String title, | ||
| String description, | ||
| Long commercialPrice, | ||
| Long nonCommercialPrice, | ||
| Long ticketPrice, | ||
| Long parentPostId, | ||
| PostStatus postStatus, | ||
| Long thumbnailImageId | ||
| ) { | ||
| this.userId = userId; | ||
| this.title = title; | ||
| this.description = description; | ||
| this.commercialPrice = commercialPrice; | ||
| this.nonCommercialPrice = nonCommercialPrice; | ||
| this.ticketPrice = ticketPrice; | ||
| this.parentPostId = parentPostId; | ||
| this.postStatus = postStatus; | ||
| this.thumbnailImageId = thumbnailImageId; | ||
| } |
There was a problem hiding this comment.
썸네일 ID null 저장 차단 필요.
thumbnailImageId 컬럼이 nullable = false인데, AiDerivedPostService에서 PostEntity를 저장할 때 바로 값을 채우지 못하고 이후에 updateThumbnailImageId로 갱신하려면 현재 제약 조건 때문에 ConstraintViolationException이 발생합니다. 파생 포스트 생성 플로우를 안전하게 만들려면 엔티티 저장 시점에 이미 PostImage ID를 확보해둬야 하며, Builder에서 null을 허용하면 안 됩니다. 앞선 서비스 수정 시 저장 후 updateThumbnailImageId를 호출하기 전에 thumbnailImageId 필드가 null이 되지 않도록 주의해 주세요.
🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/domain/post/entity/PostEntity.java around
lines 49 to 77, the PostEntity Builder currently allows thumbnailImageId to be
null while the column is annotated nullable = false, which causes
ConstraintViolationException when saving; ensure thumbnailImageId cannot be null
by making the constructor/Builder require a non-null thumbnailImageId (validate
and throw IllegalArgumentException or use Objects.requireNonNull) and update
AiDerivedPostService to create/obtain the PostImage and its ID before
constructing/saving the PostEntity so that thumbnailImageId is populated at
persist time.
| List<GetPostResponse.SimplePostDto> simpleDerivedPostDtos = derivedPosts.stream() | ||
| .map(derivedPost -> { | ||
| String firstImageUrl = postImageService.createImageGetUrl(derivedPost.getThumbnailImageId()); | ||
| return new GetPostResponse.SimplePostDto(derivedPost.getId(), firstImageUrl); | ||
| }) | ||
| .toList(); | ||
|
|
||
| return GetPostResponse.of(userName, hasUserImage, userImage, userEmail, postEntity, postImages, likeCount, | ||
| isLikedByCurrentUser, derivedPosts); | ||
| isLikedByCurrentUser, simpleDerivedPostDtos); | ||
| } |
There was a problem hiding this comment.
파생 포스트 썸네일 조회가 실패합니다.
파생 포스트의 thumbnailImageId는 PostImageEntity의 ID여야 하지만, AiDerivedPostService에서 현재 AiChatImage ID가 그대로 저장되고 있습니다. 그 결과 여기서 postImageService.createImageGetUrl(derivedPost.getThumbnailImageId())를 호출하면 존재하지 않는 PostImage ID를 조회하게 되어 썸네일 URL을 얻지 못합니다. 앞단 서비스에서 썸네일 ID를 올바른 PostImage ID로 저장하도록 수정해야 합니다. AiDerivedPostService 수정이 완료되면 이 부분은 정상 동작합니다.
🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/domain/post/service/PostService.java
around lines 128-137, the thumbnail lookup fails because AiDerivedPostService
currently stores AiChatImage IDs in derivedPost.thumbnailImageId instead of the
corresponding PostImageEntity IDs; update AiDerivedPostService so that when
creating/saving derived posts it saves the PostImageEntity.getId() (the
PostImage ID) as thumbnailImageId (e.g., create a PostImageEntity from the AI
image, persist it, and set the derived post's thumbnailImageId to that persisted
PostImage ID) so postImageService.createImageGetUrl(...) can resolve the URL
correctly.
| .thumbnailImageId(1L) | ||
| .postStatus(PostStatus.ORIGINAL) |
There was a problem hiding this comment.
🧩 Analysis chain
AI 파생 포스트의 상태 설정을 확인해주세요.
createMockAiDerivedPostWithId 메서드가 AI 파생 게시글을 생성하는데 PostStatus.ORIGINAL을 설정하고 있습니다. 메서드 이름과 목적을 고려하면 PostStatus.DERIVED_PENDING 또는 다른 DERIVED_* 상태가 더 적절해 보입니다. 이 테스트 데이터를 사용하는 테스트에서 파생 포스트 동작을 정확히 검증하는지 확인이 필요합니다.
다음 스크립트로 이 팩토리 메서드를 사용하는 테스트들을 확인해주세요:
🏁 Script executed:
#!/bin/bash
# AI 파생 포스트 팩토리 메서드 사용처 확인
rg -n "createMockAiDerivedPost" --type=java -C3Length of output: 6518
🏁 Script executed:
#!/bin/bash
rg -nP --type=java 'createMockAiDerivedPost' -C10 src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.javaLength of output: 1799
🏁 Script executed:
#!/bin/bash
rg -nP --type=java 'enum\s+PostStatus' -C5Length of output: 926
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' src/main/java/hanium/modic/backend/domain/post/enums/PostStatus.javaLength of output: 337
AI 파생 게시글 테스트팩토리 상태 수정 필요
src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java의 createMockAiDerivedPost* 메서드들이 PostStatus.ORIGINAL을 사용하고 있습니다. AI 파생 게시글은 PostStatus.DERIVED_PENDING (또는 테스트 의도에 맞는 다른 DERIVED_* 상태) 으로 수정해야 합니다.
🤖 Prompt for AI Agents
In src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java
around lines 54-55, the AI-derived post factory methods
(createMockAiDerivedPost*) set PostStatus.ORIGINAL; update these methods to use
PostStatus.DERIVED_PENDING (or another appropriate DERIVED_* status matching the
test intent). Modify the builder calls to replace
.postStatus(PostStatus.ORIGINAL) with .postStatus(PostStatus.DERIVED_PENDING)
for all createMockAiDerivedPost* variants and run tests to confirm expected
behavior.
🧩 작업 내용 요약
🔍 관련 이슈
🧠 변경 이유 및 주요 포인트
🧪 테스트 및 검증
Summary by CodeRabbit