# UUID 변환 시스템 아키텍처

## 작성일: 2025-08-21 (수정: 2025-08-28)
## 작성자: Claude (51123 서버 관리자)

---

## 1. 개요

RO-BEING 시스템의 UUID 변환 체계 문서입니다. Google OAuth와 Slack 모두 UUID4를 생성하며, Slack은 slack_user_mapping 테이블을 통해 매핑합니다.

---

## 2. UUID 타입별 용도

### 2.1 UUID 타입 구분

| 타입 | 생성 방식 | 용도 | 특징 |
|------|----------|------|------|
| UUID4 | 랜덤 생성 | Google OAuth 사용자 | auth-server에서 생성 |
| UUID | 매핑 테이블 조회 | Slack 사용자 | slack_user_mapping 테이블 사용 |
| DB 매핑 | slack_user_mapping | Slack 사용자 매핑 | 실제 사용 여부 확인 필요 |

### 2.2 네임스페이스

```python
# DNS 네임스페이스 (표준 UUID)
NAMESPACE = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
```

---

## 3. 사용자별 UUID 생성

### 3.1 Google OAuth 사용자 (UUID4)

```mermaid
flowchart LR
    A[Google 로그인] --> B[이메일 확인]
    B --> C{기존 사용자?}
    C -->|Yes| D[기존 UUID 사용]
    C -->|No| E[uuid4() 생성]
    E --> F[DB 저장]
    F --> G[JWT 생성]
```

#### 구현 코드
```python
import uuid

class GoogleAuthHandler:
    async def create_or_get_user(self, email: str, name: str):
        # 기존 사용자 확인
        user = await self.db.fetchone(
            "SELECT * FROM users WHERE email = $1",
            email
        )
        
        if user:
            return user['id']
        
        # 신규 사용자 - UUID4 생성
        new_user_id = str(uuid.uuid4())
        
        await self.db.execute("""
            INSERT INTO users (id, email, name, username, oauth_provider)
            VALUES ($1, $2, $3, $4, 'google')
        """, new_user_id, email, name, self.generate_username(email))
        
        return new_user_id
```

### 3.2 Slack 사용자 (UUID 매핑)

```mermaid
flowchart LR
    A[Slack 이벤트] --> B[Slack User ID]
    B --> C[slack_user_mapping 조회]
    C --> D[네임스페이스 + Slack ID]
    D --> E[SHA-1 해시]
    E --> F[결정적 UUID]
    F --> G{DB 확인}
    G -->|없음| H[신규 등록]
    G -->|있음| I[기존 사용]
```

#### 구현 코드
```python
import uuid

class SlackUserHandler:
    NAMESPACE = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
    
    def get_user_uuid(self, slack_user_id: str) -> str:
        """Slack User ID를 UUID로 변환 (매핑 테이블 조회)"""
        # slack_user_mapping 테이블에서 조회
        return get_uuid_from_mapping(slack_user_id)
    
    async def get_or_create_user(self, slack_user_id: str, slack_user_info: dict):
        # UUID 매핑 조회 (테이블에서 조회)
        user_uuid = self.get_user_uuid(slack_user_id)
        
        # 기존 사용자 확인
        user = await self.db.fetchone(
            "SELECT * FROM users WHERE id = $1",
            user_uuid
        )
        
        if user:
            return user_uuid
        
        # 신규 사용자 등록
        username = f"slack_{slack_user_id[:8]}"
        await self.db.execute("""
            INSERT INTO users (id, username, email, name, oauth_provider)
            VALUES ($1, $2, $3, $4, 'slack')
        """, user_uuid, username, 
            slack_user_info.get('email', ''),
            slack_user_info.get('real_name', ''))
        
        return user_uuid
```

---

## 4. Gateway에서의 UUID 변환

### 4.1 JWT Token → UUID 변환 플로우

```mermaid
sequenceDiagram
    participant Client
    participant Gateway
    participant DB
    participant Service

    Client->>Gateway: Request + JWT (username)
    Gateway->>Gateway: JWT 검증
    Gateway->>Gateway: username 추출
    
    Gateway->>DB: SELECT id FROM users WHERE username = ?
    DB-->>Gateway: UUID
    
    Gateway->>Service: Request + X-User-Id (UUID)
    Service-->>Gateway: Response
    Gateway-->>Client: Response
```

### 4.2 변환 구현

```python
class GatewayUUIDConverter:
    def __init__(self, db_pool):
        self.db_pool = db_pool
        self.cache = {}  # username -> uuid 캐시
        
    async def username_to_uuid(self, username: str) -> str:
        """Username을 UUID로 변환 (캐싱 포함)"""
        # 캐시 확인
        if username in self.cache:
            return self.cache[username]
        
        # DB 조회
        async with self.db_pool.acquire() as conn:
            row = await conn.fetchrow(
                "SELECT id FROM users WHERE username = $1",
                username
            )
            
            if not row:
                raise UserNotFoundError(f"User {username} not found")
            
            user_uuid = str(row['id'])
            
            # 캐시 저장 (5분 TTL)
            self.cache[username] = user_uuid
            asyncio.create_task(self.clear_cache_after(username, 300))
            
            return user_uuid
    
    async def clear_cache_after(self, username: str, seconds: int):
        """일정 시간 후 캐시 제거"""
        await asyncio.sleep(seconds)
        self.cache.pop(username, None)
```

---

## 5. 테스트 사용자 UUID

### 5.1 하드코딩된 테스트 UUID

```sql
-- 테스트 사용자 (고정 UUID)
INSERT INTO users (id, username, email, name) VALUES
    ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'happybell80', 'goeun2dc@gmail.com', '김종태'),
    ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'eagle0914', '0914eagle@gmail.com', '전희재'),
    ('cccccccc-cccc-cccc-cccc-cccccccccccc', 'test_user', 'test@example.com', 'Test User');
```

### 5.2 Slack 테스트 사용자 UUID 예시

```python
# Slack User ID 예시
slack_ids = {
    "U0925SXQFDK": "종태",  # UUID: 매핑 테이블 조회
    "U0123ABCDEF": "테스트"  # UUID: 매핑 테이블 조회
}

# UUID 매핑 조회 예시
for slack_id, name in slack_ids.items():
    user_uuid = get_uuid_from_mapping(slack_id)  # DB 조회
    print(f"{name} ({slack_id}): {user_uuid}")
```

---

## 6. UUID 마이그레이션

### 6.1 기존 VARCHAR user_id → UUID 마이그레이션

```sql
-- 1. 임시 컬럼 추가
ALTER TABLE gmail_tokens ADD COLUMN user_uuid UUID;

-- 2. UUID 매핑
UPDATE gmail_tokens gt
SET user_uuid = u.id
FROM users u
WHERE gt.user_id = u.username;

-- 3. 기존 컬럼 제거 및 이름 변경
ALTER TABLE gmail_tokens DROP COLUMN user_id;
ALTER TABLE gmail_tokens RENAME COLUMN user_uuid TO user_id;

-- 4. 외래키 제약 추가
ALTER TABLE gmail_tokens 
ADD CONSTRAINT fk_user_id 
FOREIGN KEY (user_id) REFERENCES users(id);
```

---

## 7. UUID 유효성 검증

### 7.1 검증 함수

```python
import re

def is_valid_uuid(uuid_string: str) -> bool:
    """UUID 형식 검증"""
    uuid_pattern = re.compile(
        r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
        re.IGNORECASE
    )
    return bool(uuid_pattern.match(uuid_string))

def get_uuid_version(uuid_string: str) -> int:
    """UUID 버전 확인"""
    try:
        u = uuid.UUID(uuid_string)
        return u.version
    except ValueError:
        return None

# 사용 예시
user_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
if is_valid_uuid(user_id):
    version = get_uuid_version(user_id)
    print(f"Valid UUID v{version}")
```

---

## 8. UUID 관련 API 응답

### 8.1 사용자 정보 API

```python
@app.get("/api/users/me")
async def get_current_user(request: Request):
    user_id = request.headers.get("X-User-Id")
    username = request.headers.get("X-Username")
    
    return {
        "id": user_id,  # UUID
        "username": username,
        "uuid_version": get_uuid_version(user_id),
        "is_slack_user": get_uuid_version(user_id) == 5
    }
```

### 8.2 UUID 디버깅 엔드포인트

```python
@app.get("/api/debug/uuid/{username}")
async def debug_uuid(username: str):
    """개발 환경에서만 사용"""
    user = await db.fetchone(
        "SELECT id, username, oauth_provider FROM users WHERE username = $1",
        username
    )
    
    if not user:
        return {"error": "User not found"}
    
    return {
        "username": username,
        "uuid": str(user['id']),
        "uuid_version": get_uuid_version(str(user['id'])),
        "oauth_provider": user['oauth_provider'],
        "is_deterministic": get_uuid_version(str(user['id'])) == 5
    }
```

---

## 9. 트러블슈팅

### 9.1 일반적인 문제

| 문제 | 원인 | 해결 방법 |
|------|------|----------|
| UUID 불일치 | Slack ID 변경 | 매핑 테이블을 통해 일관된 UUID 관리 |
| 중복 UUID | UUID4 충돌 (매우 드물음) | 재생성 또는 UNIQUE 제약 확인 |
| 변환 실패 | username 없음 | users 테이블 username 필드 확인 |
| 캐시 불일치 | TTL 만료 전 DB 변경 | 캐시 무효화 또는 TTL 단축 |

### 9.2 디버깅 쿼리

```sql
-- UUID 버전별 사용자 수
SELECT 
    CASE 
        WHEN id::text LIKE '________-____-4___-____-____________' THEN 'UUID4 (Google)'
        WHEN source = 'slack' THEN 'UUID (Slack 매핑)'
        ELSE 'Other'
    END as uuid_type,
    COUNT(*) as count
FROM users
GROUP BY uuid_type;

-- Username-UUID 매핑 확인
SELECT username, id, oauth_provider 
FROM users 
WHERE username = 'happybell80';

-- Slack 사용자 UUID 검증
SELECT 
    username,
    id,
    id = uuid_generate_v5('6ba7b810-9dad-11d1-80b4-00c04fd430c8'::uuid, 
                          SUBSTRING(username FROM 7)) as is_valid_uuid
FROM users
WHERE oauth_provider = 'slack';
```

---

## 10. 보안 고려사항

### 10.1 UUID 노출 최소화

```python
class SecureUUIDHandler:
    @staticmethod
    def mask_uuid(uuid_string: str) -> str:
        """UUID 일부 마스킹"""
        # aaaaaaaa-****-****-****-aaaaaaaaaaaa
        parts = uuid_string.split('-')
        return f"{parts[0]}-****-****-****-{parts[4]}"
    
    @staticmethod
    def should_expose_uuid(user_role: str) -> bool:
        """역할별 UUID 노출 여부"""
        return user_role in ['admin', 'developer']
```

### 10.2 UUID 추측 방지

- UUID4: 122비트 랜덤 엔트로피로 추측 불가능
- UUID 매핑: Slack ID로 테이블 조회 필요
- 네임스페이스 비공개 유지 (소스코드에서만 관리)

---

**문서 끝**