329 lines
10 KiB
Markdown
329 lines
10 KiB
Markdown
# Frontend ActivityPanel과 Backend user_preferences 연동 구현
|
|
|
|
## 작성일: 2025-08-27
|
|
## 작성자: happybell80 / 51123 서버 관리자
|
|
## 상태: ✅ 완료 - 2025-08-27 23:30
|
|
## 영향: 사용자 설정 기능 연동
|
|
## 최종 업데이트: 2025-08-27 23:30
|
|
|
|
---
|
|
|
|
## 1. 현재 상황
|
|
|
|
### ✅ 완료된 작업
|
|
- **Backend**: robeing-monitor에 preferences API 구현 (GET/PUT)
|
|
- **Frontend**: localStorage → API 호출로 변경 완료
|
|
- **배포**: robeing-monitor 51124:9024에서 실행 중
|
|
- **Gateway 라우팅**: `/api/preferences` → robeing-monitor 프록시 완료
|
|
- **PUT 500 에러**: asyncpg TIME 타입 변환 문제 해결
|
|
|
|
---
|
|
|
|
## 2. 현재 구현 상태
|
|
|
|
### 2.1 Frontend (ActivityPanel.tsx)
|
|
- **위치**: `/frontend-customer/src/components/activity-panel.tsx`
|
|
- **구현 완료**: UI 컴포넌트 및 로직
|
|
- **데이터 저장**: localStorage (목업)
|
|
- **기능**:
|
|
- 다중 브리핑 작업 관리 (task.id별)
|
|
- 키워드 추가/삭제
|
|
- 스케줄 설정 (매일/평일/주말/커스텀)
|
|
- 포함 항목 선택 (이메일/뉴스/캘린더/슬랙)
|
|
|
|
### 2.2 Backend (user_preferences 테이블)
|
|
- **위치**: PostgreSQL main_db
|
|
- **구조**:
|
|
```sql
|
|
CREATE TABLE user_preferences (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id UUID REFERENCES users(id),
|
|
slack_user_id VARCHAR(100),
|
|
news_keywords VARCHAR(128)[], -- 뉴스 키워드 배열
|
|
email_filter VARCHAR(128)[], -- 이메일 필터 (미사용)
|
|
briefing_enabled BOOLEAN DEFAULT true,
|
|
briefing_time TIME DEFAULT '09:00',
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### 2.3 rb8001 사용 현황
|
|
- **dm_skill.py**: user_preferences에서 news_keywords 조회
|
|
- **사용자별 맞춤 뉴스**: 정상 작동 중
|
|
- **브리핑 시간**: briefing_time 사용 중
|
|
|
|
---
|
|
|
|
## 3. 핵심 문제점
|
|
|
|
### 3.1 데이터 모델 불일치
|
|
|
|
| Frontend TaskSettings | Backend user_preferences | 불일치 내용 |
|
|
|---------------------|------------------------|------------|
|
|
| keywords: string[] | news_keywords VARCHAR(128)[] | ✅ 호환 가능 |
|
|
| scheduleTime: string | briefing_time TIME | ⚠️ 타입 변환 필요 |
|
|
| scheduleType: 'everyday' \| 'weekdays' \| ... | - | ❌ 필드 없음 |
|
|
| scheduleDays: string[] | - | ❌ 필드 없음 |
|
|
| includeEmail: boolean | - | ❌ 필드 없음 |
|
|
| includeNews: boolean | - | ❌ 필드 없음 |
|
|
| includeCalendar: boolean | - | ❌ 필드 없음 |
|
|
| includeSlack: boolean | - | ❌ 필드 없음 |
|
|
|
|
### 3.2 다중 작업 관리 불가
|
|
- **Frontend**: 여러 개의 scheduledTask 관리 (일일 브리핑, 주간 리포트 등)
|
|
- **Backend**: 사용자당 1개 설정만 저장 가능
|
|
- **영향**: "일일 브리핑", "주간 리포트" 등 구분 불가
|
|
|
|
### 3.3 Mock 데이터 하드코딩
|
|
```typescript
|
|
// ActivityPanel.tsx:155-210
|
|
const conversations: ConversationSession[] = [...]; // 하드코딩
|
|
const activities: ActivityLog[] = [...]; // 하드코딩
|
|
const scheduledTasks: ScheduledTask[] = [...]; // 하드코딩
|
|
```
|
|
- 실제 데이터 조회 API 없음
|
|
- conversation_logs 테이블 조회 엔드포인트 필요
|
|
|
|
### 3.4 ✅ 해결됨: API 구현
|
|
- **robeing-monitor**: preferences API 완전 구현
|
|
- **GET /api/preferences/{user_id}**: 조회
|
|
- **PUT /api/preferences/{user_id}**: 업데이트
|
|
- **고정값 반환**: 미구현 필드는 기본값 제공
|
|
|
|
### 3.5 🔴 Gateway 라우팅 문제
|
|
```python
|
|
# robeing-gateway/main.py:407
|
|
@app.get("/api/{path:path}") # 모든 GET → rb8001로
|
|
# /api/preferences도 rb8001로 가서 404 발생
|
|
```
|
|
|
|
**필요한 수정**:
|
|
- `/api/preferences/*` → robeing-monitor(9024)로 프록시
|
|
- JWT에서 UUID 추출하여 전달
|
|
|
|
### 3.6 실시간 동기화 부재
|
|
- localStorage 기반으로 다른 디바이스와 동기화 안됨
|
|
- WebSocket이나 polling 구현 없음
|
|
|
|
### 3.7 권한 검증 누락
|
|
- user_preferences 수정 시 본인 확인 로직 필요
|
|
- 다른 사용자 설정 수정 가능한 보안 문제
|
|
|
|
---
|
|
|
|
## 4. 해결 방안
|
|
|
|
### 4.1 단기 해결책 (최소 수정)
|
|
1. **Backend 스키마 확장**
|
|
```sql
|
|
ALTER TABLE user_preferences ADD COLUMN schedule_type VARCHAR(20);
|
|
ALTER TABLE user_preferences ADD COLUMN schedule_days VARCHAR(10)[];
|
|
ALTER TABLE user_preferences ADD COLUMN include_email BOOLEAN DEFAULT true;
|
|
ALTER TABLE user_preferences ADD COLUMN include_news BOOLEAN DEFAULT true;
|
|
ALTER TABLE user_preferences ADD COLUMN include_calendar BOOLEAN DEFAULT false;
|
|
ALTER TABLE user_preferences ADD COLUMN include_slack BOOLEAN DEFAULT false;
|
|
```
|
|
|
|
2. **robeing-monitor에 CRUD API 추가**
|
|
```python
|
|
GET /api/preferences/{user_id}
|
|
PUT /api/preferences/{user_id}
|
|
GET /api/conversations/{user_id}?limit=10
|
|
GET /api/activities/{user_id}?limit=10
|
|
```
|
|
|
|
3. **ActivityPanel localStorage → API 호출 변경**
|
|
|
|
### 4.2 중기 해결책 (구조 개선)
|
|
1. **scheduled_tasks 테이블 생성**
|
|
```sql
|
|
CREATE TABLE scheduled_tasks (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id UUID REFERENCES users(id),
|
|
task_type VARCHAR(50), -- 'daily_briefing', 'weekly_report' 등
|
|
title VARCHAR(255),
|
|
schedule_type VARCHAR(20),
|
|
schedule_days VARCHAR(10)[],
|
|
schedule_time TIME,
|
|
settings JSONB, -- 유연한 설정 저장
|
|
enabled BOOLEAN DEFAULT true
|
|
);
|
|
```
|
|
|
|
2. **Frontend와 Backend 인터페이스 통일**
|
|
- 공통 TypeScript 타입 정의
|
|
- API 응답 형식 표준화
|
|
|
|
### 4.3 장기 해결책 (완전 재설계)
|
|
1. **마이크로서비스 분리**
|
|
- user-preferences-service 별도 구현
|
|
- GraphQL 도입 검토
|
|
|
|
2. **실시간 동기화**
|
|
- WebSocket 구현
|
|
- 설정 변경 시 실시간 반영
|
|
|
|
---
|
|
|
|
## 5. 구현 현황 (2025-08-27 22:00)
|
|
|
|
### ✅ 완료
|
|
- **Backend API**: robeing-monitor에 구현 완료
|
|
- **Frontend 수정**: API 호출 코드 추가, 자동 저장
|
|
- **UI 처리**: 미구현 필드 비활성화 (opacity-50)
|
|
- **배포**: 51124:9024 정상 작동
|
|
|
|
### 🟡 진행중
|
|
- **Gateway 라우팅**: preferences 전용 프록시 추가 필요
|
|
|
|
### ⚠️ 작동 방식
|
|
- **현재**: Frontend → Gateway(8100) → rb8001 → 404
|
|
- **필요**: Frontend → Gateway(8100) → robeing-monitor(9024)
|
|
|
|
---
|
|
|
|
## 6. 아키텍처 상세
|
|
|
|
### 데이터 흐름
|
|
```
|
|
Frontend (브라우저)
|
|
↓
|
|
[자동 저장 트리거]
|
|
↓
|
|
fetch('/api/preferences/{UUID}') # Gateway 경유
|
|
↓
|
|
Gateway (8100) + JWT 인증
|
|
↓
|
|
[라우팅 필요: preferences → 9024]
|
|
↓
|
|
robeing-monitor (9024)
|
|
↓
|
|
PostgreSQL user_preferences 테이블
|
|
```
|
|
|
|
### UUID 체계
|
|
- **JWT sub**: UUID (1e16e9d5-59f3-54da-a661-8abeabff4230)
|
|
- **localStorage**: user_id에 UUID 저장
|
|
- **API 경로**: `/api/preferences/{UUID}`
|
|
|
|
---
|
|
|
|
## 7. 관련 파일
|
|
|
|
### Frontend
|
|
- `/home/happybell/projects/ivada/frontend-customer/src/components/activity-panel.tsx`
|
|
|
|
### Backend
|
|
- `/home/happybell/projects/ivada/robeing-monitor/app/api/items.py`
|
|
- `/home/happybell/projects/ivada/robeing-gateway/app/main.py`
|
|
- `/home/happybell/projects/ivada/rb8001/app/skills/dm_skill.py`
|
|
|
|
### 문서
|
|
- `/home/happybell/projects/ivada/DOCS/troubleshooting/250826_slack_id_column_standardization.md`
|
|
|
|
---
|
|
|
|
## 8. Gateway 라우팅 추가 코드
|
|
|
|
```python
|
|
# /robeing-gateway/app/main.py (line 407 앞에 추가)
|
|
@app.api_route("/api/preferences/{path:path}", methods=["GET", "PUT"])
|
|
async def proxy_preferences(
|
|
path: str,
|
|
request: Request,
|
|
x_user_id: str = Depends(get_verified_user)
|
|
):
|
|
"""Proxy preferences to robeing-monitor"""
|
|
monitor_url = "http://192.168.219.52:9024"
|
|
full_path = f"/api/preferences/{path}"
|
|
|
|
if request.method == "GET":
|
|
response = await http_client.get(f"{monitor_url}{full_path}")
|
|
elif request.method == "PUT":
|
|
body = await request.json()
|
|
response = await http_client.put(
|
|
f"{monitor_url}{full_path}",
|
|
json=body,
|
|
headers={"X-User-Id": x_user_id}
|
|
)
|
|
|
|
return response.json() if response.status_code == 200 else {"error": "Failed"}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. 결론
|
|
|
|
**✅ 100% 완료 - 전체 기능 정상 작동**
|
|
- Backend API ✅
|
|
- Frontend 수정 ✅
|
|
- Gateway 라우팅 ✅
|
|
- nginx 프록시 ✅ (/gateway/ 사용)
|
|
- JWT 인증 ✅
|
|
- GET 요청 ✅ (200 OK)
|
|
- PUT 요청 ✅ (200 OK - TIME 타입 변환 해결)
|
|
|
|
---
|
|
|
|
## 10. 구현 과정 (2025-08-27)
|
|
|
|
### Phase 1: Backend API 구현 (20:00)
|
|
- robeing-monitor에 preferences API 추가
|
|
- 기존 컬럼만 사용 (news_keywords, briefing_time)
|
|
- 확장 필드는 고정값 반환
|
|
|
|
### Phase 2: Frontend 수정 (21:00)
|
|
- localStorage → API 호출 변경
|
|
- 자동 저장 방식 구현
|
|
- 미구현 필드 UI 비활성화
|
|
|
|
### Phase 3: Gateway 라우팅 (22:00)
|
|
- preferences 전용 라우팅 추가
|
|
- robeing-monitor(9024)로 프록시
|
|
|
|
### Phase 4: 경로 문제 해결 (22:30)
|
|
- ❌ 처음: 직접 호출 (http://192.168.219.52:9024) - Mixed Content 에러
|
|
- ❌ 다음: /api/preferences - nginx가 frontend-base(8000)로 전달
|
|
- ✅ 해결: /gateway/api/preferences - Gateway(8100) 경유
|
|
|
|
### Phase 5: JWT 인증 추가 (22:40)
|
|
- Frontend에서 Authorization 헤더 누락 발견
|
|
- localStorage의 'token' || 'auth_token' 사용
|
|
- Bearer 토큰 형식으로 전달
|
|
|
|
### 최종 상태 (23:30)
|
|
- GET /api/preferences/{user_id}: 200 OK ✅
|
|
- PUT /api/preferences/{user_id}: 200 OK ✅
|
|
|
|
---
|
|
|
|
## 10. PUT 500 에러 해결 과정 (22:45 ~ 23:30)
|
|
|
|
### 문제 1: 존재하지 않는 컬럼 참조
|
|
- **원인**: INSERT/UPDATE 쿼리가 DB에 없는 컬럼 참조
|
|
- **해결**: schedule_type, schedule_days, include_* 컬럼 제거
|
|
|
|
### 문제 2: Frontend 필드명 불일치
|
|
- **원인**: Frontend(keywords) vs Backend(news_keywords)
|
|
- **해결**: 필드명 자동 매핑 구현
|
|
|
|
### 문제 3: TIME 타입 변환 실패 (핵심)
|
|
- **원인**: asyncpg에서 문자열 + ::time 캐스팅 처리 실패
|
|
- **잘못된 방법**:
|
|
```python
|
|
"briefing_time = $2::time"
|
|
params.append("08:30:00") # 문자열
|
|
```
|
|
- **올바른 방법**:
|
|
```python
|
|
from datetime import datetime
|
|
time_obj = datetime.strptime("08:30:00", "%H:%M:%S").time()
|
|
"briefing_time = $2" # ::time 제거
|
|
params.append(time_obj) # datetime.time 객체
|
|
```
|
|
- **핵심**: asyncpg는 Python datetime.time 객체를 PostgreSQL TIME으로 자동 변환
|
|
|
|
---
|
|
|
|
*최종 수정: 2025-08-27 23:30*
|
|
*상태: ✅ 완전 해결 - Frontend-Backend preferences 연동 완료* |