DOCS/journey/plans/archive/250819_skill_email_db_connection_completed.md
Claude-51124 21b7349f03 docs: plans/completed 폴더 제거 및 archive로 통합
- plans/completed/ 폴더 삭제 및 파일들을 plans/archive/로 통합
- 원칙 문서에 archive 이동 규칙 명시 (312_문서_작성_원칙.md)
- completed 폴더 참조를 archive로 수정 (125_베이즈_성장과_관계의_철학.md)
- Gemini API 스트리밍 개선 계획을 archive로 이동 및 간결화
- troubleshooting 문서 생성 및 링크 연결
2025-12-25 21:32:48 +09:00

8.9 KiB

skill-email DB 연결 구현 완료 보고서

작업일: 2025-08-19

작업자: 희재

상태: 완료


1. 작업 개요

목적

skill-email 서비스가 Gmail OAuth 토큰을 파일이 아닌 PostgreSQL 데이터베이스에서 직접 조회하도록 변경

배경

  • 기존: 파일 기반 토큰 관리 (FileCredentialsProvider)
  • 변경: PostgreSQL gmail_token 테이블 기반 관리 (DBCredentialsProvider)
  • 이유: 중앙집중식 토큰 관리, 보안 강화, 다중 서비스 통합

2. 구현 내용

2.1 의존성 추가

파일: /home/heejae/skill-email/requirements.txt

psycopg2-binary>=2.9.9  # PostgreSQL 연결용

2.2 환경 설정

파일: /home/heejae/skill-email/.env

# 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() 메서드

    def get_credentials(self, user_id: str) -> Optional[Credentials]:
        # gmail_token 테이블에서 조회
        # is_equipped=true인 토큰만 조회
        # token_data + oauth_config 컬럼 모두 활용
    
    • gmail_token 테이블 조회
    • token_data 컬럼: access_token, refresh_token
    • oauth_config 컬럼: client_id, client_secret, token_uri
    • Google Credentials 객체로 변환
  3. save_credentials() 메서드

    def save_credentials(self, user_id: str, creds: Credentials) -> bool:
        # 갱신된 토큰을 DB에 저장
        # access_token 필드로 저장 (DB 구조 호환)
    
  4. check_token_exists() 메서드

    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

변경 내용

# 전역 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_token 테이블

CREATE TABLE gmail_token (
    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)
);

데이터 예시

// 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 연결 테스트

cd /home/heejae/skill-email && python3 test_db.py
  • PostgreSQL 연결 성공
  • gmail_token 테이블 접근 가능
  • 3개 토큰 확인 (heejae, test, unknown)

4.2 Provider 테스트

cd /home/heejae/skill-email && python3 test_integration.py

4.3 통합 테스트 스크립트

파일: /home/heejae/skill-email/test_integration.py

  • DBCredentialsProvider 테스트
  • GmailService 통합 테스트
  • 토큰 존재 여부 확인

5. SSH 터널 설정

연결 구성

# 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 사용
  • 해결:
    token=token_data.get('access_token') or token_data.get('token')
    

6.2 is_equipped 필터

  • 문제: 모든 토큰이 is_equipped=false 상태
  • 해결:
    UPDATE gmail_token SET is_equipped=true WHERE user_id='heejae'
    

6.3 oauth_config 누락

  • 문제: client_id, client_secret이 없어 API 호출 불가
  • 해결: oauth_config 컬럼에서 추가로 조회
    cur.execute("""
        SELECT token_data, oauth_config
        FROM gmail_token
        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. 검증 체크리스트

  • PostgreSQL 연결 성공
  • gmail_token 테이블 조회
  • 토큰 데이터 파싱
  • Credentials 객체 생성
  • oauth_config 통합
  • 에러 처리 동작
  • 연결 풀 정상 작동
  • 환경변수 기반 Provider 선택
  • 실제 Gmail API 호출
  • 토큰 갱신 플로우

13. 참고 명령어

# 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_token')
for row in cur.fetchall():
    print(f'{row[0]}: equipped={row[1]}')
"

작업 완료: 2025-08-19 다음 단계: rb8001 통합 (4번 작업)