# skill-email DB 연결 구현 완료 보고서 ## 작업일: 2025-08-19 ## 작업자: 희재 ## 상태: 완료 --- ## 1. 작업 개요 ### 목적 skill-email 서비스가 Gmail OAuth 토큰을 파일이 아닌 PostgreSQL 데이터베이스에서 직접 조회하도록 변경 ### 배경 - 기존: 파일 기반 토큰 관리 (FileCredentialsProvider) - 변경: PostgreSQL gmail_tokens 테이블 기반 관리 (DBCredentialsProvider) - 이유: 중앙집중식 토큰 관리, 보안 강화, 다중 서비스 통합 --- ## 2. 구현 내용 ### 2.1 의존성 추가 **파일**: `/home/heejae/skill-email/requirements.txt` ```python psycopg2-binary>=2.9.9 # PostgreSQL 연결용 ``` ### 2.2 환경 설정 **파일**: `/home/heejae/skill-email/.env` ```bash # Skill Email Configuration PORT=8501 # Token Provider Configuration TOKEN_PROVIDER=database # PostgreSQL Connection (SSH tunnel) POSTGRES_CONNECTION_STRING=postgresql://robeings:robeings@localhost:5433/main_db # Service Name SERVICE_NAME=skill-email # Log Level LOG_LEVEL=INFO ``` ### 2.3 DBCredentialsProvider 구현 **파일**: `/home/heejae/skill-email/services/db_credentials_provider.py` #### 주요 기능 1. **연결 풀 관리** - SimpleConnectionPool 사용 (최소 1개, 최대 5개 연결) - 연결 재사용으로 성능 최적화 2. **get_credentials() 메서드** ```python def get_credentials(self, user_id: str) -> Optional[Credentials]: # gmail_tokens 테이블에서 조회 # is_equipped=true인 토큰만 조회 # token_data + oauth_config 컬럼 모두 활용 ``` - gmail_tokens 테이블 조회 - token_data 컬럼: access_token, refresh_token - oauth_config 컬럼: client_id, client_secret, token_uri - Google Credentials 객체로 변환 3. **save_credentials() 메서드** ```python def save_credentials(self, user_id: str, creds: Credentials) -> bool: # 갱신된 토큰을 DB에 저장 # access_token 필드로 저장 (DB 구조 호환) ``` 4. **check_token_exists() 메서드** ```python def check_token_exists(self, user_id: str) -> bool: # 사용자의 장착된 토큰 존재 여부 확인 ``` #### 특별 처리 사항 - **토큰 필드 매핑**: DB의 `access_token` ↔ Credentials의 `token` - **oauth_config 파싱**: JSON 형식의 client credentials 추출 - **is_equipped 필터**: 장착된 토큰만 조회 ### 2.4 main.py 수정 **파일**: `/home/heejae/skill-email/main.py` #### 변경 내용 ```python # 전역 credentials provider credentials_provider = None def get_gmail_service() -> GmailService: global credentials_provider if credentials_provider is None: if TOKEN_PROVIDER == "database": if not POSTGRES_CONNECTION_STRING: logger.error("POSTGRES_CONNECTION_STRING not set") raise ValueError("Database connection string required") logger.info("Using database credentials provider") credentials_provider = DBCredentialsProvider(POSTGRES_CONNECTION_STRING) else: logger.info("Using file credentials provider") credentials_provider = FileCredentialsProvider(TOKEN_BASE) return GmailService(credentials_provider) ``` - TOKEN_PROVIDER 환경변수로 File/DB 모드 선택 - 의존성 주입 패턴 사용 (Depends) - 기존 FileCredentialsProvider 호환성 유지 --- ## 3. 데이터베이스 구조 ### gmail_tokens 테이블 ```sql CREATE TABLE gmail_tokens ( id SERIAL PRIMARY KEY, user_id VARCHAR(100), robeing_id VARCHAR(50), token_data JSONB, -- access_token, refresh_token oauth_config JSONB, -- client_id, client_secret, token_uri scopes TEXT[], metadata JSONB, expiry TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_equipped BOOLEAN DEFAULT FALSE, equipped_to VARCHAR(50) ); ``` ### 데이터 예시 ```json // token_data { "token_type": "Bearer", "access_token": "ya29.a0AS3H6NxLyI1Ss1VJ-_WUkpR...", "refresh_token": "1//0eZrT..." } // oauth_config { "client_id": "1044056803209-0h8n5kcl22rvl740mdgpejp58v27mlro.apps.googleusercontent.com", "client_secret": "GOCSPX-...", "token_uri": "https://oauth2.googleapis.com/token" } ``` --- ## 4. 테스트 결과 ### 4.1 연결 테스트 ```bash cd /home/heejae/skill-email && python3 test_db.py ``` - ✅ PostgreSQL 연결 성공 - ✅ gmail_tokens 테이블 접근 가능 - ✅ 3개 토큰 확인 (heejae, test, unknown) ### 4.2 Provider 테스트 ```bash cd /home/heejae/skill-email && python3 test_integration.py ``` - ✅ DBCredentialsProvider 생성 성공 - ✅ heejae 토큰 로드 성공 - Access Token: ya29.a0AS3H6NxLyI1Ss1VJ-_WUkpR... - Refresh Token: 있음 - Client ID: 1044056803209-0h8n5kcl22rvl740... - Client Secret: 있음 - Token URI: https://oauth2.googleapis.com/token - Scopes: ['https://www.googleapis.com/auth/gmail.send'] ### 4.3 통합 테스트 스크립트 **파일**: `/home/heejae/skill-email/test_integration.py` - DBCredentialsProvider 테스트 - GmailService 통합 테스트 - 토큰 존재 여부 확인 --- ## 5. SSH 터널 설정 ### 연결 구성 ```bash # SSH 터널 생성 (51123 서버 → 51124 서버) sshpass -p "19800508" ssh -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 ``` - 로컬 포트 5433 → 원격 PostgreSQL 5432 - 51123 서버 (124.55.18.179)의 PostgreSQL 접근 - main_db 데이터베이스 사용 --- ## 6. 문제 해결 ### 6.1 토큰 필드 불일치 - **문제**: DB는 `access_token`, Credentials는 `token` 사용 - **해결**: ```python token=token_data.get('access_token') or token_data.get('token') ``` ### 6.2 is_equipped 필터 - **문제**: 모든 토큰이 is_equipped=false 상태 - **해결**: ```sql UPDATE gmail_tokens SET is_equipped=true WHERE user_id='heejae' ``` ### 6.3 oauth_config 누락 - **문제**: client_id, client_secret이 없어 API 호출 불가 - **해결**: oauth_config 컬럼에서 추가로 조회 ```python cur.execute(""" SELECT token_data, oauth_config FROM gmail_tokens WHERE user_id = %s AND is_equipped = true """) ``` ### 6.4 인코딩 오류 - **문제**: .env 파일 UTF-8 디코딩 오류 - **해결**: .env 파일 재작성 --- ## 7. 코드 구조 ``` /home/heejae/skill-email/ ├── .env # 환경변수 설정 ├── requirements.txt # 의존성 목록 ├── main.py # FastAPI 앱 (수정됨) ├── services/ │ ├── gmail_service.py # 기존 Gmail 서비스 │ ├── db_credentials_provider.py # 새로 추가된 DB Provider │ └── ... ├── test_db.py # DB 연결 테스트 ├── test_integration.py # 통합 테스트 └── ... ``` --- ## 8. 보안 고려사항 1. **토큰 암호화**: DB에 평문 저장 (추후 암호화 고려) 2. **연결 풀**: 최대 5개 연결로 제한 3. **에러 처리**: 토큰 정보 로그에 노출 방지 4. **SSH 터널**: PostgreSQL 직접 노출 방지 --- ## 9. 성능 최적화 1. **연결 풀링**: SimpleConnectionPool 사용 2. **JSON 파싱**: 한 번만 파싱하여 재사용 3. **로깅 레벨**: INFO로 설정 (프로덕션에서는 WARNING 권장) --- ## 10. 남은 작업 ### 즉시 필요 - [ ] 51124 서버 배포 - [ ] Docker 이미지 빌드 및 테스트 - [ ] rb8001과 통합 테스트 ### 추후 개선 - [ ] 토큰 암호화 저장 - [ ] 토큰 자동 갱신 로직 - [ ] 연결 풀 모니터링 - [ ] 캐싱 레이어 추가 --- ## 11. 호환성 ### 기존 시스템과 호환 - ✅ FileCredentialsProvider 유지 (TOKEN_PROVIDER=file) - ✅ 기존 API 인터페이스 변경 없음 - ✅ 동일한 GmailService 사용 ### 새로운 기능 - ✅ 중앙집중식 토큰 관리 - ✅ 다중 robeing 지원 (equipped_to 필드) - ✅ 감사 로그 준비 (gmail_audit_logs 테이블과 연동 가능) --- ## 12. 검증 체크리스트 - [x] PostgreSQL 연결 성공 - [x] gmail_tokens 테이블 조회 - [x] 토큰 데이터 파싱 - [x] Credentials 객체 생성 - [x] oauth_config 통합 - [x] 에러 처리 동작 - [x] 연결 풀 정상 작동 - [x] 환경변수 기반 Provider 선택 - [ ] 실제 Gmail API 호출 - [ ] 토큰 갱신 플로우 --- ## 13. 참고 명령어 ```bash # DB 연결 테스트 cd /home/heejae/skill-email && python3 test_db.py # Provider 테스트 cd /home/heejae/skill-email && python3 test_integration.py # SSH 터널 재연결 sshpass -p "19800508" ssh -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 # 토큰 상태 확인 python3 -c " import psycopg2 conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5433/main_db') cur = conn.cursor() cur.execute('SELECT user_id, is_equipped FROM gmail_tokens') for row in cur.fetchall(): print(f'{row[0]}: equipped={row[1]}') " ``` --- **작업 완료: 2025-08-19** **다음 단계: rb8001 통합 (4번 작업)**