Skip to content

Feat: 책모 챗봇 구현 - #299

Open
shinwokkang wants to merge 8 commits into
developfrom
feat/298/chatbot
Open

Feat: 책모 챗봇 구현#299
shinwokkang wants to merge 8 commits into
developfrom
feat/298/chatbot

Conversation

@shinwokkang

@shinwokkang shinwokkang commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

🚀 변경사항

책모 앱의 사용법 안내 챗봇 기능을 신규 chatbot 모듈로 추가합니다.
사용자가 로그인 여부와 무관하게 텍스트로 질문하면 Gemini API를 통해 답변하고, 필요 시 상담사 연결(핸드오프)을 안내합니다.
Spring Modulith 컨벤션에 따라 internal/web으로 분리했고, 기존 report/book 모듈의 구조·예외·모니터링 패턴을 그대로 따랐습니다.

1. DB 스키마 & 모듈 뼈대

  • chat_session(세션, session_token UUID로 비로그인 사용자도 이어서 대화 가능), chat_message(턴 단위 메시지, 마스킹된 텍스트만 저장) 2개 테이블 추가 (V20260727__create_chatbot_tables.sql)
  • 원문 대화 내용은 어디에도 저장하지 않습니다. chat_message.masked_content에는 아래 3번 PII 마스킹을 거친 텍스트만 들어갑니다.
  • chatbot 모듈은 authentication, member, common에만 의존하도록 @ApplicationModule(allowedDependencies=...)로 경계를 명시했습니다(닉네임 마스킹 위해 member의 공개 API(MemberAPI)만 사용, internal 미접근).

2. PII 마스킹 (PiiMaskingService)

  • 사용자 입력을 LLM에 보내거나 DB에 저장하기 전에 마스킹합니다. 대상: 주민등록번호, 휴대폰 번호, 이메일, "비밀번호는 ○○○" 형태의 값, 로그인한 본인 닉네임.
  • 봇 응답에는 마스킹을 적용하지 않습니다. 모델이 애초에 마스킹된 입력만 봤기 때문에 구조적으로 실제 PII를 답할 수 없고, 반대로 정규식을 답변에 적용하면 "비밀번호는 6~12자여야 합니다" 같은 정상 안내 문장이 오탐되어 훼손되는 문제를 QA 중 실제로 발견해서 방향을 바꿨습니다(자세한 내용은 아래 특이사항 참고).
  • 순수 정규식 기반 휴리스틱이라 완전한 탐지를 보장하지 않습니다(주석에 명시).

3. Gemini API 클라이언트 (GeminiApiService)

  • 별도 SDK/WebClient 없이 기존에 쓰던 **RestTemplate**로 Gemini generateContent REST 엔드포인트를 직접 호출합니다(book 모듈 AladinApiService 패턴과 동일하게 자체 RestTemplate 빈 구성).
  • 인증은 x-goog-api-key 헤더 방식(쿼리 파라미터 방식보다 안전), 요청은 Content-Type: application/json을 명시적으로 지정합니다 — 이 프로젝트에 이미 jackson-dataformat-xml(Aladin XML 파싱용)이 있어서, Content-Type을 명시하지 않으면 RestTemplate이 XML 컨버터를 먼저 골라 요청 바디가 XML로 직렬화되는 문제가 있었습니다. 실제로 겪은 이슈라 리뷰 시 참고해주세요.
  • API 키는 GEMINI_API_KEY 환경변수로 주입(application-chatbot.yml), 코드/설정 파일 어디에도 실제 키 값은 없습니다.

4. 대화 오케스트레이션 · 모델 라우팅 · 미해결 태깅

  • ChatOrchestrationService: 세션 조회/생성 → PII 마스킹 → 최근 대화 이력(최대 20턴) 조립 → 에스컬레이션 판단 → Gemini 호출 → 프롬프트 유출 검사 → 미해결 태깅 → 저장 순서로 처리합니다.
  • 모델 라우팅: 트래픽이 아직 없는 초기 단계라 비용 최소화를 우선했습니다. 기본은 gemini-3.1-flash-lite, 아래 규칙에 해당하면 gemini-3.6-flash로 에스컬레이션합니다(EscalationDecider).
    • 사용자 발화에 오류/실패/버그류 키워드 포함, 또는
    • 직전 안내에도 "안 돼요/여전히 안" 등 부정 반응이 감지된 경우
    • 추가 LLM 호출 없이 순수 키워드 매칭이라 비용이 늘지 않습니다.
  • 미해결 세션 태깅 (UnresolvedSessionTagger): 봇 응답에 헤지 문구("현재 확인된 사용법 기준으로는")가 있거나, 사용자가 부정 반응 키워드를 반복하면 세션을 unresolved로 플래그합니다. 역시 규칙 기반, 추가 LLM 호출 없음.

5. 컨트롤러 & 핸드오프 (Swagger)

  • POST /api/v1/chatbot/messages 단일 엔드포인트, @CurrentId는 nullable이라 비로그인 사용자도 이용 가능합니다(SecurityConfig에 permitAll 추가).
  • 응답에 handoffSuggested, supportUrl, inquiryFormUrl 필드를 포함합니다 — 이번 범위는 FE 버튼 없이 API 응답 필드로만 인간 상담사 연결(핸드오프)을 제공하는 것으로 사용자와 합의된 범위입니다. 값은 기존 FE EXTERNAL_LINKS와 동일한 값을 BE 프로퍼티로 관리합니다.
  • 첫 요청은 sessionToken 없이(또는 null) 보내면 서버가 새로 발급해 응답에 담아줍니다. 이후 요청에 그 값을 그대로 실어 보내면 같은 대화로 이어집니다.

6. 인젝션 방어 하드닝 & 모니터링 지표

  • PromptLeakGuard: 모델 응답에 시스템 프롬프트 "구조"(섹션 헤더 문구, 예: 답변 원칙:, 보안 지침 (매우 중요, 예외 없이 지킵니다))가 그대로 노출되면 유출로 판단해 안전한 대체 응답으로 교체합니다. 프롬프트 "내용"은 정상 답변과 자연스럽게 겹칠 수 있어 검사 대상에서 의도적으로 제외했습니다(오탐 방지).
  • CheckmoMetrics에 챗봇 전용 Micrometer 카운터 3종 추가 — 모델별 호출 수(checkmo.chatbot.model.calls, model/escalated 태그), 미해결 세션 전환 수(checkmo.chatbot.session.unresolved, 세션당 최초 전환 시 1회만 증가), 프롬프트 유출 탐지 수(checkmo.chatbot.prompt_leak.detected). 기존 Prometheus/Grafana 파이프라인(/actuator/prometheus)에 그대로 노출됩니다. Grafana 대시보드 패널 추가는 이번 PR 범위 밖으로 후속 작업입니다.

🔗 관련 이슈

✅ 체크리스트

  • 로컬에서 테스트 완료 — ./gradlew test --tests "checkmo.chatbot.*" --tests checkmo.CheckmoApplicationTests 전체 통과(챗봇 단위/API 테스트 39개 + Modulith 모듈 경계 검증). 실제 Gemini API 키로 로컬 Swagger 통해 멀티턴 대화, 에스컬레이션 전환, 인젝션 시도 시나리오까지 라이브 확인 완료.
  • 코드 리뷰 준비 완료

📝 특이사항

  • 필수 환경변수: GEMINI_API_KEY가 없으면 앱 기동 시 프로퍼티 바인딩에서 실패합니다. 배포 환경 시크릿에 반드시 추가해주세요. (AI Studio에서 발급, x-goog-api-key 헤더로 전송)
  • 신규 Modulith 의존성: chatbot 모듈이 본인 닉네임 마스킹을 위해 member 모듈의 공개 API에 의존합니다(allowedDependencies에 추가, internal 패키지 접근 없음). Modulith 경계 테스트로 검증됩니다.
  • 모델 가용성 주의: gemini-2.5-flash 계열은 이 프로젝트가 쓰는 API 키/프로젝트 기준으로 신규 사용자에게 더 이상 제공되지 않아(404) gemini-3.1-flash-lite/gemini-3.6-flash로 확정했습니다. AI Studio 플레이그라운드 UI에 모델이 보이는 것과, 실제 REST 키로 호출 가능한 것은 별개였습니다 — 나중에 모델을 바꿀 일이 있으면 curl로 실제 호출 가능 여부부터 확인해주세요.
  • PII 마스킹은 완전하지 않은 휴리스틱입니다(정규식 기반). 특히 제3자 닉네임은 일반화해서 탐지할 수 없어 로그인한 본인 닉네임만 치환합니다. 더 강한 보장이 필요하면 별도 이슈로 논의가 필요합니다.
  • PromptLeakGuard의 LEAK_MARKERS 목록은 현재 시스템 프롬프트 문구에 하드코딩되어 있어, 프롬프트를 수정할 때 같이 갱신해야 합니다.
  • 로컬 개발 환경 전용 설정 4개 파일(compose-dev.yml, application.yml, application-redis.yml, application-db.yml)은 이 PR에 포함하지 않았습니다(로컬 MySQL 포트 충돌 우회용 개인 설정).
  • 대화 이력은 최근 20턴까지만 프롬프트에 포함합니다(토큰 비용 제어 목적, ChatOrchestrationService.MAX_HISTORY_MESSAGES).

Summary by CodeRabbit

  • New Features

    • Added an AI chatbot endpoint for authenticated and unauthenticated messaging.
    • Supports session continuity, personalized responses, escalation detection, and handoff guidance.
    • Protects sensitive personal information in conversations and handles uncertain or unsafe responses.
    • Added conversation history and session tracking for improved continuity.
  • Tests

    • Added coverage for messaging, sessions, escalation, privacy masking, error handling, and prompt-safety behavior.

shinwokkang and others added 8 commits July 27, 2026 11:15
chat_session/chat_message 테이블과 ChatSession/ChatMessage 엔티티를 추가한다.
마스킹된 텍스트만 저장하도록 ChatMessage에는 원문 컬럼을 두지 않는다.

Tested: ./gradlew compileJava, ./gradlew test --tests checkmo.CheckmoApplicationTests
Not-tested: 실제 MySQL 대상 Flyway 마이그레이션 적용(로컬 QA 필요)
주민등록번호/전화번호/이메일/비밀번호 값과 로그인한 본인 닉네임을
정규식 기반으로 마스킹하는 PiiMaskingService를 추가한다.
LLM 전송 및 DB/로그 저장 전 공통으로 거치도록 설계했다.

Tested: ./gradlew test --tests "checkmo.chatbot.internal.service.PiiMaskingServiceTest", ./gradlew test --tests checkmo.CheckmoApplicationTests
RestTemplate 기반 GeminiApiService, 요청/응답 DTO, 시스템 프롬프트 상수,
프로퍼티/예외 클래스를 추가한다. 기본 모델은 gemini-3.1-flash-lite,
에스컬레이션은 gemini-3.6-flash로 설정했다(실제 계정에서 호출 가능한
모델로 재확인 후 최종 반영).

인증은 x-goog-api-key 헤더 방식을 쓰고, Content-Type을 application/json으로
명시했다. book 모듈의 Aladin 연동용 jackson-dataformat-xml이 classpath에
있어 Content-Type을 명시하지 않으면 RestTemplate이 요청 바디를 XML로
직렬화해버리는 문제가 있어 이를 방지했다.

Tested: ./gradlew test --tests "checkmo.chatbot.*" --tests checkmo.CheckmoApplicationTests
Tested: 로컬 bootRun + 실제 GEMINI_API_KEY로 curl 호출(단일 질문/멀티턴/에스컬레이션) 확인
@lob만 쓰면 기본 length(255) 때문에 Hibernate가 TINYTEXT를 기대하는데,
마이그레이션은 TEXT로 만들어서 스키마 검증이 실패했다. 로컬 부팅 테스트로
발견했다. @column(columnDefinition = "TEXT")로 명시해 해결했다.

Tested: 로컬 bootRun으로 스키마 검증 통과 확인
세션 조회/생성(비로그인은 UUID 세션 토큰 발급) → PII 마스킹 → 최근 20개
대화 이력 조립 → 규칙 기반 에스컬레이션 판단 → Gemini 호출 → 미해결
태깅 → 저장 순서로 처리하는 ChatOrchestrationService를 추가한다.
EscalationDecider/UnresolvedSessionTagger는 추가 LLM 호출 없이 키워드
매칭만으로 판단한다. 봇 응답에는 마스킹을 적용하지 않는다(모델은 이미
마스킹된 사용자 입력만 보므로 실제 PII를 답할 방법이 없어 구조적으로
안전하고, 반대로 정규식이 정상 안내 문장을 오탐해 훼손하는 위험이 더 크다).

Gemini 호출(네트워크 I/O)이 DB 트랜잭션을 길게 물지 않도록 전체를
@transactional로 감싸지 않고, 짧은 저장 호출들로 나눴다.

Tested: ./gradlew test --tests "checkmo.chatbot.*" --tests checkmo.CheckmoApplicationTests
Tested: 로컬 bootRun + 실제 Gemini API로 멀티턴/에스컬레이션 확인
POST /api/v1/chatbot/messages를 추가한다. ReportController/ReportConverter
패턴을 그대로 따랐다. 비로그인 사용자도 이용할 수 있도록 SecurityConfig에
permitAll을 추가했다(그 전에는 anyRequest().authenticated()에 걸려 401).

응답 DTO에 handoffSuggested/supportUrl/inquiryFormUrl을 포함해 Swagger UI에서
바로 확인 가능하다. sessionToken Schema 예시값을 "null"(문자열)로 잘못
적어뒀던 것을, Swagger 사용자가 그대로 보내 404가 나는 문제가 있어 수정했다.

Tested: ./gradlew test --tests "checkmo.chatbot.*" --tests checkmo.CheckmoApplicationTests
Tested: Swagger UI 노출 확인, 로컬 bootRun + 실제 Gemini API로 비로그인/로그인/멀티턴 curl 검증
"비밀번호는 abc1234!입니다" 같은 실제 값뿐 아니라 "비밀번호가 기억이
안나요", "비밀번호 변경은 어디서 해요?" 같은 정상 질문/설명 문장까지
키워드 뒤 아무 단어나 마스킹해버리던 문제를 실제 QA로 발견했다.

값 부분을 영문/숫자/기호 조합(4자 이상)으로 한정해, 한글 뒤따름 단어는
매칭 대상에서 제외했다.

Tested: ./gradlew test --tests "checkmo.chatbot.internal.service.PiiMaskingServiceTest"
Tested: 로컬 bootRun + 실제 Gemini API로 "비밀번호는 6~12자..." 안내가
더 이상 훼손되지 않음을 확인
시스템 프롬프트 구조(섹션 헤더) 유출을 탐지해 안전한 대체 응답으로
교체하는 PromptLeakGuard를 추가하고, 모델 호출/미해결 세션 전환/
프롬프트 유출 탐지에 대한 Micrometer 카운터를 CheckmoMetrics에 추가한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shinwokkang shinwokkang self-assigned this Aug 3, 2026
@shinwokkang shinwokkang added the ✨ feature Introduce new features label Aug 3, 2026
@shinwokkang shinwokkang linked an issue Aug 3, 2026 that may be closed by this pull request
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added a public chatbot messaging endpoint backed by Gemini. The feature includes validated API contracts, session and message persistence, PII masking, escalation and handoff detection, prompt-leak protection, metrics, configuration, and tests.

Changes

Chatbot messaging

Layer / File(s) Summary
Configuration and persistence foundation
src/main/java/checkmo/chatbot/internal/config/..., src/main/java/checkmo/chatbot/internal/entity/..., src/main/java/checkmo/chatbot/internal/repository/..., src/main/resources/db/migration/..., src/main/resources/application-chatbot.yml
Added Gemini properties, REST timeout configuration, Gemini payload DTOs, session and message entities, repositories, database tables, and Spring Modulith wiring.
Public messaging API
src/main/java/checkmo/chatbot/web/..., src/main/java/checkmo/chatbot/internal/converter/..., src/main/java/checkmo/authentication/internal/config/SecurityConfig.java, src/test/java/checkmo/chatbot/ChatbotApiTest.java
Added POST /api/v1/chatbot/messages, request validation, response conversion, unauthenticated access, and API tests.
Gemini integration and response policies
src/main/java/checkmo/chatbot/internal/prompt/..., src/main/java/checkmo/chatbot/internal/service/GeminiApiService.java, src/main/java/checkmo/chatbot/internal/service/{PiiMaskingService,EscalationDecider,PromptLeakGuard,UnresolvedSessionTagger}.java, src/main/java/checkmo/chatbot/internal/exception/..., src/test/java/checkmo/chatbot/internal/service/*Test.java
Added the system prompt, Gemini request handling, PII masking, escalation detection, unresolved-session tagging, prompt-leak fallback, chatbot errors, and focused unit tests.
Conversation orchestration and monitoring
src/main/java/checkmo/chatbot/internal/service/ChatOrchestrationService.java, src/main/java/checkmo/chatbot/internal/service/ChatReply.java, src/main/java/checkmo/common/monitoring/CheckmoMetrics.java, src/test/java/checkmo/chatbot/internal/service/ChatOrchestrationServiceTest.java
Added session creation and reuse, history reconstruction, model selection, message persistence, handoff metadata, metrics, and orchestration tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SecurityFilterChain
  participant ChatbotController
  participant ChatOrchestrationService
  participant GeminiApiService
  participant ChatSessionRepository
  participant ChatMessageRepository
  Client->>SecurityFilterChain: POST /api/v1/chatbot/messages
  SecurityFilterChain->>ChatbotController: Permit request
  ChatbotController->>ChatOrchestrationService: respond(sessionToken, memberId, message)
  ChatOrchestrationService->>ChatSessionRepository: Find or create session
  ChatOrchestrationService->>GeminiApiService: Generate reply
  GeminiApiService-->>ChatOrchestrationService: Reply text
  ChatOrchestrationService->>ChatMessageRepository: Persist masked messages
  ChatOrchestrationService-->>ChatbotController: ChatReply
  ChatbotController-->>Client: ChatbotResponseDTO.Reply
Loading

Possibly related PRs

  • checkmo2025/BE#35: Updates SecurityConfig, which is also modified here to permit the chatbot endpoint.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR implements a chatbot feature matching issue #298, but the issue provides no detailed requirements or acceptance criteria for full verification. Add detailed requirements or acceptance criteria to issue #298 so the implementation can be verified against explicit objectives.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The code changes support the chatbot objectives, including API integration, persistence, masking, escalation, handoff, security, monitoring, and tests.
Title check ✅ Passed The title clearly identifies the main change as implementing the CheckMo chatbot feature.
Description check ✅ Passed The description covers the required changes, issue, checklist, tests, configuration, and review notes; only the review-ready checklist item remains unchecked.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/298/chatbot

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.

@shinwokkang shinwokkang changed the title Feat/298/chatbot Feat: 책모 챗봇 구현 Aug 3, 2026

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

🧹 Nitpick comments (4)
src/main/java/checkmo/chatbot/internal/config/ChatbotRestTemplateConfig.java (1)

24-31: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a pooled HTTP client for the Gemini RestTemplate.

SimpleClientHttpRequestFactory opens a new connection per request with no pooling. Under concurrent chatbot traffic, this adds TCP/TLS handshake overhead on every Gemini call. Consider HttpComponentsClientHttpRequestFactory backed by a PoolingHttpClientConnectionManager to reuse connections to the Gemini endpoint.

🤖 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/checkmo/chatbot/internal/config/ChatbotRestTemplateConfig.java`
around lines 24 - 31, Update chatbotRestTemplate() to use
HttpComponentsClientHttpRequestFactory backed by a
PoolingHttpClientConnectionManager instead of SimpleClientHttpRequestFactory,
configuring the existing timeout values on the pooled client while preserving
the RestTemplate bean and Gemini request behavior.
src/main/resources/db/migration/V20260727__create_chatbot_tables.sql (1)

14-27: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider an in-module foreign key for chat_session_id.

chat_message.chat_session_id has no foreign key to chat_session.id. Both tables live in the same module, so this does not conflict with the retrieved learning against cross-module foreign keys. Add a FK constraint here to enforce referential integrity between session and message rows.

Based on learnings, "avoid foreign key constraints that cross module boundaries in SQL migrations... only place constraints where both tables reside in the same module," which supports adding this FK since both tables are in the chatbot module.

🤖 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/resources/db/migration/V20260727__create_chatbot_tables.sql` around
lines 14 - 27, Add an in-module foreign key for chat_message.chat_session_id
referencing chat_session.id in the CREATE TABLE definition, using the
migration’s existing constraint style and preserving the current index. Ensure
message rows cannot reference nonexistent chat sessions.

Source: Learnings

src/main/java/checkmo/authentication/internal/config/SecurityConfig.java (1)

86-86: 🩺 Stability & Availability | 🔵 Trivial

Consider rate limiting for the new public chatbot route.

permitAll() is correct for this route per the PR intent. This route is unauthenticated and each call triggers a Gemini API request, with possible escalation to a more costly model. No throttling is visible in the provided files for this route.

If no rate limiter exists upstream (gateway, WAF, CDN), add one for /api/v1/chatbot/messages to bound cost and protect availability from anonymous traffic spikes.

🤖 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/checkmo/authentication/internal/config/SecurityConfig.java` at
line 86, Keep the permitAll configuration for /api/v1/chatbot/messages, but add
rate limiting for this unauthenticated route using the project’s existing
limiter mechanism, or configure the upstream gateway/WAF/CDN if that is where
throttling is managed. Ensure anonymous requests are bounded before they can
trigger Gemini calls, including costly model escalation.
src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java (1)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hedge phrase duplicated across two files with no shared source of truth. UnresolvedSessionTagger.BOT_HEDGE_PHRASE and the hedge phrase inside ChatbotSystemPrompt.SYSTEM_PROMPT (line 29) must stay byte-for-byte identical, but nothing enforces this. An independent edit to either file silently breaks unresolved-session detection.

  • src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java#L13-L13: Make BOT_HEDGE_PHRASE package-visible (or move it into ChatbotSystemPrompt) and reference it from a single source, or add a test asserting ChatbotSystemPrompt.SYSTEM_PROMPT.contains(BOT_HEDGE_PHRASE).
  • src/main/java/checkmo/chatbot/internal/prompt/ChatbotSystemPrompt.java#L15-L199: Keep the hedge phrase at line 29 as the single source of truth that UnresolvedSessionTagger references or is tested against, so future prompt edits fail a test instead of silently breaking detection.
🤖 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/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java`
at line 13, Use the hedge phrase in ChatbotSystemPrompt.SYSTEM_PROMPT as the
single source of truth and update UnresolvedSessionTagger.BOT_HEDGE_PHRASE to
reference it, or add a test enforcing that SYSTEM_PROMPT contains the detector
phrase. Apply the corresponding consistency change in
src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java:13-13
and
src/main/java/checkmo/chatbot/internal/prompt/ChatbotSystemPrompt.java:15-199,
preserving the existing byte-for-byte phrase.
🤖 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/checkmo/chatbot/internal/config/properties/ChatbotProperties.java`:
- Around line 11-15: Update the ChatbotProperties class by adding Spring’s
`@Validated` annotation alongside `@ConfigurationProperties` so validation
constraints on apiKey, baseUrl, apiVersion, timeoutMs, and nested `@Valid` fields
execute during configuration binding.

In
`@src/main/java/checkmo/chatbot/internal/service/ChatOrchestrationService.java`:
- Around line 47-100: Update respond so the session mutations and user turn are
persisted before invoking geminiApiService.generateReply, while preserving the
existing non-transactional orchestration. Add a failure path around the Gemini
call that records a consistent failed assistant turn (or equivalent established
failure representation) and persists the updated chatSession, preventing
orphaned user history and lost activity/unresolved state.

In `@src/main/java/checkmo/chatbot/internal/service/PiiMaskingService.java`:
- Around line 65-76: Update maskOwnNickname to skip nickname replacement when
the fetched nickname is below the required minimum length, while preserving the
existing null, blank, and valid-length replacement behavior.

In `@src/main/java/checkmo/chatbot/web/dto/ChatbotRequestDTO.java`:
- Around line 23-25: Update the message field in ChatbotRequestDTO by adding a
`@Size` constraint with an appropriate maximum length, while preserving the
existing `@NotBlank` validation and schema metadata. Ensure the bound applies
before ChatOrchestrationService.respond forwards the request to external models.

In
`@src/test/java/checkmo/chatbot/internal/service/ChatOrchestrationServiceTest.java`:
- Around line 87-97: Update ChatOrchestrationService.resolveSession to validate
the loaded ChatSession.memberId against the supplied memberId, rejecting
mismatched or null ownership instead of reusing the session. Add the appropriate
ChatbotErrorStatus for unauthorized session-token reuse and propagate it through
the existing service error flow. Extend ChatOrchestrationServiceTest with
coverage for mismatched and null member IDs while preserving successful reuse
for the owning member.

---

Nitpick comments:
In `@src/main/java/checkmo/authentication/internal/config/SecurityConfig.java`:
- Line 86: Keep the permitAll configuration for /api/v1/chatbot/messages, but
add rate limiting for this unauthenticated route using the project’s existing
limiter mechanism, or configure the upstream gateway/WAF/CDN if that is where
throttling is managed. Ensure anonymous requests are bounded before they can
trigger Gemini calls, including costly model escalation.

In
`@src/main/java/checkmo/chatbot/internal/config/ChatbotRestTemplateConfig.java`:
- Around line 24-31: Update chatbotRestTemplate() to use
HttpComponentsClientHttpRequestFactory backed by a
PoolingHttpClientConnectionManager instead of SimpleClientHttpRequestFactory,
configuring the existing timeout values on the pooled client while preserving
the RestTemplate bean and Gemini request behavior.

In `@src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java`:
- Line 13: Use the hedge phrase in ChatbotSystemPrompt.SYSTEM_PROMPT as the
single source of truth and update UnresolvedSessionTagger.BOT_HEDGE_PHRASE to
reference it, or add a test enforcing that SYSTEM_PROMPT contains the detector
phrase. Apply the corresponding consistency change in
src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java:13-13
and
src/main/java/checkmo/chatbot/internal/prompt/ChatbotSystemPrompt.java:15-199,
preserving the existing byte-for-byte phrase.

In `@src/main/resources/db/migration/V20260727__create_chatbot_tables.sql`:
- Around line 14-27: Add an in-module foreign key for
chat_message.chat_session_id referencing chat_session.id in the CREATE TABLE
definition, using the migration’s existing constraint style and preserving the
current index. Ensure message rows cannot reference nonexistent chat sessions.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6cd8d469-317d-4905-bc9e-27a6461071a4

📥 Commits

Reviewing files that changed from the base of the PR and between 0b610ed and ef30c25.

📒 Files selected for processing (35)
  • src/main/java/checkmo/authentication/internal/config/SecurityConfig.java
  • src/main/java/checkmo/chatbot/internal/config/ChatbotRestTemplateConfig.java
  • src/main/java/checkmo/chatbot/internal/config/properties/ChatbotProperties.java
  • src/main/java/checkmo/chatbot/internal/converter/ChatbotConverter.java
  • src/main/java/checkmo/chatbot/internal/entity/ChatMessage.java
  • src/main/java/checkmo/chatbot/internal/entity/ChatRole.java
  • src/main/java/checkmo/chatbot/internal/entity/ChatSession.java
  • src/main/java/checkmo/chatbot/internal/exception/ChatbotErrorStatus.java
  • src/main/java/checkmo/chatbot/internal/exception/ChatbotException.java
  • src/main/java/checkmo/chatbot/internal/prompt/ChatbotSystemPrompt.java
  • src/main/java/checkmo/chatbot/internal/repository/ChatMessageRepository.java
  • src/main/java/checkmo/chatbot/internal/repository/ChatSessionRepository.java
  • src/main/java/checkmo/chatbot/internal/service/ChatOrchestrationService.java
  • src/main/java/checkmo/chatbot/internal/service/ChatReply.java
  • src/main/java/checkmo/chatbot/internal/service/EscalationDecider.java
  • src/main/java/checkmo/chatbot/internal/service/GeminiApiService.java
  • src/main/java/checkmo/chatbot/internal/service/PiiMaskingService.java
  • src/main/java/checkmo/chatbot/internal/service/PromptLeakGuard.java
  • src/main/java/checkmo/chatbot/internal/service/UnresolvedSessionTagger.java
  • src/main/java/checkmo/chatbot/internal/service/dto/GeminiApiDTO.java
  • src/main/java/checkmo/chatbot/package-info.java
  • src/main/java/checkmo/chatbot/web/controller/ChatbotController.java
  • src/main/java/checkmo/chatbot/web/dto/ChatbotRequestDTO.java
  • src/main/java/checkmo/chatbot/web/dto/ChatbotResponseDTO.java
  • src/main/java/checkmo/common/monitoring/CheckmoMetrics.java
  • src/main/resources/application-chatbot.yml
  • src/main/resources/db/migration/V20260727__create_chatbot_tables.sql
  • src/test/java/checkmo/chatbot/ChatbotApiTest.java
  • src/test/java/checkmo/chatbot/internal/service/ChatOrchestrationServiceTest.java
  • src/test/java/checkmo/chatbot/internal/service/EscalationDeciderTest.java
  • src/test/java/checkmo/chatbot/internal/service/GeminiApiServiceTest.java
  • src/test/java/checkmo/chatbot/internal/service/PiiMaskingServiceTest.java
  • src/test/java/checkmo/chatbot/internal/service/PromptLeakGuardTest.java
  • src/test/java/checkmo/chatbot/internal/service/UnresolvedSessionTaggerTest.java
  • src/test/resources/application-test.yml

Comment on lines +11 to +15
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "chatbot.gemini")
public class ChatbotProperties {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate ChatbotProperties =="
fd -a 'ChatbotProperties\.java' . || true

echo "== Inspect ChatbotProperties =="
file="$(fd 'ChatbotProperties\.java' . | head -n 1 || true)"
if [ -n "$file" ]; then
  wc -l "$file"
  sed -n '1,220p' "$file" | cat -n
fi

echo "== Search for validateConfigurationProperties / ValidationAutoConfiguration references =="
rg -n "validateConfigurationProperties|validation\.configurationProperties|`@ConfigurationProperties`|`@Validated`" src/main/java -S || true

echo "== Search for ChatbotProperties consumers =="
rg -n "ChatbotProperties|chatbotProperties|getApiKey\(|getBaseUrl\(|getApiVersion\()" src/main/java -S || true

echo "== Spring Boot version declarations =="
fd -a 'pom.xml|build.gradle|build.gradle.kts' . | sed 's#^\./##' | while read -r f; do
  echo "--- $f"
  rg -n "spring-boot|version>" "$f" -S || true
done

Repository: checkmo2025/BE

Length of output: 5862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ChatbotProperties consumers =="
rg -n "ChatbotProperties|chatbotProperties|getApiKey\\(|getBaseUrl\\(|getApiVersion\\(|GeminiApiService" src/main/java -S || true

echo "== Build files =="
fd -a '^(build.gradle\.kts|build.gradle|gradle.properties|pom.xml)$' . | sed 's#^\./##' | sort | while read -r f; do
  echo "--- $f"
  sed -n '1,140p' "$f" | cat -n
done

echo "== Check Boot property for validation of configuration properties =="
rg -n "validateConfigurationProperties|ConfigurationProperties:|enable.*validation|validation\\.configurationProperties" . -S --glob 'src/main/java' --glob 'src/main/resources' --glob '*.gradle' --glob '*.properties' --glob '*.yml' --glob '*.yaml' || true

Repository: checkmo2025/BE

Length of output: 8390


🌐 Web query:

Spring Boot 3.5 @ConfigurationPropertiesBean Validation requires@validated documentation

💡 Result:

In Spring Boot 3.5, using Bean Validation (JSR 380) annotations (such as @NotBlank, @NotNull, @Size) on a @ConfigurationProperties bean requires the class to be annotated with @Validated to trigger the validation process [1]. Key requirements for validating @ConfigurationProperties beans: 1. Dependency: You must have a JSR-303/JSR-380 implementation, such as Hibernate Validator, on your classpath. This is typically provided by including the spring-boot-starter-validation dependency [2][1][3]. 2. Enabling Validation: Adding @Validated to your @ConfigurationProperties class is mandatory [1]. Without this annotation, the constraint annotations placed on the fields will be ignored by the binding process [1]. 3. Nested Objects: To cascade validation to nested objects, you must annotate the nested field with @Valid [1][3]. Note that in some versions of Spring Boot, validation of nested objects might occur even without @Valid due to specific implementation details in ConfigurationPropertiesBinder, though using @Valid is considered the standard practice to align with the Bean Validation specification [4][3]. This approach allows your application to fail fast at startup if configuration properties do not meet the defined constraints, preventing runtime errors later [1][3]. Ensure that your @ConfigurationProperties bean is correctly registered as a bean, for example, by using @EnableConfigurationProperties or component scanning [1][5].

Citations:


Add @Validated to ChatbotProperties so constraints run during binding.

Spring Boot validates a @ConfigurationProperties class only when it is annotated with @Validated, and spring-boot-starter-validation provides the implementation. Without it, @NotBlank on apiKey, baseUrl, apiVersion, timeoutMs, and nested @Valid fields are skipped, so invalid Gemini config can bind successfully and fail later in GeminiApiService.

🛡️ Proposed fix
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.Positive;
 import lombok.Getter;
 import lombok.Setter;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;

 `@Getter`
 `@Setter`
 `@Component`
+@Validated
 `@ConfigurationProperties`(prefix = "chatbot.gemini")
 public class ChatbotProperties {
📝 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
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "chatbot.gemini")
public class ChatbotProperties {
`@Getter`
`@Setter`
`@Component`
`@Validated`
`@ConfigurationProperties`(prefix = "chatbot.gemini")
public class ChatbotProperties {
🤖 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/checkmo/chatbot/internal/config/properties/ChatbotProperties.java`
around lines 11 - 15, Update the ChatbotProperties class by adding Spring’s
`@Validated` annotation alongside `@ConfigurationProperties` so validation
constraints on apiKey, baseUrl, apiVersion, timeoutMs, and nested `@Valid` fields
execute during configuration binding.

Comment on lines +47 to +100
public ChatReply respond(String sessionToken, Long memberId, String rawUserMessage) {
ChatSession chatSession = resolveSession(sessionToken, memberId);
boolean wasUnresolved = chatSession.isUnresolved();
chatSession.recordActivity();

String maskedUserMessage = piiMaskingService.mask(rawUserMessage, memberId);
boolean userNegativeReaction = unresolvedSessionTagger.isUserNegativeReaction(maskedUserMessage);
if (userNegativeReaction) {
chatSession.flagUnresolved();
}

List<Content> contents = appendUserTurn(buildHistory(chatSession.getId()), maskedUserMessage);
chatMessageRepository.save(
ChatMessage.userMessage(chatSession.getId(), maskedUserMessage, userNegativeReaction));

boolean escalated = escalationDecider.shouldEscalate(maskedUserMessage, userNegativeReaction);
String modelName = escalated
? chatbotProperties.getEscalationModel().getName()
: chatbotProperties.getDefaultModel().getName();
checkmoMetrics.incrementChatbotModelCall(modelName, escalated);

// 봇 응답에는 마스킹을 적용하지 않는다. 모델은 이미 마스킹된 사용자 입력만 봤으므로 실제 PII를
// 답변에 포함시킬 방법이 없고(구조적으로 안전), 반대로 "비밀번호는 6~12자..." 같은 정상 안내
// 문장을 정규식이 PII로 오탐해 훼손하는 위험이 더 크다(실제 QA에서 확인됨).
String reply = geminiApiService.generateReply(ChatbotSystemPrompt.SYSTEM_PROMPT, contents, modelName);

if (promptLeakGuard.isLeaked(reply)) {
checkmoMetrics.incrementChatbotPromptLeakDetected();
reply = PromptLeakGuard.SAFE_FALLBACK_REPLY;
}

boolean botUncertain = unresolvedSessionTagger.isBotUncertain(reply);
if (botUncertain) {
chatSession.flagUnresolved();
}

chatMessageRepository.save(
ChatMessage.assistantMessage(chatSession.getId(), reply, modelName, escalated, botUncertain));
chatSessionRepository.save(chatSession);

if (!wasUnresolved && chatSession.isUnresolved()) {
checkmoMetrics.incrementChatbotUnresolvedSession();
}

return new ChatReply(
chatSession.getSessionToken(),
reply,
escalated,
modelName,
chatSession.isUnresolved(),
chatbotProperties.getHandoff().getSupportUrl(),
chatbotProperties.getHandoff().getInquiryFormUrl()
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Gemini failures can leave an orphaned user message and lose session state.

The user ChatMessage is saved and committed at line 59-60, independently of the Gemini call at line 71. If geminiApiService.generateReply throws (network failure, Gemini 5xx, timeout), the method exits before the assistant ChatMessage save (line 83-84) and before chatSessionRepository.save(chatSession) (line 85).

This means, on a Gemini failure:

  • chatSession.recordActivity() (line 50) and any flagUnresolved() call from this turn (line 55) are never persisted.
  • The user's message is left in history with no paired assistant reply.
  • The next turn's buildHistory() replays this orphaned user message as unanswered context.

The method intentionally avoids one large @Transactional boundary around the Gemini call (see the comment at line 28-29), but that design choice creates this specific gap. Persist the session-state mutation for this turn (recordActivity/flagUnresolved) before calling Gemini, or catch the failure and record a consistent state (e.g., a failed-turn assistant message) so the conversation history stays coherent after an external call failure.

As per coding guidelines, "Keep services focused on orchestration: loading, saving, deleting, event publication, logging, and transaction boundaries."

🤖 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/checkmo/chatbot/internal/service/ChatOrchestrationService.java`
around lines 47 - 100, Update respond so the session mutations and user turn are
persisted before invoking geminiApiService.generateReply, while preserving the
existing non-transactional orchestration. Add a failure path around the Gemini
call that records a consistent failed assistant turn (or equivalent established
failure representation) and persists the updated chatSession, preventing
orphaned user history and lost activity/unresolved state.

Source: Coding guidelines

Comment on lines +65 to +76
private String maskOwnNickname(String text, Long memberId) {
if (memberId == null) {
return text;
}

String nickname = memberAPI.fetchNickname(memberId);
if (!StringUtils.hasText(nickname)) {
return text;
}

return text.replace(nickname, "[닉네임]");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Short nicknames can over-mask unrelated text.

text.replace(nickname, "[닉네임]") replaces every literal occurrence of the nickname. If the nickname is short or a common word, this replaces unrelated substrings in the user's message before it is sent to Gemini and stored, corrupting conversation content.

Add a minimum-length guard before replacing, so very short nicknames are skipped.

🛡️ Proposed guard for short nicknames
         String nickname = memberAPI.fetchNickname(memberId);
-        if (!StringUtils.hasText(nickname)) {
+        if (!StringUtils.hasText(nickname) || nickname.length() < 2) {
             return text;
         }
📝 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 String maskOwnNickname(String text, Long memberId) {
if (memberId == null) {
return text;
}
String nickname = memberAPI.fetchNickname(memberId);
if (!StringUtils.hasText(nickname)) {
return text;
}
return text.replace(nickname, "[닉네임]");
}
private String maskOwnNickname(String text, Long memberId) {
if (memberId == null) {
return text;
}
String nickname = memberAPI.fetchNickname(memberId);
if (!StringUtils.hasText(nickname) || nickname.length() < 2) {
return text;
}
return text.replace(nickname, "[닉네임]");
}
🤖 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/checkmo/chatbot/internal/service/PiiMaskingService.java` around
lines 65 - 76, Update maskOwnNickname to skip nickname replacement when the
fetched nickname is below the required minimum length, while preserving the
existing null, blank, and valid-length replacement behavior.

Comment on lines +23 to +25
@NotBlank(message = "메시지는 필수입니다.")
@Schema(description = "사용자가 입력한 질문", example = "책이야기 쓰려면?")
private String message;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a maximum length to message.

message only has @NotBlank. No upper bound exists. POST /api/v1/chatbot/messages is unauthenticated (see SecurityConfig.java line 86), so any anonymous caller can submit an arbitrarily large message. ChatOrchestrationService.respond forwards this text to the Gemini API after masking, and can also route it to the escalation model. An unbounded, unauthenticated input to a paid external API is a cost and availability risk.

Add a @Size constraint to cap the request payload.

🛡️ Proposed fix to bound message length
+import jakarta.validation.constraints.Size;
...
         `@NotBlank`(message = "메시지는 필수입니다.")
+        `@Size`(max = 2000, message = "메시지가 너무 깁니다.")
         `@Schema`(description = "사용자가 입력한 질문", example = "책이야기 쓰려면?")
         private String message;
📝 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
@NotBlank(message = "메시지는 필수입니다.")
@Schema(description = "사용자가 입력한 질문", example = "책이야기 쓰려면?")
private String message;
`@NotBlank`(message = "메시지는 필수입니다.")
`@Size`(max = 2000, message = "메시지가 너무 깁니다.")
`@Schema`(description = "사용자가 입력한 질문", example = "책이야기 쓰려면?")
private String message;
🤖 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/checkmo/chatbot/web/dto/ChatbotRequestDTO.java` around lines 23
- 25, Update the message field in ChatbotRequestDTO by adding a `@Size` constraint
with an appropriate maximum length, while preserving the existing `@NotBlank`
validation and schema metadata. Ensure the bound applies before
ChatOrchestrationService.respond forwards the request to external models.

Comment on lines +87 to +97
@Test
void reusesExistingSessionWhenTokenProvided() {
ChatSession existing = ChatSession.start(5L, "existing-token");
ReflectionTestUtils.setField(existing, "id", 10L);
when(chatSessionRepository.findBySessionToken("existing-token")).thenReturn(Optional.of(existing));

ChatReply reply = chatOrchestrationService.respond("existing-token", 5L, "모임 가입하려면?");

assertThat(reply.sessionToken()).isEqualTo("existing-token");
verify(chatSessionRepository).findBySessionToken("existing-token");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect ChatOrchestrationService.respond to check session-ownership handling.
fd ChatOrchestrationService.java src/main/java/checkmo/chatbot --exec cat -n {}

Repository: checkmo2025/BE

Length of output: 7018


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate relevant files"
fd -a 'ChatOrchestrationController|ChatOrchestrationService|ChatSession|ChatbotErrorStatus|ChatbotException' src/main/java src/test/java | sed 's#^\./##'

echo
echo "Inspect ChatOrchestrationController:"
fd ChatOrchestrationController.java src/test/java/checkmo/chatbot src/main/java/checkmo/chatbot --exec sh -c 'echo "--- $1"; cat -n "$1"' sh {}

echo
echo "Inspect ChatSession.resolveSession usages and tests:"
rg -n "resolveSession|respond\\(|memberId|sessionToken|findBySessionToken|SETTLE|SESSION_NOT_FOUND|ACCESS_DENIED|FORBIDDEN" src/test/java src/main/java -S

Repository: checkmo2025/BE

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "ChatSession.java:"
cat -n src/main/java/checkmo/chatbot/internal/entity/ChatSession.java

echo
echo "Chatbot controller candidates:"
fd -i 'chatbot.*(controller|web)|controller.*(chatbot)|Chatbot.*' src/main/java | while read -r f; do
  echo "--- $f"
  cat -n "$f"
done

echo
echo "ChatbotApiTest relevant sections:"
cat -n src/test/java/checkmo/chatbot/ChatbotApiTest.java | sed -n '1,90p'

echo
echo "Targeted exception/status symbols:"
cat -n src/main/java/checkmo/chatbot/internal/exception/ChatbotErrorStatus.java
echo "---"
cat -n src/main/java/checkmo/chatbot/internal/exception/ChatbotException.java | sed -n '1,180p'
echo "---"
cat -n src/main/java/checkmo/common/exception/GlobalExceptionHandler.java | sed -n '1,220p' 2>/dev/null || true

Repository: checkmo2025/BE

Length of output: 2058


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find ChatbotApiTest and controller files:"
fd -i 'ChatbotApiTest|.*Chatbot.*Controller|.*Controller.*Chatbot|chatbot' src/main/java src/test/java | sed 's#^\./##' | sed -n '1,120p'

echo
echo "ChatbotApiTest relevant sections:"
for f in $(fd -i 'ChatbotApiTest' src/test/java); do
  echo "--- $f"
  sed -n '1,120p' "$f" | cat -n
done

echo
echo "Targeted files:"
for f in \
  src/main/java/checkmo/chatbot/internal/entity/ChatSession.java \
  src/main/java/checkmo/chatbot/internal/exception/ChatbotErrorStatus.java \
  src/main/java/checkmo/chatbot/internal/exception/ChatbotException.java \
  src/main/java/checkmo/chatbot/internal/service/ChatOrchestrationService.java \
  src/test/java/checkmo/chatbot/internal/service/ChatOrchestrationServiceTest.java
do
  echo "### $f"
  case "$(basename "$f")" in
    ChatbotErrorStatus.java|ChatbotException.java|ChatSession.java|ChatOrchestrationService.java) sed -n '1,180p' "$f" | cat -n ;;
    ChatOrchestrationServiceTest.java) sed -n '1,180p' "$f" | cat -n ;;
  esac
done

echo
echo "Find ChatbotController exactly:"
rg -l "class .*Chatbot|`@GetMapping`\".*chat|chatbot" src/main/java src/test/java | sed 's#^\./##' | sed -n '1,60p'

Repository: checkmo2025/BE

Length of output: 27739


Protect existing chatbot sessions against owner mismatch.

ChatOrchestrationService.resolveSession loads sessions only by sessionToken, so a caller can reuse another member’s token while supplying a different or null memberId and continue that member’s history. Add ownership checks for ChatSession.memberId, a ChatbotErrorStatus for this case, and service coverage for mismatched tokens.

🤖 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/checkmo/chatbot/internal/service/ChatOrchestrationServiceTest.java`
around lines 87 - 97, Update ChatOrchestrationService.resolveSession to validate
the loaded ChatSession.memberId against the supplied memberId, rejecting
mismatched or null ownership instead of reusing the session. Add the appropriate
ChatbotErrorStatus for unauthorized session-token reuse and propagate it through
the existing service error flow. Extend ChatOrchestrationServiceTest with
coverage for mismatched and null member IDs while preserving successful reuse
for the owning member.

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

Labels

✨ feature Introduce new features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] ChatBot 기능 개발

1 participant