UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

265 lines (224 loc) 6.86 kB
import { MediaConfig } from "../../types/media"; // ============================================================================= // Default Media Configuration // ============================================================================= export const DEFAULT_MEDIA_CONFIG: MediaConfig = { storage: { provider: "local", path: "/uploads", maxFileSize: 10 * 1024 * 1024, // 10MB allowedTypes: [ // Images "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/bmp", "image/tiff", // Documents "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "text/plain", "text/csv", // Archives "application/zip", "application/x-rar-compressed", "application/x-7z-compressed", // Audio "audio/mpeg", "audio/wav", "audio/ogg", "audio/mp4", // Video "video/mp4", "video/mpeg", "video/quicktime", "video/x-msvideo", "video/webm", ], }, processing: { enabled: true, thumbnailSizes: [150, 300, 600, 1200], webpConversion: true, qualitySettings: { jpeg: 85, webp: 80, png: 90, }, }, cdn: { enabled: false, cacheHeaders: { "Cache-Control": "public, max-age=31536000, immutable", }, }, security: { virusScanning: false, // Enable if ClamAV or similar is available checksumValidation: true, signedUrls: false, maxQuotaPerSite: 1024 * 1024 * 1024, // 1GB per site maxQuotaPerUser: 100 * 1024 * 1024, // 100MB per user }, features: { folders: true, collections: true, tags: true, analytics: true, watermarking: false, }, }; // ============================================================================= // Configuration Helpers // ============================================================================= export function getMediaConfig(): MediaConfig { // In a real app, this would merge environment variables or database settings return DEFAULT_MEDIA_CONFIG; } export function isFileTypeAllowed(mimeType: string): boolean { const config = getMediaConfig(); return config.storage.allowedTypes.includes(mimeType); } export function getMaxFileSize(): number { const config = getMediaConfig(); return config.storage.maxFileSize; } export function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } export function getFileTypeCategory( mimeType: string ): "image" | "document" | "audio" | "video" | "archive" | "other" { if (mimeType.startsWith("image/")) return "image"; if (mimeType.startsWith("audio/")) return "audio"; if (mimeType.startsWith("video/")) return "video"; if ( mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("sheet") || mimeType.includes("presentation") || mimeType.includes("text/") ) return "document"; if ( mimeType.includes("zip") || mimeType.includes("rar") || mimeType.includes("7z") ) return "archive"; return "other"; } export function generateThumbnailSizes(): number[] { const config = getMediaConfig(); return config.processing.thumbnailSizes; } export function shouldProcessFile(mimeType: string): boolean { const config = getMediaConfig(); return config.processing.enabled && mimeType.startsWith("image/"); } // ============================================================================= // File Validation // ============================================================================= export interface FileValidationResult { isValid: boolean; errors: string[]; warnings: string[]; } export function validateFile(file: File): FileValidationResult { const errors: string[] = []; const warnings: string[] = []; // Check file type if (!isFileTypeAllowed(file.type)) { errors.push(`파일 타입 '${file.type}'은(는) 허용되지 않습니다.`); } // Check file size const maxSize = getMaxFileSize(); if (file.size > maxSize) { errors.push( `파일 크기가 너무 큽니다. 최대 ${formatFileSize( maxSize )}까지 업로드 가능합니다.` ); } // Check file name if (file.name.length > 255) { errors.push("파일명이 너무 깁니다. 255자 이하로 줄여주세요."); } // Check for dangerous file names const dangerousPatterns = [ /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i, /[<>:"|?*]/, /^\.+$/, ]; for (const pattern of dangerousPatterns) { if (pattern.test(file.name)) { errors.push("파일명에 허용되지 않는 문자가 포함되어 있습니다."); break; } } // Warnings for large files if (file.size > 5 * 1024 * 1024) { // 5MB warnings.push("파일이 큽니다. 업로드에 시간이 걸릴 수 있습니다."); } return { isValid: errors.length === 0, errors, warnings, }; } export function validateBatchUpload(files: File[]): FileValidationResult { const errors: string[] = []; const warnings: string[] = []; if (files.length === 0) { errors.push("업로드할 파일을 선택해주세요."); return { isValid: false, errors, warnings }; } if (files.length > 50) { errors.push("한 번에 최대 50개까지 업로드할 수 있습니다."); } let totalSize = 0; const duplicateNames = new Set<string>(); const fileNames = new Set<string>(); for (const file of files) { totalSize += file.size; // Check for duplicate names if (fileNames.has(file.name)) { duplicateNames.add(file.name); } fileNames.add(file.name); // Validate individual file const validation = validateFile(file); errors.push(...validation.errors); warnings.push(...validation.warnings); } // Check total size if (totalSize > 100 * 1024 * 1024) { // 100MB warnings.push( "전체 파일 크기가 큽니다. 업로드에 시간이 오래 걸릴 수 있습니다." ); } // Check for duplicates if (duplicateNames.size > 0) { warnings.push( `중복된 파일명이 있습니다: ${Array.from(duplicateNames).join(", ")}` ); } return { isValid: errors.length === 0, errors: [...new Set(errors)], // Remove duplicates warnings: [...new Set(warnings)], }; }