Skip to content

[Feature] 포스트 썸네일 필수화 및 파생 포스트 상태 개편#192

Merged
yooooonshine merged 5 commits intodevelopfrom
feature/188-refactor-derived-post
Oct 5, 2025
Merged

[Feature] 포스트 썸네일 필수화 및 파생 포스트 상태 개편#192
yooooonshine merged 5 commits intodevelopfrom
feature/188-refactor-derived-post

Conversation

@yooooonshine
Copy link
Contributor

@yooooonshine yooooonshine commented Oct 5, 2025

🧩 작업 내용 요약

  • PostEntity에 thumbnailImageId와 PostStatus 적용, 원본/파생 포스트 흐름에 맞게 서비스/레포지토리 전반을 재구성
  • 게시글 생성/수정 요청 DTO와 컨트롤러 swagger 문서에 썸네일 필수 검증 및 관련 에러 코드 노출
  • AI 파생 포스트 생성 서비스가 생성 이미지를 썸네일로 사용하고 투표 생성·통계 초기화를 묶어 처리하며, 파생 포스트 상태를 투표 결과에 맞춰 갱신
  • AI 이미지 생성 실패 시 텍스트 응답이 null인 경우를 대비해 SSE 응답 기본값을 보강
  • 포스트/AI 파생 포스트 관련 단위·통합 테스트를 최신 도메인 모델에 맞추어 정리하고 PostFactory 등을 정비

🔍 관련 이슈


🧠 변경 이유 및 주요 포인트

  • 기존 boolean 기반 파생 포스트 식별을 Enum(PostStatus)으로 전환해 ORIGINAL과 파생 승인 상태(DERIVED_PENDING/APPROVED/REJECTED)를 명확하게 구분했습니다.
  • 게시글 생성·수정 시 썸네일 이미지를 반드시 업로드 목록 안에서 선택하도록 검증을 추가하고, 위반 시 THUMBNAIL_IMAGE_NOT_IN_IMAGE_LIST_EXCEPTION을 반환합니다. Swagger 문서에도 동일한 예외를 기재했습니다.
  • AI 파생 포스트 생성 흐름에서 생성된 이미지 ID를 썸네일로 저장하고 투표/통계 초기화를 함께 수행하도록 재구성했으며, 투표 완료 시 파생 포스트 상태가 승인/거절로 자동 반영되도록 했습니다.
  • AI 이미지 생성 실패 응답의 extContent가 null인 경우에도 공백 문자열로 안전하게 처리해 SSE 전송 중 NPE를 방지했습니다.
  • 변경된 도메인/검증 로직을 반영하도록 서비스·컨트롤러 테스트 및 팩토리 코드를 대폭 정리했습니다.

🧪 테스트 및 검증

  • 단위 테스트 통과
  • 통합 테스트 통과
  • 로컬 서버 정상 구동 확인
  • Swagger 또는 Postman 테스트 완료

Summary by CodeRabbit

  • 신기능
    • 게시글 생성/수정 시 썸네일 이미지를 지정할 수 있습니다(목록 내 포함 여부 검증).
    • 내 게시글 조회 API가 추가되었습니다.
  • 리팩터링
    • 게시글 상태 체계를 ORIGINAL/DERIVED_PENDING/DERIVED_APPROVED/DERIVED_REJECTED로 통합했습니다.
    • 목록 조회 API에서 sort 파라미터가 제거되고 응답 필드가 간소화되었습니다(상태, 이미지, 좋아요 수 중심).
    • 파생 게시글(생성형 AI) 생성 시 원본 이미지 ID 입력이 불필요해졌습니다.
  • 검증/에러
    • 채팅 메시지 내용 필수 검증 추가.
    • 썸네일이 이미지 목록에 없을 때의 에러 코드가 추가되었습니다.

@yooooonshine yooooonshine linked an issue Oct 5, 2025 that may be closed by this pull request
- 게시물 생성 요청 실패 - 잘못된 요청 수정
@coderabbitai
Copy link

coderabbitai bot commented Oct 5, 2025

Walkthrough

포스트 도메인 전반을 리팩터링하여 상태 체계(PostStatus)로 통합하고 썸네일 필수/검증 로직을 도입했습니다. AI 파생포스트 생성 플로우와 투표 반영 로직을 이에 맞게 수정했으며, 컨트롤러·서비스·DTO·리포지토리·테스트를 일관되게 업데이트했습니다. 삭제된 API: 파생포스트 삭제.

Changes

Cohort / File(s) Summary
Error codes
src/main/java/.../common/error/ErrorCode.java
포스트 관련 에러코드 추가: THUMBNAIL_IMAGE_NOT_IN_IMAGE_LIST_EXCEPTION(BAD_REQUEST, P-005).
AI Chat DTO/Listener
src/main/java/.../ai/aiChat/dto/ChatMessageRequest.java, src/main/java/.../ai/aiServer/listener/AiImageCreatedListener.java
ChatMessageRequest.textContent에 @NotNull 추가. 실패 응답 빌드 시 빈 textContent 추가 동작 반영.
Post domain model
src/main/java/.../domain/post/entity/PostEntity.java, src/main/java/.../domain/post/enums/PostStatus.java
PostStatus에 ORIGINAL/DERIVED_* 체계 도입 및 상수명 변경. PostEntity에 postStatus(ENUM STRING), thumbnailImageId 추가. isAiDerivedPost 제거, 생성자/빌더/업데이트 메서드 시그니처 변경.
Post repository
src/main/java/.../domain/post/repository/PostEntityRepository.java
isAiDerivedPost 기반 조회 제거. postStatus, postStatusIn, parentPostId+postStatus(내림차순) 조회 추가. 재귀 CTE에 DERIVED_APPROVED 필터 반영.
AI Derived Post service
src/main/java/.../domain/post/service/AiDerivedPostService.java
PostService/ImageUtil 의존성 제거. createAiDerivedPost 시 originalImageId 제거, 썸네일/상태 설정(PostStatus.DERIVED_PENDING)으로 생성. PostImageEntity에 postEntity 직접 연결. 삭제 API 로직 제거.
Post service
src/main/java/.../domain/post/service/PostService.java
create/update에 thumbnailImageId 추가 및 이미지 목록 포함 검증. 포스트 상태 ORIGINAL 설정. 목록/상세 조회가 DERIVED_APPROVED만 포함하도록 변경. getPosts 시그니처에서 sort 제거. getUserPosts 추가. 썸네일 검증 헬퍼 추가.
Voting service
src/main/java/.../domain/vote/service/VotingService.java
파생포스트 판정 조건을 ORIGINAL 아님으로 변경. 투표 결과 매핑을 DERIVED_APPROVED/DERIVED_REJECTED로 변경.
Controllers (Post, AI Derived, Profile)
src/main/java/.../web/post/controller/AiDerivedPostController.java, .../web/post/controller/PostController.java, .../web/profile/controller/ProfileController.java
파생포스트 생성에서 originalImageId 제거. 파생포스트 삭제 엔드포인트 제거. Post 생성/수정에 thumbnailImageId 전달 및 문서 업데이트. getPosts에서 sort 제거. Profile의 내/사용자 포스트 응답 타입을 GetPostsResponse로 변경.
Request DTOs
src/main/java/.../web/post/dto/request/CreateAiDerivedPostRequest.java, .../CreatePostRequest.java, .../UpdatePostRequest.java
CreateAiDerivedPostRequest에서 originalImageId 필드 제거. Create/UpdatePostRequest에 thumbnailImageId(@NotNull) 추가.
Response DTOs
src/main/java/.../web/post/dto/response/GetPostResponse.java, .../GetPostTreeResponse.java, .../GetPostsResponse.java, .../GetSimplePostsResponse.java
isAiDerivedPost 제거, postStatus 도입. GetPostsResponse/ GetSimplePostsResponse에 likeCount 포함 및 팩토리 시그니처 변경. 트리 응답에서 postStatus 접근자 변경.
Tests: domain/services
src/test/java/.../domain/post/entityfactory/PostFactory.java, .../service/AiDerivedPostServiceTest.java, .../service/PostServiceTest.java, .../web/ai/controller/AiImagePermissionControllerIntegrationTest.java
테스트 엔티티에 thumbnailImageId와 PostStatus.ORIGINAL 설정. 파생포스트 생성 시 originalImageId 제거에 맞춘 호출/검증 수정. 리포지토리/서비스 호출을 postStatus 기반으로 수정. 삭제 관련 테스트 제거.
Tests: controllers
src/test/java/.../web/post/controller/AiDerivedPostControllerIntegrationTest.java, .../AiDerivedPostControllerTest.java, .../PostControllerIntegrationTest.java, .../PostControllerTest.java, .../PublicPostControllerTest.java
파생포스트 통합/단위 테스트에서 originalImageId 제거 및 실제 Post 연결. 목록/상세 응답의 postStatus 검증으로 변경. Create/Update 요청에 thumbnailImageId 추가. getPosts 시그니처 변경 반영. 삭제 관련 테스트 제거.

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

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

토끼는 귀를 쫑긋, 썸네일을 고르네
상태는 ORIGINAL, 파생은 대기 중이네
투표 바람 붑니다, 승인·기각 갈림길
리스트엔 썸네일 꼭! 규칙은 빼먹지 말길
깡총-깡총, 코드 숲에 새 질서가 피어나네 🌿🐇

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 이 PR에서는 이슈 #188의 파생 포스트 리팩터링 외에도 포스트 썸네일 필수화 및 AI 이미지 실패 시 SSE 기본값 보강 등 본 이슈 범위를 벗어난 기능 변경이 함께 포함되어 있습니다. 파생 포스트 리팩터링 이슈와 별도의 기능(썸네일 필수화 및 SSE 기본값 보강)을 분리하여 각 변경 사항을 독립적인 PR로 관리하거나 본 PR 내에서 해당 변경의 관련성을 명확히 설명해 주세요.
Description Check ⚠️ Warning 제공된 설명이 리포지토리 템플릿의 ## 개요## 작업사항 섹션 구조를 따르지 않고 있어 요구된 형식과 일치하지 않습니다. PR 설명에 ## 개요## 작업사항 섹션을 추가하여 템플릿에 정의된 구조에 맞추고 각 섹션에 필수 정보를 간략히 정리해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.63% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목이 포스트 썸네일 필수화와 파생 포스트 상태 개편이라는 주요 변경 사항을 명확히 요약하고 있어 PR의 핵심을 잘 반영합니다.
Linked Issues Check ✅ Passed 파생 포스트 로직 전반이 PostStatus 도입, 서비스·레포지토리·컨트롤러·테스트 업데이트 등으로 리팩터링 되어 이슈 #188의 목표를 충실히 반영했습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/188-refactor-derived-post

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 and usage tips.

@yooooonshine yooooonshine requested a review from Copilot October 5, 2025 06:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 isAiDerivedPost field with PostStatus enum 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("")
Copy link

Copilot AI Oct 5, 2025

Choose a reason for hiding this comment

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

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.

Suggested change
.textContent("")

Copilot uses AI. Check for mistakes.
.ticketPrice(5L)
.isAiDerivedPost(true)
.thumbnailImageId(1L)
.postStatus(PostStatus.ORIGINAL)
Copy link

Copilot AI Oct 5, 2025

Choose a reason for hiding this comment

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

AI derived posts should not have PostStatus.ORIGINAL. This should be PostStatus.DERIVED_PENDING or another appropriate derived status.

Copilot uses AI. Check for mistakes.
@yooooonshine yooooonshine merged commit a681cd8 into develop Oct 5, 2025
1 check passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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을 업데이트해주세요.

이제 PostStatus enum이 원본 포스트(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

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef13c7 and 1b1441f.

📒 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.ORIGINALthumbnailImageId(1L) 설정이 적절하며, 모든 테스트 케이스에서 일관되게 사용됩니다.

src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java (1)

68-68: 테스트 코드가 새로운 PostStatus 기반 응답 구조에 맞게 올바르게 수정되었습니다.

isAiDerivedPost boolean 필드에서 postStatus enum으로의 전환이 적절하게 반영되었습니다.

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 메서드에서 thumbnailImageIdimageIds에 포함되는지 검증하고 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)가 PostStatus enum으로 올바르게 교체되었습니다. 이는 상태를 명시적으로 표현하여 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() 팩토리도 깔끔히 정리되었네요.

Comment on lines 138 to 139
.textContent("")
.textContent(message.textContext())
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
.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.

Comment on lines +49 to +77
@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;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

썸네일 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.

Comment on lines +128 to 137
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);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

파생 포스트 썸네일 조회가 실패합니다.
파생 포스트의 thumbnailImageIdPostImageEntity의 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.

Comment on lines +54 to +55
.thumbnailImageId(1L)
.postStatus(PostStatus.ORIGINAL)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

AI 파생 포스트의 상태 설정을 확인해주세요.

createMockAiDerivedPostWithId 메서드가 AI 파생 게시글을 생성하는데 PostStatus.ORIGINAL을 설정하고 있습니다. 메서드 이름과 목적을 고려하면 PostStatus.DERIVED_PENDING 또는 다른 DERIVED_* 상태가 더 적절해 보입니다. 이 테스트 데이터를 사용하는 테스트에서 파생 포스트 동작을 정확히 검증하는지 확인이 필요합니다.

다음 스크립트로 이 팩토리 메서드를 사용하는 테스트들을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# AI 파생 포스트 팩토리 메서드 사용처 확인
rg -n "createMockAiDerivedPost" --type=java -C3

Length of output: 6518


🏁 Script executed:

#!/bin/bash
rg -nP --type=java 'createMockAiDerivedPost' -C10 src/test/java/hanium/modic/backend/domain/post/entityfactory/PostFactory.java

Length of output: 1799


🏁 Script executed:

#!/bin/bash
rg -nP --type=java 'enum\s+PostStatus' -C5

Length of output: 926


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' src/main/java/hanium/modic/backend/domain/post/enums/PostStatus.java

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

파생포스트 리팩터링

2 participants