# 테이블 관계도 (ERD)

## 작성일: 2025-08-20

---

## 관계 다이어그램

```mermaid
erDiagram
    users ||--o{ workspace_member : "has many"
    users ||--o{ slack_user_mapping : "has many"
    users ||--o{ gmail_token : "has"
    users ||--o{ robeing_stats : "has"
    users ||--o{ conversation_log : "has many"
    
    workspaces ||--o{ workspace_member : "has many"
    workspaces }o--|| company : "belongs to"
    
    slack_workspaces ||--o{ slack_user_mapping : "has many"
    
    workspace_member ||--o{ slack_user_mapping : "references"
    
    gmail_token }o--|| users : "belongs to"
    robeing_stats }o--|| users : "belongs to"
    conversation_log }o--|| users : "belongs to"
```

---

## 핵심 관계 설명

### 1. User 중심 관계
```
users (1) ─────┬──── (*) workspace_member
               ├──── (*) slack_user_mapping  
               ├──── (0..1) gmail_token
               ├──── (0..1) robeing_stats
               └──── (*) conversation_log
```

- 한 사용자는 여러 워크스페이스의 멤버가 될 수 있음
- 한 사용자는 여러 Slack 워크스페이스와 매핑될 수 있음
- 한 사용자는 하나의 Gmail 토큰만 가질 수 있음 (현재 구조)
- 한 사용자는 하나의 로빙 통계를 가짐
- 한 사용자는 여러 대화 로그를 가짐

### 2. Workspace 관계
```
company (1) ──── (*) workspaces
workspaces (1) ──── (*) workspace_member
```

- 한 회사는 여러 워크스페이스를 가질 수 있음
- 한 워크스페이스는 여러 멤버를 가질 수 있음

### 3. Slack 매핑 관계
```
slack_workspaces (1) ──── (*) slack_user_mapping
slack_user_mapping (*) ──── (1) users
slack_user_mapping (*) ──── (0..1) workspace_member
```

- Slack 사용자 ID를 시스템 UUID로 변환하는 브릿지 역할
- workspace_member와 선택적 연결

---

## JOIN 예시

### 1. 사용자 전체 정보 조회
```sql
SELECT 
    u.id,
    u.username,
    u.name,
    u.email,
    wm.robeing_id,
    sum.slack_user_id,
    gt.is_equipped as gmail_equipped
FROM user u
LEFT JOIN workspace_member wm ON u.id = wm.user_id
LEFT JOIN slack_user_mapping sum ON u.id = sum.user_id
LEFT JOIN gmail_token gt ON u.id = gt.user_id
WHERE u.username = 'happybell80';
```

### 2. Slack ID로 사용자 찾기
```sql
SELECT 
    sum.slack_user_id,
    u.id as user_uuid,
    u.username,
    u.name,
    u.email
FROM slack_user_mapping sum
JOIN user u ON sum.user_id = u.id
WHERE sum.slack_user_id = 'U0925SXQFDK';
```

### 3. Gmail 아이템 장착 상태 확인
```sql
SELECT 
    u.name,
    u.email,
    gt.is_equipped,
    gt.equipped_to,
    rs.level,
    gt.scopes
FROM user u
JOIN gmail_token gt ON u.id = gt.user_id
LEFT JOIN robeing_stats rs ON u.id = rs.user_id
WHERE gt.is_equipped = true;
```

### 4. 워크스페이스 멤버 목록
```sql
SELECT 
    w.name as workspace_name,
    u.name as user_name,
    u.email,
    wm.role,
    wm.robeing_id
FROM workspaces w
JOIN workspace_member wm ON w.id = wm.workspace_id
JOIN user u ON wm.user_id = u.id
WHERE w.name = 'Ivada Robeing';
```

---

## 데이터 흐름

### 1. Slack 메시지 처리 흐름
```
Slack Message
    ↓
slack_user_id (U0925SXQFDK)
    ↓
slack_user_mapping 테이블 조회
    ↓
user_id (UUID) 획득
    ↓
모든 서비스에서 UUID 사용
```

### 2. Gmail 아이템 사용 흐름
```
User 요청
    ↓
user_id (UUID)로 gmail_token 조회
    ↓
is_equipped 확인
    ↓
robeing_stats에서 레벨 확인
    ↓
token_data에서 access_token 추출
    ↓
Gmail API 호출
```

### 3. 프론트엔드 인증 흐름
```
Google OAuth 로그인
    ↓
users 테이블에 사용자 생성/업데이트
    ↓
JWT 토큰 발급 (user_id 포함)
    ↓
모든 API 요청에 JWT 사용
    ↓
user_id로 권한 확인
```

---

## 데이터 정합성 규칙

### 1. UUID 일관성
- 모든 `user_id` 컬럼은 `users.id`를 참조
- 외래키 제약으로 정합성 보장
- CASCADE 옵션 미사용 (명시적 삭제 필요)

### 2. 유니크 제약
- `users.email` - 중복 이메일 방지
- `users.username` - 중복 사용자명 방지
- `slack_workspaces.team_id` - 중복 Slack 팀 방지
- `robeing_settings.robeing_id` - 중복 설정 방지

### 3. NULL 허용 정책
- 필수 관계: NOT NULL (user_id, workspace_id 등)
- 선택적 관계: NULL 허용 (robeing_id, equipped_to 등)

---

## 트랜잭션 고려사항

### 1. Gmail 아이템 장착
```sql
BEGIN;
-- 1. 레벨 확인
SELECT level FROM robeing_stats WHERE user_id = ?;

-- 2. 소유권 확인
SELECT id FROM gmail_token WHERE user_id = ?;

-- 3. 장착 처리
UPDATE gmail_token 
SET is_equipped = true, equipped_to = ?
WHERE user_id = ?;

-- 4. 감사 로그
INSERT INTO gmail_audit_logs (user_id, action, success)
VALUES (?, 'equip', true);

COMMIT;
```

### 2. 사용자 생성
```sql
BEGIN;
-- 1. users 테이블
INSERT INTO user (id, email, name, username) 
VALUES (?, ?, ?, ?);

-- 2. workspace_member 추가
INSERT INTO workspace_member (user_id, workspace_id, role)
VALUES (?, ?, 'member');

-- 3. robeing_stats 초기화
INSERT INTO robeing_stats (user_id, robeing_id, level)
VALUES (?, ?, 1);

COMMIT;
```

---

**문서 끝**