Skip to content

[feat] 피드 작성을 위한 화면 조회 api 및 기록을 피드에 핀하기 api 개발#195

Merged
hd0rable merged 37 commits into
developfrom
feat/#161-feed-get-info
Aug 13, 2025
Merged

[feat] 피드 작성을 위한 화면 조회 api 및 기록을 피드에 핀하기 api 개발#195
hd0rable merged 37 commits into
developfrom
feat/#161-feed-get-info

Conversation

@hd0rable
Copy link
Copy Markdown
Member

@hd0rable hd0rable commented Aug 11, 2025

#️⃣ 연관된 이슈

closes #161

📝 작업 내용

  • 피드 작성을 위한 화면 조회 api 및 기록을 피드에 핀하기 api를 개발하였습니다.
  • 원래는 하나의 api로 통합하여 파라미터로 경우를 분리하려고했지만, 하는 역할이 달라서 분리하였습니다. 디스코드에 관련해서 작성해놓은 기록이 있으니 참고해주시면 감사하겠습니다.
  • 피드 작성을 위한 화면 조회 api 흐름은 다음과 같습니다.
  • 컨트롤러 ->피드 작성을 위한 화면 조회 서비스 진입 -> feedQueryPort에서 DB에 존재하는 태그 모두반환(querydsl로 category,tag inner join 후 TagCategoryQueryDto의 dto로 반환 -> feedQueryMapper에서 TagCategoryQueryDto를 FeedShowWriteInfoResponse형태로 매핑 -> 응답
  • 기록을 피드에 핀하기 api 흐름은 다음과 같습니다.
  • 컨트롤러 에서 쿼리로 변환 -> 기록을 핀하기 서비스 진입 -> 방 참여자인지 검증, 기록 조회 및 검증, 기록이 해당 방에 대한 기록인지 검증, 기록 작성자인지 검증 -> 해당 방의 책 정보 조회 -> bookQueryMapper로 book정보 반환 -> RecordPinResponse에서 필요한 값만 조립 -> 응답
  • 관련 통합, 단위 테스트 코드 작성했습니다.
  • 기존에 트랜잭션 어노테이션의 import가 jakarta로 되어있던것을 org.springframework.transaction 으로 수정하였습니다. 호환은 되지만 readOnly같은 옵션은 스프링에서 제공하는 어노테이션과 호환이 높기때문에 수정해주었습니다.

📸 스크린샷

image

💬 리뷰 요구사항

피드 작성을 위한 화면 조회가 사실 DB에 존재하는 태그들을 상위 카테고리에 매핑하여 보여주는 api인데 Category가 Room도메인 하위 밸류고, Tag가 feed도메인 하위 밸류라 패키지 구조라던가 클래스명이 어색할 수 있는데 제생각에 제일 최적..해서 작성했습니다 ㅎ.. 좋은의견이 있다면 의견부탁드리겠습니다.

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • New Features
    • 피드 작성 정보 조회 API 추가: GET /feeds/write-info (카테고리별 태그 제공)
    • 기록 핀하기 API 추가: GET /rooms/{roomId}/records/{recordId}/pin (도서 제목/저자/이미지/ISBN 반환)
  • Documentation
    • RECORD_PIN 관련 API 응답/오류 코드 설명 항목 추가
  • Tests
    • /feeds/write-info 및 기록 핀 통합 테스트 추가
    • 기록 핀 권한 검증 단위 테스트 추가

@hd0rable hd0rable self-assigned this Aug 11, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 11, 2025

Walkthrough

새 API 두 개가 추가됨: 피드 작성 화면 정보 조회(GET /feeds/write-info)와 기록 핀하기 정보 조회(GET /rooms/{roomId}/records/{recordId}/pin). 관련 포트·서비스·리포지토리·매퍼·응답 DTO·도메인 검증 메서드가 추가되었고 일부 서비스의 @transactional import가 Spring으로 교체됨.

Changes

Cohort / File(s) Summary
Transactional import 교체
src/main/java/.../book/application/service/BookSavedService.java, src/main/java/.../comment/application/service/CommentDeleteService.java, src/main/java/.../comment/application/service/CommentLikeService.java, src/main/java/.../feed/application/service/FeedDeleteService.java, src/main/java/.../feed/application/service/FeedSavedService.java, src/main/java/.../post/application/service/PostLikeService.java, src/main/java/.../record/application/service/RecordCreateService.java, src/main/java/.../record/application/service/RecordDeleteService.java
jakarta.transaction.Transactionalorg.springframework.transaction.annotation.Transactional로 변경. 로직/시그니처 변경 없음.
Swagger 응답 기술 추가
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
enum 상수 RECORD_PIN 추가(ROOM_ACCESS_FORBIDDEN, BOOK_NOT_FOUND, RECORD_NOT_FOUND, RECORD_ACCESS_FORBIDDEN 포함).
피드 작성 화면 정보 조회 기능
src/main/java/konkuk/thip/feed/adapter/in/web/FeedQueryController.java, src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowWriteInfoResponse.java, src/main/java/konkuk/thip/feed/application/port/in/FeedShowWriteInfoUseCase.java, src/main/java/konkuk/thip/feed/application/port/in/dto/TagsWithCategoryResult.java, src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java, src/main/java/konkuk/thip/feed/application/port/out/dto/TagCategoryQueryDto.java, src/main/java/konkuk/thip/feed/application/service/FeedShowWriteInfoService.java, src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java
GET /feeds/write-info 엔드포인트 추가. 태그-카테고리 조회용 DTO/포트/리포지토리 메서드(findAllTags)와 QueryDSL 프로젝션 도입. 매퍼로 카테고리별 태그 리스트로 변환 후 응답 레코드로 래핑.
기록 핀하기 정보 조회 기능
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java, src/main/java/konkuk/thip/record/adapter/in/web/response/RecordPinResponse.java, src/main/java/konkuk/thip/record/application/port/in/RecordPinUseCase.java, src/main/java/konkuk/thip/record/application/port/in/dto/RecordPinQuery.java, src/main/java/konkuk/thip/record/application/service/RecordPinService.java, src/main/java/konkuk/thip/record/domain/Record.java
GET /rooms/{roomId}/records/{recordId}/pin 엔드포인트 추가. UseCase/Service/Query DTO/Response 레코드 추가. Record.validatePin 도입 및 메시지 수정. 서비스에서 룸 참여자 검증, 기록 권한 검증, 룸의 책 조회 후 응답 변환.
테스트 추가
src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowWriteInfoAPITest.java, src/test/java/konkuk/thip/record/adapter/in/web/RecordPinAPITest.java, src/test/java/konkuk/thip/record/domain/RecordTest.java
피드 작성 정보 조회 API 통합 테스트, 기록 핀하기 API 통합 테스트, Record.validatePin 단위 테스트 추가.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FeedQueryController
  participant FeedShowWriteInfoUseCase
  participant FeedQueryPort
  participant Repository
  participant Mapper

  Client->>FeedQueryController: GET /feeds/write-info
  FeedQueryController->>FeedShowWriteInfoUseCase: showFeedWriteInfo()
  FeedShowWriteInfoUseCase->>FeedQueryPort: findAllTags()
  FeedQueryPort->>Repository: findAllTags()
  Repository-->>FeedQueryPort: List<TagCategoryQueryDto>
  FeedShowWriteInfoUseCase->>Mapper: toTagsWithCategoryResult(rows)
  Mapper-->>FeedShowWriteInfoUseCase: List<TagsWithCategoryResult>
  FeedShowWriteInfoUseCase-->>FeedQueryController: FeedShowWriteInfoResponse
  FeedQueryController-->>Client: 200 OK + BaseResponse
Loading
sequenceDiagram
  participant Client
  participant RecordQueryController
  participant RecordPinUseCase
  participant RoomParticipantValidator
  participant RecordCommandPort
  participant BookCommandPort
  participant BookQueryMapper

  Client->>RecordQueryController: GET /rooms/{roomId}/records/{recordId}/pin
  RecordQueryController->>RecordPinUseCase: pinRecord(RecordPinQuery)
  RecordPinUseCase->>RoomParticipantValidator: validateUserIsRoomMember(roomId, userId)
  RecordPinUseCase->>RecordCommandPort: getByIdOrThrow(recordId)
  RecordCommandPort-->>RecordPinUseCase: Record
  RecordPinUseCase->>Record: validatePin(userId, roomId)
  RecordPinUseCase->>BookCommandPort: findBookByRoomId(roomId)
  BookCommandPort-->>RecordPinUseCase: Book
  RecordPinUseCase->>BookQueryMapper: toBookSelectableResult(Book)
  BookQueryMapper-->>RecordPinUseCase: BookSelectableResult
  RecordPinUseCase-->>RecordQueryController: RecordPinResponse
  RecordQueryController-->>Client: 200 OK + BaseResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
피드 작성을 위한 화면 정보 조회 API 제공 ( #161 )
기록을 피드에 핀하기 위한 정보 조회 API 제공 ( #161 )

Out-of-scope changes

Code Change Explanation
Transactional import 변경 (src/main/java/.../comment/application/service/CommentDeleteService.java) 트랜잭션 어노테이션 구현체 교체는 링크된 이슈의 API 개발 목적과 직접 관련 없음.
Transactional import 변경 (src/main/java/.../post/application/service/PostLikeService.java) 동일: 관련 이슈에 명시된 기능 범위(피드 작성/기록 핀 조회)와 무관한 전역적 import 교체임.

Possibly related PRs

Suggested reviewers

  • seongjunnoh
  • buzz0331

Poem

토끼가 말하네, 태그를 모아 한 움큼 🐇
카테고리 줄 맞추고, 글감은 반짝 반짝
핀으로 콕—기록은 책으로 이어지고
제목과 저자, 이미지까지 찰칵 📚
코드 통과하면 당근 한 조각! 🥕


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c582cd and be815cf.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T05:22:32.258Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.258Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.

Applied to files:

  • src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java
⏰ 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 (4)
src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (4)

6-6: 필요한 DTO import 추가 적절

toTagsWithCategoryResult에서 사용되는 TagCategoryQueryDto 임포트가 정확합니다.


12-12: 응답 DTO import 추가 적절

TagsWithCategoryResult 매핑용 임포트가 정확합니다.


19-20: 필요한 표준 컬렉션/수집기 임포트 추가 OK

java.util.* 및 Collectors 임포트가 본문 구현에서 실제로 사용되고 있습니다.


105-108: LinkedHashSet 사용으로 태그 중복 제거 + 순서 보장 좋습니다

태그 수집을 LinkedHashSet으로 처리해 중복 제거와 삽입 순서 보존을 동시에 달성한 선택이 적절합니다. 해당 부분은 과거 러닝(LinkedHashSet 선호)에 부합합니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#161-feed-get-info

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 11, 2025

Test Results

383 tests  +9   383 ✅ +9   30s ⏱️ ±0s
113 suites +3     0 💤 ±0 
113 files   +3     0 ❌ ±0 

Results for commit be815cf. ± Comparison against base commit 076b6a9.

♻️ This comment has been updated with latest results.

…-info

# Conflicts:
#	src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java
Copy link
Copy Markdown

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

🔭 Outside diff range comments (8)
src/main/java/konkuk/thip/book/application/service/BookSavedService.java (2)

26-28: 외부 API 호출을 트랜잭션 경계 밖으로 이동 권장

changeSavedBook 내부에서 Naver API(bookApiQueryPort.findDetailBookByIsbn)를 호출하고 있어 트랜잭션이 불필요하게 길어지고 DB 리소스 점유/락 시간이 늘어날 수 있습니다. 외부 I/O는 실패/지연 가능성이 높으므로 트랜잭션 시작 전에 호출하고, 저장 단계만 짧은 쓰기 트랜잭션으로 감싸는 구조로 리팩터링을 권장합니다.

예시 접근:

  • changeSavedBook 메서드에서 조건 분기 후, API 호출을 먼저 수행
  • 실제 저장/연결만 별도 private 메서드에 @transactional 부여

Also applies to: 50-53


38-45: 경합 상황 대비: saved_books 테이블에 복합 유니크 제약 및 예외 매핑 필요
현재 SavedBookJpaEntity에 (user_id, book_id) 복합 유니크 제약이 없어, 동시 요청 시 중복 레코드가 생성될 수 있습니다. 아래와 같이 개선을 권장합니다.

엔티티/DDL에 복합 유니크 제약 추가

  • SavedBookJpaEntity
    @Table(
      name = "saved_books",
      uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "book_id"})
    )
    와 같이 어노테이션 또는 마이그레이션 스크립트로 인덱스를 추가합니다.

중복 저장 시 예외 처리 로직 추가

  • BookCommandPersistenceAdapter.saveSavedBook 호출부에서
    try {
      savedBookJpaRepository.save(entity);
    } catch (DataIntegrityViolationException e) {
      throw new DuplicateSaveBookException("이미 저장된 책입니다.");
    }
    와 같이 제약 위반 예외를 비즈니스 예외로 매핑하세요.

(선택) 서비스 계층에서의 idempotent 처리

  • BookSavedServicesave 흐름을 idempotent하게 변경하거나, 위 비즈니스 예외를 그대로 핸들링해 적절한 응답을 반환하도록 합니다.
src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java (1)

42-48: 댓글 수 동시성 제어 구현 필요 (TODO 처리 권장)

댓글 수 감소는 경쟁 조건의 대표 포인트입니다. 현재 TODO만 존재하므로 낙관적 락(@Version) 또는 DB 원자 연산(update ... set comment_count = comment_count - 1 where id=? and comment_count > 0) 같은 확정적 동시성 제어를 구현하세요. 이벤트 기반 집계로 일관성을 유지하는 방안도 고려 가능합니다.

원하시면 PostHandler/CountUpdatable 경유가 아닌 저장소 레벨 원자 감소 메서드 시그니처와 예외 매핑까지 포함한 패치를 제안하겠습니다.

src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (1)

30-31: S3 이미지 삭제는 트랜잭션 외부에서 이벤트 기반 처리 권장

외부 I/O(S3)는 트랜잭션 밖에서 처리하세요. 도메인 이벤트 발행 → @TransactionalEventListener(phase = AFTER_COMMIT) 또는 아웃박스 패턴으로 비동기 삭제를 적용하면 일관성과 내고장성을 확보할 수 있습니다.

필요 시 Feed 삭제 이벤트/리스너, 아웃박스 엔티티, 퍼블리셔 견본 코드를 제공하겠습니다.

src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java (1)

38-48: 좋아요 토글 동시성 및 카운트 정합성 강화 필요

현재 댓글 좋아요 로직은 다음과 같은 경쟁 조건과 비정합 위험을 안고 있습니다.

  1. DB 레벨의 (user_id, comment_id) 유니크 제약이 없어 동시 중복 insert/delete 발생 가능
  2. Comment.updateLikeCount 도메인 기반 증감 후 전체 엔티티 저장 방식(commentCommandPort.update)은 비원자적 업데이트로 동시 요청 시 증감 손실

아래 항목을 반영해 수정이 필요합니다.

• CommentLikeJpaEntity에 유니크 제약 추가

@Table(
  name = "comment_likes",
  uniqueConstraints = @UniqueConstraint(columnNames = {"user_id","comment_id"})
)

• 댓글 좋아요 카운트는 DB 원자 연산 또는 낙관적 락 적용
예시:

public interface CommentJpaRepository extends JpaRepository<CommentJpaEntity, Long> {
  @Modifying
  @Query("UPDATE CommentJpaEntity c SET c.likeCount = c.likeCount + :delta WHERE c.commentId = :id")
  void adjustLikeCount(@Param("id") Long id, @Param("delta") int delta);
}

• CommentLikeService에서 save/delete 후 도메인 증감 대신 위 원자 메서드 호출

점검 위치:

  • src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java (@Table 설정)
  • src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (update 로직)
  • src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java (원자 증감 쿼리 추가)
  • src/main/java/konkuk/thip/comment/domain/Comment.java (updateLikeCount 도메인 증감 로직)

위 변경으로 동시성 안전성과 카운트 정합성을 확보해주세요.

src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (1)

12-41: (userId, feedId)에 대한 DB 유니크 제약 및 중복키 처리 보강 필요
현재 SavedFeed 테이블에 (userId, feedId) 조합에 대한 고유 제약이 없어 동시성 경쟁 시 중복 저장이 발생할 수 있습니다.
다음 사항을 반영해주세요:

  • SavedFeedJpaEntity@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"user_id","feed_id"})) 추가하거나, 별도 DB 마이그레이션 스크립트로 UNIQUE 인덱스 생성
  • FeedCommandPersistenceAdapter.saveSavedFeed에서 중복키 예외(예: DataIntegrityViolationException 또는 DuplicateKeyException)를 잡아 idempotent 하게 처리하는 방어 로직 구현
src/main/java/konkuk/thip/post/application/service/PostLikeService.java (2)

31-47: isLiked 조회시 postType 미반영 — 교차 타입 충돌 가능성

Line 37에서 좋아요 여부 조회가 userId+postId만으로 수행됩니다. 여러 PostType이 공존하고 동일한 postId가 다른 타입에도 존재할 경우 오탐/누락이 발생할 수 있습니다. 저장(save)은 postType을 전달하지만 조회는 전달하지 않아 불일치가 있습니다. 포트 시그니처를 postType까지 포함하도록 확장하세요.

다음 수정안을 적용해 주세요.

  • 본 파일 (호출부) 수정:
-        boolean alreadyLiked = postLikeQueryPort.isLikedPostByUser(command.userId(), command.postId());
+        boolean alreadyLiked = postLikeQueryPort.isLikedPostByUser(command.userId(), command.postId(), command.postType());
  • 포트/어댑터 시그니처 예시(다른 파일 수정 필요):
// PostLikeQueryPort.java
boolean isLikedPostByUser(Long userId, Long postId, PostType postType);

// JPA/Query 어댑터 예시
@Override
public boolean isLikedPostByUser(Long userId, Long postId, PostType postType) {
    return repository.existsByUserIdAndPostIdAndPostType(userId, postId, postType);
}

39-51: 좋아요 카운트 동시성 제어 보강 필요

Line 40의 TODO대로, 동시 요청 시 카운트 꼬임이 발생할 수 있습니다. 아래 중 하나를 적용 권장합니다.

  • 좋아요 테이블에 (userId, postType, postId) 유니크 인덱스 + DB 레벨 upsert/삭제 결과를 기반으로 카운트 증감
  • 게시물 카운트 컬럼에 대한 낙관적 락(Version) + 재시도 정책
  • 별도 카운트 테이블을 두고 원자적 UPDATE ... SET like_count = like_count ± 1

현재 구현에서는 updateLikeCount가 애플리케이션 레벨 계산이어서 레이스에 취약합니다.

🧹 Nitpick comments (12)
src/main/java/konkuk/thip/book/application/service/BookSavedService.java (1)

33-35: 에러 코드 불일치 가능성 확인

삭제 요청 시 책 미저장 케이스에 대해 두 곳에서 서로 다른 에러 코드를 던집니다: BOOK_NOT_SAVED_DB_CANNOT_DELETE(라인 33) vs BOOK_NOT_SAVED_CANNOT_DELETE(라인 73). 의도된 구분인지, 도메인 정책상 동일 코드로 통일해야 하는지 확인 부탁드립니다.

Also applies to: 71-74

src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java (1)

39-41: 주석 오타 수정 제안

"피드 게시물 좋아요 삭제" → "기록 게시물 좋아요 삭제"로 의미를 명확히 해주세요.

적용 diff:

-        // 3-2. 피드 게시물 좋아요 삭제
+        // 3-2. 기록 게시물 좋아요 삭제
src/main/java/konkuk/thip/record/domain/Record.java (2)

100-103: 오류 메시지 맞춤법/띄어쓰기 보정

"핀 할"은 "핀할"로 붙여 쓰는 것이 자연스럽습니다.

다음처럼 수정하세요.

-            throw new InvalidStateException(RECORD_ACCESS_FORBIDDEN, new IllegalArgumentException("기록 작성자만 기록을 수정/삭제/핀 할 수 있습니다."));
+            throw new InvalidStateException(RECORD_ACCESS_FORBIDDEN, new IllegalArgumentException("기록 작성자만 기록을 수정/삭제/핀할 수 있습니다."));

117-120: validatePin이 validateDeletable과 동일 로직 — 중복 제거 권장

두 메서드가 동일하게 roomId/creator를 검증합니다. 공통 헬퍼로 추출하여 중복을 제거하면 변경 비용이 줄어듭니다.

아래처럼 공통 메서드로 정리하세요.

-    public void validatePin(Long userId,Long roomId) {
-        validateRoomId(roomId);
-        validateCreator(userId);
-    }
+    public void validatePin(Long userId, Long roomId) {
+        validateCreatorAndRoom(userId, roomId);
+    }

이 변경을 지원하기 위해 클래스 내부에 다음 헬퍼를 추가하고, validateDeletable에서도 이를 사용하도록 변경을 제안합니다.

// 클래스 내부에 추가 (다른 메서드들과 동일 접근제어자 컨벤션 유지)
private void validateCreatorAndRoom(Long userId, Long roomId) {
    validateRoomId(roomId);
    validateCreator(userId);
}

// 선택: 기존 메서드도 공통 헬퍼 사용
public void validateDeletable(Long userId, Long roomId) {
    validateCreatorAndRoom(userId, roomId);
}
src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (1)

43-44: 포트에서 DB 프로젝션 DTO 노출 최소화 검토

현재 포트가 List (QueryDSL 프로젝션 성격)를 그대로 노출합니다. 선택지는 두 가지입니다:

  • 유지: 현 구조 유지 시, 구현에서 정렬/중복 제거를 보장하고, 애플리케이션 서비스가 그룹핑(FeedQueryMapper)을 담당합니다.
  • 개선(권장): 그룹핑(카테고리별 태그 리스트)을 Persistence Adapter/Repository 레이어로 이동하고, 포트는 List를 반환하도록 변경해 애플리케이션 서비스가 영속성 세부에 덜 의존하도록 분리합니다.

둘 중 어떤 방식을 택하든, 메서드 계약(정렬, 중복, 비어있는 카테고리 처리)을 JavaDoc로 명확히 해 주세요.

src/test/java/konkuk/thip/record/domain/RecordTest.java (1)

233-237: 테스트 메서드 네이밍 일관성 유지 제안

동일 파일 내에서 Success/SUCCESS 표기 혼재가 있습니다. 기존 validateDeletable_byCreator_byRoomId_Success와 맞추어 대문자 S로 통일하는 편이 좋습니다.

아래와 같이 메서드명을 통일해 주세요:

-    void validatePin_byCreator_byRoomId_success() {
+    void validatePin_byCreator_byRoomId_Success() {
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (1)

152-155: findAllTags 위임은 적절하나, 메서드 명시성과 캐싱 고려 필요

  • 명확성: 반환이 “카테고리-태그 매핑용 행”이라면 메서드명을 findAllTagsWithCategory 정도로 더 구체화하면 의도가 분명합니다. 현재 명명은 태그 엔터티 전체 조회로 오해될 수 있습니다.
  • 성능: 화면 렌더링용으로 전 태그/카테고리를 매 요청마다 로드한다면 캐싱(예: Spring Cache) 혹은 변경 감지 기반 무효화 전략 고려가 필요합니다. 데이터 규모가 커지면 API 레이턴시가 증가할 수 있습니다.

필수 변경은 아니나, API 호출 빈도가 높을 경우 캐시 도입을 권장합니다.

src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowWriteInfoResponse.java (1)

10-12: 응답 리스트를 불변/널-세이프로 감싸기

외부에서 전달된 리스트가 변경되어도 안전하도록 방어적 복사를 권장합니다. 또한 null 입력 시 빈 리스트로 대체하면 클라이언트 처리 부담이 줄어듭니다.

-    public static FeedShowWriteInfoResponse of(List<TagsWithCategoryResult> categoryList) {
-        return new FeedShowWriteInfoResponse(categoryList);
-    }
+    public static FeedShowWriteInfoResponse of(List<TagsWithCategoryResult> categoryList) {
+        return new FeedShowWriteInfoResponse(
+                categoryList == null ? java.util.List.of() : java.util.List.copyOf(categoryList)
+        );
+    }
src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowWriteInfoAPITest.java (1)

71-84: 테스트 견고성 강화: 태그 개수 검증 추가 및 순서 의존성 완화 고려

현재 테스트는 태그 순서를 인덱스로 단정하지만(비결정적 순서가 될 수 있음), 저장소/매퍼에서 정렬·순서를 보장하지 않으면 플래키해질 수 있습니다. 저장소/매퍼에 정렬/LinkedHashMap 적용을 병행하면서, 테스트에도 태그 개수 검증을 추가해 의도를 명확히 하세요.

아래 diff로 태그 리스트 크기 검증을 추가할 수 있습니다.

                 .andExpect(jsonPath("$.data.categoryList", hasSize(2)))
                 .andExpect(jsonPath("$.data.categoryList[0].category", is(literatureCategory.getValue())))
+                .andExpect(jsonPath("$.data.categoryList[0].tagList", hasSize(3)))
                 .andExpect(jsonPath("$.data.categoryList[0].tagList[0]", is(KOREAN_NOVEL.getValue())))
                 .andExpect(jsonPath("$.data.categoryList[0].tagList[1]", is(FOREIGN_NOVEL.getValue())))
                 .andExpect(jsonPath("$.data.categoryList[0].tagList[2]", is(CLASSIC_LITERATURE.getValue())))
                 .andExpect(jsonPath("$.data.categoryList[1].category", is(scienceCategory.getValue())))
+                .andExpect(jsonPath("$.data.categoryList[1].tagList", hasSize(3)))
                 .andExpect(jsonPath("$.data.categoryList[1].tagList[0]", is(GENERAL_SCIENCE.getValue())))
                 .andExpect(jsonPath("$.data.categoryList[1].tagList[1]", is(PHYSICS.getValue())))
                 .andExpect(jsonPath("$.data.categoryList[1].tagList[2]", is(CHEMISTRY.getValue())));

만약 저장소/매퍼에서 순서 보장을 하지 않기로 결정한다면, containsInAnyOrder 매처 사용으로 순서 의존성을 제거하는 것도 대안입니다.

src/main/java/konkuk/thip/feed/adapter/in/web/FeedQueryController.java (1)

101-108: 새 엔드포인트 추가 적절. 캐시 고려 제안

정적에 가까운 카테고리/태그 매핑 조회 API 특성상, 서비스 레이어에 캐시(@Cacheable) 도입을 고려하면 DB 부하를 크게 줄일 수 있습니다. 변경 빈도가 낮다면 TTL/수동 무효화 전략으로 충분합니다.

src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1)

70-81: 엔드포인트 설계 타당. 메서드 명칭을 의도에 맞게 구체화 제안

GET은 상태 변경이 없고 검증/조회 기능만 수행하므로 적절합니다. 다만 메서드명이 pinRecord라서 실제로 핀을 수행한다고 오해될 수 있습니다. 의미 전달을 위해 메서드명을 getRecordPinInfo 정도로 변경하는 것을 제안합니다(경로는 유지).

다음과 같이 메서드명만 수정 가능합니다:

-    public BaseResponse<RecordPinResponse> pinRecord(
+    public BaseResponse<RecordPinResponse> getRecordPinInfo(
src/main/java/konkuk/thip/record/application/service/RecordPinService.java (1)

36-36: 사소한 스타일: 인자 간 공백

가독성을 위해 콤마 뒤 공백을 권장합니다.

-        record.validatePin(query.userId(),query.roomId());
+        record.validatePin(query.userId(), query.roomId());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 076b6a9 and 40f50a8.

📒 Files selected for processing (29)
  • src/main/java/konkuk/thip/book/application/service/BookSavedService.java (1 hunks)
  • src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/in/web/FeedQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowWriteInfoResponse.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java (2 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/port/in/FeedShowWriteInfoUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/in/dto/TagsWithCategoryResult.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/port/out/dto/TagCategoryQueryDto.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedShowWriteInfoService.java (1 hunks)
  • src/main/java/konkuk/thip/post/application/service/PostLikeService.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/response/RecordPinResponse.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/port/in/RecordPinUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/port/in/dto/RecordPinQuery.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordCreateService.java (2 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordPinService.java (1 hunks)
  • src/main/java/konkuk/thip/record/domain/Record.java (2 hunks)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowWriteInfoAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/record/adapter/in/web/RecordPinAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.

Applied to files:

  • src/main/java/konkuk/thip/record/application/service/RecordCreateService.java
🧬 Code Graph Analysis (3)
src/test/java/konkuk/thip/record/adapter/in/web/RecordPinAPITest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (35-361)
src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowWriteInfoAPITest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (35-361)
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1)
src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java (1)
  • Tag (21-58)
🔇 Additional comments (26)
src/main/java/konkuk/thip/book/application/service/BookSavedService.java (1)

13-13: Spring @transactional 전환 적절

jakarta → Spring 트랜잭션 애노테이션 전환은 일관성 측면과 readOnly 등 Spring 확장 옵션 활용 측면에서 적절합니다.

src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java (1)

12-12: Spring @transactional 전환 적절

트랜잭션 애노테이션 전환 자체는 문제 없습니다. deleteComment는 쓰기 트랜잭션이어야 하므로 기본 설정(readOnly=false)도 적절합니다.

src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java (2)

12-12: Spring @transactional 전환 적절

deleteRecord는 쓰기 작업이므로 기본 트랜잭션 설정으로 적절합니다.


28-35: 도메인/권한 검증 흐름 적절

Room 참여자 검증 후 기록 삭제 권한 검증 흐름은 프로젝트 학습사항(Record/Vote는 Room 소속, Feed는 아님)에 부합합니다.

src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (2)

10-10: Spring @transactional 전환 적절

삭제 플로우에 쓰기 트랜잭션 적용 적절합니다.


10-10: jakarta.transaction.Transactional 사용 검토 완료

rg를 통해 jakarta.transaction.Transactional 패키지 사용 여부를 전역 검색한 결과, 잔여 사용이 발견되지 않았습니다. 추가 조치가 필요 없습니다.

src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java (1)

15-15: Spring @transactional 전환 적절

좋아요 토글은 쓰기 트랜잭션 필요하므로 전환이 적절합니다.

src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (1)

12-12: Spring @transactional로의 마이그레이션 적합

jakarta → Spring의 트랜잭션 애노테이션 변경은 일관성 및 세밀한 옵션 활용 측면에서 적절합니다. 다른 서비스들과 동일 패턴 유지도 👍

src/main/java/konkuk/thip/post/application/service/PostLikeService.java (1)

14-14: Spring @transactional 사용으로의 통일 👍

Spring 트랜잭션 애노테이션 사용으로 옵션 호환성이 좋아집니다. 다른 모듈과의 일관성도 확보되었습니다.

src/main/java/konkuk/thip/record/application/service/RecordCreateService.java (3)

18-18: Spring @transactional로 변경된 점 확인

트랜잭션 애노테이션을 Spring으로 통일한 변경, 문제 없습니다.


36-41: 트랜잭션 경계 지정 및 룸 멤버십 검증 호출 방식 적절

메서드 레벨 @transactional 적용은 본 유즈케이스에 적합합니다. 또한 RoomParticipantValidator를 통해 룸 멤버십을 검증하는 패턴은 팀 선호(학습 노트: RoomParticipantPolicy로의 캡슐화)와 부합합니다.


50-55: Book 조회 안전성은 이미 보장됩니다
BookCommandPort.findById(...)의 구현(BookCommandPersistenceAdapter)에서 bookJpaRepository.findById(id).orElseThrow(...)을 호출하므로, 조회 실패 시 예외가 발생하고 null이 반환되지 않습니다. 또한 getByIdOrThrow 스타일의 메서드는 BookCommandPort에 존재하지 않습니다.
– 해당 제안은 불필요하므로 무시하셔도 좋습니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/thip/feed/application/port/out/dto/TagCategoryQueryDto.java (1)

1-15: QueryDSL 5.0.0 + APT 설정 검증 완료

  • build.gradle에 이미 다음 의존성과 APT 설정이 적용되어 있습니다.
    • implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    • annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
  • build/generated/querydsl 디렉터리가 main.java.srcDirs에 포함되어 있어 QType 생성 경로가 올바르게 설정되어 있습니다.
  • QueryDSL 5.x부터 record의 canonical constructor에 @QueryProjection 사용을 공식적으로 지원하므로, 추가 조치 없이 정상적으로 QTagCategoryQueryDto가 생성됩니다.
src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java (1)

3-3: 새 DTO import 추가 적절

findAllTags 도입에 따른 DTO import가 올바르게 반영되었습니다.

src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (1)

5-5: 포트 의존성 import 추가 적절

TagCategoryQueryDto, List import 추가가 변경 사항과 일치하며 문제 없습니다.

Also applies to: 9-9

src/test/java/konkuk/thip/record/domain/RecordTest.java (1)

209-237: validatePin 도메인 검증 케이스 3종(작성자 아님/roomId 불일치/성공) 커버 양호

핵심 제약을 간결히 검증하고 있으며, 에러 코드(RECORD_ACCESS_FORBIDDEN)도 일관되게 점검하고 있어 좋습니다.

src/main/java/konkuk/thip/record/application/port/in/RecordPinUseCase.java (1)

3-7: Query API의 직접 DTO 참조는 팀 컨벤션에 부합합니다.
THIP 프로젝트의 CQRS 아키텍처에서는 조회 전용 application 계층(use case)이 adapter.in.web.response 패키지의 DTO를 직접 반환하는 것을 허용하고 있으므로, RecordPinUseCase의 RecordPinResponse 반환은 의도된 설계입니다. 해당 포트의 반환형 변경 제안은 불필요합니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/thip/feed/application/port/in/FeedShowWriteInfoUseCase.java (1)

3-7: 계층 분리 위반: Application 포트 레이어가 웹 어댑터 타입에 의존함
application.port.in 및 port.out 모듈에서 adapter.in.web.response 패키지의 DTO를 import/반환하고 있습니다. 포트 레이어는 웹 어댑터를 몰라야 하며, 애플리케이션 전용 DTO(application.port.in.dto)로 반환형을 변경한 뒤, 컨트롤러에서 웹 응답 DTO로 감싸는 리팩터링이 필요합니다.

확인된 주요 위치 (rg 스크립트 결과):

  • feed/application/port/in/*.java
  • feed/application/port/out/*.java
  • room/application/port/in/*.java
  • room/application/port/out/*.java
  • user, vote, record, comment, recentSearch 등 다수의 모듈

예시: FeedShowWriteInfoUseCase

-import konkuk.thip.feed.adapter.in.web.response.FeedShowWriteInfoResponse;
+import konkuk.thip.feed.application.port.in.dto.TagsWithCategoryResult;

 public interface FeedShowWriteInfoUseCase {
-    FeedShowWriteInfoResponse showFeedWriteInfo();
+    java.util.List<TagsWithCategoryResult> showFeedWriteInfo();
 }

컨트롤러에서는 반환된 DTO 리스트를

FeedShowWriteInfoResponse.of(tagsWithCategoryResultList)

처럼 변환해 사용해주세요.

포트 전반에 걸친 어댑터 의존 제거 및 application DTO 활용으로 구조를 개선해야 합니다.

⛔ Skipped due to learnings
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#78
File: src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java:3-3
Timestamp: 2025-07-14T18:22:56.538Z
Learning: THIP 프로젝트에서는 Query API(조회 API)에 한해서는 application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하는 것을 허용함. 이는 CQRS 아키텍처에서 읽기 전용 작업의 효율성을 위한 팀 컨벤션임.
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)

139-144: RECORD_PIN API에서 ROOM_NOT_FOUND/USER_NOT_FOUND 미발생으로 Swagger 수정 불필요
현재 RecordPinService#pinRecord 흐름을 보면:

  • 방 존재 여부를 별도로 조회하지 않고 RoomParticipantValidator.validateUserIsRoomMember → 존재하지 않거나 미참여 시 ROOM_ACCESS_FORBIDDEN
  • 기록 조회 getByIdOrThrowRECORD_NOT_FOUND
  • 권한 검사 record.validatePinRECORD_ACCESS_FORBIDDEN
    따라서 RECORD_PIN 응답 정의에 ROOM_NOT_FOUND 혹은 USER_NOT_FOUND를 추가할 이유가 없습니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/thip/feed/application/service/FeedShowWriteInfoService.java (1)

14-16: 간결한 유스케이스 구현 및 트랜잭션 설정이 적절합니다

의존성 주입과 @transactional(readOnly = true) 설정으로 조회 유스케이스에 맞게 잘 구성되었습니다.

src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (1)

11-16: 임포트 변경 사항 문제 없음

기능적 영향이 없는 임포트 변경으로 보입니다.

src/main/java/konkuk/thip/record/adapter/in/web/response/RecordPinResponse.java (1)

5-17: 단순하고 명확한 DTO 변환 팩토리 메서드 설계 좋습니다

레코드 기반 응답과 정적 팩토리로의 매핑이 간결합니다. 현재 요구사항에 적합해 보입니다.

src/main/java/konkuk/thip/feed/adapter/in/web/FeedQueryController.java (2)

28-28: use case 주입 추가 적절

@requiredargsconstructor로 안전하게 주입되며 책임 분리가 명확합니다.


10-10: 와일드카드 import 사용 규칙 확인 필요

프로젝트의 Checkstyle/Spotless 설정에서 import ...* 허용 여부가 다를 수 있습니다. 빌드 파이프라인에서 실패하지 않는지 확인해주세요. 필요 시 명시적 import로 되돌리는 편이 안전합니다.

src/main/java/konkuk/thip/record/application/service/RecordPinService.java (2)

27-41: 검증 순서 적절하며 정보 노출 최소화에 유리

방 참여자 검증 → 기록 존재/권한 검증 → 책 조회 순으로 구성돼 비참여자에게 리소스 존재 여부를 누설하지 않습니다. 프로젝트 러닝(Record/Vote는 Room 소속이고 관련 작업 시 Room 참가자 검증 필요)에 부합합니다.


3-5: CQRS 관점에서 QueryPort 사용 권장 (CommandPort 오남용 개선)

조회 용도의 findBookByRoomIdBookCommandPort에서 호출하고 있습니다. 읽기 전용 유스케이스이므로 BookQueryPort 사용이 더 명확합니다. 포트가 존재한다면 아래와 같이 교체를 권장합니다.

-import konkuk.thip.book.application.port.out.BookCommandPort;
+import konkuk.thip.book.application.port.out.BookQueryPort;
@@
-    private final BookCommandPort bookCommandPort;
+    private final BookQueryPort bookQueryPort;
@@
-        Book book = bookCommandPort.findBookByRoomId(query.roomId());
+        Book book = bookQueryPort.findBookByRoomId(query.roomId());

포트가 없다면 생성해 분리하거나, 최소한 메서드 명/포트 명을 Query 성격에 맞게 리네이밍하여 의도를 드러내 주세요.

Also applies to: 21-22, 39-39

⛔ Skipped due to learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.

Comment thread src/test/java/konkuk/thip/record/domain/RecordTest.java
Copy link
Copy Markdown

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40f50a8 and 6c582cd.

📒 Files selected for processing (3)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedShowWriteInfoService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/konkuk/thip/feed/application/service/FeedShowWriteInfoService.java
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java
⏰ 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 (2)
src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (2)

108-109: LinkedHashMap 사용으로 카테고리 표시 순서 보존: 좋습니다

DB에서 넘어오는 순서를 그대로 유지할 수 있도록 LinkedHashMap을 지정하신 점 적절합니다.


112-115: Stream.toList()는 Java 17에서도 지원됩니다.
build.gradle의 Gradle Toolchain 설정이 JavaLanguageVersion.of(17)으로 되어 있어 Stream.toList() 사용에 호환성 이슈가 없습니다.

Copy link
Copy Markdown
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!! 리뷰 요구사항에 작성해주신 패키지 구조 관련해서는 어차피 리펙때 많은 수정이 있을거같으니 괜찮은 것 같습니다!!
트랜잭션 import 수정도 감사합니다!

리뷰 몇개 남겨봤는데 확인 부탁드립니다!!

Comment on lines +7 to +9
public record FeedShowWriteInfoResponse(
List<TagsWithCategoryResult> categoryList
) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3 : response 가 result를 의존하여 내부 데이터를 구성하는게 아니라, result를 내부 필드로 직접적으로 가지고 있는 것이라면, 그냥 response 내부에서 String category, List tagList 를 inner class 로 가지고 있는게 더 낫지 않나 생각합니다!

그런데 뭐 사소하니 넘어가도 좋을 것 같습니다!

}

public void validatePin(Long userId,Long roomId) {
validateRoomId(roomId);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이런 메서드가 있는줄은 몰랐네여 좋습니다!


public void validatePin(Long userId,Long roomId) {
validateRoomId(roomId);
validateCreator(userId);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

굳굳

Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

굿굿 코드가 깔끔하네여~~ 사소한 리뷰 하나 남겼습니다

Comment on lines +5 to +17
public record RecordPinResponse(
String bookTitle,
String authorName,
String bookImageUrl,
String isbn
) {
static public RecordPinResponse of(BookSelectableResult bookSelectableResult) {
return new RecordPinResponse(
bookSelectableResult.bookTitle(),
bookSelectableResult.authorName(),
bookSelectableResult.bookImageUrl(),
bookSelectableResult.isbn());
}
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.

엇 이거 저번에 한 api당 result도 각각 나눠서 쓰기로 했던 것 같은데 RecordPinResult 같은 걸로 주입 받는게 어떨까욥

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

아하 저는 재사용하려고해서 쓴거긴한데 일단 배포해두고 추후에 수정하겠습니닷

Comment on lines +104 to +110
default List<TagsWithCategoryResult> toTagsWithCategoryResult(List<TagCategoryQueryDto> rows) {
Map<String, List<String>> grouped = rows.stream()
.collect(Collectors.groupingBy(
TagCategoryQueryDto::categoryValue,
LinkedHashMap::new, // 순서 보장
Collectors.mapping(TagCategoryQueryDto::tagValue, Collectors.toList())
));
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.

오호 굿굿입니다

@hd0rable hd0rable merged commit 3b7b536 into develop Aug 13, 2025
2 checks passed
@hd0rable hd0rable deleted the feat/#161-feed-get-info branch August 13, 2025 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-219] [feat] 피드 작성을 위한 화면 조회 / 기록을 피드에 핀하기 필요한 정보 조회 api 개발

3 participants