# PostgreSQL 테이블명 단수형 통일 작업

## 작성 정보
- **작성일**: 250911
- **작성자**: happybell80
- **문제**: 코드는 복수형, DB는 단수형 테이블명 사용으로 불일치

## 1. 데이터베이스 연결 정보

### 메인 데이터베이스
- **main_db**: `postgresql://robeings:robeings@192.168.219.45:5432/main_db`
- **robeing_metrics**: `postgresql://postgres:@172.17.0.1:5432/robeing_metrics`

### 사용 라이브러리
| 저장소 | 라이브러리 |
|--------|------------|
| auth-server | SQLAlchemy, asyncpg, psycopg2 |
| rb8001 | SQLAlchemy |
| rb10508_micro | asyncpg |
| frontend-base | asyncpg |
| robeing-gateway | SQLAlchemy, asyncpg |
| robeing-monitor | SQLAlchemy, asyncpg |
| skill-email | psycopg2 |

## 2. 테이블별 사용 위치 상세

### 2.1 user 테이블 (실제 DB: user, 코드: users 참조)
**실제 DB 컬럼**: id(UUID), team_id(FK), email, username, oauth_provider, oauth_id, created_at, updated_at
**코드 모델 차이**: 
- auth-server: picture 컬럼 (DB에 없음)
- robeing-gateway: avatar_url, provider, provider_id (DB에 없음)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/models/user.py | 21-35 | 테이블 모델 정의 (picture 컬럼) |
| auth-server/app/providers/gmail_passport.py | 178 | SELECT id FROM user WHERE username = $1 |
| auth-server/app/providers/gmail_passport.py | 187 | SELECT username FROM user WHERE id = $1 |
| robeing-gateway/app/database.py | 98-112 | JOIN 쿼리 (workspace_member, workspaces와 함께) |
| robeing-gateway/app/database.py | 182-191 | SELECT * FROM user WHERE username = $1 |
| robeing-gateway/app/database.py | 321-328 | SELECT * FROM user (전체 조회) |
| robeing-gateway/app/models.py | 11-24 | 테이블 모델 정의 (avatar_url 컬럼) |

### 2.2 company/team 테이블 (실제 DB: company+team, 코드: workspaces 참조)
**실제 DB**: company(id, name, url) + team(id, company_id, name) 구조
**코드 모델**: workspaces 테이블 참조 (DB에 없음)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/models/workspace.py | 15-31 | 테이블 모델 정의 |
| auth-server/scripts/run_migration.py | 54 | SELECT id, name, subdomain, robeing_id FROM workspaces |
| auth-server/migrations/add_user_workspace_tables.sql | 25-28 | ALTER TABLE company RENAME TO workspaces |
| robeing-gateway/app/database.py | 98-112 | JOIN 쿼리 |
| robeing-gateway/app/database.py | 216-230 | JOIN 쿼리 (workspace_member와 함께) |
| robeing-gateway/app/models.py | 26-44 | 테이블 모델 정의 |

### 2.3 workspace_member 테이블 (실제 DB: workspace_member, 레코드 0건)
**실제 DB 컬럼**: user_id(FK), role(user_role enum), created_at, updated_at
**코드 모델**: workspace_member 테이블 참조 (복수형 차이)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/models/user.py | 46-60 | 테이블 모델 정의 |
| auth-server/migrations/add_user_workspace_tables.sql | 41-52 | CREATE TABLE 정의 |
| robeing-gateway/app/database.py | 98-112 | JOIN 쿼리 |
| robeing-gateway/app/database.py | 263-266 | 중복 체크 쿼리 |
| robeing-gateway/app/database.py | 274-282 | UPDATE 쿼리 |
| robeing-gateway/app/database.py | 285-299 | INSERT 쿼리 (gen_random_uuid() 사용) |
| robeing-gateway/app/models.py | 46-60 | 테이블 모델 정의 |

### 2.4 slack_user_mapping 테이블 (실제 DB: 없음)
**코드에서 참조하나 실제 DB에 테이블 없음**

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/providers/slack.py | 266 | SELECT user_id FROM slack_user_mapping WHERE slack_user_id = :slack_user_id AND slack_workspace_id = :slack_workspace_id |
| auth-server/app/providers/slack.py | 308-312 | INSERT INTO slack_user_mapping ... DO UPDATE SET (slack_user_id, slack_workspace_id 조합 기준) |
| rb8001/app/router/router.py | 92 | SELECT user_id FROM slack_user_mapping WHERE slack_user_id = :slack_id |
| robeing-gateway/app/main.py | 479-486 | SELECT user_id FROM slack_user_mapping WHERE slack_user_id = $1 |

### 2.5 slack_workspace 테이블 (실제 DB: slack_workspace, 2건)
**실제 DB 컬럼**: id(UUID), team_id(FK), team_name, bot_token, bot_user_id, app_id, scope(json), created_at, updated_at
**코드 모델**: slack_workspaces 참조 (복수형 차이)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/models/workspace.py | 43-62 | 테이블 모델 정의 (현재 company_id로 정의됨) |
| auth-server/migrations/add_user_workspace_tables.sql | 60-76 | ALTER TABLE 정의 (company_id → workspace_id 변경) |
| robeing-gateway/app/main.py | 522-525 | SELECT bot_token FROM slack_workspace WHERE team_id = $1 |
| robeing-gateway/app/main.py | 567-570 | SELECT bot_token FROM slack_workspace WHERE team_id = $1 |

### 2.6 gmail_token 테이블 (실제 DB: gmail_token, 3건)
**실제 DB 컬럼**: user_id(FK), token_data(jsonb), oauth_config(jsonb), created_at, updated_at
**코드 혼용**: auth-server는 gmail_token(복수)와 gmail_token(단수) 혼용

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| auth-server/app/providers/gmail_passport.py | 193 | INSERT INTO gmail_token (단수) |
| auth-server/app/providers/gmail_passport.py | 264, 313, 354, 370 | gmail_token (단수) 사용 |
| auth-server/app/api/gmail_refresh.py | 36, 43, 115, 124, 173, 179 | gmail_token (복수) 사용 |
| skill-email/services/db_credentials_provider.py | 60-70 | SELECT 쿼리 (is_equipped 필터) |
| skill-email/services/db_credentials_provider.py | 163-177 | UPDATE 쿼리 |
| skill-email/services/db_credentials_provider.py | 231-235 | COUNT 쿼리 |
| robeing-monitor/app/api/items.py | 97-111 | SELECT 쿼리 |
| robeing-monitor/app/api/items.py | 185-190 | 소유권 확인 쿼리 |
| robeing-monitor/app/api/items.py | 197-211 | UPDATE 쿼리 (장착/해제) |
| robeing-monitor/app/api/items.py | 252-256 | UPDATE 쿼리 (해제) |
| robeing-monitor/app/api/items.py | 307-319 | SELECT 쿼리 (장착된 토큰) |
| robeing-monitor/app/api/items.py | 384-388 | DELETE 쿼리 |

### 2.7 gmail_audit_logs 테이블 (실제 DB: 없음)
**코드에서 참조하나 실제 DB에 테이블 없음**

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| robeing-monitor/app/api/items.py | 214-223 | INSERT 쿼리 |
| robeing-monitor/app/api/items.py | 263-270 | INSERT 쿼리 |
| robeing-monitor/app/api/items.py | 395-401 | INSERT 쿼리 |

### 2.8 robeing 테이블 (실제 DB: robeing, 2건)
**실제 DB 컬럼**: id(UUID), product_id(FK), team_id(FK), memory, compute, react, empathy, leadership, created_at, updated_at
**코드 모델**: robeing_stats 테이블 참조 (테이블명 불일치)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| rb8001/app/state/database.py | 24-38 | 테이블 모델 정의 |
| rb10508_micro/app/api/endpoints.py | 155-161 | SELECT 쿼리 |
| robeing-monitor/app/state/database.py | 19-33 | 테이블 모델 정의 |
| robeing-monitor/app/state/state_service.py | 55 | SELECT 쿼리 |
| robeing-monitor/app/state/state_service.py | 59-62 | INSERT 쿼리 |
| robeing-monitor/app/state/state_service.py | 81 | SELECT 쿼리 |
| robeing-monitor/app/state/state_service.py | 215 | SELECT 전체 조회 |
| robeing-monitor/app/api/items.py | 169-173 | SELECT level 쿼리 |

### 2.9 robeing_settings 테이블 (실제 DB: 없음)
**코드에서 참조하나 실제 DB에 테이블 없음**

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| rb8001/app/state/database.py | 40-50 | 테이블 모델 정의 |
| robeing-monitor/app/state/database.py | 35-45 | 테이블 모델 정의 |
| robeing-monitor/app/state/state_service.py | 110 | SELECT 쿼리 |
| robeing-monitor/app/state/state_service.py | 114-117 | INSERT 쿼리 |
| robeing-monitor/app/state/state_service.py | 133 | SELECT 쿼리 |

### 2.10 conversation_log 테이블 (실제 DB: conversation_log, 0건)
**실제 DB 컬럼**: user_id(FK), message, response, intent, channel_id, timestamp 등
**코드 수정**: robeing_id, slack_user_id 컬럼 제거 (DB에 없음), channel_id만 사용

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| rb8001/app/state/database.py | 52-65 | 테이블 모델 정의 (slack_user_id 포함) |
| rb8001/app/state/database.py | 86-92 | SELECT 쿼리 (최근 대화, slack_user_id 포함) |
| rb8001/app/router/router.py | 464-496 | INSERT 쿼리 (slack_user_id 포함) |
| robeing-monitor/app/state/database.py | 47-59 | 테이블 모델 정의 (slack_user_id 없음) |
| robeing-monitor/app/state/state_service.py | 156-159 | INSERT 쿼리 (slack_user_id 없음) |
| robeing-monitor/app/state/state_service.py | 173-177 | SELECT 쿼리 (최근 로그, slack_user_id 없음) |
| robeing-monitor/app/state/state_service.py | 194-203 | SELECT 쿼리 (7일간, slack_user_id 없음) |

**2025-12-04 추가**:
- rb8001/app/state/conversation_repository.py에서 ConversationLog 생성 시 실제 스키마에 맞춰 `user_id`+`channel_id`만 저장하도록 수정, `ensure_conversation_log_sequence_aligned()`로 시퀀스 정렬 후 INSERT 일관 유지

### 2.11 user_preference 테이블 (실제 DB: user_preference, 0건)
**실제 DB 컬럼**: user_id(FK), slack_user_id, news_keywords[], briefing_enabled, briefing_time, created_at, updated_at
**코드 모델**: user_preference 참조 (복수형 차이)

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| rb8001/app/skills/dm_skill.py | 466 | SELECT news_keywords WHERE user_id = :user_id |
| rb8001/app/skills/dm_skill.py | 471 | SELECT news_keywords WHERE slack_user_id = :slack_user_id |
| robeing-monitor/app/api/monitor.py | 148-165 | SELECT 쿼리 |
| robeing-monitor/app/api/monitor.py | 276-292 | UPDATE 쿼리 (동적 쿼리 생성) |
| robeing-monitor/app/api/monitor.py | 309-316 | INSERT 쿼리 |

### 2.12 news 테이블 (실제 DB: news, 0건)
**실제 DB 컬럼**: user_id(FK), data(jsonb), created_at, updated_at
**코드 참조**: 확인 필요

### 2.13 product 테이블 (실제 DB: product, 1건)
**실제 DB 컬럼**: id(UUID), name, app_id, scope(json), created_at, updated_at
**코드 참조**: 확인 필요

| 파일 경로 | 라인 번호 | 작업 내용 |
|-----------|-----------|-----------|
| frontend-base/backend/metrics_database.py | 58-60 | INSERT INTO system_metrics |
| frontend-base/backend/metrics_database.py | 93-100 | SELECT (time_bucket 사용) |

## 3. 수정 작업 체크리스트

### 3.1 테이블 구조 변경 시
- [ ] SQLAlchemy 모델 클래스 수정
- [ ] Migration 스크립트 작성
- [ ] 기존 데이터 마이그레이션 계획

### 3.2 컬럼명 변경 시
- [ ] 모든 SELECT 쿼리 수정
- [ ] 모든 INSERT 쿼리 수정
- [ ] 모든 UPDATE 쿼리 수정
- [ ] 모든 DELETE 쿼리 수정
- [ ] SQLAlchemy 모델 속성명 수정
- [ ] API 응답 필드명 확인

### 3.3 저장소별 작업
- [x] auth-server: 이미 완료 상태
- [x] rb8001: 테이블명 단수형 변경 완료
- [x] rb10508_micro: 사용 안함 (폐기 예정)
- [x] frontend-base: system_metrics 비활성화 완료
- [x] robeing-gateway: 이미 완료 상태
- [x] robeing-monitor: 테이블명 변경 및 import 수정 완료
- [x] skill-email: gmail_token 변경 완료

### 3.4 테스트 계획
- [ ] 각 저장소별 단위 테스트
- [ ] 통합 테스트 (API 엔드포인트)
- [ ] 데이터 마이그레이션 테스트
- [ ] 롤백 계획 수립

## 4. 위험 요소 - 치명적 불일치

1. **테이블명 전체 불일치**:
   - 코드: users, workspaces, workspace_member (복수형)
   - DB: user, company/team, workspace_member (단수형)
   
2. **존재하지 않는 테이블 참조**:
   - slack_user_mapping (코드 참조, DB 없음)
   - gmail_audit_logs (코드 참조, DB 없음)
   - robeing_settings (코드 참조, DB 없음)
   - workspaces (코드 참조, DB는 company+team)
   
3. **구조 불일치**:
   - 코드: workspace 중심 구조
   - DB: company → team → user 계층 구조
   
4. **비어있는 주요 테이블** (레코드 0건):
   - conversation_log, gmail_token, news, user_preference, workspace_member

## 5. 수행 결과

1. **완료**: 모든 복수형 테이블명을 단수형으로 통일
2. **주석 처리**: 존재하지 않는 테이블 참조 코드
3. **추가 수정**: conversation_log의 robeing_id, slack_user_id 컬럼 제거 (DB에 없음)
4. **배포 성공**: 모든 서비스 정상 동작 확인

## 6. 롤백 계획

1. 변경 전 전체 데이터베이스 백업
2. 각 테이블별 데이터 덤프
3. 이전 버전 코드 태그 생성
4. 단계별 배포 (테스트 환경 → 프로덕션)

---
**교훈**: 
- DB 테이블명은 단수형 사용이 PostgreSQL 관례
- 코드와 DB 구조 일치 필수
- 존재하지 않는 테이블은 주석 처리로 명시
