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 { backendFetchUpload } from '@/lib/http/backend-fetch';
import {
MAX_STREAM_UPLOAD_BYTES,
findFileModule,
} from '@/lib/domain/file-module';
/**
* 큰 파일 업로드 중계 — 브라우저 → 이 라우트 → 백엔드로 본문을 그대로 흘려보낸다.
*
* Server Action을 쓰지 않는 이유는 그 경로의 multipart가 10MB 부근에서 잘리기 때문이다
* (`MAX_ATTACHMENT_BYTES` 주석). 브라우저가 백엔드를 직접 부를 수는 없다 — 토큰이 httpOnly
* 세션 안에만 있어서 여기서 붙여 준다.
*/
const UPLOAD_PATH = '/api/v1/common/file/upload';
function fail(status: number, message: string): Response {
return Response.json({ message }, { status });
}
export async function POST(request: Request) {
await verifySession();
const moduleId = new URL(request.url).searchParams.get('module') ?? '';
const storage = findFileModule(moduleId);
if (!storage) {
return fail(400, '알 수 없는 저장소입니다.');
}
const contentType = request.headers.get('content-type') ?? '';
if (!contentType.startsWith('multipart/form-data')) {
return fail(400, '요청 형식이 올바르지 않습니다.');
}
const declaredLength = Number(request.headers.get('content-length') ?? '');
if (Number.isFinite(declaredLength) && declaredLength > MAX_STREAM_UPLOAD_BYTES) {
return fail(
413,
`${Math.floor(MAX_STREAM_UPLOAD_BYTES / 1_000_000)}MB 이하만 올릴 수 있습니다.`
);
}
if (!request.body) {
return fail(400, '파일이 없습니다.');
}
const accessToken = await getSessionAccessToken();
const result = await backendFetchUpload<unknown>(
`${UPLOAD_PATH}/${storage.id}`,
{
body: request.body,
contentType,
accessToken: accessToken ?? undefined,
}
);
if (!result.ok) {
return fail(502, result.message);
}
if (typeof result.data !== 'string' || result.data === '') {
return fail(502, '업로드 응답에 파일 식별자가 없습니다.');
}
return Response.json({ id: result.data });
}