186 lines
5.5 KiB
JavaScript
186 lines
5.5 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'])
|
|
|
|
export const DEFAULT_UPLOAD_MAX_FILE_COUNT = 20
|
|
export const DEFAULT_UPLOAD_MAX_TOTAL_SIZE = 314572800
|
|
export const MIN_UPLOAD_MAX_FILE_COUNT = 1
|
|
export const MAX_UPLOAD_MAX_FILE_COUNT = 50
|
|
export const MIN_UPLOAD_MAX_TOTAL_SIZE = 1048576
|
|
export const MAX_UPLOAD_MAX_TOTAL_SIZE = 1073741824
|
|
export const UPLOAD_FILE_COUNT_OPTIONS = [5, 10, 20, 30, 50]
|
|
export const UPLOAD_TOTAL_SIZE_OPTIONS = [52428800, 104857600, 209715200, 314572800, 524288000, 1073741824]
|
|
|
|
/** @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
|
|
})
|
|
|
|
/**
|
|
* 업로드 최대 파일 수를 정규화한다.
|
|
* @param {unknown} value - 원본 값
|
|
* @returns {number} 최대 파일 수
|
|
*/
|
|
export const normalizeUploadMaxFileCount = (value) => {
|
|
const parsed = Number(value)
|
|
if (!Number.isFinite(parsed)) {
|
|
return DEFAULT_UPLOAD_MAX_FILE_COUNT
|
|
}
|
|
|
|
const rounded = Math.round(parsed)
|
|
if (rounded < MIN_UPLOAD_MAX_FILE_COUNT) {
|
|
return MIN_UPLOAD_MAX_FILE_COUNT
|
|
}
|
|
if (rounded > MAX_UPLOAD_MAX_FILE_COUNT) {
|
|
return MAX_UPLOAD_MAX_FILE_COUNT
|
|
}
|
|
|
|
return rounded
|
|
}
|
|
|
|
/**
|
|
* 업로드 최대 총량을 정규화한다.
|
|
* @param {unknown} value - 원본 값
|
|
* @returns {number} 최대 총량 바이트
|
|
*/
|
|
export const normalizeUploadMaxTotalSize = (value) => {
|
|
const parsed = Number(value)
|
|
if (!Number.isFinite(parsed)) {
|
|
return DEFAULT_UPLOAD_MAX_TOTAL_SIZE
|
|
}
|
|
|
|
const rounded = Math.round(parsed)
|
|
if (rounded < MIN_UPLOAD_MAX_TOTAL_SIZE) {
|
|
return MIN_UPLOAD_MAX_TOTAL_SIZE
|
|
}
|
|
if (rounded > MAX_UPLOAD_MAX_TOTAL_SIZE) {
|
|
return MAX_UPLOAD_MAX_TOTAL_SIZE
|
|
}
|
|
|
|
return rounded
|
|
}
|
|
|
|
/**
|
|
* 업로드 파일 크기를 반환한다.
|
|
* @param {Object} file - 브라우저 File 또는 서버 multipart 파일 파트
|
|
* @returns {number} 파일 크기
|
|
*/
|
|
export const getUploadFileSize = (file) => {
|
|
const size = file?.size ?? file?.data?.length ?? 0
|
|
const parsed = Number(size)
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0
|
|
}
|
|
|
|
/**
|
|
* 업로드 파일 전체 크기를 반환한다.
|
|
* @param {Iterable<Object>|ArrayLike<Object>} files - 업로드 파일 목록
|
|
* @returns {number} 전체 크기
|
|
*/
|
|
export const getUploadFilesTotalSize = (files) => Array.from(files || [])
|
|
.reduce((total, file) => total + getUploadFileSize(file), 0)
|
|
|
|
/**
|
|
* 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]
|