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>
This commit is contained in:
@@ -1,24 +1,8 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Process, ProcessFormData, ClassificationRule } from '@/types/process';
|
||||
|
||||
// ============================================================================
|
||||
// API 헤더 생성
|
||||
// ============================================================================
|
||||
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// API 타입 정의
|
||||
// ============================================================================
|
||||
@@ -144,9 +128,8 @@ export async function getProcessList(params?: {
|
||||
q?: string;
|
||||
status?: string;
|
||||
process_type?: string;
|
||||
}): Promise<{ success: boolean; data?: { items: Process[]; total: number; page: number; totalPages: number }; error?: string }> {
|
||||
}): Promise<{ success: boolean; data?: { items: Process[]; total: number; page: number; totalPages: number }; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
@@ -155,11 +138,19 @@ export async function getProcessList(params?: {
|
||||
if (params?.status) searchParams.set('status', params.status);
|
||||
if (params?.process_type) searchParams.set('process_type', params.process_type);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.API_URL}/v1/processes?${searchParams.toString()}`,
|
||||
{ method: 'GET', headers, cache: 'no-store' }
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '목록 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<PaginatedResponse<ApiProcess>> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -184,16 +175,21 @@ export async function getProcessList(params?: {
|
||||
/**
|
||||
* 공정 상세 조회
|
||||
*/
|
||||
export async function getProcessById(id: string): Promise<{ success: boolean; data?: Process; error?: string }> {
|
||||
export async function getProcessById(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiProcess> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -210,17 +206,23 @@ export async function getProcessById(id: string): Promise<{ success: boolean; da
|
||||
/**
|
||||
* 공정 생성
|
||||
*/
|
||||
export async function createProcess(data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string }> {
|
||||
export async function createProcess(data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '등록에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiProcess> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -237,17 +239,23 @@ export async function createProcess(data: ProcessFormData): Promise<{ success: b
|
||||
/**
|
||||
* 공정 수정
|
||||
*/
|
||||
export async function updateProcess(id: string, data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string }> {
|
||||
export async function updateProcess(id: string, data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiProcess> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -264,15 +272,20 @@ export async function updateProcess(id: string, data: ProcessFormData): Promise<
|
||||
/**
|
||||
* 공정 삭제
|
||||
*/
|
||||
export async function deleteProcess(id: string): Promise<{ success: boolean; error?: string }> {
|
||||
export async function deleteProcess(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<null> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -289,16 +302,21 @@ export async function deleteProcess(id: string): Promise<{ success: boolean; err
|
||||
/**
|
||||
* 공정 일괄 삭제
|
||||
*/
|
||||
export async function deleteProcesses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string }> {
|
||||
export async function deleteProcesses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes`, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
body: JSON.stringify({ ids: ids.map((id) => parseInt(id, 10)) }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '일괄 삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<{ deleted_count: number }> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -315,15 +333,20 @@ export async function deleteProcesses(ids: string[]): Promise<{ success: boolean
|
||||
/**
|
||||
* 공정 상태 토글
|
||||
*/
|
||||
export async function toggleProcessActive(id: string): Promise<{ success: boolean; data?: Process; error?: string }> {
|
||||
export async function toggleProcessActive(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/${id}/toggle`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/${id}/toggle`, {
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiProcess> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -344,16 +367,22 @@ export async function getProcessOptions(): Promise<{
|
||||
success: boolean;
|
||||
data?: Array<{ id: string; processCode: string; processName: string; processType: string; department: string }>;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/options`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/options`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '옵션 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<Array<{ id: number; process_code: string; process_name: string; process_type: string; department: string }>> =
|
||||
await response.json();
|
||||
|
||||
@@ -384,16 +413,22 @@ export async function getProcessStats(): Promise<{
|
||||
success: boolean;
|
||||
data?: { total: number; active: number; inactive: number; byType: Record<string, number> };
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(`${process.env.API_URL}/v1/processes/stats`, {
|
||||
const { response, error } = await serverFetch(`${process.env.API_URL}/v1/processes/stats`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '통계 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<{ total: number; active: number; inactive: number; by_type: Record<string, number> }> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
Reference in New Issue
Block a user