import { mkdir, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { createError, readMultipartFormData } from 'h3' import sharp from 'sharp' import { requireAdminSession } from '../../../../utils/admin-auth' import { upsertMediaMetadataCategory } from '../../../../utils/media-library' const allowedImageTypes = new Set(['image/jpeg', 'image/png', 'image/webp']) const homeCoverWidth = 720 /** * 시스템 자산 파일명 접미사 * @returns {string} */ const createSystemAssetSuffix = () => { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') return `${year}${month}-${Math.random().toString(36).slice(2, 8)}` } /** * 메인 화면 커버 이미지 업로드 API * @param {import('h3').H3Event} event - 요청 이벤트 * @returns {Promise} 수정된 사이트 설정 */ export default defineEventHandler(async (event) => { requireAdminSession(event) const config = useRuntimeConfig() const maxFileSize = Number(config.maxFileSize || 10485760) const formData = await readMultipartFormData(event) const file = (formData || []).find((part) => part.name === 'file' && part.filename) if (!file) { throw createError({ statusCode: 400, message: '업로드할 커버 이미지가 없습니다.' }) } if (!allowedImageTypes.has(file.type)) { throw createError({ statusCode: 400, message: 'JPG, PNG, WebP 이미지만 커버로 사용할 수 있습니다.' }) } if (file.data.length > maxFileSize) { throw createError({ statusCode: 413, message: '업로드 가능한 파일 크기를 초과했습니다.' }) } const metadata = await sharp(file.data).metadata() if (!metadata.width || !metadata.height) { throw createError({ statusCode: 400, message: '이미지 메타데이터를 읽을 수 없습니다.' }) } const uploadBaseUrl = String(config.uploadDir || '/uploads').replace(/\/+$/g, '') || '/uploads' const publicBasePath = uploadBaseUrl.replace(/^\/+/, '') const directoryPath = join(process.cwd(), 'public', publicBasePath, 'system') const assetSuffix = createSystemAssetSuffix() const coverFileName = `home-cover-${assetSuffix}.webp` const coverPath = join(directoryPath, coverFileName) const homeCoverImageUrl = `${uploadBaseUrl}/system/${coverFileName}` await mkdir(directoryPath, { recursive: true }) const coverBuffer = await sharp(file.data) .rotate() .resize({ width: homeCoverWidth, withoutEnlargement: true }) .webp({ quality: 88 }) .toBuffer() await writeFile(coverPath, coverBuffer) await upsertMediaMetadataCategory(homeCoverImageUrl, '시스템') return { homeCoverImageUrl } })