128 lines
3.6 KiB
JavaScript
128 lines
3.6 KiB
JavaScript
import { rm } from 'node:fs/promises'
|
|
import { createError } from 'h3'
|
|
import { requireAdminSession } from '../../../../utils/admin-auth'
|
|
import {
|
|
createPostImportJob,
|
|
createPostImportJobZipPath,
|
|
queuePostImportJobRun
|
|
} from '../../../../repositories/post-import-repository'
|
|
import { saveMultipartFileToDisk } from '../../../../utils/multipart-file-upload'
|
|
|
|
const MAX_IMPORT_ZIP_BYTES = 300 * 1024 * 1024
|
|
|
|
/**
|
|
* 관리자 게시물 Import API
|
|
* @param {import('h3').H3Event} event - 요청 이벤트
|
|
* @returns {Promise<Object>} 생성된 Import 작업
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const adminSession = requireAdminSession(event)
|
|
const zipFilePath = await createPostImportJobZipPath('upload.zip')
|
|
let shouldRemoveZip = true
|
|
|
|
try {
|
|
const uploaded = await saveMultipartFileToDisk(event, {
|
|
fieldName: 'file',
|
|
filePath: zipFilePath,
|
|
maxBytes: MAX_IMPORT_ZIP_BYTES
|
|
})
|
|
|
|
if (!String(uploaded.filename || '').toLowerCase().endsWith('.zip')) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'ZIP 파일만 Import할 수 있습니다.'
|
|
})
|
|
}
|
|
|
|
if (!uploaded.size) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '비어 있는 ZIP 파일은 Import할 수 없습니다.'
|
|
})
|
|
}
|
|
|
|
const job = await createPostImportJob({
|
|
zipFilePath,
|
|
requestedBy: adminSession.userId,
|
|
requestedEmail: adminSession.email,
|
|
originalFileName: uploaded.filename || 'upload.zip'
|
|
})
|
|
shouldRemoveZip = false
|
|
queuePostImportJobRun(job.id)
|
|
|
|
return job
|
|
} catch (error) {
|
|
if (error?.statusCode) {
|
|
throw error
|
|
}
|
|
|
|
if (error?.code === 'MULTIPART_FILE_TOO_LARGE') {
|
|
throw createError({
|
|
statusCode: 413,
|
|
message: 'Import ZIP 파일은 최대 300MB까지 처리할 수 있습니다.'
|
|
})
|
|
}
|
|
|
|
if (['MULTIPART_FILE_NOT_FOUND', 'INVALID_MULTIPART_REQUEST'].includes(error?.code)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'Import할 ZIP 파일을 선택해 주세요.'
|
|
})
|
|
}
|
|
|
|
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 ([
|
|
'ZIP_ENTRY_COUNT_LIMIT_EXCEEDED',
|
|
'ZIP_ENTRY_SIZE_LIMIT_EXCEEDED',
|
|
'ZIP_TOTAL_SIZE_LIMIT_EXCEEDED',
|
|
'ZIP_COMPRESSION_RATIO_LIMIT_EXCEEDED',
|
|
'IMPORT_MARKDOWN_SIZE_LIMIT_EXCEEDED'
|
|
].includes(error?.message)) {
|
|
throw createError({
|
|
statusCode: 413,
|
|
message: 'ZIP 내부 파일의 개수·압축률 또는 압축 해제 크기가 허용 범위를 초과했습니다.'
|
|
})
|
|
}
|
|
|
|
if ([
|
|
'INVALID_ZIP_FILE',
|
|
'UNSAFE_ZIP_ENTRY_PATH',
|
|
'DUPLICATE_ZIP_ENTRY_PATH'
|
|
].includes(error?.message)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '안전하게 처리할 수 없는 ZIP 파일입니다.'
|
|
})
|
|
}
|
|
|
|
if (error?.code === '23505') {
|
|
throw createError({
|
|
statusCode: 409,
|
|
message: '게시물 Import 중 중복 데이터가 감지되었습니다. 다시 시도해 주세요.'
|
|
})
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
message: '게시물 Import 작업을 시작하지 못했습니다.'
|
|
})
|
|
} finally {
|
|
if (shouldRemoveZip) {
|
|
await rm(zipFilePath, { force: true })
|
|
}
|
|
}
|
|
})
|