DOCS/journey/troubleshooting/250818_gmail_tokens_database_setup.md
Claude-51124 22557e7132 docs: 오래된 트러블슈팅 아카이브 및 구조 정리
- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동
- book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서)
- 빈 폴더 제거 (journey/assets/*)
2025-11-17 14:06:05 +09:00

5.8 KiB

Gmail Tokens 데이터베이스 구성

작성일: 2025-08-18

작성자: Claude (with heejae)


1. 테이블 생성 정보

1.1 데이터베이스 접속 정보

Host: localhost
Port: 5432
Database: main_db
User: robeings
Password: robeings

1.2 생성된 테이블: gmail_token

CREATE TABLE gmail_token (
    id SERIAL PRIMARY KEY,
    user_id VARCHAR(100) UNIQUE NOT NULL,
    robeing_id VARCHAR(50),
    token_data JSONB NOT NULL,
    oauth_config JSONB,
    scopes JSONB,
    metadata JSONB,
    expiry TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2. JSONB 컬럼 구조

2.1 token_data (필수)

{
    "access_token": "ya29.xxxx",
    "refresh_token": "1//xxxx",
    "token_type": "Bearer"
}

2.2 oauth_config

{
    "client_id": "xxx.apps.googleusercontent.com",
    "client_secret": "GOCSPX-xxxx",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth"
}

2.3 scopes

[
    "https://www.googleapis.com/auth/gmail.send",
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/gmail.modify"
]

2.4 metadata

{
    "email": "user@gmail.com",
    "display_name": "사용자명",
    "account_type": "gmail",
    "slack_user_id": "U091UNVE41M",
    "source_file": "original_filename.json",
    "imported_at": "2025-08-18"
}

3. 인덱스 구성

-- 기본 인덱스
CREATE INDEX idx_gmail_token_user_id ON gmail_token(user_id);
CREATE INDEX idx_gmail_token_robeing_id ON gmail_token(robeing_id);

-- JSON 검색용 GIN 인덱스
CREATE INDEX idx_gmail_token_token_data ON gmail_token USING GIN (token_data);
CREATE INDEX idx_gmail_token_oauth_config ON gmail_token USING GIN (oauth_config);

4. 자동 업데이트 트리거

-- updated_at 자동 갱신 함수
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 트리거 생성
CREATE TRIGGER update_gmail_token_updated_at 
BEFORE UPDATE ON gmail_token 
FOR EACH ROW 
EXECUTE FUNCTION update_updated_at_column();

5. 마이그레이션 완료 데이터

5.1 원본 파일 위치

/home/admin/auth-server/tokens/
├── heejae_gmail.json
├── test_gmail.json
└── unknown_gmail.json

5.2 현재 저장된 데이터

user_id robeing_id 권한 상태 이메일 발송 가능
heejae rb8001 gmail.modify만 있음 불가능
test rb8001 gmail.send + modify 가능
unknown NULL 프로필 권한만 불가능

5.3 권한 문제

  • 중요: gmail.modify만으로는 이메일 발송 불가
  • 이메일 발송하려면 gmail.send 권한 필수
  • heejae 계정은 재인증 필요

6. 유용한 쿼리

6.1 토큰 조회

-- 특정 사용자 토큰 조회
SELECT token_data->>'access_token' as access_token
FROM gmail_token 
WHERE user_id = 'test';

-- 이메일 발송 가능한 사용자 찾기
SELECT user_id, robeing_id
FROM gmail_token 
WHERE scopes @> '["https://www.googleapis.com/auth/gmail.send"]';

6.2 토큰 업데이트

-- 액세스 토큰 갱신
UPDATE gmail_token 
SET token_data = jsonb_set(
    token_data, 
    '{access_token}', 
    '"new_access_token"'
)
WHERE user_id = 'test';

-- 스코프 추가
UPDATE gmail_token 
SET scopes = scopes || '["https://www.googleapis.com/auth/gmail.send"]'::jsonb
WHERE user_id = 'heejae';

6.3 메타데이터 활용

-- Slack 사용자와 매핑
SELECT * FROM gmail_token 
WHERE metadata->>'slack_user_id' = 'U091UNVE41M';

-- 특정 로빙의 Gmail 계정 찾기
SELECT user_id, metadata->>'email' as gmail_account
FROM gmail_token 
WHERE robeing_id = 'rb8001';

7. Python 연동 예시

import psycopg2
import json
from psycopg2.extras import RealDictCursor

# 연결
conn = psycopg2.connect(
    host="localhost",
    database="main_db",
    user="robeings",
    password="robeings"
)

# 토큰 조회
with conn.cursor(cursor_factory=RealDictCursor) as cur:
    cur.execute("""
        SELECT 
            token_data->>'access_token' as access_token,
            token_data->>'refresh_token' as refresh_token,
            oauth_config,
            scopes
        FROM gmail_token 
        WHERE user_id = %s
    """, ('test',))
    
    token_info = cur.fetchone()
    
    # Google OAuth 객체 생성에 사용
    credentials = {
        'token': token_info['access_token'],
        'refresh_token': token_info['refresh_token'],
        'client_id': token_info['oauth_config']['client_id'],
        'client_secret': token_info['oauth_config']['client_secret'],
        'scopes': token_info['scopes']
    }

8. 다음 단계 TODO

  1. 권한 수정 필요

    • heejae 계정에 gmail.send 권한 추가 (재인증 필요)
    • unknown 계정 용도 확인 및 권한 설정
  2. Slack 사용자 매핑

    • metadata에 slack_user_id 추가
    • Slack User ID ↔ Gmail 계정 매핑 테이블 고려
  3. 토큰 자동 갱신

    • refresh_token 사용한 자동 갱신 로직 구현
    • expiry 필드 활용한 만료 체크
  4. 보안 강화

    • 토큰 암호화 고려
    • 접근 로그 테이블 추가
  5. skill-email 서비스 수정

    • 파일 기반 → DB 기반 토큰 조회로 변경
    • PostgreSQL 연결 설정 추가

9. 참고사항

  • JSONB 타입 사용으로 유연한 스키마 확장 가능
  • GIN 인덱스로 JSON 내부 검색 성능 최적화
  • 트리거로 updated_at 자동 관리
  • 모든 토큰 정보가 중앙 집중식으로 관리됨