Skip to content

feat: 채팅방 멤버 목록 조회 API 추가#549

Merged
dh2906 merged 11 commits intodevelopfrom
feat/get-chat-member
Apr 15, 2026
Merged

feat: 채팅방 멤버 목록 조회 API 추가#549
dh2906 merged 11 commits intodevelopfrom
feat/get-chat-member

Conversation

@dh2906
Copy link
Copy Markdown
Contributor

@dh2906 dh2906 commented Apr 15, 2026

🔍 개요

채팅방에 접속했을 때 해당 채팅방에 속한 유저의 목록을 조회할 수 있는 API를 추가합니다.


🚀 주요 변경 내용

  • DTO 추가: ChatRoomMemberResponse, ChatRoomMembersResponse

    • 멤버 정보: userId, name, profileImageUrl, isOwner, joinedAt
  • Repository: findActiveMembersByChatRoomId 메서드 추가

    • JOIN FETCH로 User 정보 한 번에 로딩 (N+1 방지)
    • leftAt IS NULL 조건으로 나간 멤버 필터링
  • Service: getChatRoomMembers 메서드 구현

    • 채팅방 존재 여부 확인 (404)
    • 멤버십 권한 검증 (403)
    • 탈퇴한 유저 필터링
    • 어드민은 시스템 어드민 방 멤버 조회 가능
  • Controller: GET /chats/rooms/{chatRoomId}/members 엔드포인트 추가

    • Swagger 문서화 완료
  • 테스트: ChatRoomMembershipServiceTest 단위 테스트 추가

    • 멤버 조회 성공
    • 비멤버 접근 시 403
    • 나간 멤버 필터링

💬 참고 사항

  • 예외 처리 순서: 채팅방 없음(404) → 비멤버 접근(403)
  • joinedAtChatRoomMember.createdAt을 사용
  • Integer 타입으로 기존 코드와 일관성 유지

✅ Checklist (완료 조건)

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

dh2906 added 10 commits April 15, 2026 10:59
- ChatRoomMemberResponse: 개별 멤버 정보 (userId, name, imageUrl, isOwner, joinedAt)
- ChatRoomMembersResponse: 멤버 목록 래퍼

클라이언트가 채팅방 참여자 정보를 확인할 수 있도록 응답 객체 정의
- findActiveMembersByChatRoomId: 채팅방 ID로 활성 멤버 조회
- JOIN FETCH로 User 정보를 한 번에 로딩 (N+1 방지)
- leftAt IS NULL 조건으로 나간 멤버 필터링
- getChatRoomMembers: 멤버 목록 조회 비즈니스 로직
- validateMembership: existsByChatRoomIdAndUserId로 권한 검증
- toMemberResponse: Entity → DTO 변환 (createdAt을 joinedAt으로 매핑)

비멤버 접근 시 FORBIDDEN_CHAT_ROOM_ACCESS 예외 발생
- GET /chats/rooms/{chatRoomId}/members 엔드포인트 추가
- ChatApi 인터페이스: Swagger 문서화 (설명, 에러 코드)
- ChatController 구현체: @userid로 사용자 ID 주입

채팅방 접속 시 해당 방의 멤버 목록을 조회할 수 있는 API 제공
- 멤버 조회 성공 케이스 검증
- 비멤버 접근 시 FORBIDDEN 예외 발생 검증
- 나간 멤버는 조회되지 않음 검증

Mockito를 사용하여 Repository 의존성 모킹
- Service: WHAT 설명 주석 제거 (메서드명이 충분히 표현)
- Service: 코드 변경 이력 주석 제거
- Test: 사용되지 않는 any import 제거

코드 리뷰 피드백 반영하여 가독성 개선
- getChatRoomMembers: 탈퇴한 유저(deletedAt != null) 제외
- Stream filter로 deletedAt == null인 멤버만 변환

탈퇴한 사용자는 채팅방 멤버 목록에 노출되지 않음
- getChatRoomMembers: 현재 사용자 정보를 UserRepository에서 조회
- validateMembership: 어드민이 시스템 어드민 방 조회 시 권한 검증 스킵
- NOT_FOUND_USER import 추가
- 테스트: UserRepository mock 설정 추가

어드민은 시스템 어드민 방의 멤버 목록을 조회할 수 있음
- getChatRoomMembers: 채팅방 존재 여부를 멤버십 검증 전에 확인
- 존재하지 않는 채팅방: NOT_FOUND_CHAT_ROOM (404)
- 비멤버 접근: FORBIDDEN_CHAT_ROOM_ACCESS (403)
- validateMembership 파라미터를 ChatRoom으로 변경
- 테스트: ChatRoomRepository mock 설정 추가

잘못된 순서로 인해 존재하지 않는 방도 403이 뜨던 문제 해결
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Warning

Rate limit exceeded

@dh2906 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 12 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 52 minutes and 12 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d834c81-e8f2-4241-bc8b-fe7a4e1a7c73

📥 Commits

Reviewing files that changed from the base of the PR and between da85394 and 8598eb6.

📒 Files selected for processing (3)
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java
  • src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java
📝 Walkthrough

Walkthrough

채팅방의 활성 멤버 목록을 조회하는 새로운 기능이 추가되었습니다. API 인터페이스, 컨트롤러 구현, 데이터 전송 객체(DTO), 저장소 쿼리, 비즈니스 로직 서비스 및 단위 테스트가 포함되었습니다.

Changes

Cohort / File(s) Summary
API 및 컨트롤러
src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java, src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java
GET /rooms/{chatRoomId}/members 엔드포인트 추가. 경로 변수 문서화를 위한 Swagger @Parameter 어노테이션 포함.
데이터 전송 객체
src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java, src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java
멤버 응답 구조 정의. ChatRoomMemberResponse는 사용자 ID, 이름, 프로필 이미지, 소유자 여부, 입장 시간을 포함하는 레코드. ChatRoomMembersResponse는 멤버 목록을 포함.
저장소 및 서비스
src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java, src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java
findActiveMembersByChatRoomId 쿼리 메서드 추가. getChatRoomMembers 서비스 메서드 구현으로 멤버 접근 권한 검증, 활성 멤버 필터링, DTO 변환 처리.
테스트
src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java
활성 멤버 반환, 접근 권한 거부, 퇴장 멤버 제외 등 세 가지 시나리오에 대한 단위 테스트 추가.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

기능

Poem

🐰 멤버들의 목록이 나타나네,
권한 검증으로 안전하게,
활성 멤버만 쏙 뽑아내고,
테스트까지 완벽하게!
채팅방도 요즘 똑똑해졌어요! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 채팅방 멤버 목록 조회 API 추가라는 주요 변경 사항을 명확하고 간결하게 설명합니다.
Description check ✅ Passed PR 설명은 변경 사항의 모든 부분(DTO, Repository, Service, Controller, 테스트)과 구현 세부사항을 상세히 설명하고 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/get-chat-member

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.

@dh2906 dh2906 added the 기능 새로운 기능을 개발합니다. label Apr 15, 2026
@dh2906 dh2906 self-assigned this Apr 15, 2026
@dh2906 dh2906 marked this pull request as ready for review April 15, 2026 02:36
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 15, 2026

🧪 JaCoCo Coverage Report (Changed Files)

Summary

  • Overall Coverage: 63.7% ⚠️
  • Covered Lines: 72 / 113
  • Changed Files: 6

Coverage by File

Class Coverage Lines Status
ChatRoomMembershipService
gg.agit.konect.domain.chat.service
55.7% 49/88 ⚠️
ChatController
gg.agit.konect.domain.chat.controller
92.0% 23/25

⚠️ 알림: 커버리지가 70% 미만입니다. 테스트 추가를 권장합니다.

📊 View Workflow Run

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java`:
- Around line 70-71: Replace the membership check in ChatRoomMembershipService
that uses chatRoomMemberRepository.existsByChatRoomIdAndUserId(...) with a new
repository method that only counts active members (e.g.
existsActiveByChatRoomIdAndUserId or a query including "AND crm.leftAt IS NULL")
so users who previously left are not treated as members; update the conditional
that currently throws CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS) to use this
active-membership check, and add a test case asserting a 403 response when a
user with crm.leftAt != null calls GET /chats/rooms/{chatRoomId}/members to
ensure the rejected-members scenario is covered.
🪄 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: df78a80b-59e0-4b5d-b0a3-e65ae172d8e2

📥 Commits

Reviewing files that changed from the base of the PR and between 3a01152 and da85394.

📒 Files selected for processing (7)
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java
  • src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java
  • src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java
  • src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.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 (2)
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/chat/dto/ChatRoomMembersResponse.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java
  • src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java
**/*

⚙️ CodeRabbit configuration file

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

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

Files:

  • src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java
  • src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java
  • src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java
  • src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java
🧠 Learnings (1)
📚 Learning: 2026-04-13T00:26:23.225Z
Learnt from: dh2906
Repo: BCSDLab/KONECT_BACK_END PR: 533
File: src/main/java/gg/agit/konect/domain/chat/service/ChatService.java:1511-1516
Timestamp: 2026-04-13T00:26:23.225Z
Learning: In ChatService.java (Spring Boot + JPA, MySQL InnoDB), within a `Transactional(readOnly = true)` method, retrying a repository count query (e.g., `countNewerMessagesByChatRoomId`) to handle concurrent inserts is ineffective under REPEATABLE READ isolation: the same DB snapshot is used throughout the transaction, so the retry always returns the same result. A new transaction (`Propagation.REQUIRES_NEW`) would be required for a true retry, but accepting a 1-page offset as a UX tradeoff is preferred for search navigation in this codebase.

Applied to files:

  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java

Comment thread src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java Outdated
- existsActiveByChatRoomIdAndUserId 메서드 추가 (leftAt IS NULL 조건)
- validateMembership에서 활성 멤버만 조회 가능하도록 변경
- 나간 멤버 접근 시 FORBIDDEN 예외 발생 테스트 추가

나간 멤버가 여전히 멤버로 인식되는 문제 해결
@dh2906 dh2906 merged commit e944770 into develop Apr 15, 2026
5 checks passed
@dh2906 dh2906 deleted the feat/get-chat-member branch April 15, 2026 02:47
JanooGwan added a commit that referenced this pull request Apr 24, 2026
* feat: 채팅방 멤버 목록 조회 API 추가 (#549)

* feat: 채팅방 멤버 조회 응답 DTO 추가

- ChatRoomMemberResponse: 개별 멤버 정보 (userId, name, imageUrl, isOwner, joinedAt)
- ChatRoomMembersResponse: 멤버 목록 래퍼

클라이언트가 채팅방 참여자 정보를 확인할 수 있도록 응답 객체 정의

* feat: 활성 멤버 조회 Repository 메서드 추가

- findActiveMembersByChatRoomId: 채팅방 ID로 활성 멤버 조회
- JOIN FETCH로 User 정보를 한 번에 로딩 (N+1 방지)
- leftAt IS NULL 조건으로 나간 멤버 필터링

* feat: 채팅방 멤버 조회 Service 로직 구현

- getChatRoomMembers: 멤버 목록 조회 비즈니스 로직
- validateMembership: existsByChatRoomIdAndUserId로 권한 검증
- toMemberResponse: Entity → DTO 변환 (createdAt을 joinedAt으로 매핑)

비멤버 접근 시 FORBIDDEN_CHAT_ROOM_ACCESS 예외 발생

* feat: 채팅방 멤버 조회 API 엔드포인트 추가

- GET /chats/rooms/{chatRoomId}/members 엔드포인트 추가
- ChatApi 인터페이스: Swagger 문서화 (설명, 에러 코드)
- ChatController 구현체: @userid로 사용자 ID 주입

채팅방 접속 시 해당 방의 멤버 목록을 조회할 수 있는 API 제공

* test: 채팅방 멤버 조회 Service 단위 테스트 추가

- 멤버 조회 성공 케이스 검증
- 비멤버 접근 시 FORBIDDEN 예외 발생 검증
- 나간 멤버는 조회되지 않음 검증

Mockito를 사용하여 Repository 의존성 모킹

* refactor: 불필요한 주석과 import 제거

- Service: WHAT 설명 주석 제거 (메서드명이 충분히 표현)
- Service: 코드 변경 이력 주석 제거
- Test: 사용되지 않는 any import 제거

코드 리뷰 피드백 반영하여 가독성 개선

* feat: 탈퇴한 유저 필터링 추가

- getChatRoomMembers: 탈퇴한 유저(deletedAt != null) 제외
- Stream filter로 deletedAt == null인 멤버만 변환

탈퇴한 사용자는 채팅방 멤버 목록에 노출되지 않음

* feat: 어드민 시스템 어드민 방 멤버 조회 권한 추가

- getChatRoomMembers: 현재 사용자 정보를 UserRepository에서 조회
- validateMembership: 어드민이 시스템 어드민 방 조회 시 권한 검증 스킵
- NOT_FOUND_USER import 추가
- 테스트: UserRepository mock 설정 추가

어드민은 시스템 어드민 방의 멤버 목록을 조회할 수 있음

* fix: 존재하지 않는 채팅방 조회 시 404 반환

- getChatRoomMembers: 채팅방 존재 여부를 멤버십 검증 전에 확인
- 존재하지 않는 채팅방: NOT_FOUND_CHAT_ROOM (404)
- 비멤버 접근: FORBIDDEN_CHAT_ROOM_ACCESS (403)
- validateMembership 파라미터를 ChatRoom으로 변경
- 테스트: ChatRoomRepository mock 설정 추가

잘못된 순서로 인해 존재하지 않는 방도 403이 뜨던 문제 해결

* chore: 코드 포맷팅

* fix: 나간 멤버 권한 검증 로직 수정

- existsActiveByChatRoomIdAndUserId 메서드 추가 (leftAt IS NULL 조건)
- validateMembership에서 활성 멤버만 조회 가능하도록 변경
- 나간 멤버 접근 시 FORBIDDEN 예외 발생 테스트 추가

나간 멤버가 여전히 멤버로 인식되는 문제 해결

* test: ChatRoomMembershipService 테스트 커버리지 보강 (#551)

* test: ChatRoomMembershipService 테스트 커버리지 보강

- 멤버십 서비스의 public 메서드별 정상·예외 분기를 테스트로 고정해 회귀를 막는다
- 중복 생성과 접근 권한 같은 비정상 흐름도 함께 검증해 실제 서비스 로직의 의도를 드러낸다
- lastReadAt 갱신 기준과 system-admin 예외 경로를 명시적으로 확인해 경계 조건 누락을 방지한다

* chore: 코드 포맷팅

* chore: 코드 포맷팅

* test: 리뷰 코멘트의 primitive 타입 제안을 반영

- null 가능성이 없는 테스트 지역 변수는 primitive 타입으로 정리해 불필요한 boxing 경고를 없앤다
- roomId 선언을 int로 통일해 정적 분석 코멘트를 반영하면서 기존 테스트 동작은 그대로 유지한다
- 대상 테스트를 다시 실행해 리뷰 반영 이후에도 병합된 테스트 파일이 정상 동작함을 확인한다

* chore: 롤링 로그 gzip 압축 적용 (#552)

- Logback 롤링 설정이 새로 생성되는 일별 로그를 즉시 gzip으로 압축하도록 조정
- 운영 중 특정 날짜 로그를 파일 단위로 확인하기 쉬운 구조는 유지하면서 디스크 사용량을 줄이도록 선택
- 별도 배치 압축 작업 없이도 backend 로그와 scheduler 로그가 동일한 방식으로 보관되도록 맞춤

* fix: ClaudeClient 시스템 프롬프트 study_time 테이블 힌트 구체화

* fix: ClaudeClient 시스템 프롬프트 study_time 테이블 힌트 구체화

* fix: show tables 선행 조회 원칙 추가 및 줄 길이 120자 제한 준수

* fix: show tables 선행 조회 원칙 추가 및 줄 길이 120자 제한 준수

* fix: JaCoCo 제외 목록에 claude, mcp 인프라 클라이언트 추가

* fix: JaCoCo 제외 목록에 claude, mcp 인프라 클라이언트 추가

* fix: 메시지 검색 결과에 unreadCount, isMuted 필드 추가 (#555)

roomMatches와 동일하게 messageMatches 응답에도
읽지 않은 메시지 수와 알림 뮤트 여부를 포함하도록 수정

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 완료된 SSE emitter 전송 예외를 정리하도록 처리 (#556)

* fix: 완료된 SSE emitter 전송 예외를 정리하도록 처리

- 이미 complete된 emitter에 send를 시도하면 IllegalStateException이 발생할 수 있어
  알림 전송 자체보다 끊어진 연결 정리를 우선하도록 변경
- IOException만 completeWithError로 마무리하고 IllegalStateException은 emitter 제거로만 끝내
  이미 종료된 emitter를 다시 종료하면서 예외가 전파되는 상황을 막음
- 완료된 emitter가 맵에 남아 있어도 예외 없이 정리되는 단위 테스트를 추가해
  재연 경로를 고정하고 회귀를 방지

* fix: SSE emitter 종료 경합 예외를 추가로 흡수

- send 실패 후 completeWithError 호출도 이미 종료된 emitter와 경합할 수 있어
  정리 단계의 예외가 다시 상위로 전파되지 않도록 별도 보호 구문을 추가
- 알림 전송 실패 처리의 목적은 끊어진 emitter를 제거하는 것이므로
  종료 경쟁에서 생기는 IllegalStateException은 로그만 남기고 삼키도록 선택
- 기존 단위 테스트를 다시 실행해 알림 inbox SSE 경로가 회귀 없이 유지되는지 확인

* test: 완료된 SSE emitter 테스트를 결정적으로 고정

- subscribe 경로의 completion callback 타이밍에 따라 테스트가 false positive가 될 수 있어
  완료된 emitter를 직접 주입하는 방식으로 재현 조건을 고정
- 이번 테스트의 목적은 IllegalStateException 정리 분기를 안정적으로 검증하는 것이므로
  구독 로직 부수효과보다 완료된 emitter 상태 자체를 최소 셋업으로 구성
- 수정 후 NotificationInboxSseService 단위 테스트를 다시 실행해 회귀 없이 통과함을 확인

* chore: 모니터링 스택 제거 (#557)

* chore: OpenTelemetry Java agent 배선을 제거해 배포 이미지를 단순화

- Docker 이미지와 stage/prod 배포 워크플로우에서 OTel Java agent 의존성을 함께 제거해 런타임 추적 구성을 저장소 밖으로 정리한다.
- CI 빌드가 더 이상 agent 캐시, checksum, 런타임 전용 환경변수 해제 흐름에 기대지 않도록 바꿔 이미지 빌드 경로를 단순하게 유지한다.

* chore: Prometheus 노출 설정을 제거해 백엔드 모니터링 의존성을 정리

- Prometheus registry 의존성과 별도 monitoring 설정 파일을 제거해 애플리케이션이 더 이상 메트릭 수집 구성을 내장하지 않도록 정리한다.
- Docker healthcheck가 여전히 /actuator/health 를 사용하므로 actuator 는 유지하고, 배포 안정성에 직접 영향이 큰 health 경로만 남긴다.

* chore: 저장소에서 monitoring 스택 배포 자산을 제거

- Prometheus, Grafana, Loki, Promtail 배포 워크플로우와 compose 자산을 함께 제거해 저장소가 더 이상 별도 모니터링 스택을 관리하지 않도록 정리한다.
- monitoring 전용 env 예외와 타깃 갱신 스크립트까지 함께 삭제해 남은 운영 진입점을 줄이고 제거 범위를 한 커밋 안에 묶는다.

* [codex] 로그 추적 상관관계를 정리하고 OTel 흔적을 제거 (#558)

* chore: 로그 추적 상관관계를 정리하고 OTel 흔적을 제거

- 요청 단위 추적이 로그와 비동기 작업 경계에서 끊기지 않도록 requestId 응답 헤더와 MDC 전파를 추가했다
- Datadog과 기존 MDC 키를 함께 읽는 로그 패턴으로 맞춰 배포 환경 차이 때문에 trace 상관관계가 깨지는 상황을 줄였다
- Dockerfile, 예시 환경변수, 배포 워크플로우에서 OpenTelemetry agent 관련 설정을 제거해 현재 운영 기준과 맞지 않는 추적 흔적이 남지 않게 했다
- 관련 단위 테스트를 추가하고 실행해 로깅 상관관계와 설정 회귀를 방지했다

* fix: CORS에서 request id 응답 헤더를 노출

- 응답에 내려주는 X-Request-ID를 브라우저가 읽지 못하면 프론트와 서버 로그를 같은 요청으로 묶기 어려워진다
- WebConfig의 exposedHeaders에 X-Request-ID를 추가해 cross-origin 환경에서도 요청 상관관계 헤더를 읽을 수 있게 했다
- 설정 회귀를 막기 위해 CORS 등록값을 직접 검증하는 단위 테스트를 추가했다

* fix: request id 헤더를 검증 후 로그에 반영

- 클라이언트가 보낸 X-Request-ID를 그대로 신뢰하면 로그 포맷 손상과 추적 품질 저하가 생길 수 있다
- request id를 trim한 뒤 허용 문자와 길이를 검증하고 실패하면 새 값을 발급하도록 바꿔 응답 헤더, 로그, MDC가 같은 정제된 값을 쓰게 했다
- 유효한 값, 공백 포함 값, 잘못된 값을 각각 검증하는 단위 테스트를 추가해 입력 검증 회귀를 막았다

* fix: Datadog 로그 패턴을 canonical key로 정리

- 여러 MDC 키를 이어붙이면 trace와 span 값이 하나의 문자열처럼 보일 수 있어 로그 파싱과 추적 연결이 불안정해진다
- 운영 로그 패턴을 dd.trace_id와 dd.span_id 기준으로 단순화해 한 종류의 canonical key만 출력하도록 정리했다
- 설정 파일 문자열 포함 여부가 아니라 실제 패턴 렌더링 결과를 검증하는 테스트로 바꿔 로그 포맷 회귀를 막았다

* chore: 코드 포맷팅

* fix: 채팅방 마지막 메시지 메타데이터 동기화 (#559)

* fix: 채팅방 마지막 메시지 메타데이터를 DB에 즉시 동기화

- chat_room.last_message_*가 null로 남아 채팅방 목록과 실제 메시지 상태가 어긋나는 문제를 막는다
- 메시지 저장 직후 chat_room 메타데이터를 명시적으로 업데이트해 영속성 컨텍스트 clear 경로에 의존하지 않도록 정리한다
- 회원가입 환영 메시지처럼 같은 패턴을 쓰는 경로도 함께 맞춰 신규 방의 초기 메타데이터 누락을 방지한다
- 기존 운영 데이터는 Flyway 백필 마이그레이션과 회귀 테스트로 함께 보강해 재발을 막는다

* chore: 코드 포맷팅

* refactor: 마지막 메시지 동기화 코드를 읽기 쉽게 정리

- sendMessage의 명시적 readOnly=false 표기를 기본 @transactional로 단순화해 클래스 레벨 설정과의 관계만 남긴다
- ChatRoomRepository updateLastMessage 시그니처 타입 표기를 정리해 읽는 부담을 줄인다
- UserService의 환영 메시지 경로도 syncLastMessage 헬퍼를 사용하도록 맞춰 채팅 메타데이터 갱신 책임을 일관되게 유지한다

* fix: 리뷰 지적 반영해 마지막 메시지 갱신 조건을 보강

- last_message 메타데이터 갱신 쿼리에서 clearAutomatically를 제거해 direct room 복구 흐름의 엔티티 변경이 detach로 유실되지 않게 한다
- 최신 메시지보다 오래된 트랜잭션이 chat_room 마지막 메시지 요약을 덮어쓰지 못하도록 messageId tie-breaker 조건을 추가한다
- direct room 재노출과 조건부 메타데이터 갱신을 테스트로 고정해 리뷰에서 지적된 회귀를 막는다

* fix: 마지막 메시지 동기화의 자기 자신 비교를 제외

- 마지막 메시지 갱신 조건에서 현재 저장한 메시지 자신은 비교 대상에서 제외해 DB timestamp 정밀도 차이로 인한 오판을 막는다
- direct 채팅방 목록과 재입장 시나리오가 chat_room.last_message_* 컬럼에 의존하므로 간헐적으로 이전 메시지가 남는 상태를 방지한다
- CI에서 흔들리던 채팅방 나가기/마지막 메시지 메타데이터 테스트와 jacoco 경로를 로컬에서 다시 검증했다

* chore: .gitignore 항목 추가

* docs: 채팅 도메인 가이드 추가 및 회귀 테스트 보강 (#562)

* docs: 채팅 도메인 정책 가이드를 추가

- 채팅 도메인 작업 전에 AI가 먼저 읽어야 할 정책 중심 가이드를 chat 하위 AGENTS.md로 정리했다.
- direct, group, club group, SYSTEM_ADMIN 문의방의 차이와 목록 요약, 읽음, 멤버십, 검색, 초대 정책을 한 문서에 모아 회귀 포인트를 드러냈다.
- 마지막 메시지, unreadCount, visibleMessageFrom, 문의방 재사용처럼 수정 시 쉽게 놓치는 연쇄 영향을 명시해 안전한 변경 기준을 만들었다.

* test: 채팅 정책 회귀 테스트를 보강

- 문의방 멤버 목록 조회, 문의방 admin 뮤트, 일반 group 검색 제외를 통합 테스트로 추가해 문서화한 정책을 직접 잠갔다.
- 테스트 작성 과정에서 문의방 senderId 표현 정책이 문서처럼 단정적이지 않다는 점을 확인해 AGENTS 문구를 실제 동작 기준의 주의사항으로 낮췄다.
- 문서와 테스트를 함께 조정해 채팅 도메인 가이드가 현재 구현과 어긋난 전제를 주입하지 않도록 정리했다.
- 검증은 ChatApiTest 단위로 실행해 새 케이스와 기존 채팅 API 시나리오가 함께 통과하는지 확인했다.

* docs: 문의방 senderId 문구 실제 동작 기준으로 조정

* test: ChatApiTest 타입 참조를 import로 정리

- parseChatRoomId와 extractRoomIds에서 fully-qualified 타입 참조를 제거해 테스트 가독성을 높였다.
- MvcResult, JsonNode, ArrayList import를 추가하고 시그니처와 지역 변수 선언을 단순 타입으로 맞췄다.
- 로직은 바꾸지 않고 표현만 정리해 리뷰 지적을 반영했다.

* docs: 동아리 도메인 가이드 문서 작성 (#563)

* docs(club): add club domain guide

* test(club): add unit coverage for domain policies

* chore: 코드 포맷팅

* test: 리뷰 코멘트 반영을 위해 club 권한 검증 근거를 보강

- 동아리 도메인 가이드에서 회원 제거 권한과 club_group 접근 검증 기준을 실제 구현 흐름에 맞춰 명확히 정리했다.
- 어드민 비회원 사용자가 leader 권한 검증을 우회하는 정책은 서비스 테스트 목 대신 검증기 단위 테스트로 고정해 책임 경계를 분리했다.
- removeMember 서비스 테스트에는 validateLeaderAccess 위임 검증을 추가해 서비스가 권한 판단을 자체 구현으로 우회하지 않도록 막았다.

* chore: 코드 포맷팅

* fix: 운영 환경에서 Swagger를 비활성화 (#565)

* fix: 운영 환경에서 Swagger를 비활성화

- 운영 서버에서 API 명세와 Swagger UI가 외부에 노출되지 않도록 prod 프로필에서 springdoc을 끈다
- Swagger 관련 Bean과 커스텀 initializer 컨트롤러도 prod에서는 생성되지 않게 막아 우회 노출 가능성을 줄인다
- stage/local 개발 편의성은 유지하고, 운영 환경에만 최소 범위로 적용한다

* chore: 코드 포맷팅

* fix: cache ai database schema context

* fix: DB 스키마 캐시 생명주기 및 테이블 설명 정리

* fix: DB 스키마 캐시 실패 처리 및 도구 우선순위 정리

---------

Co-authored-by: 이동훈 <64298482+dh2906@users.noreply.github.com>
Co-authored-by: 신관규 <soundbar91@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: 이동훈 <2dh2@naver.com>
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