Skip to content

fix: 웹사이트 동아리 import 품질 검증 및 통계 캐시 추가#667

Merged
JanooGwan merged 3 commits into
developfrom
feat/web-club-import-quality
Jun 12, 2026
Merged

fix: 웹사이트 동아리 import 품질 검증 및 통계 캐시 추가#667
JanooGwan merged 3 commits into
developfrom
feat/web-club-import-quality

Conversation

@JanooGwan

@JanooGwan JanooGwan commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🔍 개요

  • 웹사이트 동아리 시트 import 과정에서 소개 문장, placeholder, 연락처성 문구가 운영 데이터로 유입되는 것을 막고, 대학별 동아리 목록 API의 반복 통계 count 조회를 줄입니다.

🚀 주요 변경 내용

  • WebsiteClubStatsReader를 추가해 대학 전체 동아리 수와 검색어별 카테고리 카운트를 읽기 전용 캐시로 조회합니다.
  • 웹사이트 동아리 목록/상세 API가 통계 조회 시 WebsiteClubStatsReader를 사용하도록 변경했습니다.
  • 웹사이트 동아리 시트 preview/confirm 단계에서 동아리명, 주제, 한 줄 소개의 의심 데이터를 감지하고 제외하도록 검증을 추가했습니다.
  • 의심 데이터 차단과 통계 캐시/무효화 동작을 단위 테스트로 고정했습니다.

💬 참고 사항


✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

WebsiteClubStatsReader를 도입해 대학·쿼리별 클럽 수를 Caffeine으로 캐시하고, WebsiteService 호출을 해당 리더로 전환했습니다. AdminWebsiteClubSheetImportService에 의심 텍스트 검증을 추가해 preview에서 비활성화하고 confirm에서 경고가 있는 행은 저장을 건너뛰며, 저장이 이뤄진 경우 커밋 이후 대학 통계 캐시를 무효화합니다.

변경 사항

Website Club Statistics Caching

Layer / File(s) 요약
WebsiteClubStatsReader 캐싱 구현
src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
Caffeine 캐시(대학별 총계, 대학+정규화 쿼리→카테고리 카운트)를 추가하고 getUniversityClubCount/getCategoryCounts/normalizeQuery/invalidateUniversity를 구현했습니다.
WebsiteService 통합 및 빌드 의존성
src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java, build.gradle
WebsiteService가 통계 조회를 WebsiteClubStatsReader로 전환하고, com.github.ben-manes.caffeine:caffeine 의존성이 추가되었습니다.
WebsiteClubStatsReader 단위 테스트
src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
캐시 히트/미스, 쿼리 정규화, invalidate 동작과 동시성 시나리오를 검증하는 테스트가 추가되었습니다.

Club Sheet Import Content Validation

Layer / File(s) 요약
콘텐츠 검증 규칙 및 헬퍼
src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
플레이스홀더 텍스트 집합과 URL/SNS·전화번호 정규식 상수를 추가하고, validateClubContent/isSuspiciousName/isSuspiciousShortText/isPlaceholder 헬퍼를 도입했습니다.
import 흐름: preview·confirm·after-commit 무효화
src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
buildImportPlan에서 경고 누적 및 enabled 결정 변경, confirmImport에서 경고가 있는 행 저장 스킵, 저장 발생 시 invalidateWebsiteStatsAfterCommit으로 커밋 이후 websiteClubStatsReader.invalidateUniversity를 등록합니다.
import 검증 테스트
src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
프리뷰에서 의심 행이 disabled 되는지, confirm에서 의심 행이 저장되지 않는지, 저장 후 트랜잭션 커밋 이후에만 통계 무효화가 호출되는지를 검증하는 테스트가 추가/수정되었습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

기능

Suggested reviewers

  • dh2906

🐰 캐시 속 통계 포근히 자고,
의심스런 글은 살며시 멈추고,
커밋 뒤 통계가 새로 깨어나,
동아리 목록은 반짝이며,
오늘도 코드에 당근 한입!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 pull request의 주요 변경 내용을 명확하게 반영하고 있습니다. '웹사이트 동아리 import 품질 검증 및 통계 캐시 추가'는 실제 변경사항인 의심 데이터 검증 추가와 WebsiteClubStatsReader 캐싱 기능 도입을 정확히 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명은 변경 사항과 관련이 있으며, 웹사이트 동아리 import 품질 검증 및 통계 캐시 추가에 대해 명확하게 설명하고 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-club-import-quality

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.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

🧪 JaCoCo Coverage Report (Changed Files)

Summary

  • Overall Coverage: 88.0% ✅
  • Covered Lines: 234 / 266
  • Changed Files: 3

Coverage by File

Class Coverage Lines Status
AdminWebsiteClubSheetImportService
gg.agit.konect.admin.website.service
84.8% 179/211
WebsiteClubStatsReader
gg.agit.konect.domain.website.service
100.0% 15/15
WebsiteService
gg.agit.konect.domain.website.service
100.0% 40/40

📊 View Workflow Run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java`:
- Around line 265-279: The isSuspiciousName method currently combines many
business rules (HEADER_NAME, contains "한 줄 소개"/"상세소개"/"동아리입니다",
endsWith("입니다.")/endsWith("합니다."), URL_OR_SNS_PATTERN, PHONE_NUMBER_PATTERN,
isPlaceholder) which is hard to read; add short inline comments above or beside
each predicate in isSuspiciousName explaining the intent of that specific check
(e.g., that HEADER_NAME detects header rows, "한 줄 소개"/"상세소개" detect literal
intro labels, "동아리입니다"/endsWith("입니다.")/endsWith("합니다.") catch generic
descriptive sentences, URL_OR_SNS_PATTERN detects links/handles,
PHONE_NUMBER_PATTERN detects phone numbers, isPlaceholder catches filler values)
and keep references to the existing symbols (HEADER_NAME, URL_OR_SNS_PATTERN,
PHONE_NUMBER_PATTERN, isPlaceholder) so future readers understand why each
condition flags a name.
- Around line 162-164: The cache invalidation using
websiteClubStatsReader.invalidateUniversity(universityId) inside the
`@Transactional` method in AdminWebsiteClubSheetImportService must be moved to run
after commit to avoid stale state on rollback; emit a domain event (e.g.,
ClubImportedEvent with universityId) after saveAll and remove the direct
invalidateUniversity call, then add a listener component
(ClubImportEventListener) with a `@TransactionalEventListener`(phase =
TransactionPhase.AFTER_COMMIT) method that calls
websiteClubStatsReader.invalidateUniversity(event.universityId()) so cache
invalidation only occurs after the transaction successfully commits.

In
`@src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java`:
- Around line 44-49: Add a concise comment above the normalizeQuery method
explaining the normalization rules and rationale: state that null or blank
inputs intentionally return null to indicate absence (used when building cache
keys), that trim() removes surrounding whitespace, and that
toLowerCase(Locale.ROOT) is used to ensure locale-independent, stable
lower-casing for cache key consistency; mention any business rule combining the
two conditions (null/blank -> null) so future maintainers understand why both
checks exist.
- Around line 18-19: The two unbounded maps universityClubCountCache and
categoryCountCache in WebsiteClubStatsReader risk memory leaks; replace them
with bounded Caffeine caches (e.g., com.github.benmanes.caffeine.cache.Cache)
such as Cache<Integer, Long> universityClubCountCache and
Cache<CategoryCountCacheKey, Map<ClubCategory, Long>> categoryCountCache
configured with maxSize and expireAfterWrite (TTL), and update all usages
(reads/writes) of universityClubCountCache and categoryCountCache to use
cache.getIfPresent/get or cache.get(key, mappingFunction) and
cache.put/putIfAbsent equivalents so eviction/TTL is enforced. Ensure the new
caches are constructed where the current fields are initialized (in
WebsiteClubStatsReader) and keep the same key types (Integer and
CategoryCountCacheKey) and value types to minimize call-site changes.
- Around line 39-42: The invalidateUniversity call is executed before
transaction commit which can let other threads rebuild stale caches; modify
AdminWebsiteClubSheetImportService.confirmImport to invoke
WebsiteClubStatsReader.invalidateUniversity only after the transaction commits
by registering a callback via
TransactionSynchronizationManager.registerSynchronization (afterCommit) or by
publishing an event and handling it with `@TransactionalEventListener`(phase =
AFTER_COMMIT) in WebsiteClubStatsReader; update WebsiteClubStatsReader to expose
a method that performs the same cache removals when invoked from the
after-commit callback, and add an integration test that runs concurrent read
requests during import to verify cache is invalidated only after commit.

In
`@src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java`:
- Around line 57-68: Add verification that the statistics cache invalidation is
triggered when import is confirmed: in AdminWebsiteClubSheetImportServiceTest,
update the existing test confirmImportSavesEnabledAndNonDuplicateClubsOnly (or
add a new test exercising service.confirmImport(...)) to assert that
websiteClubStatsReader.invalidateUniversity(UNIVERSITY_ID) is invoked by calling
Mockito.verify(websiteClubStatsReader).invalidateUniversity(UNIVERSITY_ID) after
the confirmImport call; ensure the test arranges any required stubs for
googleSheetsService/webUniversityRepository/webClubRepository so the
confirmImport path executes.

In
`@src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java`:
- Around line 58-68: Add a concurrency test that verifies
WebsiteClubStatsReader.invalidateUniversity (which uses removeIf internally) is
safe under concurrent reads: create a test method (e.g.,
invalidateUniversityIsSafeUnderConcurrentAccess) that stubs
websiteQueryRepository.countClubsByUniversityId and countClubCategories, starts
an ExecutorService with multiple threads where half repeatedly call
getCategoryCounts (or getUniversityClubCount) and the other half repeatedly call
invalidateUniversity(UNIVERSITY_ID), collect Futures and assert that
future.get(...) does not throw any exception, then shutdown the executor; this
ensures concurrent calls to getCategoryCounts/getUniversityClubCount and
invalidateUniversity do not raise race conditions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d78496a4-2aa3-43a6-a4a6-d6e927491c37

📥 Commits

Reviewing files that changed from the base of the PR and between c7c6c75 and e414385.

📒 Files selected for processing (5)
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
📜 Review details
⏰ 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). (2)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: coverage
🧰 Additional context used
📓 Path-based instructions (4)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.java: Java 코드에서 import로 해결할 수 있는 경우 FQCN(Full Qualified Class Name)을 사용하지 않도록 지적한다
JPA/QueryDSL 조회 변경 시 N+1, 잘못된 fetch join, count 쿼리 왜곡, pagination 깨짐, distinct 누락을 확인한다
권한 로직은 관리자 우회, 요청자와 대상자 관계, 클럽/채팅방/공지/일정의 소속 검증이 빠지지 않았는지 확인한다
soft delete, 탈퇴 사용자, 차단/제외 조건, 중복 제거가 필요한 조회에서는 응답에 노출되면 안 되는 데이터가 포함되는지 확인한다
DTO 응답 변경은 기존 클라이언트가 기대하는 필드명, nullability, enum/string 값, 정렬 순서를 깨지 않는지 확인한다
조건이 2개 이상 결합된 비즈니스 규칙, 권한 조건, soft delete 제외, 중복 제거, fallback 우선순위, 대표값 선택, DTO 변환, count 쿼리 분리, fetch join 선택 이유처럼 코드만으로 의도가 숨겨지는 지점에는 주석을 권장한다
단순 생성자 호출, 필드 매핑, 컬렉션 반환, 이름만으로 명확한 분기에는 주석을 요구하지 않는다

Files:

  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
**/*.{sql,java}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

데이터베이스 변경에서는 마이그레이션 순서, 기존 데이터 호환성, nullable/default 처리, 롤백 난이도, 인덱스 필요성을 확인한다

Files:

  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
🔇 Additional comments (1)
src/main/java/gg/agit/konect/domain/website/service/WebsiteService.java (1)

37-37: LGTM!

Also applies to: 75-76, 83-83

Comment thread src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java Outdated
@JanooGwan JanooGwan self-assigned this Jun 12, 2026
@JanooGwan JanooGwan added the 리팩토링 리팩터링을 위한 이슈입니다. label Jun 12, 2026
@JanooGwan

Copy link
Copy Markdown
Contributor Author

리뷰 반영했습니다.

  • invalidateUniversity를 트랜잭션 커밋 이후 실행되도록 변경했습니다.
  • 통계 캐시를 Caffeine 기반 size/TTL 제한 캐시로 변경했습니다.
  • import 의심 데이터 검증 조건과 query normalization 의도를 짧은 주석으로 보강했습니다.
  • 캐시 무효화 호출 시점 및 동시성 안전성 테스트를 추가했습니다.

검증:
./gradlew.bat test --tests "gg.agit.konect.unit.admin.website.service.AdminWebsiteClubSheetImportServiceTest" --tests "gg.agit.konect.unit.domain.website.service.WebsiteClubStatsReaderTest" --tests "gg.agit.konect.integration.domain.website.WebsiteApiTest" checkstyleMain 통과

커밋: bb24aaf8 fix: 웹사이트 동아리 통계 캐시 무효화 안정화

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java (1)

41-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

[LEVEL: medium]

캐시 키 정규화와 실제 조회 정규화 기준이 다릅니다.

문제: Line 42와 Line 62는 Locale.ROOT로 캐시 키를 만들지만, 실제 조회 조건은 src/main/java/gg/agit/konect/domain/website/repository/WebsiteQueryRepository.java Line 216-224에서 기본 로케일 toLowerCase()를 사용합니다. 영향: JVM 기본 로케일이 Turkish 같은 환경이면 같은 검색어가 서로 다른 정규화 결과로 처리되어 카테고리 수 캐시가 잘못 재사용될 수 있습니다. 제안: 캐시 키와 DB 조회가 동일한 정규화 함수를 공유하도록 한 곳으로 모으거나, 저장소 쪽도 Locale.ROOT 기준으로 맞추세요.

Also applies to: 57-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java`
around lines 41 - 49, The cache key normalization and the DB query normalization
diverge (cache uses normalizeQuery/CategoryCountCacheKey with Locale.ROOT while
WebsiteQueryRepository.countClubCategories uses String.toLowerCase() with
default locale), causing cache misses or wrong reuse under locales like Turkish;
fix by centralizing the normalization so both sides use the same
Locale.ROOT-based normalization: either update
WebsiteQueryRepository.countClubCategories (and any other repository query
methods referenced by getCategoryCounts and the similar block at lines 57-62) to
call the same normalizeQuery(Locale.ROOT) helper, or move normalization into a
shared utility used by both CategoryCountCacheKey/normalizeQuery and
WebsiteQueryRepository.countClubCategories so all code paths use the identical
Locale.ROOT lowercasing.
src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java (1)

67-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

[LEVEL: low]

카테고리 캐시 무효화 회귀 테스트가 빠져 있습니다.

문제: Line 68-76은 invalidateUniversity 이후 대학 총계 캐시만 다시 조회하고, 실제로 같이 변경된 categoryCountCache 무효화 경로는 검증하지 않습니다. 영향: 총계 테스트가 통과해도 카테고리 수가 stale 하게 남는 회귀를 이번 테스트 세트가 잡지 못합니다. 제안: getCategoryCounts를 한 번 채운 뒤 invalidateUniversity 후 다른 repository 응답이 다시 반영되는지 별도 테스트를 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java`
around lines 67 - 76, Add a regression test that ensures invalidateUniversity
invalidates the category cache as well: using websiteClubStatsReader and mocked
websiteQueryRepository, first stub countClubsByUniversityId and
countClubsByUniversityIdByCategory (or the method used by getCategoryCounts) to
return initial values and call getCategoryCounts to populate categoryCountCache,
then change the mock to return updated category counts, call
websiteClubStatsReader.invalidateUniversity(UNIVERSITY_ID), call
getCategoryCounts again and assert it returns the updated values, and verify the
repository category-count method was invoked again (times(2)) to confirm the
cache was cleared.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java`:
- Around line 41-49: The cache key normalization and the DB query normalization
diverge (cache uses normalizeQuery/CategoryCountCacheKey with Locale.ROOT while
WebsiteQueryRepository.countClubCategories uses String.toLowerCase() with
default locale), causing cache misses or wrong reuse under locales like Turkish;
fix by centralizing the normalization so both sides use the same
Locale.ROOT-based normalization: either update
WebsiteQueryRepository.countClubCategories (and any other repository query
methods referenced by getCategoryCounts and the similar block at lines 57-62) to
call the same normalizeQuery(Locale.ROOT) helper, or move normalization into a
shared utility used by both CategoryCountCacheKey/normalizeQuery and
WebsiteQueryRepository.countClubCategories so all code paths use the identical
Locale.ROOT lowercasing.

In
`@src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java`:
- Around line 67-76: Add a regression test that ensures invalidateUniversity
invalidates the category cache as well: using websiteClubStatsReader and mocked
websiteQueryRepository, first stub countClubsByUniversityId and
countClubsByUniversityIdByCategory (or the method used by getCategoryCounts) to
return initial values and call getCategoryCounts to populate categoryCountCache,
then change the mock to return updated category counts, call
websiteClubStatsReader.invalidateUniversity(UNIVERSITY_ID), call
getCategoryCounts again and assert it returns the updated values, and verify the
repository category-count method was invoked again (times(2)) to confirm the
cache was cleared.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d00d3a8-1921-4c52-80a0-10c2f9131526

📥 Commits

Reviewing files that changed from the base of the PR and between e414385 and bb24aaf.

📒 Files selected for processing (5)
  • build.gradle
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
📜 Review details
⏰ 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). (2)
  • GitHub Check: coverage
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (4)
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • build.gradle
  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.java: Java 코드에서 import로 해결할 수 있는 경우 FQCN(Full Qualified Class Name)을 사용하지 않도록 지적한다
JPA/QueryDSL 조회 변경 시 N+1, 잘못된 fetch join, count 쿼리 왜곡, pagination 깨짐, distinct 누락을 확인한다
권한 로직은 관리자 우회, 요청자와 대상자 관계, 클럽/채팅방/공지/일정의 소속 검증이 빠지지 않았는지 확인한다
soft delete, 탈퇴 사용자, 차단/제외 조건, 중복 제거가 필요한 조회에서는 응답에 노출되면 안 되는 데이터가 포함되는지 확인한다
DTO 응답 변경은 기존 클라이언트가 기대하는 필드명, nullability, enum/string 값, 정렬 순서를 깨지 않는지 확인한다
조건이 2개 이상 결합된 비즈니스 규칙, 권한 조건, soft delete 제외, 중복 제거, fallback 우선순위, 대표값 선택, DTO 변환, count 쿼리 분리, fetch join 선택 이유처럼 코드만으로 의도가 숨겨지는 지점에는 주석을 권장한다
단순 생성자 호출, 필드 매핑, 컬렉션 반환, 이름만으로 명확한 분기에는 주석을 요구하지 않는다

Files:

  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
**/*.{sql,java}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

데이터베이스 변경에서는 마이그레이션 순서, 기존 데이터 호환성, nullable/default 처리, 롤백 난이도, 인덱스 필요성을 확인한다

Files:

  • src/test/java/gg/agit/konect/unit/domain/website/service/WebsiteClubStatsReaderTest.java
  • src/test/java/gg/agit/konect/unit/admin/website/service/AdminWebsiteClubSheetImportServiceTest.java
  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/admin/website/service/AdminWebsiteClubSheetImportService.java
  • src/main/java/gg/agit/konect/domain/website/service/WebsiteClubStatsReader.java

@JanooGwan JanooGwan merged commit b0a6d76 into develop Jun 12, 2026
5 checks passed
@JanooGwan JanooGwan deleted the feat/web-club-import-quality branch June 12, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

리팩토링 리팩터링을 위한 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant