some fixes
This commit is contained in:
733
.history/routes/media_20251022195139.js
Normal file
733
.history/routes/media_20251022195139.js
Normal file
@@ -0,0 +1,733 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication required'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
|
||||
files: 10
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Allow images only
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload single image
|
||||
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = req.file.path;
|
||||
const filename = req.file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Keep original as well (converted to webp for better compression)
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
// Remove the original uploaded file
|
||||
await fs.unlink(originalPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image uploaded and optimized successfully',
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
originalName: req.file.originalname,
|
||||
mimeType: req.file.mimetype,
|
||||
size: req.file.size
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload multiple images
|
||||
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No files uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const originalPath = file.path;
|
||||
const filename = file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Original as webp
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
uploadedImages.push({
|
||||
originalName: file.originalname,
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
mimeType: file.mimetype,
|
||||
size: file.size
|
||||
}
|
||||
});
|
||||
|
||||
// Remove original file
|
||||
await fs.unlink(originalPath);
|
||||
} catch (fileError) {
|
||||
console.error(`Error processing file ${file.originalname}:`, fileError);
|
||||
// Clean up this file and continue with others
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${uploadedImages.length} images uploaded and optimized successfully`,
|
||||
images: uploadedImages
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Multiple images upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.files) {
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Security check - ensure filename doesn't contain path traversal
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadPath, filename);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Image not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List uploaded images with advanced filtering and search
|
||||
router.get('/list', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
// Directory might already exist, continue
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 24;
|
||||
const search = req.query.search?.toLowerCase() || '';
|
||||
const sortBy = req.query.sortBy || 'date'; // date, name, size
|
||||
const sortOrder = req.query.sortOrder || 'desc'; // asc, desc
|
||||
const fileType = req.query.fileType || 'all'; // all, image, video, document
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(uploadPath);
|
||||
} catch (readdirError) {
|
||||
if (readdirError.code === 'ENOENT') {
|
||||
return res.json({
|
||||
success: true,
|
||||
images: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
limit,
|
||||
totalItems: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false
|
||||
},
|
||||
filters: {
|
||||
search: '',
|
||||
sortBy: 'date',
|
||||
sortOrder: 'desc',
|
||||
fileType: 'all'
|
||||
}
|
||||
});
|
||||
}
|
||||
throw readdirError;
|
||||
}
|
||||
|
||||
// Filter by file type
|
||||
let filteredFiles = files;
|
||||
if (fileType !== 'all') {
|
||||
const typePatterns = {
|
||||
image: /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i,
|
||||
video: /\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i,
|
||||
document: /\.(pdf|doc|docx|txt|rtf|odt)$/i
|
||||
};
|
||||
|
||||
const pattern = typePatterns[fileType];
|
||||
if (pattern) {
|
||||
filteredFiles = files.filter(file => pattern.test(file));
|
||||
}
|
||||
} else {
|
||||
// Only show supported media files
|
||||
filteredFiles = files.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?|mp4|webm|avi|mov|mkv|pdf|doc|docx)$/i.test(file)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (search) {
|
||||
filteredFiles = filteredFiles.filter(file =>
|
||||
file.toLowerCase().includes(search)
|
||||
);
|
||||
}
|
||||
|
||||
// Get file stats and create file objects
|
||||
const filesWithStats = await Promise.all(
|
||||
filteredFiles.map(async (file) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return { file, stats, filePath };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = filesWithStats.filter(item => item !== null);
|
||||
|
||||
// Sort files
|
||||
validFiles.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
aValue = a.file.toLowerCase();
|
||||
bValue = b.file.toLowerCase();
|
||||
break;
|
||||
case 'size':
|
||||
aValue = a.stats.size;
|
||||
bValue = b.stats.size;
|
||||
break;
|
||||
case 'date':
|
||||
default:
|
||||
aValue = a.stats.mtime;
|
||||
bValue = b.stats.mtime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sortOrder === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
const total = validFiles.length;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
|
||||
const paginatedFiles = validFiles.slice(start, end);
|
||||
|
||||
const filesWithDetails = await Promise.all(
|
||||
paginatedFiles.map(async ({ file, stats, filePath }) => {
|
||||
try {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
let fileDetails = {
|
||||
filename: file,
|
||||
url: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
isImage: false,
|
||||
isVideo: false,
|
||||
isDocument: false
|
||||
};
|
||||
|
||||
// Determine file type and get additional info
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(file)) {
|
||||
fileDetails.isImage = true;
|
||||
fileDetails.mimetype = `image/${ext.replace('.', '')}`;
|
||||
|
||||
// Get image dimensions
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileDetails.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileDetails.format = metadata.format;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${file}`);
|
||||
}
|
||||
} else if (/\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i.test(file)) {
|
||||
fileDetails.isVideo = true;
|
||||
fileDetails.mimetype = `video/${ext.replace('.', '')}`;
|
||||
} else if (/\.(pdf|doc|docx|txt|rtf|odt)$/i.test(file)) {
|
||||
fileDetails.isDocument = true;
|
||||
fileDetails.mimetype = `application/${ext.replace('.', '')}`;
|
||||
}
|
||||
|
||||
// Generate thumbnail for images
|
||||
if (fileDetails.isImage && !file.includes('-thumbnail.')) {
|
||||
const thumbnailPath = path.join(uploadPath, `${path.parse(file).name}-thumbnail.webp`);
|
||||
try {
|
||||
await fs.access(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch {
|
||||
// Thumbnail doesn't exist, create it
|
||||
try {
|
||||
await sharp(filePath)
|
||||
.resize(200, 150, {
|
||||
fit: 'cover',
|
||||
withoutEnlargement: false
|
||||
})
|
||||
.webp({ quality: 80 })
|
||||
.toFile(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch (thumbError) {
|
||||
console.warn(`Could not create thumbnail for ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileDetails;
|
||||
} catch (error) {
|
||||
console.error(`Error getting details for ${file}:`, error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validMedia = filesWithDetails.filter(item => item !== null);
|
||||
|
||||
// Calculate storage stats
|
||||
const totalSize = validFiles.reduce((sum, file) => sum + file.stats.size, 0);
|
||||
const imageCount = validMedia.filter(f => f.isImage).length;
|
||||
const videoCount = validMedia.filter(f => f.isVideo).length;
|
||||
const documentCount = validMedia.filter(f => f.isDocument).length;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
files: validMedia,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
filters: {
|
||||
search,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
fileType
|
||||
},
|
||||
stats: {
|
||||
totalFiles: total,
|
||||
totalSize,
|
||||
imageCount,
|
||||
videoCount,
|
||||
documentCount,
|
||||
formattedSize: formatFileSize(totalSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List media error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error listing media files'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create folder structure
|
||||
router.post('/folder', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { folderName } = req.body;
|
||||
|
||||
if (!folderName || !folderName.trim()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder name is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize folder name
|
||||
const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
const folderPath = path.join(__dirname, '../public/uploads', sanitizedName);
|
||||
|
||||
try {
|
||||
await fs.mkdir(folderPath, { recursive: true });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Folder created successfully',
|
||||
folderName: sanitizedName,
|
||||
folderPath: `/uploads/${sanitizedName}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'EEXIST') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder already exists'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Create folder error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error creating folder'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get media file info
|
||||
router.get('/info/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '../public/uploads', filename);
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
|
||||
let fileInfo = {
|
||||
filename,
|
||||
url: `/uploads/${filename}`,
|
||||
size: stats.size,
|
||||
formattedSize: formatFileSize(stats.size),
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
mimetype: getMimeType(ext)
|
||||
};
|
||||
|
||||
// Get additional info for images
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(filename)) {
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileInfo.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileInfo.format = metadata.format;
|
||||
fileInfo.hasAlpha = metadata.hasAlpha;
|
||||
fileInfo.density = metadata.density;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
fileInfo
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'File not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get file info error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error getting file information'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Resize image
|
||||
router.post('/resize/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const { width, height, quality = 85 } = req.body;
|
||||
|
||||
if (!width && !height) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Width or height must be specified'
|
||||
});
|
||||
}
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = path.join(__dirname, '../public/uploads', filename);
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
const resizedPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${width || 'auto'}x${height || 'auto'}.webp`
|
||||
);
|
||||
|
||||
try {
|
||||
let sharpInstance = sharp(originalPath);
|
||||
|
||||
if (width && height) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width), parseInt(height), {
|
||||
fit: 'cover'
|
||||
});
|
||||
} else if (width) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width));
|
||||
} else {
|
||||
sharpInstance = sharpInstance.resize(null, parseInt(height));
|
||||
}
|
||||
|
||||
await sharpInstance
|
||||
.webp({ quality: parseInt(quality) })
|
||||
.toFile(resizedPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image resized successfully',
|
||||
originalFile: filename,
|
||||
resizedFile: path.basename(resizedPath),
|
||||
resizedUrl: `/uploads/${path.basename(resizedPath)}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Original file not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Resize image error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error resizing image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Utility functions
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function getMimeType(ext) {
|
||||
const mimeTypes = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.mov': 'video/quicktime',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.pdf': 'application/pdf',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
};
|
||||
|
||||
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user