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,8 +1,7 @@
|
||||
'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';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://sam.kr:8080';
|
||||
|
||||
@@ -49,7 +48,7 @@ function transformFromApi(data: ApiWorkSetting): WorkSettingFormData {
|
||||
return {
|
||||
workType: data.work_type,
|
||||
workDays: data.work_days || ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: data.start_time?.substring(0, 5) || '09:00', // HH:mm:ss → HH:mm
|
||||
workStartTime: data.start_time?.substring(0, 5) || '09:00',
|
||||
workEndTime: data.end_time?.substring(0, 5) || '18:00',
|
||||
weeklyWorkHours: data.standard_hours,
|
||||
weeklyOvertimeHours: data.overtime_hours,
|
||||
@@ -65,10 +64,9 @@ function transformFromApi(data: ApiWorkSetting): WorkSettingFormData {
|
||||
*/
|
||||
function transformToApi(data: Partial<WorkSettingFormData>): Record<string, unknown> {
|
||||
const apiData: Record<string, unknown> = {};
|
||||
|
||||
if (data.workType !== undefined) apiData.work_type = data.workType;
|
||||
if (data.workDays !== undefined) apiData.work_days = data.workDays;
|
||||
if (data.workStartTime !== undefined) apiData.start_time = `${data.workStartTime}:00`; // HH:mm → HH:mm:ss
|
||||
if (data.workStartTime !== undefined) apiData.start_time = `${data.workStartTime}:00`;
|
||||
if (data.workEndTime !== undefined) apiData.end_time = `${data.workEndTime}:00`;
|
||||
if (data.weeklyWorkHours !== undefined) apiData.standard_hours = data.weeklyWorkHours;
|
||||
if (data.weeklyOvertimeHours !== undefined) apiData.overtime_hours = data.weeklyOvertimeHours;
|
||||
@@ -76,7 +74,6 @@ function transformToApi(data: Partial<WorkSettingFormData>): Record<string, unkn
|
||||
if (data.breakMinutes !== undefined) apiData.break_minutes = data.breakMinutes;
|
||||
if (data.breakStartTime !== undefined) apiData.break_start = `${data.breakStartTime}:00`;
|
||||
if (data.breakEndTime !== undefined) apiData.break_end = `${data.breakEndTime}:00`;
|
||||
|
||||
return apiData;
|
||||
}
|
||||
|
||||
@@ -85,48 +82,12 @@ function transformToApi(data: Partial<WorkSettingFormData>): Record<string, unkn
|
||||
/**
|
||||
* 근무 설정 조회
|
||||
*/
|
||||
export async function getWorkSetting(): Promise<{
|
||||
success: boolean;
|
||||
data?: WorkSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('getWorkSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: '근무 설정을 불러오는데 실패했습니다.',
|
||||
};
|
||||
}
|
||||
export async function getWorkSetting(): Promise<ActionResult<WorkSettingFormData>> {
|
||||
return executeServerAction({
|
||||
url: `${API_BASE_URL}/api/v1/settings/work`,
|
||||
transform: (data: ApiWorkSetting) => transformFromApi(data),
|
||||
errorMessage: '근무 설정을 불러오는데 실패했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,46 +95,12 @@ export async function getWorkSetting(): Promise<{
|
||||
*/
|
||||
export async function updateWorkSetting(
|
||||
data: Partial<WorkSettingFormData>
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
data?: WorkSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(transformToApi(data)),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('updateWorkSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: '근무 설정 저장에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
): Promise<ActionResult<WorkSettingFormData>> {
|
||||
return executeServerAction({
|
||||
url: `${API_BASE_URL}/api/v1/settings/work`,
|
||||
method: 'PUT',
|
||||
body: transformToApi(data),
|
||||
transform: (data: ApiWorkSetting) => transformFromApi(data),
|
||||
errorMessage: '근무 설정 저장에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user