Files
sam-react-prod/src/components/settings/SubscriptionManagement/actions.ts
유병철 55e0791e16 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>
2026-02-09 16:14:06 +09:00

85 lines
2.6 KiB
TypeScript

'use server';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
import type { SubscriptionApiData, UsageApiData, SubscriptionInfo } from './types';
import { transformApiToFrontend } from './utils';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// ===== 현재 활성 구독 조회 =====
export async function getCurrentSubscription(): Promise<ActionResult<SubscriptionApiData>> {
return executeServerAction({
url: `${API_URL}/api/v1/subscriptions/current`,
errorMessage: '구독 정보를 불러오는데 실패했습니다.',
});
}
// ===== 사용량 조회 =====
export async function getUsage(): Promise<ActionResult<UsageApiData>> {
return executeServerAction({
url: `${API_URL}/api/v1/subscriptions/usage`,
errorMessage: '사용량 정보를 불러오는데 실패했습니다.',
});
}
// ===== 구독 취소 =====
export async function cancelSubscription(
id: number,
reason?: string
): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/subscriptions/${id}/cancel`,
method: 'POST',
body: { reason },
errorMessage: '구독 취소에 실패했습니다.',
});
}
// ===== 데이터 내보내기 요청 =====
export async function requestDataExport(
exportType: string = 'all'
): Promise<ActionResult<{ id: number; status: string }>> {
return executeServerAction({
url: `${API_URL}/api/v1/subscriptions/export`,
method: 'POST',
body: { export_type: exportType },
transform: (data: { id: number; status: string }) => ({ id: data.id, status: data.status }),
errorMessage: '내보내기 요청에 실패했습니다.',
});
}
// ===== 통합 데이터 조회 (현재 구독 + 사용량) =====
export async function getSubscriptionData(): Promise<{
success: boolean;
data: SubscriptionInfo | null;
error?: string;
}> {
try {
const [subscriptionResult, usageResult] = await Promise.all([
getCurrentSubscription(),
getUsage(),
]);
if (!subscriptionResult.success && !usageResult.success) {
return {
success: false,
data: null,
error: subscriptionResult.error || usageResult.error,
};
}
const data = transformApiToFrontend(
subscriptionResult.data ?? null,
usageResult.data ?? null
);
return { success: true, data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[SubscriptionActions] getSubscriptionData error:', error);
return { success: false, data: null, error: '서버 오류가 발생했습니다.' };
}
}