# 07. API 레퍼런스

## 서버 실행

```bash
source .venv/bin/activate
alembic upgrade head
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

- Swagger UI: http://localhost:8000/docs
- 이미지 파일: http://localhost:8000/storage/...

---

## 클라이언트 API (api.js)

프론트엔드에서는 `api.js`의 고수준 함수만 사용합니다. **사용자 식별은 이름(string)으로만** 수행하며, UUID는 내부에서 자동 처리됩니다.

### 사용자

| 함수 | 설명 |
|------|------|
| `listUsers()` | 사용자 목록 + 의류 수 (UUID 제외, 이름만 반환) |

### 얼굴

| 함수 | 설명 |
|------|------|
| `registerFace(image, "엄마")` | 얼굴 등록 (이름 필수) |

### 의류 분석

| 함수 | 설명 |
|------|------|
| `analyzeOnly(image)` | 분석만 수행 (DB 저장 안 함) |
| `analyzeForUser(image, "엄마")` | "엄마" 옷장에 저장 (없으면 자동 생성) |
| `analyzeWithFace(image)` | 사진 속 얼굴로 사용자 자동 결정 |

### 스타일 분석

| 함수 | 설명 |
|------|------|
| `analyzeStyle(image)` | 전체 코디 스타일 평가 (색상 균형, 핏, 디테일 점수) |

### 옷장 조회

| 함수 | 설명 |
|------|------|
| `listGarments({ userName, categoryMain, page, size })` | 필터 + 페이지네이션 (userName은 내부에서 UUID로 변환) |
| `getGarment(garmentId)` | 상세 조회 |

### 고스트 마네킹

| 함수 | 설명 |
|------|------|
| `generateGhost(garmentId)` | 고스트 마네킹 이미지 생성 (느린 작업) |

### VTON (가상 피팅)

| 함수 | 설명 |
|------|------|
| `generateVton(image, garmentIds, { userName })` | 인물 이미지 + 옷장 의류 ID 리스트 → 합성 URL (얼굴 필수, ghost 필수) |

### 이미지 생성 모델 선택

고스트 마네킹과 VTON에 사용할 OpenAI 이미지 생성 모델을 선택합니다. 한 번 설정하면 이후 `generateGhost` / `generateGhostPreview` / `generateVton` 호출에 자동 적용됩니다.

| 함수 | 설명 |
|------|------|
| `setImageModel(model)` | 이미지 모델 선택 (`'gpt-image-2'` \| `'gpt-image-1.5'` \| `null`). `null`이면 서버 기본값 사용 |
| `getImageModel()` | 현재 선택된 모델 반환 (`null` = 서버 기본값) |
| `IMAGE_MODELS` | 선택 가능한 모델 목록 상수 |

```js
import * as API from './api.js';

API.setImageModel('gpt-image-1.5');   // 이후 고스트/VTON 호출에 적용
API.setImageModel(null);              // 서버 기본값으로 복귀
```

> Gemini provider 사용 시 서버에서 이 값은 무시됩니다.

> **참고:** 사용자 삭제, 얼굴 삭제, 의류 삭제/이동, 관리(초기화/정리) 등은 서버 REST API로만 제공됩니다 (api.js에 미포함). 하단 "REST API" 섹션을 참고하세요.

### 사용 예시

```js
import * as API from './api.js';

// 엄마 옷장에 옷 추가
await API.analyzeForUser(photoFile, "엄마");

// 엄마 얼굴 등록
await API.registerFace(selfieFile, "엄마");

// 엄마 옷장 조회
const result = await API.listGarments({ userName: "엄마" });
console.log(result.items);

// 사용자 목록으로 메뉴 구성
const users = await API.listUsers();
users.forEach(u => {
    console.log(`${u.name} 옷장 (${u.garment_count}벌)`);
});
```

---

## REST API (curl 레퍼런스)

서버 REST API의 상세 스펙입니다. 프론트엔드 `api.js`를 사용하지 않고 직접 호출할 때 참고하세요.

### 엔드포인트 목록

| 메서드 | 경로 | 설명 |
|--------|------|------|
| `GET` | `/api/v1/health` | 헬스 체크 |
| `GET` | `/api/v1/users` | 사용자 목록 |
| `POST` | `/api/v1/users` | 사용자 생성 (get-or-create) |
| `DELETE` | `/api/v1/users/{user_id}` | 사용자 삭제 |
| `POST` | `/api/v1/faces/register` | 얼굴 등록 (이름 필수) |
| `POST` | `/api/v1/faces/identify` | 얼굴 식별 |
| `DELETE` | `/api/v1/faces/{user_id}` | 얼굴 데이터 삭제 |
| `POST` | `/api/v1/garments/analyze` | 의류 분석 |
| `POST` | `/api/v1/garments/style-analyze` | 스타일 분석 |
| `POST` | `/api/v1/garments/save` | 분석 결과 저장 |
| `POST` | `/api/v1/garments/ghost-preview` | 고스트 마네킹 프리뷰 |
| `POST` | `/api/v1/garments/{id}/ghost` | 고스트 마네킹 생성 |
| `GET` | `/api/v1/garments` | 의류 목록 (필터/페이지네이션) |
| `GET` | `/api/v1/garments/{id}` | 의류 상세 |
| `PATCH` | `/api/v1/garments/{id}` | 의류 소유자 변경 |
| `PATCH` | `/api/v1/garments/bulk` | 의류 일괄 소유자 변경 |
| `DELETE` | `/api/v1/garments/{id}` | 의류 삭제 |
| `POST` | `/api/v1/vton/generate` | VTON (가상 피팅) — 인물 + 옷장 합성 |
| `DELETE` | `/api/v1/admin/reset` | 전체 초기화 |
| `POST` | `/api/v1/admin/seed` | 프리셋 옷장 시딩 (unknown 사용자) |
| `POST` | `/api/v1/admin/cleanup` | 고아 파일 정리 |

---

### 사용자

**생성 (get-or-create)**

```bash
curl -X POST http://localhost:8000/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"name": "홍길동"}'
```
```json
{ "user_id": "a1b2c3d4-...", "name": "홍길동", "created": true }
```
같은 이름으로 재호출하면 `"created": false`와 동일한 UUID를 반환합니다.

**목록 조회**

```bash
curl http://localhost:8000/api/v1/users
```
```json
{
    "users": [
        { "id": "00000000-...", "name": "미분류", "garment_count": 5 },
        { "id": "a1b2c3d4-...", "name": "홍길동", "garment_count": 8 }
    ]
}
```

**삭제** — 의류, 얼굴, 이미지 파일 모두 cascade 삭제. unknown 사용자(00000000-...)는 삭제 불가.

```bash
curl -X DELETE http://localhost:8000/api/v1/users/{user_id}
```
```json
{ "user_id": "a1b2c3d4-...", "deleted_garments": 12, "deleted_faces": 3, "deleted_files": 27 }
```

---

### 얼굴

**등록** — `user_name` 필수. 얼굴 매칭 우선, 실패 시 이름으로 사용자 get-or-create.

```bash
curl -X POST http://localhost:8000/api/v1/faces/register \
  -F "image=@selfie.jpg" \
  -F "user_name=홍길동"
```
```json
{ "user_id": "a1b2c3d4-...", "user_name": "홍길동", "is_new_user": true, "confidence": 1.0 }
```
기존 얼굴과 매칭되면 `is_new_user: false`, `confidence: 0.87` 등으로 반환됩니다.

**식별**

```bash
curl -X POST http://localhost:8000/api/v1/faces/identify \
  -F "image=@photo.jpg"
```
성공: `{ "user_id": "...", "user_name": "홍길동", "confidence": 0.85 }` / 실패: `404`

**삭제** — 얼굴 데이터만 삭제. 사용자와 의류는 유지.

```bash
curl -X DELETE http://localhost:8000/api/v1/faces/{user_id}
```
```json
{ "user_id": "a1b2c3d4-...", "deleted_count": 3 }
```

---

### 의류 분석

3가지 모드:

```bash
# 미분류(글로벌)에 저장
curl -X POST .../garments/analyze -F "image=@outfit.jpg"

# 특정 사용자 지정
curl -X POST .../garments/analyze -F "image=@outfit.jpg" -F "user_id={user_id}"

# 얼굴 자동 식별
curl -X POST .../garments/analyze -F "image=@outfit.jpg" -F "use_face_id=true"
```

```json
{
    "user_id": "a1b2c3d4-...",
    "user_name": "홍길동",
    "face_status": "known",
    "garments": [
        {
            "id": "garment-uuid",
            "category_main": "top",
            "category_sub": "t-shirt",
            "description": "블랙 코튼 반팔 티셔츠",
            "tags": { "color": ["black"], "style": ["casual"] },
            "ghost_image_url": null
        }
    ]
}
```

`face_status` 값: `known` (매칭 성공), `unknown` (미등록 얼굴), `not_detected` (얼굴 없음), `not_used` (FaceID 미사용)

---

### 스타일 분석

전체 코디 사진을 LLM으로 분석하여 색상 균형, 핏/실루엣, 디테일 포인트를 평가합니다.

```bash
curl -X POST http://localhost:8000/api/v1/garments/style-analyze \
  -F "image=@outfit.jpg"
```
```json
{
    "color_palette": {
        "score": 5,
        "comment": "차분한 파스텔 톤의 스카이 블루와 그레이 조합이..."
    },
    "silhouette": {
        "score": 4,
        "comment": "골반 위로 떨어지는 세미 크롭 기장이 매우 좋아 보입니다..."
    },
    "detail": {
        "score": 4,
        "comment": "카디건 이너의 원포인트 티셔츠로 살짝 노출하여..."
    },
    "overall_score": 4.5,
    "overall_comment": "전체적으로 세련된 파스텔 코디네이션이 돋보입니다...",
    "style_tags": ["casual", "minimal", "pastel", "layered"]
}
```

---

### 의류 CRUD

**목록 조회** — 필터: `user_id`, `category_main`, `style`, `color`, `page`, `size`

```bash
curl "http://localhost:8000/api/v1/garments?category_main=top&page=1&size=20"
```

**상세 조회**

```bash
curl http://localhost:8000/api/v1/garments/{garment_id}
```

**삭제** — DB 레코드 + 이미지 파일(원본, 고스트) 모두 삭제.

```bash
curl -X DELETE http://localhost:8000/api/v1/garments/{garment_id}
```

**소유자 변경**

```bash
# 단건
curl -X PATCH .../garments/{garment_id} \
  -H "Content-Type: application/json" -d '{"user_id": "target-uuid"}'

# 일괄
curl -X PATCH .../garments/bulk \
  -H "Content-Type: application/json" \
  -d '{"garment_ids": ["id1", "id2"], "user_id": "target-uuid"}'
```

**고스트 마네킹 생성** — 분석 후 별도 호출 (느린 작업).

```bash
curl -X POST http://localhost:8000/api/v1/garments/{garment_id}/ghost

# 이미지 모델 지정 (선택): gpt-image-2 | gpt-image-1.5, 미지정 시 서버 기본값
curl -X POST "http://localhost:8000/api/v1/garments/{garment_id}/ghost?image_model=gpt-image-1.5"
```
```json
{ "id": "garment-uuid", "ghost_image_url": "/storage/ghost_mannequin/..." }
```

> `POST /api/v1/garments/ghost-preview`도 동일하게 JSON 본문에 `"image_model"` 필드(선택)를 받습니다.

---

### VTON (가상 피팅)

**요구사항**
- 인물 사진에 **얼굴이 감지되어야 함** (없으면 400)
- 선택한 garment는 **ghost_image_path가 있어야 함** (없으면 400)
- 상반신만 있는 사진에 하의를 같이 전달해도 오류가 아니라 LLM이 자동 skip

```bash
curl -X POST http://localhost:8000/api/v1/vton/generate \
  -F "image=@tests/fixtures/person.jpg" \
  -F "garment_ids=id_top_uuid" \
  -F "garment_ids=id_bottom_uuid"

# 이미지 모델 지정 (선택): gpt-image-2 | gpt-image-1.5, 미지정 시 서버 기본값
curl -X POST http://localhost:8000/api/v1/vton/generate \
  -F "image=@tests/fixtures/person.jpg" \
  -F "garment_ids=id_top_uuid" \
  -F "image_model=gpt-image-1.5"
```
```json
{
  "result_image_url": "/storage/vton/00000000-0000-0000-0000-000000000000/4f...f.png",
  "result_image_path": "vton/00000000-0000-0000-0000-000000000000/4f...f.png",
  "used_garment_ids": ["id_top_uuid"],
  "skipped_garment_ids": ["id_bottom_uuid"],
  "llm_note": "Skipped #2 because the person's legs are not visible in frame."
}
```

특정 사용자 옷장/폴더에 저장하려면:
```bash
curl -X POST http://localhost:8000/api/v1/vton/generate \
  -F "image=@person.jpg" \
  -F "user_id=user-uuid" \
  -F "garment_ids=id1" \
  -F "garment_ids=id2"
```

자세한 설계는 [09-vton-api.md](09-vton-api.md)를 참고하세요.

---

### 관리

**전체 초기화** — 모든 데이터 삭제, unknown 사용자만 보존. 되돌릴 수 없음.

```bash
curl -X DELETE http://localhost:8000/api/v1/admin/reset
```
```json
{ "deleted_garments": 45, "deleted_faces": 10, "deleted_users": 3, "deleted_files": 102 }
```

**프리셋 옷장 시딩** — `/home/ubuntu/ootd-poc/resources/{men,women}_garments/`의 이미지를 unknown 사용자 옷장에 일괄 등록합니다. 각 이미지에 대해 LLM 분석 + ghost mannequin 생성까지 수행하므로 수 분 동안 블로킹됩니다.

- `mode: "reset"` — DB/스토리지 초기화 후 시딩 (unknown 사용자만 보존)
- `mode: "add"` (기본) — 기존 데이터 유지 후 추가 (재실행 시 중복 생성됨)
- `resource_dirs` (선택) — 리소스 디렉터리 오버라이드

```bash
curl -X POST http://localhost:8000/api/v1/admin/seed \
  -H "Content-Type: application/json" \
  -d '{"mode":"reset"}'
```
```json
{
  "mode": "reset",
  "images_processed": 17,
  "garments_created": 17,
  "ghosts_generated": 17,
  "errors": [],
  "reset_summary": { "deleted_garments": 0, "deleted_faces": 0, "deleted_users": 0, "deleted_files": 0 }
}
```

CLI 스크립트로 서버 없이 실행하는 방법은 [10-seeding-script.md](10-seeding-script.md)를 참고하세요.

**고아 파일 정리** — DB에 없는 파일 탐지/삭제.

```bash
# 스캔만
curl -X POST "http://localhost:8000/api/v1/admin/cleanup?dry_run=true"

# 실제 삭제
curl -X POST "http://localhost:8000/api/v1/admin/cleanup?dry_run=false"
```
```json
{ "orphaned_files": ["originals/.../abc.jpg", "..."], "deleted_count": 2 }
```

---

## DB 직접 관리

API 없이 직접 DB를 조회/수정할 때 참고합니다.

```bash
sudo -u postgres psql ootd_poc
```

```sql
-- 사용자별 현황
SELECT u.name, COUNT(g.id) as garments, COUNT(f.id) as faces
FROM users u
LEFT JOIN garments g ON g.user_id = u.id
LEFT JOIN face_embeddings f ON f.user_id = u.id
GROUP BY u.id, u.name;

-- 사용자 삭제 (garments, face_embeddings CASCADE)
DELETE FROM users WHERE id = 'user-uuid';

-- 전체 초기화
DELETE FROM garments;
DELETE FROM face_embeddings;
DELETE FROM users WHERE id != '00000000-0000-0000-0000-000000000000';
```

DB 직접 삭제 후 남은 이미지 파일 정리:
```bash
# 특정 사용자
rm -rf storage/{originals,ghost_mannequin,faces}/{user-uuid}/

# 또는 고아 파일 API로 자동 정리
curl -X POST "http://localhost:8000/api/v1/admin/cleanup?dry_run=false"
```
