게시물 가져오기 작업 센터 추가

This commit is contained in:
2026-07-23 18:35:29 +09:00
parent 85b6fe7033
commit ef7c1216e4
16 changed files with 1178 additions and 95 deletions

View File

@@ -0,0 +1,19 @@
import { requireAdminSession } from '../../../../utils/admin-auth'
import {
listPostImportJobs,
queuePendingPostImportJobs
} from '../../../../repositories/post-import-repository'
/**
* 관리자 게시물 Import 작업 목록 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<Array>} Import 작업 목록
*/
export default defineEventHandler((event) => {
requireAdminSession(event)
queuePendingPostImportJobs().catch((error) => {
console.error('대기 중인 게시물 Import 작업 실행 실패', error)
})
return listPostImportJobs()
})

View File

@@ -0,0 +1,32 @@
import { createError, getRouterParam } from 'h3'
import { requireAdminSession } from '../../../../../utils/admin-auth'
import { deletePostImportJob } from '../../../../../repositories/post-import-repository'
/**
* 관리자 게시물 Import 작업 삭제 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<{ ok: boolean }>} 삭제 결과
*/
export default defineEventHandler(async (event) => {
requireAdminSession(event)
const jobId = getRouterParam(event, 'jobId')
if (!jobId) {
throw createError({
statusCode: 400,
statusMessage: 'Import 작업 ID가 필요합니다.'
})
}
const deleted = await deletePostImportJob(jobId)
if (!deleted) {
throw createError({
statusCode: 404,
statusMessage: 'Import 작업을 찾을 수 없습니다.'
})
}
return { ok: true }
})

View File

@@ -1,9 +1,11 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { rm } from 'node:fs/promises'
import { createError } from 'h3'
import { requireAdminSession } from '../../../../utils/admin-auth'
import { importPostsFromExportZip } from '../../../../repositories/post-import-repository'
import {
createPostImportJob,
createPostImportJobZipPath,
queuePostImportJobRun
} from '../../../../repositories/post-import-repository'
import { saveMultipartFileToDisk } from '../../../../utils/multipart-file-upload'
const MAX_IMPORT_ZIP_BYTES = 300 * 1024 * 1024
@@ -11,12 +13,12 @@ const MAX_IMPORT_ZIP_BYTES = 300 * 1024 * 1024
/**
* 관리자 게시물 Import API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<{ importedCount: number, skippedCount: number, assetCount: number, reusedAssetCount: number, warningCount: number, warnings: Array<string>, posts: Array<Object>, skippedPosts: Array<Object> }>} Import 결과
* @returns {Promise<Object>} 생성된 Import 작업
*/
export default defineEventHandler(async (event) => {
const adminSession = requireAdminSession(event)
const tempDirectory = await mkdtemp(join(tmpdir(), 'sori-studio-import-'))
const zipFilePath = join(tempDirectory, 'upload.zip')
const zipFilePath = await createPostImportJobZipPath('upload.zip')
let shouldRemoveZip = true
try {
const uploaded = await saveMultipartFileToDisk(event, {
@@ -39,10 +41,16 @@ export default defineEventHandler(async (event) => {
})
}
return await importPostsFromExportZip({
const job = await createPostImportJob({
zipFilePath,
authorId: adminSession.userId
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
@@ -109,12 +117,11 @@ export default defineEventHandler(async (event) => {
throw createError({
statusCode: 500,
message: '게시물 Import를 완료하지 못했습니다.'
message: '게시물 Import 작업을 시작하지 못했습니다.'
})
} finally {
await rm(tempDirectory, {
recursive: true,
force: true
})
if (shouldRemoveZip) {
await rm(zipFilePath, { force: true })
}
}
})

View File

@@ -82,7 +82,7 @@ const resolveUploadFilePath = (pathname) => {
const relativeUrlPath = decodedPath.replace(/^\/uploads\/?/g, '')
if (!relativeUrlPath || relativeUrlPath.includes('\0')) {
if (!relativeUrlPath || relativeUrlPath.includes('\0') || relativeUrlPath === '_import-jobs' || relativeUrlPath.startsWith('_import-jobs/')) {
throw createError({
statusCode: 404,
message: '업로드 파일을 찾을 수 없습니다.'