# SAM API 페이지네이션 정책

**작성일**: 2025-12-09
**수정일**: 2025-12-30
**적용 범위**: 모든 목록 조회 API

---

## 1. 응답 구조

### 1.1 표준 페이지네이션 응답 (Laravel 기본)

```json
{
  "success": true,
  "message": "조회되었습니다.",
  "data": {
    "current_page": 1,
    "data": [
      { "id": 1, "name": "항목1" },
      { "id": 2, "name": "항목2" }
    ],
    "first_page_url": "http://sam.kr/api/v1/items?page=1",
    "from": 1,
    "last_page": 5,
    "last_page_url": "http://sam.kr/api/v1/items?page=5",
    "links": [
      { "url": null, "label": "&laquo; Previous", "active": false },
      { "url": "http://sam.kr/api/v1/items?page=1", "label": "1", "active": true },
      { "url": "http://sam.kr/api/v1/items?page=2", "label": "Next &raquo;", "active": false }
    ],
    "next_page_url": "http://sam.kr/api/v1/items?page=2",
    "path": "http://sam.kr/api/v1/items",
    "per_page": 20,
    "prev_page_url": null,
    "to": 20,
    "total": 100
  }
}
```

### 1.2 응답 구조 설명

| 키 | 타입 | 설명 |
|----|------|------|
| `success` | boolean | 요청 성공 여부 |
| `message` | string | 응답 메시지 (i18n 키) |
| `data` | object | **페이지네이션 객체** (Laravel 기본) |

### 1.3 data 객체 필드

| 필드 | 타입 | 설명 |
|------|------|------|
| `current_page` | int | 현재 페이지 번호 (1부터 시작) |
| `data` | array | **실제 데이터 배열** |
| `per_page` | int | 페이지당 항목 수 |
| `total` | int | 전체 항목 수 |
| `last_page` | int | 마지막 페이지 번호 |
| `from` | int\|null | 현재 페이지 첫 번째 항목 번호 |
| `to` | int\|null | 현재 페이지 마지막 항목 번호 |
| `first_page_url` | string | 첫 페이지 URL |
| `last_page_url` | string | 마지막 페이지 URL |
| `next_page_url` | string\|null | 다음 페이지 URL |
| `prev_page_url` | string\|null | 이전 페이지 URL |
| `path` | string | 기본 경로 |
| `links` | array | 페이지 링크 배열 |

---

## 2. 요청 파라미터

### 2.1 표준 파라미터

| 파라미터 | 타입 | 기본값 | 설명 |
|----------|------|--------|------|
| `page` | int | 1 | 페이지 번호 |
| `size` | int | 20 | 페이지당 항목 수 (max: 100) |

### 2.2 요청 예시

```http
GET /api/v1/items?page=1&size=20
GET /api/v1/items?page=2&size=50&search=스크린
```

---

## 3. 구현 가이드

### 3.1 Controller 구현

```php
public function index(IndexRequest $request)
{
    $items = $this->service->getItems($request->validated());

    return ApiResponse::success($items, __('message.fetched'));
}
```

### 3.2 Service 구현

```php
public function getItems(array $params): LengthAwarePaginator
{
    $size = min($params['size'] ?? 20, 100);  // 최대 100개

    return Model::query()
        ->when($params['search'] ?? null, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
        ->orderByDesc('created_at')
        ->paginate($size);
}
```

### 3.3 최대 페이지 크기 제한

```php
$size = min($params['size'] ?? 20, 100);  // 최대 100개
```

- 기본값: 20
- 최대값: 100
- 서버 부하 방지 및 응답 시간 최적화

---

## 4. 프론트엔드 사용 가이드

### 4.1 TypeScript 타입 정의

```typescript
interface PaginatedResponse<T> {
  success: boolean;
  message: string;
  data: {
    current_page: number;
    data: T[];
    first_page_url: string;
    from: number | null;
    last_page: number;
    last_page_url: string;
    links: Array<{
      url: string | null;
      label: string;
      active: boolean;
    }>;
    next_page_url: string | null;
    path: string;
    per_page: number;
    prev_page_url: string | null;
    to: number | null;
    total: number;
  };
}
```

### 4.2 React 사용 예시

```typescript
const fetchItems = async (page: number, size: number) => {
  const response = await api.get<PaginatedResponse<Item>>('/items', {
    params: { page, size }
  });

  // 데이터 접근
  const items = response.data.data.data;

  // 페이지네이션 정보
  const { current_page, total, last_page } = response.data.data;

  return { items, pagination: response.data.data };
};
```

### 4.3 간편 접근 패턴

```typescript
// 구조 분해로 간결하게 사용
const { data: pagination } = response.data;
const items = pagination.data;
const { current_page, total, last_page, per_page } = pagination;
```

---

## 5. 설계 원칙

### 5.1 Laravel 기본 형식 유지

- 프레임워크 기본 동작 활용
- 추가 변환 로직 불필요
- 생태계 호환성 유지

### 5.2 일관성 우선

- 기존 API와 동일한 형식 유지
- 프론트엔드 코드 재사용
- 학습 비용 최소화

### 5.3 최대 페이지 크기 제한

- 서버 부하 방지
- 응답 시간 최적화
- DoS 공격 방어

---

## 6. 관련 문서

- [API 개발 규칙](./api-rules.md)