[BE-12] 방목록 미리보기 확장 + 채팅 인증 JWT 통일

작업 내용 (설계 의도)

변경 사항

근거 TDD: 20260704-채팅시스템고도화-tdd.md (인증 계약·REST API 계약·응답 DTO 필드 스키마·방목록 미리보기 N+1 회피). FE 설계 역제안 1·4·5 반영.

방목록 미리보기 필드를 추가하고, 기존 /rooms REST 컨트롤러의 X-User-Id 임시 인증을 Bearer JWT로 전환한다. SecurityConfig 승격과 컨트롤러 전환을 한 티켓에서 원자적으로 수행해 과도기(경로는 authenticated인데 컨트롤러는 헤더 기대) 노출을 방지한다.

방목록 미리보기 (FR-9)

  • domain/message/vo/RoomListView.kt (신규 projection VO): roomId/type/name/contextType/lastMessageContent/lastMessageAt. QueryDSL @QueryProjection.
  • domain/message/repository/RoomCustomRepository.kt + RoomCustomRepositoryImpl.kt: findMyRoomViews(userId, keyword): List<RoomListView> 추가 — rooms + 마지막 메시지 1건을 단일 쿼리 조인(상관 서브쿼리 MAX(m.id) 또는 last_message_at 기준). 방마다 메시지 재조회하는 N+1 금지.
  • presentation/message/dto/response/RoomResponse.kt (변경): contextType(nullable), lastMessagePreview(nullable, lastMessageContent를 최대 50자로 잘라 생성), lastMessageAt(nullable) 필드 추가. of(RoomListView) 오버로드 추가(기존 of(Room) 유지).
  • application/message/usecase/ListMyRoomsUseCase.kt (변경): findMyRoomViews 사용하도록 전환.

인증 JWT 통일 (FR 공통, NFR)

  • SecurityConfig.kt (변경): /rooms/** permitAll → authenticated() 승격. /products/**는 기존 유지(goods 채팅 BE-11은 자체 경로). — wave3에서 본 티켓만 SecurityConfig 수정(BE-04는 wave2·/ws·/communities; BE-08은 SecurityConfig 미수정).
  • presentation/message/controller/RoomApiController.kt + MessageApiController.kt (변경): @RequestHeader("X-User-Id")@AuthenticationPrincipal principal: UserPrincipal(principal.id)로 전환. TODO(AUTH-03/04) 주석 제거.
  • 롤백: SecurityConfig·컨트롤러 전환은 동시 배포라 원자적. 미리보기 필드는 additive(구 클라이언트는 새 필드 무시). 문제 시 직전 태그로 compose 롤백.

의존

  • BE-03a (Room.contextType — RoomResponse.contextType, RoomCustomRepository findByContext와 같은 파일이므로 이후)
  • BE-04 (SecurityConfig 선행 수정자 — wave2 이후 wave3에서 순차 수정)

다이어그램

처리 흐름

sequenceDiagram
    participant C as Client
    participant Ctl as RoomApiController
    participant UC as ListMyRoomsUseCase
    participant R as RoomCustomRepository
    C->>Ctl: GET /rooms/me (Bearer JWT)
    Ctl->>Ctl: @AuthenticationPrincipal → userId
    Ctl->>UC: execute(userId, keyword)
    UC->>R: findMyRoomViews(userId, keyword) 단일 조인
    R-->>Ctl: List<RoomListView>
    Ctl-->>C: List<RoomResponse> (preview/lastMessageAt/contextType)

테스트 케이스

  • GET /rooms/me는 각 방의 lastMessagePreview(최대 50자)·lastMessageAt·contextType을 포함해 반환한다.
  • 메시지가 없는 방은 lastMessagePreview·lastMessageAt이 null이다.
  • 기존 DIRECT/GROUP 방은 contextType이 null로 반환된다.
  • 방목록 조회가 방 개수와 무관하게 단일 쿼리로 수행된다 (N+1 없음 — 쿼리 카운트 검증).
  • Bearer JWT 없이 /rooms/me 호출 시 401이 반환된다 (authenticated 승격).
  • 유효 JWT의 principal.id로 내 방만 조회된다 (X-User-Id 미사용).
  • POST /rooms·GET /rooms/{id}·DELETE /rooms/{id}도 principal.id 기반으로 정상 동작한다.