Skip to content

Refactor/#138 vibe tone#139

Open
wlsldm wants to merge 3 commits intodevfrom
refactor/#138_vibe-tone
Open

Refactor/#138 vibe tone#139
wlsldm wants to merge 3 commits intodevfrom
refactor/#138_vibe-tone

Conversation

@wlsldm
Copy link
Collaborator

@wlsldm wlsldm commented Feb 12, 2026

🎋 이슈 및 작업중인 브랜치

🔑 주요 내용

  • 리캡 > 사용자 이용 패턴 조회 > 사용한 태그 개수 가 태그 개수가 아닌 업로드한 개수로 집계되어 이 부분 수정했습니다
  • 주간 리캡에서 지정하는 날자를 해당 주 월요일~조회한 당일 에서 해당 주의 월요일 ~ 일요일로 수정했습니다

Check List

  • Reviewers 등록을 하였나요?
  • Assignees 등록을 하였나요?
  • 라벨(Label) 등록을 하였나요?
  • PR 머지하기 전 반드시 CI가 정상적으로 작동하는지 확인해주세요!

Summary by CodeRabbit

릴리스 노트

  • Bug Fixes

    • 기간별 태그 집계에서 중복 카운팅 제거 처리 추가
  • Refactor

    • 기간 범위 계산 로직 개선 및 표준화를 통해 코드 안정성 강화

@wlsldm wlsldm self-assigned this Feb 12, 2026
@wlsldm wlsldm added the ♻️ REFACTOR 리팩토링 관련 라벨 label Feb 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

이미지 태그를 중복 제거하여 카운트하도록 쿼리를 수정하고, RecapServiceImpl의 시간 범위 처리를 새로운 TimeRange 레코드로 리팩토링하며, 해당 TimeRange 값 객체를 새로 추가합니다.

Changes

Cohort / File(s) Summary
Repository Query Update
src/main/java/com/umc/nuvibe/domain/archive/repository/BoardImageRepository.java
countTotalTagByPeriod 쿼리에서 count(distinct bi.image.imageTag)를 사용하여 중복 제거된 이미지 태그 개수를 카운트하도록 변경됨.
Service Refactoring
src/main/java/com/umc/nuvibe/domain/archive/service/RecapServiceImpl.java
calculateStartTime 메서드의 반환 타입을 LocalDateTime에서 TimeRange로 변경하고, 모든 호출 지점(getRecapTags, getRecapBoard, getRecapActive)을 업데이트하여 TimeRange에서 시작 및 종료 시간을 추출하도록 리팩토링됨. 주간(WEEK) 기간의 종료일 계산 로직이 조정됨.
Value Object Addition
src/main/java/com/umc/nuvibe/domain/archive/vo/TimeRange.java
start와 end LocalDateTime 필드를 포함하는 새로운 public 레코드 TimeRange 추가.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목이 실제 변경 사항과 일치하지 않습니다. 제목 'Refactor/#138 vibe tone'은 GitHub 이슈 번호와 모호한 설명만 포함하며, 실제 변경 사항(태그 중복 집계 수정, 주간 기간 계산 변경)을 명확하게 설명하지 않습니다. 제목을 'Fix weekly recap period calculation and distinct tag counting'과 같이 구체적으로 변경하여 실제 주요 변경 사항(기간 계산 변경 및 태그 중복 제거)을 명확하게 전달하세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 refactor/#138_vibe-tone

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/umc/nuvibe/domain/archive/repository/BoardImageRepository.java (1)

173-183: ⚠️ Potential issue | 🟡 Minor

distinct 추가로 태그 종류 수 집계가 올바르게 수정되었습니다.

count(distinct bi.image.imageTag)로 변경하여 업로드 수가 아닌 태그 종류 수를 반환하도록 수정한 것은 PR 목적에 부합합니다. SQL의 COUNT(DISTINCT ...) 특성상 NULL 값은 자동으로 제외되므로 imageTag가 null인 레코드도 안전하게 처리됩니다.

다만, 이 쿼리의 상단 경계 연산자(<= :end)와 다른 쿼리들의 경계 연산자 간 불일치가 있습니다:

쿼리 상단 경계
countTotalTagByPeriod (여기) <= :end
countTotalImageByPeriod <= :end
findTopTagsByPeriod < :end
findTopBoardByPeriod < :end
findTopHourByPeriod < :end

WEEK 기간의 경우 end가 다음 주 월요일 00:00:00으로 설정되므로, <= :end를 사용하는 쿼리는 정확히 월요일 자정에 생성된 데이터를 포함할 수 있습니다. 모든 기간 쿼리에서 경계 조건을 통일하는 것을 권장합니다 (예: 모두 < :end로 통일).

🔧 경계 연산자 통일 제안
-    `@Query` ("select count(distinct bi.image.imageTag) from BoardImage bi " +
+    `@Query`("select count(distinct bi.image.imageTag) from BoardImage bi " +
             "where bi.board.user.id = :userId " +
             "and bi.image.createdAt >= :start " +
-            "and bi.image.createdAt <= :end"
+            "and bi.image.createdAt < :end"
     )

동일하게 countTotalImageByPeriod (Line 116), findMaxDailyDropCount (Line 189), findTopDayOfWeekByPeriod (Line 206), countActiveBoardsByPeriod (Line 164-165)에서도 <= :end / between 대신 < :end로 통일하는 것을 검토해 주세요.

🤖 Fix all issues with AI agents
In `@src/main/java/com/umc/nuvibe/domain/archive/service/RecapServiceImpl.java`:
- Around line 41-58: Rename the method calculateStartTime in RecapServiceImpl to
calculateTimeRange (returning TimeRange) and update all references/usages
(including interfaces, callers, and tests) to the new name; keep the internal
logic that uses RecapPeriod, TimeRange, userRepository and User::getCreatedAt
unchanged, and ensure method signatures (visibility and parameters: RecapPeriod
period, Long userId) remain identical so compilation still succeeds.
- Around line 91-94: Extract the duplicated endDate calculation into TimeRange
(or a small helper) by adding a method like getDisplayEndDate()/endDate() on the
TimeRange record that returns (period == RecapPeriod.WEEK) ?
end.toLocalDate().minusDays(1) : end.toLocalDate(); then update RecapServiceImpl
to call TimeRange.endDate() (replace the three duplicated blocks in
getRecapTags, getRecapBoard, and getRecapActive) so all callers use the single
canonical implementation.

In `@src/main/java/com/umc/nuvibe/domain/archive/vo/TimeRange.java`:
- Around line 1-6: TimeRange as a record currently allows nulls and start after
end; add a compact constructor in the TimeRange record to perform defensive
validation: use Objects.requireNonNull on start and end (or throw
NullPointerException) and check that !start.isAfter(end) (or
start.compareTo(end) <= 0), throwing IllegalArgumentException with a clear
message if the invariant fails; implement this inside the TimeRange compact
constructor to guarantee the VO invariant at creation.

Comment on lines +41 to 58
private TimeRange calculateStartTime(RecapPeriod period, Long userId){

LocalDateTime start;
if (period == RecapPeriod.WEEK){
LocalDate startWeek = LocalDate.now().with(java.time.DayOfWeek.MONDAY);
start = startWeek.atStartOfDay();
LocalDateTime start = startWeek.atStartOfDay();
LocalDateTime end = startWeek.plusWeeks(1).atStartOfDay();
return new TimeRange(start, end);

} else {
start = userRepository.findById(userId)
LocalDateTime start = userRepository.findById(userId)
.map(User::getCreatedAt)
.orElseThrow(()->new BusinessException(UserErrorCode.USER_NOT_FOUND));
LocalDateTime end = LocalDateTime.now();
return new TimeRange(start, end);
}
return start;


}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

메서드 이름이 반환 타입과 불일치합니다.

calculateStartTime은 이제 TimeRange(시작 + 종료)를 반환하지만, 메서드 이름은 여전히 "StartTime"만 계산하는 것처럼 보입니다. calculateTimeRange 또는 calculatePeriodRange 등으로 변경하면 가독성이 향상됩니다.

♻️ 메서드명 변경 제안
-    private TimeRange calculateStartTime(RecapPeriod period, Long userId){
+    private TimeRange calculateTimeRange(RecapPeriod period, Long userId){
📝 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
private TimeRange calculateStartTime(RecapPeriod period, Long userId){
LocalDateTime start;
if (period == RecapPeriod.WEEK){
LocalDate startWeek = LocalDate.now().with(java.time.DayOfWeek.MONDAY);
start = startWeek.atStartOfDay();
LocalDateTime start = startWeek.atStartOfDay();
LocalDateTime end = startWeek.plusWeeks(1).atStartOfDay();
return new TimeRange(start, end);
} else {
start = userRepository.findById(userId)
LocalDateTime start = userRepository.findById(userId)
.map(User::getCreatedAt)
.orElseThrow(()->new BusinessException(UserErrorCode.USER_NOT_FOUND));
LocalDateTime end = LocalDateTime.now();
return new TimeRange(start, end);
}
return start;
}
private TimeRange calculateTimeRange(RecapPeriod period, Long userId){
if (period == RecapPeriod.WEEK){
LocalDate startWeek = LocalDate.now().with(java.time.DayOfWeek.MONDAY);
LocalDateTime start = startWeek.atStartOfDay();
LocalDateTime end = startWeek.plusWeeks(1).atStartOfDay();
return new TimeRange(start, end);
} else {
LocalDateTime start = userRepository.findById(userId)
.map(User::getCreatedAt)
.orElseThrow(()->new BusinessException(UserErrorCode.USER_NOT_FOUND));
LocalDateTime end = LocalDateTime.now();
return new TimeRange(start, end);
}
}
🤖 Prompt for AI Agents
In `@src/main/java/com/umc/nuvibe/domain/archive/service/RecapServiceImpl.java`
around lines 41 - 58, Rename the method calculateStartTime in RecapServiceImpl
to calculateTimeRange (returning TimeRange) and update all references/usages
(including interfaces, callers, and tests) to the new name; keep the internal
logic that uses RecapPeriod, TimeRange, userRepository and User::getCreatedAt
unchanged, and ensure method signatures (visibility and parameters: RecapPeriod
period, Long userId) remain identical so compilation still succeeds.

Comment on lines +91 to +94
LocalDate endDate = (period == RecapPeriod.WEEK)
? end.toLocalDate().minusDays(1)
: end.toLocalDate();

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

endDate 계산 로직이 3곳에서 중복됩니다.

동일한 endDate 계산 패턴이 getRecapTags (Line 91-93), getRecapBoard (Line 130-132), getRecapActive (Line 181-183)에서 반복됩니다. 이를 TimeRange에 메서드로 추출하거나, 별도 헬퍼로 분리하면 유지보수성이 향상됩니다.

♻️ TimeRange에 endDate 메서드 추가 제안

TimeRange 레코드에 display용 endDate를 반환하는 메서드를 추가:

 public record TimeRange(LocalDateTime start, LocalDateTime end) {
+    public LocalDate startDate() {
+        return start.toLocalDate();
+    }
+
+    public LocalDate endDate(RecapPeriod period) {
+        return (period == RecapPeriod.WEEK)
+                ? end.toLocalDate().minusDays(1)
+                : end.toLocalDate();
+    }
 }

그러면 각 메서드에서 다음과 같이 간결하게 사용할 수 있습니다:

-        LocalDate endDate = (period == RecapPeriod.WEEK)
-                ? end.toLocalDate().minusDays(1)
-                : end.toLocalDate();
-
      return new RecapTagResponse(
              period,
-             start.toLocalDate(),
-             endDate,
+             range.startDate(),
+             range.endDate(period),
              totalDrops,
              ranks
      );
🤖 Prompt for AI Agents
In `@src/main/java/com/umc/nuvibe/domain/archive/service/RecapServiceImpl.java`
around lines 91 - 94, Extract the duplicated endDate calculation into TimeRange
(or a small helper) by adding a method like getDisplayEndDate()/endDate() on the
TimeRange record that returns (period == RecapPeriod.WEEK) ?
end.toLocalDate().minusDays(1) : end.toLocalDate(); then update RecapServiceImpl
to call TimeRange.endDate() (replace the three duplicated blocks in
getRecapTags, getRecapBoard, and getRecapActive) so all callers use the single
canonical implementation.

Comment on lines +1 to +6
package com.umc.nuvibe.domain.archive.vo;

import java.time.LocalDateTime;

public record TimeRange(LocalDateTime start, LocalDateTime end) {
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

방어적 유효성 검증 추가를 고려해 주세요.

현재 TimeRangestartend보다 나중이거나, 필드가 null인 경우에도 생성이 가능합니다. 현재는 RecapServiceImpl 내부에서만 사용되어 즉각적인 문제는 없지만, VO로서 불변식(invariant)을 보장하면 향후 재사용 시 안전합니다.

🛡️ compact constructor를 통한 유효성 검증 추가
 public record TimeRange(LocalDateTime start, LocalDateTime end) {
+    public TimeRange {
+        if (start == null || end == null) {
+            throw new IllegalArgumentException("start and end must not be null");
+        }
+        if (start.isAfter(end)) {
+            throw new IllegalArgumentException("start must not be after end");
+        }
+    }
 }
📝 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
package com.umc.nuvibe.domain.archive.vo;
import java.time.LocalDateTime;
public record TimeRange(LocalDateTime start, LocalDateTime end) {
}
package com.umc.nuvibe.domain.archive.vo;
import java.time.LocalDateTime;
public record TimeRange(LocalDateTime start, LocalDateTime end) {
public TimeRange {
if (start == null || end == null) {
throw new IllegalArgumentException("start and end must not be null");
}
if (start.isAfter(end)) {
throw new IllegalArgumentException("start must not be after end");
}
}
}
🤖 Prompt for AI Agents
In `@src/main/java/com/umc/nuvibe/domain/archive/vo/TimeRange.java` around lines 1
- 6, TimeRange as a record currently allows nulls and start after end; add a
compact constructor in the TimeRange record to perform defensive validation: use
Objects.requireNonNull on start and end (or throw NullPointerException) and
check that !start.isAfter(end) (or start.compareTo(end) <= 0), throwing
IllegalArgumentException with a clear message if the invariant fails; implement
this inside the TimeRange compact constructor to guarantee the VO invariant at
creation.

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

Labels

♻️ REFACTOR 리팩토링 관련 라벨

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant