refactor(WEB): Server Action 공통화 및 보안 강화
- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일) - sanitize 유틸 추가 (XSS 방지) - middleware CSP 헤더 추가 및 Open Redirect 방지 - 프록시 라우트 로깅 개발환경 한정으로 변경 - 프로덕션 불필요 console.log 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { LeavePolicySettings } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
// ===== API 응답 타입 =====
|
||||
interface LeavePolicyApi {
|
||||
id: number;
|
||||
@@ -40,7 +41,6 @@ function transformLeavePolicy(data: LeavePolicyApi): LeavePolicySettings {
|
||||
// ===== Frontend → API 변환 =====
|
||||
function transformToApi(data: Partial<LeavePolicySettings>): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
if (data.standardType !== undefined) result.standard_type = data.standardType;
|
||||
if (data.fiscalStartMonth !== undefined) result.fiscal_start_month = data.fiscalStartMonth;
|
||||
if (data.fiscalStartDay !== undefined) result.fiscal_start_day = data.fiscalStartDay;
|
||||
@@ -50,119 +50,27 @@ function transformToApi(data: Partial<LeavePolicySettings>): Record<string, unkn
|
||||
if (data.carryOverEnabled !== undefined) result.carry_over_enabled = data.carryOverEnabled;
|
||||
if (data.carryOverMaxDays !== undefined) result.carry_over_max_days = data.carryOverMaxDays;
|
||||
if (data.carryOverExpiryMonths !== undefined) result.carry_over_expiry_months = data.carryOverExpiryMonths;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===== 휴가 정책 조회 =====
|
||||
export async function getLeavePolicy(): Promise<{
|
||||
success: boolean;
|
||||
data?: LeavePolicySettings;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/leave-policy`;
|
||||
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[LeavePolicyActions] GET error:', response?.status);
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.message || '휴가 정책 조회에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const transformedData = transformLeavePolicy(result.data);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transformedData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[LeavePolicyActions] getLeavePolicy error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: '서버 오류가 발생했습니다.',
|
||||
};
|
||||
}
|
||||
export async function getLeavePolicy(): Promise<ActionResult<LeavePolicySettings>> {
|
||||
return executeServerAction({
|
||||
url: `${API_URL}/api/v1/leave-policy`,
|
||||
transform: (data: LeavePolicyApi) => transformLeavePolicy(data),
|
||||
errorMessage: '휴가 정책 조회에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 휴가 정책 업데이트 =====
|
||||
export async function updateLeavePolicy(data: Partial<LeavePolicySettings>): Promise<{
|
||||
success: boolean;
|
||||
data?: LeavePolicySettings;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/leave-policy`;
|
||||
const apiData = transformToApi(data);
|
||||
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(apiData),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[LeavePolicyActions] PUT error:', response?.status);
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.message || '휴가 정책 저장에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const transformedData = transformLeavePolicy(result.data);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transformedData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[LeavePolicyActions] updateLeavePolicy error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: '서버 오류가 발생했습니다.',
|
||||
};
|
||||
}
|
||||
export async function updateLeavePolicy(
|
||||
data: Partial<LeavePolicySettings>
|
||||
): Promise<ActionResult<LeavePolicySettings>> {
|
||||
return executeServerAction({
|
||||
url: `${API_URL}/api/v1/leave-policy`,
|
||||
method: 'PUT',
|
||||
body: transformToApi(data),
|
||||
transform: (data: LeavePolicyApi) => transformLeavePolicy(data),
|
||||
errorMessage: '휴가 정책 저장에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user