Skip to content

[refactor] 기록장 조회 api 리팩토링#226

Merged
buzz0331 merged 6 commits into
developfrom
refactor/#225-get-record
Aug 15, 2025
Merged

[refactor] 기록장 조회 api 리팩토링#226
buzz0331 merged 6 commits into
developfrom
refactor/#225-get-record

Conversation

@buzz0331
Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 commented Aug 14, 2025

#️⃣ 연관된 이슈

closes #225

📝 작업 내용

기록장 조회 api를 다음과 같이 수정하였습니다.

  1. dtype 도입에 따른 instance of 구문 모두 제거
  2. isOverview 반환
  3. 헬퍼 서비스 도입 (RecordAccessValidator)
  4. RecordQueryMapper 도입

전체적으로 서비스 로직을 조금 분리하고자 했습니다! 아직 쿼리 성능이 최적화되지 않은 상태여서 이는 저희가 수정하기로 했던 상속 전략을 도입해보고 한번 다시 테스트 해봐야 할 것 같습니다!

추가적으로 EC2에서 OOME 에러로 인해 서버가 다운되는 경우가 발생해서 Dockerfile을 통해 이미지 빌드시에 JAVA OPTION을 달도록 추가했습니다. (로컬에서는 빌드가 되긴했는데 cd에서 어떻게 될지는 모르겠네여 하핳)

📸 스크린샷

스크린샷 2025-08-15 오전 1 46 50

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

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

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

Summary by CodeRabbit

  • New Features
    • 검색 결과에 게시글의 개요 노출 여부(isOverview) 필드가 추가되어 클라이언트 표시가 정교해졌습니다.
  • Refactor
    • 필터 검증과 데이터 매핑이 개선되어 정렬·필터 결과가 더 일관되며, 잠금/블러 처리로 비공개 콘텐츠 표시가 예측 가능해졌습니다.
  • Chores
    • 컨테이너 JVM 메모리 기본값을 설정하고 메모리 부족 시 즉시 종료하도록 구성해 서비스 안정성을 향상했습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 14, 2025

Walkthrough

Docker JVM options added. RecordSearchResponse.PostDto gains isOverview. Query repository switches from instanceof checks to dtype-based logic. New RecordQueryMapper introduced. RecordSearchService refactored to use RecordAccessValidator and mapper; validation/blur moved. New RecordAccessValidator added.

Changes

Cohort / File(s) Summary
Container config
Dockerfile
Add ENV JAVA_TOOL_OPTIONS with -Xms512m, -Xmx2g, -XX:+ExitOnOutOfMemoryError after ARG PORT=8000.
API DTO shape
src/main/java/.../RecordSearchResponse.java
PostDto adds boolean isOverview between commentCount and isLiked; public record signature updated.
Query dtype refactor
src/main/java/.../RecordQueryRepositoryImpl.java
Replace JPA instanceof checks with discriminator post.dtype comparisons (RECORD/VOTE). Remove postTypeExpr; pass dtype in projection; adjust conditions and expressions accordingly.
Mapper introduction
src/main/java/.../RecordQueryMapper.java
New MapStruct mapper to build RecordSearchResponse.PostDto from PostQueryDto and extras; formats date, maps isOverview, strict unmapped policy.
Service refactor
src/main/java/.../RecordSearchService.java
Delegate validations/blur to RecordAccessValidator; use RecordQueryMapper for DTO creation; constructor updated; use VOTE.getType() in filters; remove local helpers.
Validation utility
src/main/java/.../service/validator/RecordAccessValidator.java
New validator with group/my filter checks, blur utility, and lock check; throws BusinessException on invalid params.

Sequence Diagram(s)

sequenceDiagram
  participant API as Controller
  participant Svc as RecordSearchService
  participant Val as RecordAccessValidator
  participant Repo as RecordQueryPort
  participant Map as RecordQueryMapper

  API->>Svc: searchRecords(params)
  Svc->>Val: validate filters / isLocked / blur
  Svc->>Repo: fetch PostQueryDto list (dtype-based query)
  Repo-->>Svc: PostQueryDto[]
  Svc->>Map: toPostDto(dto, content, flags, voteItems)
  Map-->>Svc: RecordSearchResponse.PostDto
  Svc-->>API: RecordSearchResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
기록장 조회 API 쿼리 개선으로 OOME 완화 (#225)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add JVM memory/OOM flags (Dockerfile, ENV JAVA_TOOL_OPTIONS after ARG PORT=8000) Not related to “쿼리 개선” objective; container runtime tuning is outside the query refactor scope.

Possibly related PRs

Suggested reviewers

  • seongjunnoh
  • hd0rable

Poem

토끼는 귀 꼭 세우고 코드를 훑어본다
dtype 따라 길을 나누고, 맵퍼로 길을 잇는다
검증은 밭을 매듯 깨끗이 다듬고
메모리는 상자에 곱게 담아 둔다 🧺
깡총, 검색의 들판에 OOM은 없구나! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#225-get-record

🪧 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

Test Results

397 tests   397 ✅  32s ⏱️
119 suites    0 💤
119 files      0 ❌

Results for commit 0d9b6b4.

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

🧹 Nitpick comments (5)
src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordQueryRepositoryImpl.java (1)

191-191: dtype 필드명 변경 시 영향도 고려 필요

주석에 "추후에 상속 구조 해지시 type 필드로 구분"이라고 되어 있는데, 이는 향후 마이그레이션 계획을 암시합니다. 필드명 변경 시 API 응답과 데이터베이스 스키마에 영향을 줄 수 있으니 신중한 계획이 필요합니다.

상속 구조 해지 시 고려사항:

  • 데이터베이스 마이그레이션 스크립트 준비
  • API 버전 관리를 통한 단계적 전환
  • 클라이언트 코드 호환성 유지
src/main/java/konkuk/thip/record/application/service/validator/RecordAccessValidator.java (2)

71-73: isLocked 메서드의 파라미터명 개선 필요

currentPage라는 파라미터명이 혼란을 줄 수 있습니다. 실제로는 사용자가 읽은 페이지 수를 의미하는 것 같습니다.

-public boolean isLocked(int currentPage, int bookPageSize) {
-    return currentPage < bookPageSize;
+public boolean isLocked(int userReadPages, int recordPage) {
+    return userReadPages < recordPage;
 }

22-24: 80% 진행률 기준값을 상수로 정의 권장

총평보기 필터의 80% 기준값이 하드코딩되어 있습니다. 향후 변경 가능성을 고려하여 상수로 정의하는 것이 좋습니다.

 @HelperService
 public class RecordAccessValidator {
 
     private static final String BLURRED_STRING = "여긴 못 지나가지롱~~";
+    private static final double OVERVIEW_MINIMUM_PROGRESS_PERCENTAGE = 80.0;
 
     public void validateGroupRecordFilters(Integer pageStart, Integer pageEnd, Boolean isPageFilter, Boolean isOverview, int bookPageSize, double currentPercentage) {
         // ... 
         if(!isPageFilter && isOverview) { // 총평보기 필터만 적용된 경우
             if (pageStart != null || pageEnd != null) {
                 throw new BusinessException(ErrorCode.API_INVALID_PARAM, new IllegalArgumentException("총평보기 필터만 적용된 경우 pageStart와 pageEnd는 null이어야 합니다."));
             }
-            if (currentPercentage < 80) {
+            if (currentPercentage < OVERVIEW_MINIMUM_PROGRESS_PERCENTAGE) {
                 throw new BusinessException(ErrorCode.API_INVALID_PARAM, new IllegalArgumentException("총평보기 필터가 적용된 경우 현재 독서 진행률은 80% 이상이어야 합니다."));
             }
         }
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (2)

135-143: 타입 분기 로직 개선 제안: 확장성/견고성 강화

현재는 RECORD면 빈 리스트, 그 외는 VOTE로 가정합니다. 타입 추가 시 안전하게 실패하도록 명시적인 분기를 고려해 주세요.

예:

if (RECORD.getType().equals(dto.postType())) {
  return List.of();
} else if (VOTE.getType().equals(dto.postType())) {
  // map
} else {
  // 알 수 없는 타입 - log/예외 처리
}

146-164: 경계 케이스 검증 보강 제안 (빈 목록/합계 0/사이즈 불일치)

  • items가 비어있을 때 즉시 List.of() 반환하면 불필요 계산을 줄일 수 있습니다.
  • VoteItem.calculatePercentages(counts)가 항상 items.size()와 동일 길이를 반환한다는 계약이 필요합니다. 계약이 불분명하다면 방어 코드(사이즈 동일성 체크)를 추가해 주십시오.
  • 159라인의 itemName이 null일 경우 createBlurredString의 null 처리 보장 필요.

예시(간단 가드):

-    private List<RecordSearchResponse.PostDto.VoteItemDto> mapToVoteItemDtos(List<VoteItemQueryDto> items, boolean isLocked) {
+    private List<RecordSearchResponse.PostDto.VoteItemDto> mapToVoteItemDtos(List<VoteItemQueryDto> items, boolean isLocked) {
+        if (items.isEmpty()) return List.of();
         List<Integer> counts = items.stream()
             .map(VoteItemQueryDto::voteCount)
             .toList();
         List<Integer> percentages = VoteItem.calculatePercentages(counts);
+        // (선택) percentages.size() 검증 및 mismatch 대응 로직
         return IntStream.range(0, items.size())
             .mapToObj(i -> RecordSearchResponse.PostDto.VoteItemDto.of(
                 items.get(i).voteItemId(),
-                isLocked ? recordAccessValidator.createBlurredString(items.get(i).itemName()) : items.get(i).itemName(),
+                isLocked ? recordAccessValidator.createBlurredString(items.get(i).itemName()) : items.get(i).itemName(),
                 percentages.get(i),
                 items.get(i).isVoted()
             ))
             .toList();
     }
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 82c4a6b and 0d9b6b4.

📒 Files selected for processing (6)
  • Dockerfile (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordQueryRepositoryImpl.java (6 hunks)
  • src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (9 hunks)
  • src/main/java/konkuk/thip/record/application/service/validator/RecordAccessValidator.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java (1)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
  • DateUtil (12-62)
🔇 Additional comments (5)
Dockerfile (1)

4-4: 컨테이너 재시작 정책 미확인: 수동 검증 필요
리포지토리 내 YAML/JSON 파일에서 restart 또는 restartPolicy 설정을 찾지 못했습니다. 사용 중인 오케스트레이션(Compose, Kubernetes, ECS 등) 레벨에서 컨테이너 재시작 정책이 적절히 설정되어 있는지 수동으로 확인해 주세요.

추가로 JVM OOME 대응을 위해 아래 사항을 권장드립니다:

  • EC2 인스턴스 메모리(최소 2GB 이상) 사양 확인
  • 힙 덤프 생성 옵션 추가
    -ENV JAVA_TOOL_OPTIONS="-Xms512m -Xmx2g -XX:+ExitOnOutOfMemoryError"
    +ENV JAVA_TOOL_OPTIONS="-Xms512m -Xmx2g -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap-dump.hprof"
src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordQueryRepositoryImpl.java (1)

23-24: dtype 기반 구분 로직으로 개선됨

instanceof 체크를 dtype 기반으로 변경한 것은 좋은 리팩토링입니다. 성능 향상과 코드 가독성 개선이 기대됩니다.

Also applies to: 63-64, 67-68, 100-101, 111-112, 129-131, 137-139

src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java (1)

12-38: MapStruct 매퍼 구현이 깔끔합니다

MapStruct를 활용한 매핑 로직이 명확하고 유지보수하기 좋습니다. unmappedTargetPolicy = ReportingPolicy.ERROR 설정으로 매핑 누락을 방지한 점도 좋습니다.

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

49-51: 의존성 분리 LGTM — Validator/Mapper 주입으로 책임이 명확해졌습니다

서비스 내 복잡도가 줄고 테스트 용이성이 좋아졌습니다.


126-133: LGTM — 정렬 분기 메서드 추출로 응집도/가독성 향상

정렬별 조회 로직을 메서드로 분리한 점 좋습니다. 예외 케이스(알 수 없는 정렬값)에 대한 방어로직은 RecordSearchSortParams.from(sort)에서 이미 처리된다고 가정하겠습니다.

String content,
int likeCount,
int commentCount,
boolean isOverview,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

API 응답 변경에 대한 하위 호환성 확인 필요

PostDto에 새로운 필드 isOverview가 추가되었습니다. 이는 API 응답 구조를 변경하므로 클라이언트 측 영향도 확인이 필요합니다.

또한 이 필드가 기록 타입과 투표 타입 모두에 적용되는 것이 맞는지 확인이 필요합니다.


🏁 Script executed:

#!/bin/bash
# API 문서나 클라이언트 코드에서 RecordSearchResponse 사용 확인
rg "RecordSearchResponse|PostDto" --type-add 'docs:*.{md,adoc,txt}' -t docs -t java -t kotlin -t swift -A 2

Length of output: 7142


🏁 Script executed:

#!/bin/bash
# Display the PostDto record definition around the isOverview field
rg -n "record PostDto" -A 10 src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java

Length of output: 453


🏁 Script executed:

#!/bin/bash
# PostDto 에 isOverview 필드가 포함되었는지 확인
rg -n "isOverview" -A3 src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java
# RecordQueryMapper 의 toPostDto 시그니처 확인
rg -n "PostDto toPostDto" -A5 src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java

Length of output: 645


추가 확인: RecordQueryMapper.toPostDtoisOverview 파라미터 미반영

다음 지점에서 PostDto 레코드에 선언된 boolean isOverview 필드를 매핑하기 위한 인자가 빠져 있습니다.
MapStruct 매핑 인터페이스 시그니처를 업데이트해야 컴파일 에러 및 API 응답 누락을 방지할 수 있습니다.

  • 파일: src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java
    • 기존 시그니처:
      RecordSearchResponse.PostDto toPostDto(
          PostQueryDto dto,
          String content,
          boolean isLiked,
          boolean isWriter,
          boolean isLocked,
          List<RecordSearchResponse.PostDto.VoteItemDto> voteItems
      );
    • 수정안 예시:
      RecordSearchResponse.PostDto toPostDto(
          PostQueryDto dto,
          String content,
          boolean isLiked,
          boolean isWriter,
          boolean isLocked,
          boolean isOverview, // ➡️ 추가
          List<RecordSearchResponse.PostDto.VoteItemDto> voteItems
      );
  • RecordSearchService 등 호출부에서도 isOverview 값을 함께 전달하도록 변경 필요

또한 isOverview가 기록 타입(RECORD)과 투표 타입(VOTE) 모두에 올바르게 적용되는지 로직 검증을 권장합니다.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java
around line 28, the PostDto declares a boolean isOverview but
RecordQueryMapper.toPostDto signature is missing that parameter; update the
mapper interface signature to include boolean isOverview in the correct
position, update all callers (e.g., RecordSearchService and other call sites) to
pass the isOverview value when invoking toPostDto, and ensure the MapStruct
mapping and any conditional logic apply isOverview correctly for both RECORD and
VOTE types.

public interface RecordQueryMapper {

@Mapping(target = "postId", source = "dto.postId")
@Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(dto.postDate()))")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

날짜 포맷팅 null 체크 필요

dto.postDate()가 null일 경우 NPE가 발생할 수 있습니다. DateUtil.formatBeforeTime 메서드가 null을 처리하는지 확인이 필요합니다.

필요시 null 체크 추가:

-@Mapping(target = "postDate",       expression = "java(DateUtil.formatBeforeTime(dto.postDate()))")
+@Mapping(target = "postDate",       expression = "java(dto.postDate() != null ? DateUtil.formatBeforeTime(dto.postDate()) : null)")

🏁 Script executed:

#!/bin/bash
# DateUtil.formatBeforeTime 메서드의 null 처리 확인
ast-grep --pattern 'public static String formatBeforeTime($_) {
  $$$
}'

Length of output: 883


날짜 포맷팅 시 NullPointerException 방지용 null 체크 추가 필요

RecordQueryMapper.java에서 dto.postDate()가 null일 경우 DateUtil.formatBeforeTime이 null을 처리하지 않아 NPE가 발생할 수 있습니다.

  • 파일: src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java
  • 라인: 16

아래와 같이 null 체크를 추가해 주세요:

- @Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(dto.postDate()))")
+ @Mapping(target = "postDate", expression = "java(dto.postDate() != null ? DateUtil.formatBeforeTime(dto.postDate()) : null)")
📝 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
@Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(dto.postDate()))")
@Mapping(target = "postDate", expression = "java(dto.postDate() != null ? DateUtil.formatBeforeTime(dto.postDate()) : null)")
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/mapper/RecordQueryMapper.java
around line 16, the Mapping expression uses
DateUtil.formatBeforeTime(dto.postDate()) which will NPE if dto.postDate() is
null; update the mapping expression to perform a null check and return null when
postDate is null (e.g. use a conditional/ternary: dto.postDate() != null ?
DateUtil.formatBeforeTime(dto.postDate()) : null) so MapStruct won't call the
formatter with a null value.

CursorBasedList<PostQueryDto> cursorBasedList = switch(RecordSearchTypeParams.from(recordSearchQuery.type())) {
case GROUP -> {
validateGroupRecordFilters(pageStart, pageEnd, isPageFilter, isOverview, book.getPageCount(), roomParticipant.getUserPercentage());
recordAccessValidator.validateGroupRecordFilters(pageStart, pageEnd, isPageFilter, isOverview, book.getPageCount(), roomParticipant.getUserPercentage());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Boolean null 처리 누락으로 NPE 가능 — null-safe 비교로 수정 필요

isOverviewnull일 경우 if (!isOverview)에서 NPE가 발생합니다. Validator가 선행 검증을 하더라도 이 지점에서 바로 NPE가 날 수 있으므로 null-safe 비교로 바꿔주세요.

적용 diff:

-                if(!isOverview) {
+                if (Boolean.FALSE.equals(isOverview)) {
                     if(pageStart == null) pageStart = 0;
                     if(pageEnd == null) pageEnd = book.getPageCount();
                 }

추가로, 기본값 주입 로직(pageStart, pageEnd)은 Validator로 이관하면 서비스 레이어가 더 단순해집니다.

Also applies to: 78-80

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
around lines 75 (and also apply same change at 78-80), the direct boolean
unboxing `if (!isOverview)` can throw NPE when isOverview is null; replace those
checks with a null-safe comparison such as `if
(!Boolean.TRUE.equals(isOverview))` (or `if (Boolean.FALSE.equals(isOverview))`
depending on intended semantics) so null won't NPE, and move the defaulting
logic for pageStart/pageEnd into the validator to simplify the service layer.

Comment on lines +84 to 86
recordAccessValidator.validateMyRecordFilters(pageStart, pageEnd, isPageFilter, isOverview, recordSearchQuery.sort());
yield recordQueryPort.searchMyRecords(roomId, userId, cursor);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

MINE 케이스에 정렬(sort) 파라미터가 반영되지 않습니다 — 요구사항 확인 필요

validateMyRecordFilters에서 sort를 검증하지만, 실제 조회는 recordQueryPort.searchMyRecords(...)로 고정되어 정렬이 적용되지 않습니다. 이전 동작이 정렬 지원이었다면 회귀 가능성이 있습니다. 의도라면 Validator에서 sort를 제한(LATEST만 허용 등)하고 API 문서로 명확히 하거나, 아니라면 MINE도 정렬 분기를 추가해야 합니다.

필요시, GROUP과 유사한 정렬 위임 메서드를 도입하는 방안을 고려해 주세요.

아래 스크립트로 포트/리포지토리에 MINE 정렬 메서드 존재 여부를 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# MINE 정렬 관련 메서드 존재 여부 확인
rg -n "searchMyRecords\(" -A 3
rg -n "searchMyRecordsBy" -A 2
rg -n "RecordSearchSortParams" -A 10
fd -a "RecordQueryPort.java" | xargs -I{} rg -n "searchMyRecords" {}
fd -a "RecordQueryRepository" | xargs -I{} rg -n "searchMyRecords" {}

Length of output: 7788


MINE 케이스에 sort 파라미터가 실제로 적용되지 않음 — 구현 수정 필요
validateMyRecordFilters에서만 sort를 검증하고, 실제 호출(recordQueryPort.searchMyRecords)에는 전달되지 않아 정렬이 기본값으로 고정됩니다.
요구사항에 따라 아래 중 하나를 적용해주세요:

  • MINE 케이스에도 GROUP과 유사하게 RecordSearchSortParams.from(sort) 분기 추가
    searchMyRecordsByLatest / searchMyRecordsByLike / searchMyRecordsByComment 등 메서드 추가 및 호출
  • 또는 API 문서·Validator에서 허용 가능한 정렬 조건(LATEST만 허용 등)을 명시하여 sort 파라미터 사용을 제한

확인 위치:

  • src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (84–86줄)
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
around lines 84–86, the MINE branch validates sort but does not apply it to the
query; update the MINE case to mirror GROUP: convert the incoming sort via
RecordSearchSortParams.from(sort) and branch on that enum to call the
corresponding recordQueryPort methods (e.g., searchMyRecordsByLatest /
searchMyRecordsByLike / searchMyRecordsByComment) passing roomId, userId and
cursor as appropriate; ensure method signatures on recordQueryPort exist or are
added and that the existing validator remains unchanged.

Comment on lines +91 to 93
.filter(postQueryDto -> postQueryDto.postType().equals(VOTE.getType()))
.map(PostQueryDto::postId)
.collect(Collectors.toSet()), userId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

NPE 위험: equals 호출 순서 통일 필요 (상수-우선 비교)

postQueryDto.postType()가 null이면 NPE가 발생합니다. 아래와 같이 상수-우선 비교로 변경해 주세요. 137라인에서는 이미 상수-우선 비교를 사용 중이므로 스타일도 통일됩니다.

적용 diff:

-                .filter(postQueryDto -> postQueryDto.postType().equals(VOTE.getType()))
+                .filter(postQueryDto -> VOTE.getType().equals(postQueryDto.postType()))
📝 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
.filter(postQueryDto -> postQueryDto.postType().equals(VOTE.getType()))
.map(PostQueryDto::postId)
.collect(Collectors.toSet()), userId);
.filter(postQueryDto -> VOTE.getType().equals(postQueryDto.postType()))
.map(PostQueryDto::postId)
.collect(Collectors.toSet()), userId);
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
around lines 91 to 93, the current equals call uses
postQueryDto.postType().equals(VOTE.getType()) which can NPE if postType() is
null; change to the constant-first comparison
VOTE.getType().equals(postQueryDto.postType()) to avoid NPE and match the style
used at line 137, ensuring null-safe comparison and consistent coding style.

Comment on lines +102 to +112
.map(dto -> {
boolean isLocked = recordAccessValidator.isLocked(roomParticipant.getCurrentPage(), dto.page());
boolean isWriter = dto.userId().equals(userId);
boolean isLiked = likedPostIds.contains(dto.postId());
String content = isLocked ? recordAccessValidator.createBlurredString(dto.content()) : dto.content();

List<RecordSearchResponse.PostDto.VoteItemDto> voteItems =
getVoteItemDtosIfApplicable(dto, voteItemQueryMap, isLocked);

return recordQueryMapper.toPostDto(dto, content, isLiked, isWriter, isLocked, voteItems);
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

잠재적 NPE 및 소폭의 가독성 개선 제안

  • dto.userId().equals(userId)dto.userId()가 null이면 NPE가 날 수 있습니다. Objects.equals 사용을 권장합니다.
  • createBlurredString(dto.content())dto.content()가 null일 때의 처리 방식을 확인해 주세요. null을 그대로 반환해야 한다면 Validator 메서드가 null-safe임을 보장해야 합니다.

적용 diff:

-                    boolean isWriter = dto.userId().equals(userId);
+                    boolean isWriter = java.util.Objects.equals(dto.userId(), userId);

추가(선택): 파일 상단 import로 대체 가능

import java.util.Objects;

🏁 Script executed:

#!/bin/bash
# RecordSearchService 파일에서 map(dto -> ...) 주변 코드 확인
sed -n '80,140p' src/main/java/konkuk/thip/record/application/service/RecordSearchService.java

# RecordAccessValidator에서 createBlurredString의 null 처리 확인
fd RecordAccessValidator.java | xargs sed -n '1,200p'

Length of output: 6849


Objects.equals로 NPE 방지 제안
dto.userId().equals(userId) 대신 Objects.equals(dto.userId(), userId)를 사용해 NPE를 방지하세요.

createBlurredString()는 내부에서 contents == null일 때 그대로 반환하므로 null-safe가 이미 보장되어 있습니다. 별도 수정은 필요하지 않습니다.

수정 위치:

  • 파일: src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
  • 약 105번째 줄

적용 예시 diff:

-    boolean isWriter = dto.userId().equals(userId);
+    boolean isWriter = Objects.equals(dto.userId(), userId);

선택: 파일 상단에 import 추가

import java.util.Objects;
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
around lines 102 to 112, replace the null-unsafe equals call
dto.userId().equals(userId) with Objects.equals(dto.userId(), userId) to avoid
possible NPEs, and add the import java.util.Objects at the top of the file.

Comment on lines +50 to +69
public String createBlurredString(String contents) {
if (contents == null || contents.isEmpty()) {
return contents;
}

int originalLength = contents.length();
int blurLen = BLURRED_STRING.length();

// 필요한 전체 반복 횟수 계산
int repeat = originalLength / blurLen;

StringBuilder sb = new StringBuilder(originalLength);

// 몫 만큼 반복
for (int i = 0; i < repeat + 1; i++) {
sb.append(BLURRED_STRING);
}

return sb.toString();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

블러 처리 로직의 문자열 길이 불일치 문제

createBlurredString 메서드에서 원본 문자열 길이와 블러 처리된 문자열 길이가 일치하지 않습니다. 이는 UI 레이아웃 문제를 일으킬 수 있습니다.

 public String createBlurredString(String contents) {
     if (contents == null || contents.isEmpty()) {
         return contents;
     }
 
     int originalLength = contents.length();
     int blurLen = BLURRED_STRING.length();
 
-    // 필요한 전체 반복 횟수 계산
-    int repeat = originalLength / blurLen;
+    // 필요한 전체 반복 횟수 계산 (올림)
+    int repeat = (originalLength + blurLen - 1) / blurLen;
 
     StringBuilder sb = new StringBuilder(originalLength);
 
     // 몫 만큼 반복
-    for (int i = 0; i < repeat + 1; i++) {
+    for (int i = 0; i < repeat; i++) {
         sb.append(BLURRED_STRING);
     }
 
-    return sb.toString();
+    // 원본 길이만큼만 반환
+    return sb.substring(0, originalLength);
 }
📝 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
public String createBlurredString(String contents) {
if (contents == null || contents.isEmpty()) {
return contents;
}
int originalLength = contents.length();
int blurLen = BLURRED_STRING.length();
// 필요한 전체 반복 횟수 계산
int repeat = originalLength / blurLen;
StringBuilder sb = new StringBuilder(originalLength);
// 몫 만큼 반복
for (int i = 0; i < repeat + 1; i++) {
sb.append(BLURRED_STRING);
}
return sb.toString();
}
public String createBlurredString(String contents) {
if (contents == null || contents.isEmpty()) {
return contents;
}
int originalLength = contents.length();
int blurLen = BLURRED_STRING.length();
// 필요한 전체 반복 횟수 계산 (올림)
int repeat = (originalLength + blurLen - 1) / blurLen;
StringBuilder sb = new StringBuilder(originalLength);
// 몫 만큼 반복
for (int i = 0; i < repeat; i++) {
sb.append(BLURRED_STRING);
}
// 원본 길이만큼만 반환
return sb.substring(0, originalLength);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/record/application/service/validator/RecordAccessValidator.java
around lines 50 to 69, the method createBlurredString builds a blurred string by
repeating BLURRED_STRING but can produce a string longer than the original,
causing UI layout issues; change the logic to ensure the returned string length
exactly equals contents.length(): keep the existing null/empty check, compute
int originalLength = contents.length(), int blurLen = BLURRED_STRING.length(),
int repeat = originalLength / blurLen and int remainder = originalLength %
blurLen, initialize StringBuilder with capacity originalLength, append
BLURRED_STRING repeat times and then if remainder > 0 append
BLURRED_STRING.substring(0, remainder), and return the sb.toString(); this
guarantees exact length matching and efficient allocation.

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.

기록장 조회 이 친구 쉽지않네요 하하
일단 isOverview response 가 추가되는게 요구사항이었으니, 배포해서 api 연동상황보면서 성능 체크 다시 해보면서 개선 포인트 정리해보면 좋을 것 같습니다!

Comment thread Dockerfile
FROM openjdk:17

ARG PORT=8000
ENV JAVA_TOOL_OPTIONS="-Xms512m -Xmx2g -XX:+ExitOnOutOfMemoryError"
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.

도커 컨테이너 실행시 JVM 의 힙 메모리 크기를 제한하셨군요!
좀 찾아보니 컨테이너 전체의 메모리를 제한하고, 이 중 일정 비율을 힙 메모리에 할당하는 방식도 있는 것 같은데, 저희는 우선 테이블 구조 수정으로 쿼리 성능을 먼저 개선하는 것이 최우선순위 인 것 같네요 하하

Comment on lines -65 to +63
voteCondition.and(post.instanceOf(VoteJpaEntity.class))
voteCondition.and(post.dtype.eq(VOTE.getType()))
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.

LGTM
instanceOf(Vote ,,) 는 타입이 Vote 임을 확인하기 위해 posts table과 votes table 의 join 이 발생하는데, post의 dtype으로 비교하는 쿼리는 단순히 where 절로 비교하니 쿼리가 훨씬 가벼워질것 같습니다!

Comment on lines +7 to +8
@HelperService
public class RecordAccessValidator {
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.

굳굳

@buzz0331 buzz0331 merged commit 9b3d86b into develop Aug 15, 2025
4 checks passed
@buzz0331 buzz0331 deleted the refactor/#225-get-record branch August 15, 2025 04:52
@buzz0331 buzz0331 restored the refactor/#225-get-record branch August 15, 2025 04:53
@buzz0331 buzz0331 deleted the refactor/#225-get-record branch August 17, 2025 16:27
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-270] [refactor] 기록장 조회 api 수정

2 participants