feat(WEB): API 인프라 리팩토링, CEO 대시보드 현황판 개선 및 문서 시스템 강화
- API: fetch-wrapper/proxy/refresh-token 리팩토링, authenticated-fetch 신규 추가 - CEO 대시보드: EnhancedSections 현황판 기능 개선, dashboard transformers/types 확장 - 문서 시스템: ApprovalLine/DocumentHeader/DocumentToolbar/DocumentViewer 개선 - 작업지시서: 검사보고서/작업일지 문서 컴포넌트 개선 (벤딩/스크린/슬랫) - 레이아웃: Sidebar/AuthenticatedLayout 수정 - 작업자화면: WorkerScreen 수정 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ export {
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { AUTH_CONFIG } from './auth/auth-config';
|
||||
import { refreshAccessToken } from './refresh-token';
|
||||
import { authenticatedFetch } from './authenticated-fetch';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
/**
|
||||
@@ -34,15 +34,14 @@ import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
* 특징:
|
||||
* - 쿠키에서 access_token 자동 읽기
|
||||
* - X-API-KEY + Bearer 토큰 자동 포함
|
||||
* - 401 발생 시 토큰 자동 갱신 후 재시도
|
||||
* - 401 발생 시 authenticatedFetch 게이트웨이를 통한 자동 갱신
|
||||
*/
|
||||
class ServerApiClient {
|
||||
private baseURL: string;
|
||||
private apiKey: string;
|
||||
|
||||
constructor() {
|
||||
// API URL에 /api/v1 prefix 자동 추가
|
||||
const apiUrl = AUTH_CONFIG.apiUrl.replace(/\/$/, ''); // trailing slash 제거
|
||||
const apiUrl = AUTH_CONFIG.apiUrl.replace(/\/$/, '');
|
||||
this.baseURL = `${apiUrl}/api/v1`;
|
||||
this.apiKey = process.env.API_KEY || '';
|
||||
}
|
||||
@@ -115,7 +114,7 @@ class ServerApiClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 요청 실행 (토큰 자동 갱신 포함)
|
||||
* HTTP 요청 실행 (authenticatedFetch 게이트웨이를 통한 자동 갱신)
|
||||
*/
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
@@ -127,47 +126,30 @@ class ServerApiClient {
|
||||
const headers = await this.getAuthHeaders();
|
||||
|
||||
const url = `${this.baseURL}${endpoint}`;
|
||||
let response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...headers,
|
||||
...options?.headers,
|
||||
|
||||
// authenticatedFetch 게이트웨이로 요청 실행
|
||||
// skipAuthRetry=true면 refreshToken을 넘기지 않아 401 시 갱신 안 함
|
||||
const { response, newTokens, authFailed } = await authenticatedFetch(
|
||||
url,
|
||||
{
|
||||
...options,
|
||||
headers: {
|
||||
...headers,
|
||||
...options?.headers,
|
||||
},
|
||||
cache: 'no-store',
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
options?.skipAuthRetry ? undefined : refreshToken,
|
||||
'ServerApiClient'
|
||||
);
|
||||
|
||||
// 401 발생 시 토큰 갱신 후 재시도
|
||||
if (response.status === 401 && !options?.skipAuthRetry && refreshToken) {
|
||||
console.log('🔄 [ServerApiClient] 401 발생, 토큰 갱신 시도...');
|
||||
// 새 토큰 → 쿠키 저장
|
||||
if (newTokens) {
|
||||
await this.setNewTokenCookies(newTokens);
|
||||
}
|
||||
|
||||
const refreshResult = await refreshAccessToken(refreshToken, 'ServerApiClient');
|
||||
|
||||
if (refreshResult.success && refreshResult.accessToken) {
|
||||
console.log('✅ [ServerApiClient] 토큰 갱신 성공, 재시도...');
|
||||
await this.setNewTokenCookies(refreshResult);
|
||||
|
||||
const newHeaders = await this.getAuthHeaders(refreshResult.accessToken);
|
||||
response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...newHeaders,
|
||||
...options?.headers,
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
console.warn('🔴 [ServerApiClient] 재시도 실패, 로그인 리다이렉트');
|
||||
await this.clearTokenCookies();
|
||||
redirect('/login');
|
||||
}
|
||||
} else {
|
||||
console.warn('🔴 [ServerApiClient] 토큰 갱신 실패, 로그인 리다이렉트');
|
||||
await this.clearTokenCookies();
|
||||
redirect('/login');
|
||||
}
|
||||
} else if (response.status === 401 && !options?.skipAuthRetry) {
|
||||
console.warn('🔴 [ServerApiClient] 401 (refresh token 없음), 로그인 리다이렉트');
|
||||
// 인증 실패 → 쿠키 삭제 + 리다이렉트
|
||||
if (authFailed && !options?.skipAuthRetry) {
|
||||
await this.clearTokenCookies();
|
||||
redirect('/login');
|
||||
}
|
||||
@@ -247,4 +229,4 @@ class ServerApiClient {
|
||||
}
|
||||
|
||||
// 서버 액션용 API 클라이언트 인스턴스
|
||||
export const apiClient = new ServerApiClient();
|
||||
export const apiClient = new ServerApiClient();
|
||||
|
||||
Reference in New Issue
Block a user