Files
sori.studio/lib/upload-size-limit.js
zenn 095a8fa5f0 v1.4.1: 관리자 미디어 업로드 한도·라이브 에디터 UX 개선
종류별 업로드 크기 한도와 413 안내를 추가하고, 임베드·미디어 라이브 프리뷰·제목 Enter 포커스·스크롤 동작을 보정한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 15:33:23 +09:00

114 lines
3.3 KiB
JavaScript

/**
* 파일명에서 확장자를 추출한다. (브라우저·서버 공용, node:path 미사용)
* @param {string} [filename] - 파일명
* @returns {string} 소문자 확장자(점 포함, 예: `.mp4`)
*/
const getFileExtension = (filename = '') => {
const normalized = String(filename).trim()
const lastDot = normalized.lastIndexOf('.')
if (lastDot <= 0 || lastDot === normalized.length - 1) {
return ''
}
return normalized.slice(lastDot).toLowerCase()
}
/** @type {Record<string, string>} 업로드 종류 */
export const UPLOAD_KIND = {
image: 'image',
video: 'video',
audio: 'audio',
document: 'document'
}
const VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov'])
const AUDIO_EXTENSIONS = new Set(['.mp3', '.wav', '.ogg', '.m4a'])
const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif'])
/** @type {Record<string, string>} 업로드 종류 한글 라벨 */
const UPLOAD_KIND_LABELS = {
[UPLOAD_KIND.image]: '이미지',
[UPLOAD_KIND.video]: '비디오',
[UPLOAD_KIND.audio]: '오디오',
[UPLOAD_KIND.document]: '파일'
}
/**
* 기본 업로드 크기 한도(바이트)를 만든다.
* @param {Object} [overrides] - 종류별 한도 덮어쓰기
* @returns {{ image: number, video: number, audio: number, document: number }} 종류별 한도
*/
export const buildDefaultUploadSizeLimits = (overrides = {}) => ({
image: 10485760,
video: 209715200,
audio: 52428800,
document: 52428800,
...overrides
})
/**
* MIME·파일명으로 업로드 종류를 판별한다.
* @param {string} [mimeType] - MIME 타입
* @param {string} [filename] - 파일명
* @returns {'image'|'video'|'audio'|'document'} 업로드 종류
*/
export const getUploadKind = (mimeType = '', filename = '') => {
const extension = getFileExtension(filename)
if (mimeType.startsWith('video/') || VIDEO_EXTENSIONS.has(extension)) {
return UPLOAD_KIND.video
}
if (mimeType.startsWith('audio/') || AUDIO_EXTENSIONS.has(extension)) {
return UPLOAD_KIND.audio
}
if (mimeType.startsWith('image/') || IMAGE_EXTENSIONS.has(extension)) {
return UPLOAD_KIND.image
}
return UPLOAD_KIND.document
}
/**
* 업로드 종류별 최대 바이트를 반환한다.
* @param {'image'|'video'|'audio'|'document'} kind - 업로드 종류
* @param {{ image: number, video: number, audio: number, document: number }} limits - 종류별 한도
* @returns {number} 최대 바이트
*/
export const getMaxUploadBytesForKind = (kind, limits) => limits[kind] ?? limits.image
/**
* 바이트를 사람이 읽기 쉬운 용량 문자열로 변환한다.
* @param {number} bytes - 바이트
* @returns {string} 용량 문자열
*/
export const formatUploadSizeLimit = (bytes) => {
if (!Number.isFinite(bytes) || bytes <= 0) {
return '0B'
}
if (bytes >= 1073741824) {
const gigabytes = bytes / 1073741824
return `${Number.isInteger(gigabytes) ? gigabytes : gigabytes.toFixed(1)}GB`
}
if (bytes >= 1048576) {
return `${Math.round(bytes / 1048576)}MB`
}
if (bytes >= 1024) {
return `${Math.round(bytes / 1024)}KB`
}
return `${bytes}B`
}
/**
* 업로드 종류 한글 라벨을 반환한다.
* @param {'image'|'video'|'audio'|'document'} kind - 업로드 종류
* @returns {string} 한글 라벨
*/
export const getUploadKindLabel = (kind) => UPLOAD_KIND_LABELS[kind] || UPLOAD_KIND_LABELS[UPLOAD_KIND.image]