DOCS/300_architecture/sequences/chat_history_flow.md
happybell80 97e0888ce0 Fix more incorrect table names in documentation
- users → user in SQL contexts (94 occurrences)
- robeings → robeing in SQL contexts
- user_preferences → user_preference (14 files)
- slack_workspaces → slack_workspace in SQL contexts (17 files)

All table names now correctly match PostgreSQL schema
2025-09-26 00:52:15 +09:00

5.1 KiB

대화 히스토리 조회 플로우

작성일: 2025-09-02

대상: rb8001/rb10508_micro 대화 히스토리

상태: 구현 완료


1. 전체 플로우

sequenceDiagram
    participant F as Frontend
    participant G as Gateway
    participant R as rb8001
    participant DB as PostgreSQL

    F->>F: localStorage에서 JWT 토큰 가져오기
    F->>F: JWT decode하여 user_id 추출
    F->>G: GET /gateway/api/history?limit=30<br/>Authorization: Bearer {JWT}
    G->>G: JWT 검증 (서명, 만료시간)
    G->>G: JWT에서 sub(UUID) 추출
    G->>G: workspace_member 테이블 조회<br/>robeing_id 확인 (rb8001)
    G->>R: GET /api/history?limit=30<br/>Authorization: Bearer {JWT}<br/>X-User-Id: {UUID}
    R->>R: JWT 검증 및 user_id 추출
    R->>DB: SELECT * FROM conversation_log<br/>WHERE user_id = (:user_id)::uuid
    DB-->>R: 대화 기록 반환
    R->>R: DB row를 Frontend 형식으로 변환<br/>(user/robeing 메시지 분리)
    R-->>G: {"messages": [...], "has_more": true}
    G-->>F: 응답 전달
    F->>F: 화면에 메시지 렌더링

2. JWT 토큰 구조

2.1 auth-server 발급 토큰

{
  "sub": "1e16e9d5-59f3-54da-a661-8abeabff4230",  // UUID
  "email": "goeun2dc@gmail.com",
  "name": "김종태",
  "username": "happybell80",
  "picture": "https://...",
  "exp": 1759046456
}

2.2 중요 필드

  • sub: 사용자 UUID (users.id)
  • username: 사용자명
  • exp: 만료 시간 (Unix timestamp)

3. Gateway 처리

3.1 JWT 검증

# Gateway main.py
def verify_jwt_token(token: str):
    payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=["HS256"])
    return payload["sub"]  # UUID 반환

3.2 robeing 라우팅

# workspace_member 테이블 조회
SELECT robeing_id FROM workspace_member 
WHERE user_id = :user_id

# 결과: rb8001 또는 rb10508_micro

3.3 헤더 전달

headers = {
    "Authorization": request.headers.get("Authorization"),  # JWT 전달
    "X-User-Id": user_id  # UUID 전달
}

4. rb8001 처리

4.1 JWT 인증

# rb8001 main.py
async def get_current_user(authorization: str = Header(None)):
    token = authorization.replace("Bearer ", "")
    payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=["HS256"])
    return payload["sub"]  # UUID

4.2 DB 조회

# database.py
async def get_paginated_conversations(user_id: str, before: float = None, limit: int = 30):
    query = """
        SELECT id, message, response, timestamp 
        FROM conversation_log 
        WHERE user_id = (:user_id)::uuid 
          AND robeing_id = 'rb8001'
          AND (:before::timestamp IS NULL OR timestamp < :before)
        ORDER BY timestamp DESC 
        LIMIT :limit
    """

4.3 응답 변환

# DB row → Frontend 형식
messages = []
for row in results:
    # User 메시지
    messages.append({
        "id": f"{row['id']}_user",
        "text": row["message"],
        "sender": "user",
        "timestamp": row["timestamp"].isoformat()
    })
    # Robeing 응답
    messages.append({
        "id": f"{row['id']}_robeing",
        "text": row["response"],
        "sender": "robeing",
        "timestamp": row["timestamp"].isoformat()
    })

5. Frontend 처리

5.1 API 호출

// robeing-api.ts
export async function getHistory(before?: string, limit: number = 30) {
  const params = new URLSearchParams({ limit: limit.toString() });
  if (before) params.append('before', before);
  
  const response = await fetch(`${API_URL}/api/history?${params}`, {
    headers: {
      'Authorization': `Bearer ${localStorage.getItem('token')}`
    }
  });
  return response.json();
}

5.2 무한 스크롤

// Intersection Observer로 스크롤 감지
const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting && hasMore) {
    const oldestMessage = messages[0];
    loadMoreMessages(oldestMessage.timestamp);
  }
});

6. 데이터베이스 스키마

6.1 users 테이블

CREATE TABLE user (
    id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    username VARCHAR(50) UNIQUE,
    name VARCHAR(255)
);

6.2 workspace_member 테이블

CREATE TABLE workspace_member (
    user_id UUID REFERENCES user(id),
    workspace_id UUID,
    robeing_id VARCHAR(50)  -- rb8001, rb10508_micro 등
);

6.3 conversation_log 테이블

CREATE TABLE conversation_log (
    id INTEGER PRIMARY KEY,
    robeing_id VARCHAR,
    message VARCHAR,
    response VARCHAR,
    timestamp TIMESTAMP,
    user_id UUID REFERENCES user(id)
);

7. 환경변수 설정

7.1 Gateway

JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664
DEFAULT_ROBEING_HOST=192.168.219.52
DEFAULT_ROBEING_PORT=8001

7.2 rb8001

JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664
MESSAGE_BATCH_SIZE=30
MAX_MESSAGES_IN_DOM=200

8. 트러블슈팅 참고

자세한 문제 해결 과정은 다음 문서 참조:

  • troubleshooting/250901_rb8001_chat_history_implementation_issues.md