o2o-negosium-original/backend/router/v1/chat/chat.py
민헌 1a933409a3 [refactor] backend services/router 정리 — chat 분리·staticmethod 집약·protocol 문서화·테스트 보강
- router: 채팅 라우터/프로토콜을 negotiation/ 에서 chat/ 으로 분리(git mv, URL 유지).
  chat_protocol.py → chat/protocol.py (폴더당 protocol.py convention 복원), router 등록·import 갱신.
- chat_service: 순수 헬퍼/매퍼를 모듈 함수 → 클래스 @staticmethod 로 이동하고 클래스 상단에 집약.
- protocol: Req 모델 + 코드/enum 필드에 Field(description=...) 추가(Swagger 노출),
  Req 문자열 필드에 max_length(DB 컬럼 정합) 추가 → 초과 입력이 DB 오류 대신 422.
- models: updated_at 에 onupdate 추가 → UPDATE 시 자동 갱신(negodata convention 일치).
- tests: negotiation reject e2e 6케이스 + chat 순수 헬퍼 단위 4케이스 추가(45→55 passed).
- enums: 장황한 주석/docstring 축약(코드값 변경 없음).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:07:54 +09:00

59 lines
2.4 KiB
Python

from fastapi import APIRouter, Depends
from fastapi.security import HTTPAuthorizationCredentials
from common.models.gmodel import UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse, security
from services.chat_service import ChatService
from .protocol import Req_ChatSend, Res_ChatInit, Res_ChatMessages, Res_ChatSend
# URL 은 협상 세션의 하위 리소스라 prefix 는 /v1/negotiation 유지(파일만 chat/ 로 분리).
router = APIRouter(prefix="/v1/negotiation", tags=["Chat"], responses={404: {"description": "Not found"}})
@router.get(
path="/sessions/{session_id}/chat/init",
response_model=Res_ChatInit,
summary="채팅 진입(상품·견적 메타)",
description="채팅 화면 진입용. 상품/견적 정보 + 현재 세션 상태 + 마감 시각(타이머)을 반환한다. 소유(공급사) 검증.",
)
async def chat_init(
session_id: str,
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: ChatService = Depends(),
):
return RemoveNoneResponse(await service.init(user_info, credentials.credentials, session_id))
@router.get(
path="/sessions/{session_id}/chat/messages",
response_model=Res_ChatMessages,
summary="대화 히스토리",
description="세션의 대화 말풍선 목록(seq 오름차순). 비어 있고 협상중이면 오프닝 메시지를 생성해 포함한다.",
)
async def chat_messages(
session_id: str,
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: ChatService = Depends(),
):
return RemoveNoneResponse(await service.messages(user_info, credentials.credentials, session_id))
@router.post(
path="/sessions/{session_id}/chat/send",
response_model=Res_ChatSend,
summary="협상 한 턴 전송",
description="유저 입력을 보내고 agent 가 만든 봇 응답 1건을 반환한다(append-only). 종료 시 세션 입찰을 확정한다.",
)
async def chat_send(
session_id: str,
req: Req_ChatSend,
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: ChatService = Depends(),
):
return RemoveNoneResponse(
await service.send(user_info, credentials.credentials, session_id, req.user_input_type, req.user_input)
)