File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import { getSessionAccessToken, verifySession } from '@/lib/auth/dal';
import { backendFetchStream } from '@/lib/http/backend-fetch';
/**
* 관리자 회원 목록 엑셀 다운로드 — 백엔드가 만든 xlsx를 브라우저로 중계한다.
*
* 라우트 핸들러인 이유는 학생 목록(`students/excel/route.ts`)과 같다 — 브라우저는 백엔드를 직접
* 호출하지 않고, 백엔드 엑셀 API가 요구하는 ROLE_ADMIN 토큰은 httpOnly 세션 안에만 있다. 본문은
* 파싱하지 않고 업스트림 스트림을 그대로 흘려보낸다.
*
* 파일에 담기는 열은 백엔드가 정한다 — 번호·이름·아이디·휴대폰번호·이메일·생성일
* (`MngrAdminApiController.downloadExcel`). 화면의 "역할" 열은 파일에 없다.
*/
const ADMIN_MEMBER_EXCEL_PATH = '/api/v1/mngr/admin/excel/download';
/**
* 파일은 화면의 검색·페이징과 무관하게 **항상 전체**를 담는다(학생 목록과 같은 사양).
*
* 백엔드 엑셀 API가 목록과 같은 `selectPagination`을 그대로 쓰므로 페이징이 걸린다 — 파라미터를
* 생략하면 기본값(`recordCountPerPage=10`)이 적용돼 10건만 담긴 파일이 나온다. "전체" 모드가 따로
* 없어 충분히 큰 상한을 명시해 1페이지로 전부 받는다. 이 상한을 넘으면 파일이 조용히 잘린다.
*/
const EXCEL_ROW_LIMIT = 100_000;
/** 전체 행을 모아 워크북을 만드는 시간이 있어 일반 조회보다 넉넉히 잡는다. */
const EXCEL_TIMEOUT_MS = 60_000;
const DOWNLOAD_FAILED_MESSAGE =
'엑셀 파일을 내려받지 못했습니다. 잠시 후 다시 시도해 주세요.';
const XLSX_CONTENT_TYPE =
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export async function GET() {
// 라우트 핸들러는 UI를 거치지 않고 직접 호출될 수 있으므로 여기서 직접 인증을 확인한다
// (proxy의 쿠키 존재 확인은 낙관적 필터일 뿐이다 — 설계서 §8).
await verifySession();
const accessToken = await getSessionAccessToken();
const result = await backendFetchStream(ADMIN_MEMBER_EXCEL_PATH, {
query: { pageIndex: 1, recordCountPerPage: EXCEL_ROW_LIMIT },
accessToken: accessToken ?? undefined,
timeoutMs: EXCEL_TIMEOUT_MS,
});
if (!result.ok) {
// 실패 사유(코드·백엔드 메시지)는 backendFetchStream이 서버 콘솔에 남긴다.
return new Response(DOWNLOAD_FAILED_MESSAGE, {
status: 502,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
const upstream = result.data;
return new Response(upstream.body, {
status: 200,
headers: {
'Content-Type': upstream.headers.get('content-type') ?? XLSX_CONTENT_TYPE,
'Content-Disposition':
upstream.headers.get('content-disposition') ??
'attachment; filename="admins.xlsx"',
},
});
}