diff --git a/300_architecture/sequences/chat_history_flow.md b/300_architecture/sequences/chat_history_flow.md new file mode 100644 index 0000000..40ebf5b --- /dev/null +++ b/300_architecture/sequences/chat_history_flow.md @@ -0,0 +1,222 @@ +# 대화 히스토리 조회 플로우 + +## 작성일: 2025-09-02 +## 대상: rb8001/rb10508_micro 대화 히스토리 +## 상태: ✅ 구현 완료 + +--- + +## 1. 전체 플로우 + +```mermaid +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
Authorization: Bearer {JWT} + G->>G: JWT 검증 (서명, 만료시간) + G->>G: JWT에서 sub(UUID) 추출 + G->>G: workspace_members 테이블 조회
robeing_id 확인 (rb8001) + G->>R: GET /api/history?limit=30
Authorization: Bearer {JWT}
X-User-Id: {UUID} + R->>R: JWT 검증 및 user_id 추출 + R->>DB: SELECT * FROM conversation_logs
WHERE user_id = (:user_id)::uuid + DB-->>R: 대화 기록 반환 + R->>R: DB row를 Frontend 형식으로 변환
(user/robeing 메시지 분리) + R-->>G: {"messages": [...], "has_more": true} + G-->>F: 응답 전달 + F->>F: 화면에 메시지 렌더링 +``` + +--- + +## 2. JWT 토큰 구조 + +### 2.1 auth-server 발급 토큰 +```json +{ + "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 검증 +```python +# 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 라우팅 +```python +# workspace_members 테이블 조회 +SELECT robeing_id FROM workspace_members +WHERE user_id = :user_id + +# 결과: rb8001 또는 rb10508_micro +``` + +### 3.3 헤더 전달 +```python +headers = { + "Authorization": request.headers.get("Authorization"), # JWT 전달 + "X-User-Id": user_id # UUID 전달 +} +``` + +--- + +## 4. rb8001 처리 + +### 4.1 JWT 인증 +```python +# 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 조회 +```python +# database.py +async def get_paginated_conversations(user_id: str, before: float = None, limit: int = 30): + query = """ + SELECT id, message, response, timestamp + FROM conversation_logs + 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 응답 변환 +```python +# 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 호출 +```typescript +// 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 무한 스크롤 +```typescript +// Intersection Observer로 스크롤 감지 +const observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasMore) { + const oldestMessage = messages[0]; + loadMoreMessages(oldestMessage.timestamp); + } +}); +``` + +--- + +## 6. 데이터베이스 스키마 + +### 6.1 users 테이블 +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY, + email VARCHAR(255) UNIQUE, + username VARCHAR(50) UNIQUE, + name VARCHAR(255) +); +``` + +### 6.2 workspace_members 테이블 +```sql +CREATE TABLE workspace_members ( + user_id UUID REFERENCES users(id), + workspace_id UUID, + robeing_id VARCHAR(50) -- rb8001, rb10508_micro 등 +); +``` + +### 6.3 conversation_logs 테이블 +```sql +CREATE TABLE conversation_logs ( + id INTEGER PRIMARY KEY, + robeing_id VARCHAR, + message VARCHAR, + response VARCHAR, + timestamp TIMESTAMP, + user_id UUID REFERENCES users(id) +); +``` + +--- + +## 7. 환경변수 설정 + +### 7.1 Gateway +```env +JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664 +DEFAULT_ROBEING_HOST=192.168.219.52 +DEFAULT_ROBEING_PORT=8001 +``` + +### 7.2 rb8001 +```env +JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664 +MESSAGE_BATCH_SIZE=30 +MAX_MESSAGES_IN_DOM=200 +``` + +--- + +## 8. 트러블슈팅 참고 + +자세한 문제 해결 과정은 다음 문서 참조: +- troubleshooting/250901_rb8001_chat_history_implementation_issues.md \ No newline at end of file diff --git a/plans/250901_rb8001_chat_history_implementation_plan.md b/plans/250901_rb8001_chat_history_implementation_plan.md index 54cf4ab..0b7f0f5 100644 --- a/plans/250901_rb8001_chat_history_implementation_plan.md +++ b/plans/250901_rb8001_chat_history_implementation_plan.md @@ -1,142 +1,29 @@ -# rb8001 카톡 스타일 대화 히스토리 구현 계획 +# rb8001 카톡 스타일 대화 히스토리 구현 완료 ## 작성일: 2025-09-01 +## 완료일: 2025-09-02 ## 대상: rb8001 (프로덕션 로빙) -## 참조: rb10508_micro 구현 코드 -## 수정: PostgreSQL 전용 히스토리 구현으로 변경 +## 상태: ✅ 완료 --- -## 현재 상태 분석 -- **rb10508_micro**: /api/config 엔드포인트 구현됨 (endpoints.py:407) -- **rb8001**: - - database.py: ConversationLog 모델 있음 (line 52) - - get_recent_conversations() 함수 있음 (line 81) - - get_paginated_conversations() 함수 없음 (추가 필요) - - main.py: @app.get() 직접 사용, router 분리 없음 +## 구현 완료 사항 -## UUID 및 인증 체계 -- **내부 처리**: 모든 user_id는 UUID 형식으로 통일 -- **JWT 토큰**: X-User-Id 헤더로 UUID 전달 -- **Slack 사용자**: slack_user_mapping 테이블로 UUID 매핑 -- **Frontend**: JWT에서 user_id(UUID) 추출하여 API 호출 +### rb8001 백엔드 +1. ✅ `/api/config` 엔드포인트 추가 +2. ✅ `/api/history` 엔드포인트 추가 +3. ✅ get_paginated_conversations() 함수 구현 +4. ✅ SQL 파라미터 바인딩 문법 수정 ((:user_id)::uuid) -## 수정 필요 파일 +### Gateway +1. ✅ /api/history 라우팅 추가 +2. ✅ Authorization 헤더 전달 추가 -### 백엔드 (rb8001) -1. ✅ `app/core/config.py` - 환경변수 추가 완료 -2. ✅ `app/state/database.py` - get_paginated_conversations() 추가 완료 -3. `main.py` - 엔드포인트 수정 필요 - - ✅ `/api/config` 이미 추가됨 - - ❌ `/api/messages` 삭제 필요 (잘못 추가함) - - ⚠️ `/api/history` 추가 필요 (이것만!) -4. ✅ `.env` 파일 - 환경변수 설정 완료 - -### 프론트엔드 (frontend-customer) -1. ✅ 이미 완료됨 (250818 rb10508_micro에서 구현) -2. ✅ `/api/history` 호출 중 (수정 불필요) +### Frontend +1. ✅ 이미 구현됨 (rb10508_micro 참조) --- -## API 스펙 - -```typescript -// GET /api/config -{ message_batch_size: 30, max_messages_in_dom: 200 } - -// GET /api/history?before={timestamp}&limit=30 -// Frontend 기대 형식 (robeing-api.ts:154-161) -{ - messages: Array<{ - id: string; - text: string; - sender: 'user' | 'robeing'; - timestamp: string; - metadata?: any; - }>; - has_more: boolean; -} - -// 현재 rb8001 /api/messages 반환 형식 (변환 필요!) -{ - "user_message": "...", - "robeing_response": "...", - "timestamp": "...", - "user_id": "..." -} -``` - -### PostgreSQL 쿼리 예시 -```sql --- user_id는 항상 UUID 형식 -SELECT id, message, response, timestamp, user_id -FROM conversation_logs -WHERE user_id = $1::uuid -- UUID 타입 캐스팅 - AND robeing_id = 'rb8001' - AND timestamp < $2 -ORDER BY timestamp DESC -LIMIT 30; -``` - -### API 호출 예시 -```javascript -// Frontend에서 JWT의 user_id 사용 -const token = localStorage.getItem('token'); -const decoded = jwt_decode(token); -const userId = decoded.user_id; // UUID 형식 - -// API 호출 (Frontend가 이미 호출 중) -await fetch(`/api/history?before=${timestamp}&limit=30`); -``` - ---- - -## 데이터베이스 스키마 - -```sql --- conversation_logs 테이블 (실제) -CREATE TABLE conversation_logs ( - id INTEGER PRIMARY KEY (auto-increment), - robeing_id VARCHAR, - channel_id VARCHAR, - message VARCHAR, - response VARCHAR, - intent VARCHAR, - confidence DOUBLE PRECISION, - timestamp TIMESTAMP, - user_id UUID (FK → users), - slack_user_id VARCHAR(100), - thread_ts VARCHAR(128), - channel_type VARCHAR(32) -); -``` - ---- - -## 주의사항 - -### API 경로 -```python -# main.py에 직접 추가 (rb8001은 router 분리 없음) -@app.get("/api/config") -@app.get("/api/messages") -``` - -### rb10508_micro 참조 코드 -```python -# rb10508_micro/app/config.py:101-103 -MESSAGE_BATCH_SIZE: int = int(os.getenv("MESSAGE_BATCH_SIZE", 30)) -SCROLL_THRESHOLD: int = int(os.getenv("SCROLL_THRESHOLD", 100)) -MAX_MESSAGES_IN_DOM: int = int(os.getenv("MAX_MESSAGES_IN_DOM", 200)) -``` - -### 실제 필요 작업 -1. ✅ 백엔드 대부분 완료 (bbf9c50 커밋) -2. ⚠️ **rb8001 main.py 수정 필요**: - - `/api/messages` → `/api/history` 이름 변경 - - **응답 형식 변환 필수**: - - DB 각 row를 2개 메시지로 분리 (user, robeing) - - text, sender, timestamp 형식 맞추기 - - 배열로 반환 -3. ✅ Frontend 수정 불필요 (이미 /api/history 호출 중) -4. ✅ 배포는 Gitea Actions 자동 처리 \ No newline at end of file +## 참고 문서 +- troubleshooting/250901_rb8001_chat_history_implementation_issues.md +- 300_architecture/sequences/chat_history_flow.md \ No newline at end of file diff --git a/troubleshooting/250901_rb8001_chat_history_implementation_issues.md b/troubleshooting/250901_rb8001_chat_history_implementation_issues.md new file mode 100644 index 0000000..4c7f5b8 --- /dev/null +++ b/troubleshooting/250901_rb8001_chat_history_implementation_issues.md @@ -0,0 +1,198 @@ +# rb8001 대화 히스토리 구현 트러블슈팅 + +## 작성일: 2025-09-02 +## 작성자: 51123 서버 관리자 +## 대상: rb8001 카톡 스타일 대화 히스토리 +## 최종 상태: ✅ 해결 완료 + +--- + +## 1. 초기 계획과 실제 구현의 차이 + +### 1.1 잘못된 초기 계획 +- **착각**: `/api/messages` 엔드포인트 추가하려 함 +- **실제**: `/api/history`가 정답 (rb10508_micro 문서 확인 미흡) +- **원인**: 250818 문서를 제대로 읽지 않음 + +### 1.2 놓친 문서 확인 +- Frontend 응답 형식 확인 안 함 (robeing-api.ts:154-161) +- Gateway 라우팅 구조 확인 안 함 +- rb10508_micro 구현 내용 상세 확인 미흡 + +--- + +## 2. Gateway 라우팅 문제 + +### 2.1 문제 상황 +```bash +# Frontend 요청 +GET /gateway/api/history?limit=1 + +# 응답 +{} # 빈 객체 (content-length: 2) +``` + +### 2.2 원인 분석 +- Gateway에 `/api/history` 라우팅 없었음 +- `/api/config`는 있었지만 `/api/history`는 누락 + +### 2.3 해결 +- Gateway main.py에 `/api/history` → rb8001 프록시 라우팅 추가 + +--- + +## 3. UUID 변환 문제 + +### 3.1 잘못된 추측 +- **추측**: users 테이블 id가 integer +- **실제**: users.id는 UUID 타입 +- **확인 방법**: `\d users` 명령으로 확인 필요했음 + +### 3.2 Gateway UUID 변환 제거 +- JWT의 sub 필드는 이미 UUID +- username 변환 시도가 불필요했음 +- 직접 UUID 전달로 해결 + +--- + +## 4. JWT Authorization 헤더 미전달 + +### 4.1 문제 상황 +```python +# rb8001 응답 +{"detail": "Invalid authentication credentials"} # 401 Unauthorized +``` + +### 4.2 원인 +- Gateway가 X-User-Id 헤더만 전달 +- Authorization 헤더 전달 안 함 +- rb8001은 JWT 토큰 검증 필요 + +### 4.3 해결 +```python +# Gateway main.py Line 463 +headers = { + "X-User-Id": x_user_id, + "Authorization": request.headers.get("Authorization") # 추가 +} +``` + +--- + +## 5. SQL 파라미터 파싱 에러 + +### 5.1 에러 메시지 +``` +syntax error at or near ":" +WHERE user_id = :user_id::uuid +``` + +### 5.2 원인 분석 +- SQLAlchemy text()는 `:user_id`를 파라미터로 인식 +- PostgreSQL `::uuid` 캐스팅과 충돌 +- `:user_id::uuid` 전체를 하나의 토큰으로 파싱 + +### 5.3 해결 방법 +```sql +-- 잘못된 문법 +WHERE user_id = :user_id::uuid + +-- 올바른 문법 (괄호로 분리) +WHERE user_id = (:user_id)::uuid +``` + +--- + +## 6. Frontend 응답 형식 불일치 + +### 6.1 Frontend 기대 형식 +```typescript +{ + messages: Array<{ + id: string; + text: string; + sender: 'user' | 'robeing'; + timestamp: string; + }>; + has_more: boolean; +} +``` + +### 6.2 rb8001 초기 반환 형식 +```json +{ + "user_message": "...", + "robeing_response": "...", + "timestamp": "..." +} +``` + +### 6.3 해결 +- DB 각 row를 2개 메시지로 분리 (user, robeing) +- text, sender, timestamp 형식으로 변환 + +--- + +## 7. 전체 작업 순서 (실제) + +### 7.1 rb8001 백엔드 +1. ✅ config.py 환경변수 추가 (MESSAGE_BATCH_SIZE=30) +2. ✅ database.py get_paginated_conversations() 구현 +3. ❌ `/api/messages` 잘못 추가 → ✅ `/api/history`로 수정 +4. ✅ SQL 문법 수정: `(:user_id)::uuid` + +### 7.2 Gateway +1. ✅ `/api/history` 라우팅 추가 +2. ✅ UUID 변환 제거 (JWT sub 직접 사용) +3. ✅ Authorization 헤더 전달 추가 + +### 7.3 Frontend +- 수정 불필요 (이미 250818에 구현됨) + +--- + +## 8. 교훈 + +### 8.1 문서 확인의 중요성 +- 기존 구현 문서 먼저 상세히 읽기 +- Frontend 코드 확인 후 백엔드 작업 +- Gateway 라우팅 구조 먼저 파악 + +### 8.2 테스트 우선 +- curl로 직접 테스트 +- JWT 토큰 생성해서 확인 +- 로그 확인 필수 + +### 8.3 추측 금지 +- DB 스키마는 직접 확인 +- 에러 메시지 정확히 읽기 +- 문서화된 내용 신뢰 + +--- + +## 9. 최종 작동 확인 + +### 9.1 테스트 결과 +```bash +# Gateway 통해 요청 +GET /gateway/api/history?limit=30 +Authorization: Bearer {JWT_TOKEN} + +# 정상 응답 +{ + "messages": [...], + "has_more": true +} +``` + +### 9.2 Frontend +- 무한 스크롤 정상 작동 +- 날짜 구분선 표시 +- 대화 히스토리 로드 성공 + +--- + +## 10. 참고 문서 +- plans/250901_rb8001_chat_history_implementation_plan.md (완료) +- troubleshooting/250818_happybell80_대화히스토리구현.md (rb10508_micro) +- 300_architecture/sequences/chat_history_flow.md (신규 작성) \ No newline at end of file