Files
sam-docs/changes/20260318_subscription_export_frontend_guide.md

7.4 KiB

[프론트엔드] 구독관리 내보내기 API 변경 안내

날짜: 2026-03-18 작성자: Claude Code 대상: React 프론트엔드 개발자 배포: 개발서버 반영 완료


1. 변경 배경

구독관리 페이지의 "자료 내보내기" 버튼 클릭 시 백엔드에서 400 (이미 진행 중인 내보내기가 있습니다) 오류가 반복 발생했다.

원인: 비동기 처리 Job이 미구현 상태로, DataExport 레코드가 pending에서 영원히 변경되지 않아 후속 요청이 차단되었다.

수정: 비동기 → 동기 처리로 전환. POST /export 호출 시 즉시 Excel 파일이 생성되고, 응답에 completed 상태와 파일 정보가 포함된다.


2. API 변경 사항

2.1 변경된 엔드포인트

Method Path 변경 내용
POST /api/v1/subscriptions/export 응답에 status: 'completed' + 파일 정보 포함 (기존: status: 'pending')

2.2 신규 엔드포인트

Method Path 설명
GET /api/v1/subscriptions/export/{id}/download 생성된 Excel 파일 다운로드

2.3 POST /export 응답 비교

기존 응답 (status: pending, 파일 없음):

{
  "success": true,
  "message": "내보내기 요청이 접수되었습니다.",
  "data": {
    "id": 1,
    "status": "pending",
    "file_path": null,
    "file_name": null,
    "file_size": null
  }
}

변경 후 응답 (status: completed, 파일 즉시 생성):

{
  "success": true,
  "message": "내보내기 요청이 접수되었습니다.",
  "data": {
    "id": 2,
    "status": "completed",
    "export_type": "all",
    "file_path": "exports/subscriptions_287_20260318_141023.xlsx",
    "file_name": "subscriptions_287_20260318_141023.xlsx",
    "file_size": 4523,
    "started_at": "2026-03-18T14:10:23.000000Z",
    "completed_at": "2026-03-18T14:10:23.000000Z"
  }
}

실패 시 status: 'failed'error_message가 포함된다.


3. 프론트엔드 수정 가이드

3.1 현재 코드 (수정 대상)

파일: react/src/components/settings/SubscriptionManagement/actions.ts

// 현재: POST 후 toast만 표시
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: '내보내기 요청에 실패했습니다.',
  });
}

파일: react/src/components/settings/SubscriptionManagement/SubscriptionClient.tsx

// 현재: "완료되면 알림을 보내드립니다" (실제로 알림 없음)
const handleExportData = useCallback(async () => {
  setIsExporting(true);
  try {
    const result = await requestDataExport('all');
    if (result.success) {
      toast.success('내보내기 요청이 등록되었습니다. 완료되면 알림을 보내드립니다.');
    } else {
      toast.error(result.error || '내보내기 요청에 실패했습니다.');
    }
  } catch (_error) {
    toast.error('서버 오류가 발생했습니다.');
  } finally {
    setIsExporting(false);
  }
}, []);

3.2 수정 방향

POST 응답에서 id를 받아 즉시 다운로드 URL로 리다이렉트하는 방식으로 변경한다.

actions.ts 수정

// ===== 데이터 내보내기 요청 =====
export async function requestDataExport(
  exportType: string = 'all'
): Promise<ActionResult<{ id: number; status: string; file_name: string | null }>> {
  return executeServerAction({
    url: `${API_URL}/api/v1/subscriptions/export`,
    method: 'POST',
    body: { export_type: exportType },
    transform: (data: { id: number; status: string; file_name: string | null }) => ({
      id: data.id,
      status: data.status,
      file_name: data.file_name,
    }),
    errorMessage: '내보내기 요청에 실패했습니다.',
  });
}

// ===== 내보내기 파일 다운로드 URL 생성 =====
export function getExportDownloadUrl(exportId: number): string {
  return `${API_URL}/api/v1/subscriptions/export/${exportId}/download`;
}

SubscriptionClient.tsx 수정

const handleExportData = useCallback(async () => {
  setIsExporting(true);
  try {
    const result = await requestDataExport('all');
    if (result.success && result.data) {
      if (result.data.status === 'completed') {
        // 즉시 다운로드
        const downloadUrl = getExportDownloadUrl(result.data.id);
        window.open(downloadUrl, '_blank');
        toast.success('Excel 파일이 다운로드됩니다.');
      } else if (result.data.status === 'failed') {
        toast.error('내보내기 처리 중 오류가 발생했습니다.');
      }
    } else {
      toast.error(result.error || '내보내기 요청에 실패했습니다.');
    }
  } catch (_error) {
    toast.error('서버 오류가 발생했습니다.');
  } finally {
    setIsExporting(false);
  }
}, []);

참고: 다운로드 URL은 인증이 필요한 API이므로, window.open 대신 fetch + blob 패턴이 필요할 수 있다. 기존 프로젝트의 파일 다운로드 패턴을 확인하여 적용한다.

3.3 인증이 필요한 경우 (fetch + blob 패턴)

API가 auth:sanctum 미들웨어 아래에 있으므로, 토큰 전달이 필요하다면:

// actions.ts에 다운로드 함수 추가
export async function downloadExportFile(exportId: number): Promise<void> {
  const response = await fetch(
    `${API_URL}/api/v1/subscriptions/export/${exportId}/download`,
    {
      headers: {
        'Authorization': `Bearer ${token}`,  // 기존 인증 헤더 패턴 참고
        'X-API-KEY': apiKey,
      },
    }
  );

  if (!response.ok) throw new Error('다운로드 실패');

  const blob = await response.blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `구독관리_${new Date().toISOString().slice(0, 10)}.xlsx`;
  a.click();
  URL.revokeObjectURL(url);
}

4. Excel 파일 내용

생성되는 Excel 파일의 컬럼 구조:

No 요금제 요금제 코드 월 요금 결제주기 시작일 종료일 상태 취소일 취소 사유
1 스탠다드 STD 50,000 월간 2026-01-01 2026-02-01 활성 - -

5. 테스트 체크리스트

  • "자료 내보내기" 버튼 클릭 → Excel 파일 다운로드 확인
  • 다운로드된 파일 열기 → 데이터 정상 확인
  • 연속 클릭 시 중복 차단 메시지 없이 재다운로드 가능 확인
  • 구독 데이터 없는 테넌트에서 빈 Excel 생성 확인
  • 개발서버 URL: https://dev.codebridge-x.com → 설정 → 구독관리

6. 관련 파일

위치 파일 설명
React src/components/settings/SubscriptionManagement/actions.ts 서버 액션 (수정 대상)
React src/components/settings/SubscriptionManagement/SubscriptionClient.tsx 컴포넌트 (수정 대상)
API app/Http/Controllers/Api/V1/SubscriptionController.php 컨트롤러
API app/Services/SubscriptionService.php 서비스 (동기 처리 로직)

최종 업데이트: 2026-03-18