관리자 업로드 제한 설정 추가

This commit is contained in:
2026-07-01 17:58:30 +09:00
parent 129b68b939
commit e5d3eac2fd
17 changed files with 308 additions and 20 deletions

View File

@@ -29,6 +29,10 @@ import {
DEFAULT_TAG_POST_LIST_PAGE_SIZE,
normalizeDisplayCount
} from '../../lib/display-count-settings.js'
import {
normalizeUploadMaxFileCount,
normalizeUploadMaxTotalSize
} from '../../lib/upload-size-limit.js'
import { normalizeSocialLinks } from '../../lib/social-links.js'
import {
createPostBodyThumbnailsForContent,
@@ -157,6 +161,8 @@ const mapSiteSettingsRow = (row) => ({
tagPostListPageSize: normalizeDisplayCount(row.tag_post_list_page_size, DEFAULT_TAG_POST_LIST_PAGE_SIZE),
featuredPostLimit: normalizeDisplayCount(row.featured_post_limit, DEFAULT_FEATURED_POST_LIMIT),
pageSectionItemLimit: normalizeDisplayCount(row.page_section_item_limit, DEFAULT_PAGE_SECTION_ITEM_LIMIT),
uploadMaxFileCount: normalizeUploadMaxFileCount(row.upload_max_file_count),
uploadMaxTotalSize: normalizeUploadMaxTotalSize(row.upload_max_total_size),
homeCoverImageUrl: row.home_cover_image_url || '',
homeCoverDarkImageUrl: row.home_cover_dark_image_url || '',
homeCoverTitle: row.home_cover_title || '',
@@ -1463,6 +1469,8 @@ export const updateSiteSettings = async (input) => {
tag_post_list_page_size,
featured_post_limit,
page_section_item_limit,
upload_max_file_count,
upload_max_total_size,
home_cover_image_url,
home_cover_dark_image_url,
home_cover_title,
@@ -1503,6 +1511,8 @@ export const updateSiteSettings = async (input) => {
${normalizeDisplayCount(input.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE)},
${normalizeDisplayCount(input.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT)},
${normalizeDisplayCount(input.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT)},
${normalizeUploadMaxFileCount(input.uploadMaxFileCount)},
${normalizeUploadMaxTotalSize(input.uploadMaxTotalSize)},
${input.homeCoverImageUrl || ''},
${input.homeCoverDarkImageUrl || ''},
${input.homeCoverTitle || ''},
@@ -1543,6 +1553,8 @@ export const updateSiteSettings = async (input) => {
tag_post_list_page_size = EXCLUDED.tag_post_list_page_size,
featured_post_limit = EXCLUDED.featured_post_limit,
page_section_item_limit = EXCLUDED.page_section_item_limit,
upload_max_file_count = EXCLUDED.upload_max_file_count,
upload_max_total_size = EXCLUDED.upload_max_total_size,
home_cover_image_url = EXCLUDED.home_cover_image_url,
home_cover_dark_image_url = EXCLUDED.home_cover_dark_image_url,
home_cover_title = EXCLUDED.home_cover_title,

View File

@@ -4,13 +4,17 @@ import { createError, readMultipartFormData } from 'h3'
import {
buildDefaultUploadSizeLimits,
formatUploadSizeLimit,
getUploadFilesTotalSize,
getMaxUploadBytesForKind,
getUploadKind,
getUploadKindLabel
getUploadKindLabel,
normalizeUploadMaxFileCount,
normalizeUploadMaxTotalSize
} from '../../../../lib/upload-size-limit.js'
import { requireAdminSession } from '../../../utils/admin-auth'
import { getRuntimeEnvNumber } from '../../../utils/runtime-env.js'
import { upsertMediaMetadataCategory } from '../../../utils/media-library'
import { getSiteSettings } from '../../../repositories/content-repository'
const allowedUploadTypes = new Map([
['image/jpeg', '.jpg'],
@@ -135,6 +139,9 @@ export default defineEventHandler(async (event) => {
audio: getRuntimeEnvNumber('MAX_AUDIO_FILE_SIZE', 'maxAudioFileSize', 52428800),
document: getRuntimeEnvNumber('MAX_DOCUMENT_FILE_SIZE', 'maxDocumentFileSize', 52428800)
})
const settings = await getSiteSettings()
const uploadMaxFileCount = normalizeUploadMaxFileCount(settings.uploadMaxFileCount)
const uploadMaxTotalSize = normalizeUploadMaxTotalSize(settings.uploadMaxTotalSize)
const formData = await readMultipartFormData(event)
const files = (formData || []).filter((part) => part.name === 'files' && part.filename)
@@ -145,6 +152,22 @@ export default defineEventHandler(async (event) => {
})
}
if (files.length > uploadMaxFileCount) {
throw createError({
statusCode: 413,
message: `한 번에 업로드할 수 있는 파일은 최대 ${uploadMaxFileCount}개입니다.`
})
}
const uploadTotalSize = getUploadFilesTotalSize(files)
if (uploadTotalSize > uploadMaxTotalSize) {
throw createError({
statusCode: 413,
message: `한 번에 업로드 가능한 전체 용량을 초과했습니다. (최대 ${formatUploadSizeLimit(uploadMaxTotalSize)})`
})
}
const now = new Date()
const year = String(now.getFullYear())
const month = String(now.getMonth() + 1).padStart(2, '0')

View File

@@ -35,6 +35,16 @@ import {
MIN_DISPLAY_COUNT,
normalizeDisplayCount
} from '../../lib/display-count-settings.js'
import {
DEFAULT_UPLOAD_MAX_FILE_COUNT,
DEFAULT_UPLOAD_MAX_TOTAL_SIZE,
MAX_UPLOAD_MAX_FILE_COUNT,
MAX_UPLOAD_MAX_TOTAL_SIZE,
MIN_UPLOAD_MAX_FILE_COUNT,
MIN_UPLOAD_MAX_TOTAL_SIZE,
normalizeUploadMaxFileCount,
normalizeUploadMaxTotalSize
} from '../../lib/upload-size-limit.js'
export const adminSiteSettingsInputSchema = z.object({
title: z.string().trim().min(1),
@@ -52,6 +62,8 @@ export const adminSiteSettingsInputSchema = z.object({
tagPostListPageSize: z.coerce.number().int().min(MIN_DISPLAY_COUNT).max(MAX_DISPLAY_COUNT).optional().default(DEFAULT_TAG_POST_LIST_PAGE_SIZE),
featuredPostLimit: z.coerce.number().int().min(MIN_DISPLAY_COUNT).max(MAX_DISPLAY_COUNT).optional().default(DEFAULT_FEATURED_POST_LIMIT),
pageSectionItemLimit: z.coerce.number().int().min(MIN_DISPLAY_COUNT).max(MAX_DISPLAY_COUNT).optional().default(DEFAULT_PAGE_SECTION_ITEM_LIMIT),
uploadMaxFileCount: z.coerce.number().int().min(MIN_UPLOAD_MAX_FILE_COUNT).max(MAX_UPLOAD_MAX_FILE_COUNT).optional().default(DEFAULT_UPLOAD_MAX_FILE_COUNT),
uploadMaxTotalSize: z.coerce.number().int().min(MIN_UPLOAD_MAX_TOTAL_SIZE).max(MAX_UPLOAD_MAX_TOTAL_SIZE).optional().default(DEFAULT_UPLOAD_MAX_TOTAL_SIZE),
homeCoverImageUrl: z.string().trim().max(500).optional().default(''),
homeCoverDarkImageUrl: z.string().trim().max(500).optional().default(''),
homeCoverTitle: z.string().trim().max(120).optional().default(''),
@@ -109,6 +121,8 @@ export const adminSiteSettingsInputSchema = z.object({
tagPostListPageSize: normalizeDisplayCount(data.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE),
featuredPostLimit: normalizeDisplayCount(data.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT),
pageSectionItemLimit: normalizeDisplayCount(data.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT),
uploadMaxFileCount: normalizeUploadMaxFileCount(data.uploadMaxFileCount),
uploadMaxTotalSize: normalizeUploadMaxTotalSize(data.uploadMaxTotalSize),
announcementUrl: normalizeAnnouncementUrl(data.announcementUrl),
announcementBackgroundColor: normalizeAnnouncementBackgroundColor(data.announcementBackgroundColor),
announcementAlignment: normalizeAnnouncementAlignment(data.announcementAlignment),

View File

@@ -12,6 +12,10 @@ import {
DEFAULT_POST_LIST_PAGE_SIZE,
DEFAULT_TAG_POST_LIST_PAGE_SIZE
} from '../../lib/display-count-settings.js'
import {
DEFAULT_UPLOAD_MAX_FILE_COUNT,
DEFAULT_UPLOAD_MAX_TOTAL_SIZE
} from '../../lib/upload-size-limit.js'
/**
* 기본 사이트 설정 반환
@@ -37,6 +41,8 @@ export const getDefaultSiteSettings = () => {
tagPostListPageSize: DEFAULT_TAG_POST_LIST_PAGE_SIZE,
featuredPostLimit: DEFAULT_FEATURED_POST_LIMIT,
pageSectionItemLimit: DEFAULT_PAGE_SECTION_ITEM_LIMIT,
uploadMaxFileCount: DEFAULT_UPLOAD_MAX_FILE_COUNT,
uploadMaxTotalSize: DEFAULT_UPLOAD_MAX_TOTAL_SIZE,
homeCoverImageUrl: '',
homeCoverDarkImageUrl: '',
homeCoverTitle: '',