diff --git a/standards/README.md b/standards/README.md index 3b1dc13..7da04a1 100644 --- a/standards/README.md +++ b/standards/README.md @@ -12,6 +12,7 @@ | 문서 | 설명 | 필수 확인 시점 | |------|------|--------------| | [api-rules.md](api-rules.md) | API 개발 규칙 (Service-First, FormRequest, i18n) | API 개발 전 | +| [pagination-policy.md](pagination-policy.md) | 페이지네이션 정책 (응답 구조, 파라미터) | 목록 API 개발 전 | | [git-conventions.md](git-conventions.md) | Git 커밋 메시지, 브랜치 전략 | 커밋 전 | | [quality-checklist.md](quality-checklist.md) | 코드 품질 체크리스트 | PR 전 | diff --git a/standards/pagination-policy.md b/standards/pagination-policy.md new file mode 100644 index 0000000..80b95dc --- /dev/null +++ b/standards/pagination-policy.md @@ -0,0 +1,286 @@ +# SAM API 페이지네이션 정책 + +**작성일**: 2025-12-09 +**적용 범위**: 모든 목록 조회 API + +--- + +## 1. 응답 구조 + +### 1.1 표준 페이지네이션 응답 + +```json +{ + "success": true, + "message": "조회되었습니다.", + "data": [ + { "id": 1, "name": "항목1", ... }, + { "id": 2, "name": "항목2", ... } + ], + "pagination": { + "current_page": 1, + "per_page": 20, + "total": 100, + "last_page": 5, + "from": 1, + "to": 20 + } +} +``` + +### 1.2 응답 구조 설명 + +| 키 | 타입 | 설명 | +|----|------|------| +| `success` | boolean | 요청 성공 여부 | +| `message` | string | 응답 메시지 (i18n 키) | +| `data` | array | **데이터 배열** (바로 접근 가능) | +| `pagination` | object | **페이지네이션 정보** | + +### 1.3 pagination 객체 필드 + +| 필드 | 타입 | 설명 | +|------|------|------| +| `current_page` | int | 현재 페이지 번호 (1부터 시작) | +| `per_page` | int | 페이지당 항목 수 | +| `total` | int | 전체 항목 수 | +| `last_page` | int | 마지막 페이지 번호 | +| `from` | int\|null | 현재 페이지 첫 번째 항목 번호 | +| `to` | int\|null | 현재 페이지 마지막 항목 번호 | + +--- + +## 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(Request $request) +{ + $items = $this->service->getItems($request->all()); + + return ApiResponse::success([ + 'data' => $items->items(), + 'pagination' => [ + 'current_page' => $items->currentPage(), + 'per_page' => $items->perPage(), + 'total' => $items->total(), + 'last_page' => $items->lastPage(), + 'from' => $items->firstItem(), + 'to' => $items->lastItem(), + ], + ], __('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 Helper 함수 (권장) + +`ApiResponse` 클래스에 페이지네이션 헬퍼 추가: + +```php +// app/Responses/ApiResponse.php + +public static function paginated(LengthAwarePaginator $paginator, string $message = null): JsonResponse +{ + return self::success([ + 'data' => $paginator->items(), + 'pagination' => [ + 'current_page' => $paginator->currentPage(), + 'per_page' => $paginator->perPage(), + 'total' => $paginator->total(), + 'last_page' => $paginator->lastPage(), + 'from' => $paginator->firstItem(), + 'to' => $paginator->lastItem(), + ], + ], $message ?? __('message.fetched')); +} +``` + +**사용 예시:** +```php +public function index(Request $request) +{ + $items = $this->service->getItems($request->all()); + return ApiResponse::paginated($items); +} +``` + +--- + +## 4. 설계 원칙 + +### 4.1 data는 항상 배열 + +```json +// ✅ 올바른 형식 - data가 바로 배열 +{ + "data": [...] +} + +// ❌ 잘못된 형식 - data.data 중첩 +{ + "data": { + "data": [...] + } +} +``` + +### 4.2 pagination 분리 + +- 페이지네이션 정보는 `pagination` 객체로 명확히 분리 +- Laravel 기본 형식(first_page_url, links 등) 대신 필수 정보만 포함 +- 응답 경량화 및 프론트엔드 접근 편의성 확보 + +### 4.3 최대 페이지 크기 제한 + +```php +$size = min($params['size'] ?? 20, 100); // 최대 100개 +``` + +- 기본값: 20 +- 최대값: 100 +- 서버 부하 방지 및 응답 시간 최적화 + +--- + +## 5. 프론트엔드 사용 가이드 + +### 5.1 TypeScript 타입 정의 + +```typescript +interface PaginatedResponse { + success: boolean; + message: string; + data: T[]; + pagination: { + current_page: number; + per_page: number; + total: number; + last_page: number; + from: number | null; + to: number | null; + }; +} +``` + +### 5.2 React 사용 예시 + +```typescript +const fetchItems = async (page: number, size: number) => { + const response = await api.get>('/items', { + params: { page, size } + }); + + // 데이터 직접 접근 + const items = response.data.data; + + // 페이지네이션 정보 + const { current_page, total, last_page } = response.data.pagination; + + return { items, pagination: response.data.pagination }; +}; +``` + +--- + +## 6. 기존 API 마이그레이션 + +### 6.1 변경 전 (Laravel 기본) + +```json +{ + "success": true, + "data": { + "current_page": 1, + "data": [...], + "first_page_url": "http://...", + "from": 1, + "last_page": 5, + "links": [...], + "next_page_url": "http://...", + "path": "http://...", + "per_page": 20, + "prev_page_url": null, + "to": 20, + "total": 100 + } +} +``` + +### 6.2 변경 후 (SAM 표준) + +```json +{ + "success": true, + "message": "조회되었습니다.", + "data": [...], + "pagination": { + "current_page": 1, + "per_page": 20, + "total": 100, + "last_page": 5, + "from": 1, + "to": 20 + } +} +``` + +### 6.3 Breaking Changes + +| 항목 | 변경 전 | 변경 후 | +|------|---------|---------| +| 데이터 접근 | `response.data.data` | `response.data` | +| 페이지 정보 | `response.data.current_page` | `response.pagination.current_page` | +| URL 정보 | 포함 (first_page_url 등) | 제거 | +| links 배열 | 포함 | 제거 | + +--- + +## 7. 적용 API 목록 + +| API | 상태 | 비고 | +|-----|------|------| +| `GET /api/v1/items` | 🔧 예정 | Items API 통합 작업 시 적용 | +| `GET /api/v1/products` | ⏳ 대기 | | +| `GET /api/v1/materials` | ⏳ 대기 | | +| `GET /api/v1/pricing` | ⏳ 대기 | | +| `GET /api/v1/employees` | ⏳ 대기 | | + +--- + +## 8. 관련 문서 + +- [API 개발 규칙](./api-rules.md) +- [Items API 통합 계획](../plans/items-api-unified-plan.md) \ No newline at end of file