# Slack Lists 첨부파일 업로드 구현

**날짜**: 2025-10-14
**작성자**: happybell80
**관련 파일**: `rb8001/app/scheduler/jobs/coldmail_briefing.py`
**전체 시나리오**: 251014_coldmail_ir_analysis_scenario.md

---

## 문제 상황

coldmail_briefing.py:207에서 document_id를 attachment에 전달하지만, Slack Lists API는 file_id 필요

---

## 현재 상태

- skill-rag-file/app/main.py:54-55: upload, search만 등록, download 없음
- skill-slack/app/api/__init__.py:4: files import 없음

---

## 해결 방안

### 1단계: skill-rag-file 다운로드 API 추가
**파일**: skill-rag-file/app/api/download.py (신규)
**import**: upload.py:1, 10, 12 참고 (FastAPI, get_db, TeamDocument)
**router**: `@router.get("/download/{document_id}")`
**DB 조회**: upload.py:60-65 패턴 사용
- `select(TeamDocument).where(TeamDocument.id == document_id)`
- `db.execute(stmt)`, `result.scalar_one_or_none()`
- 없으면 HTTPException(404)
**반환**: `FileResponse(document.storage_path, media_type=document.mime_type or "application/pdf")`
**주의**: MIME 타입은 DB에서 가져오기 (PDF 고정 금지)
**등록**: skill-rag-file/app/main.py:55 이후
- `from app.api import upload, search, download`
- `app.include_router(download.router, prefix="/api", tags=["download"])`

### 2단계: skill-slack 파일 업로드 API 추가
**파일**: skill-slack/app/api/endpoints/files.py (신규)
**import**: lists.py:4-9 참고 (FastAPI, settings, verify_api_key)
- `from slack_sdk import WebClient` (test_lists_with_file.py:4)
**router**: `@router.post("/files/upload", dependencies=[Depends(verify_api_key)])`
**파라미터**: file (UploadFile), title (str), token (Optional[str])
**업로드**: test_lists_with_file.py:18, 21-24 패턴
- `client = WebClient(token or settings.SLACK_BOT_TOKEN)`
- 임시 파일 저장 → `client.files_upload_v2(file=temp_path, title=title)`
- 업로드 후 임시 파일 삭제
**주의**:
- 임시 파일 경로에 원본 filename을 그대로 쓰면 `Errno 36 (File name too long)`로 실패할 수 있음 → `NamedTemporaryFile(delete=False, suffix=.pdf)` 등으로 안전한 temp 경로 사용
- 원본 filename은 로그/메타데이터 용도로만 유지
**반환**: `{"file_id": result["file"]["id"], "url_private": result["file"]["url_private"]}`
**에러**: HTTPException(400 or 500)
**등록**: skill-slack/app/api/__init__.py:4, 13 수정

### 3단계: coldmail_briefing.py 통합

**환경변수 추가** (coldmail_briefing.py:19 이후):
- `SKILL_RAG_FILE_URL = os.getenv("SKILL_RAG_FILE_URL", "http://localhost:8508")`

**파일 변환 로직** (coldmail_briefing.py:189 이후, 191 이전):
- GET `{SKILL_RAG_FILE_URL}/api/download/{document_ids[0]}` → pdf_content
- aiohttp.FormData(): file (filename, content_type 명시), title 추가
- headers: `{"X-API-Key": os.getenv("SKILL_SLACK_API_KEY")}`
- POST `{SKILL_SLACK_URL}/api/v1/files/upload` + headers → file_id
- 실패 시 file_id = None, logger.error()

**attachment 필드 수정** (coldmail_briefing.py:207):
- 변경 전: `[f"document_{doc_id}" for doc_id in document_ids][:1]`
- 변경 후: `[file_id] if file_id else []`

**Lists API 호출 수정** (coldmail_briefing.py:212-214):
- 현재 X-API-Key 헤더 없음 → 401 발생
- headers 추가: `{"X-API-Key": os.getenv("SKILL_SLACK_API_KEY")}`

---

## 구현 완료

**커밋**:
- skill-rag-file: download.py 추가, main.py 라우터 등록
- skill-slack: files.py 추가, __init__.py 라우터 등록
- rb8001 (ee59bbe): coldmail_briefing.py:191-221, 241, 246

**테스트**:
- 1단계: GET /api/download/650c305b-e45a-4ff4-8bcb-87096f804605 → 200, 4.8MB PDF
- 2단계: POST /api/v1/files/upload (X-API-Key) → 200, file_id: F09LAQBBXU5
- 3단계: POST /api/v1/lists/items → 200, item_id: Rec09KYQGBJKZ
- 워크스페이스: T0925SXPS4D, 리스트: F09J1HPPQJG

---

## 참고

- files_upload_v2: channel 파라미터 없이 업로드 가능
- 컬럼 ID: F09J1HPPQJG(테스트) ≠ F09L4S2C6BG(운영), 동적 매핑 필요
