Files
sam-react-prod/claudedocs/api/[IMPL-2025-12-30] fetch-wrapper-migration.md
byeongcheolryu d38b1242d7 feat: fetchWrapper 마이그레이션 및 토큰 리프레시 캐싱 구현
- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션
- 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts)
- ApiErrorContext 추가로 전역 에러 처리 개선
- HR EmployeeForm 컴포넌트 개선
- 참조함(ReferenceBox) 기능 수정
- juil 테스트 URL 페이지 추가
- claudedocs 문서 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 17:00:18 +09:00

263 lines
8.2 KiB
Markdown

# Fetch Wrapper Migration Checklist
**생성일**: 2025-12-30
**목적**: 모든 Server Actions의 API 통신을 `serverFetch`로 중앙화
## 목적 및 배경
### 왜 fetch-wrapper를 도입했는가?
1. **중앙화된 인증 처리**
- 401 에러(세션 만료) 발생 시 → 로그인 페이지 리다이렉트
- 모든 API 호출에서 **일관된 인증 검증**
2. **개발 규칙 표준화**
- 새 작업자도 `serverFetch` 사용하면 자동으로 인증 검증 적용
- 개별 파일마다 인증 로직 구현 불필요
3. **유지보수성 향상**
- 인증 로직 변경 시 **`fetch-wrapper.ts` 한 파일만** 수정
- 403, 네트워크 에러 등 공통 에러 처리도 중앙화
---
## 마이그레이션 패턴
### Before (기존 패턴)
```typescript
import { cookies } from 'next/headers';
async function getApiHeaders(): Promise<HeadersInit> {
const cookieStore = await cookies();
const token = cookieStore.get('access_token')?.value;
return {
'Authorization': token ? `Bearer ${token}` : '',
// ...
};
}
export async function getSomething() {
const headers = await getApiHeaders();
const response = await fetch(url, { headers });
// 401 처리 없음!
}
```
### After (새 패턴)
```typescript
import { serverFetch } from '@/lib/api/fetch-wrapper';
export async function getSomething() {
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error) {
// 401/403/네트워크 에러 자동 처리됨
return { success: false, error: error.message };
}
const data = await response.json();
// ...
}
```
---
## 마이그레이션 체크리스트
### Accounting 도메인 (12 files) ✅ 완료
- [x] `SalesManagement/actions.ts`
- [x] `VendorManagement/actions.ts`
- [x] `PurchaseManagement/actions.ts`
- [x] `DepositManagement/actions.ts`
- [x] `WithdrawalManagement/actions.ts`
- [x] `VendorLedger/actions.ts`
- [x] `ReceivablesStatus/actions.ts`
- [x] `ExpectedExpenseManagement/actions.ts`
- [x] `CardTransactionInquiry/actions.ts`
- [x] `DailyReport/actions.ts`
- [x] `BadDebtCollection/actions.ts`
- [x] `BankTransactionInquiry/actions.ts`
### HR 도메인 (6 files) ✅ 완료
- [x] `EmployeeManagement/actions.ts` ✅ (이미 마이그레이션됨)
- [x] `VacationManagement/actions.ts`
- [x] `SalaryManagement/actions.ts`
- [x] `CardManagement/actions.ts`
- [x] `DepartmentManagement/actions.ts`
- [x] `AttendanceManagement/actions.ts`
### Approval 도메인 (4 files) ✅ 완료
- [x] `ApprovalBox/actions.ts`
- [x] `DraftBox/actions.ts`
- [x] `ReferenceBox/actions.ts`
- [x] `DocumentCreate/actions.ts` (파일 업로드는 직접 fetch 유지)
### Production 도메인 (4 files) ✅ 완료
- [x] `WorkerScreen/actions.ts`
- [x] `WorkOrders/actions.ts`
- [x] `WorkResults/actions.ts`
- [x] `ProductionDashboard/actions.ts`
### Settings 도메인 (10 files) ✅ 완료
- [x] `WorkScheduleManagement/actions.ts`
- [x] `SubscriptionManagement/actions.ts`
- [x] `PopupManagement/actions.ts`
- [x] `PaymentHistoryManagement/actions.ts`
- [x] `LeavePolicyManagement/actions.ts`
- [x] `NotificationSettings/actions.ts`
- [x] `AttendanceSettingsManagement/actions.ts`
- [x] `CompanyInfoManagement/actions.ts`
- [x] `AccountInfoManagement/actions.ts`
- [x] `AccountManagement/actions.ts`
### 기타 도메인 (12 files) ✅ 완료
- [x] `process-management/actions.ts`
- [x] `outbound/ShipmentManagement/actions.ts`
- [x] `material/StockStatus/actions.ts`
- [x] `material/ReceivingManagement/actions.ts`
- [x] `customer-center/shared/actions.ts`
- [x] `board/actions.ts`
- [x] `reports/actions.ts`
- [x] `quotes/actions.ts`
- [x] `board/BoardManagement/actions.ts`
- [x] `attendance/actions.ts`
- [x] `pricing/actions.ts`
- [x] `quality/InspectionManagement/actions.ts`
---
## 진행 상황
| 도메인 | 파일 수 | 완료 | 상태 |
|--------|---------|------|------|
| Accounting | 12 | 12 | ✅ 완료 |
| HR | 6 | 6 | ✅ 완료 |
| Approval | 4 | 4 | ✅ 완료 |
| Production | 4 | 4 | ✅ 완료 |
| Settings | 10 | 10 | ✅ 완료 |
| 기타 | 12 | 12 | ✅ 완료 |
| **총계** | **48** | **48** | **100%** ✅ |
### 완료된 파일 (완전 마이그레이션)
**Accounting 도메인 (12/12)**
- [x] `SalesManagement/actions.ts`
- [x] `VendorManagement/actions.ts`
- [x] `PurchaseManagement/actions.ts`
- [x] `DepositManagement/actions.ts`
- [x] `WithdrawalManagement/actions.ts`
- [x] `VendorLedger/actions.ts`
- [x] `ReceivablesStatus/actions.ts`
- [x] `ExpectedExpenseManagement/actions.ts`
- [x] `CardTransactionInquiry/actions.ts`
- [x] `DailyReport/actions.ts`
- [x] `BadDebtCollection/actions.ts`
- [x] `BankTransactionInquiry/actions.ts`
**HR 도메인 (6/6)**
- [x] `EmployeeManagement/actions.ts` (이미 마이그레이션됨)
- [x] `VacationManagement/actions.ts`
- [x] `SalaryManagement/actions.ts`
- [x] `CardManagement/actions.ts`
- [x] `DepartmentManagement/actions.ts`
- [x] `AttendanceManagement/actions.ts`
**Approval 도메인 (4/4)**
- [x] `ApprovalBox/actions.ts`
- [x] `DraftBox/actions.ts`
- [x] `ReferenceBox/actions.ts`
- [x] `DocumentCreate/actions.ts` (파일 업로드는 직접 fetch 유지)
**Production 도메인 (4/4)**
- [x] `WorkerScreen/actions.ts`
- [x] `WorkOrders/actions.ts`
- [x] `WorkResults/actions.ts`
- [x] `ProductionDashboard/actions.ts`
**Settings 도메인 (10/10)**
- [x] `WorkScheduleManagement/actions.ts`
- [x] `SubscriptionManagement/actions.ts`
- [x] `PopupManagement/actions.ts`
- [x] `PaymentHistoryManagement/actions.ts`
- [x] `LeavePolicyManagement/actions.ts`
- [x] `NotificationSettings/actions.ts`
- [x] `AttendanceSettingsManagement/actions.ts`
- [x] `CompanyInfoManagement/actions.ts`
- [x] `AccountInfoManagement/actions.ts`
- [x] `AccountManagement/actions.ts`
**기타 도메인 (12/12)** ✅ 완료
- [x] `process-management/actions.ts`
- [x] `outbound/ShipmentManagement/actions.ts`
- [x] `material/StockStatus/actions.ts`
- [x] `material/ReceivingManagement/actions.ts`
- [x] `customer-center/shared/actions.ts`
- [x] `board/actions.ts`
- [x] `reports/actions.ts`
- [x] `quotes/actions.ts`
- [x] `board/BoardManagement/actions.ts`
- [x] `attendance/actions.ts`
- [x] `pricing/actions.ts`
- [x] `quality/InspectionManagement/actions.ts`
---
## 참조 파일
- **fetch-wrapper**: `src/lib/api/fetch-wrapper.ts`
- **errors**: `src/lib/api/errors.ts`
- **완료된 예시**: `src/components/accounting/BillManagement/actions.ts` (참고용)
---
## 주의사항
1. **기존 `getApiHeaders()` 함수 제거** - `serverFetch`가 헤더 자동 생성
2. **`import { cookies } from 'next/headers'` 제거** - wrapper에서 처리
3. **에러 응답 구조 맞추기** - `{ success: false, error: string }` 형태 유지
4. **빌드 테스트 필수** - 마이그레이션 후 `npm run build` 확인
---
## 🔜 추가 작업 (마이그레이션 완료 후)
### Phase 2: 리프레시 토큰 자동 갱신 적용
**현재 문제:**
- access_token 만료 시 (약 2시간) 바로 로그인 리다이렉트됨
- refresh_token (7일)을 사용한 자동 갱신 로직이 호출되지 않음
- 결과: 40분~2시간 후 세션 만료 → 재로그인 필요
**목표:**
- 401 발생 시 → 리프레시 토큰으로 갱신 시도 → 성공 시 재시도
- 7일간 세션 유지 (refresh_token 만료 시에만 재로그인)
**적용 범위:**
| 영역 | 적용 위치 | 작업 |
|------|----------|------|
| Server Actions | `fetch-wrapper.ts` | 401 시 리프레시 후 재시도 로직 추가 |
| 품목관리 | `ItemListClient.tsx` 등 | 클라이언트 fetch에 리프레시 로직 추가 |
| 품목기준관리 | 관련 컴포넌트들 | 클라이언트 fetch에 리프레시 로직 추가 |
**관련 파일:**
- `src/lib/auth/token-refresh.ts` - 리프레시 함수 (이미 존재)
- `src/app/api/auth/refresh/route.ts` - 리프레시 API (이미 존재)
**예상 구현:**
```typescript
// fetch-wrapper.ts 401 처리 부분
if (response.status === 401 && !options?.skipAuthCheck) {
// 1. 리프레시 토큰으로 갱신 시도
const refreshResult = await refreshTokenServer(refreshToken);
if (refreshResult.success) {
// 2. 새 토큰으로 원래 요청 재시도
return serverFetch(url, { ...options, skipAuthCheck: true });
}
// 3. 리프레시도 실패하면 로그인 리다이렉트
redirect('/login');
}
```