import { createError, readMultipartFormData } from 'h3' import { requireAdminSession } from '../../../../utils/admin-auth' import { importPostsFromExportZip } from '../../../../repositories/post-import-repository' const MAX_IMPORT_ZIP_BYTES = 300 * 1024 * 1024 /** * 관리자 게시물 Import API * @param {import('h3').H3Event} event - 요청 이벤트 * @returns {Promise<{ importedCount: number, assetCount: number, warningCount: number, warnings: Array, posts: Array }>} Import 결과 */ export default defineEventHandler(async (event) => { const adminSession = requireAdminSession(event) const formData = await readMultipartFormData(event) const file = (formData || []).find((part) => part.name === 'file' && part.filename) if (!file) { throw createError({ statusCode: 400, message: 'Import할 ZIP 파일을 선택해 주세요.' }) } if (!String(file.filename || '').toLowerCase().endsWith('.zip')) { throw createError({ statusCode: 400, message: 'ZIP 파일만 Import할 수 있습니다.' }) } if (!file.data?.length) { throw createError({ statusCode: 400, message: '비어 있는 ZIP 파일은 Import할 수 없습니다.' }) } if (file.data.length > MAX_IMPORT_ZIP_BYTES) { throw createError({ statusCode: 413, message: 'Import ZIP 파일은 최대 300MB까지 처리할 수 있습니다.' }) } try { return await importPostsFromExportZip({ zipBuffer: file.data, authorId: adminSession.userId }) } catch (error) { if (error?.message === 'IMPORT_MARKDOWN_NOT_FOUND') { throw createError({ statusCode: 400, message: 'ZIP 안에서 Import할 Markdown 게시물을 찾지 못했습니다.' }) } if (error?.message === 'IMPORT_POST_LIMIT_EXCEEDED') { throw createError({ statusCode: 400, message: '한 번에 Import할 수 있는 게시물 수를 초과했습니다.' }) } if (error?.code === '23505') { throw createError({ statusCode: 409, message: '게시물 Import 중 중복 데이터가 감지되었습니다. 다시 시도해 주세요.' }) } throw createError({ statusCode: 500, message: '게시물 Import를 완료하지 못했습니다.' }) } })