more edits

This commit is contained in:
yotakii
2026-02-15 16:28:31 +03:00
parent 6417fd6b01
commit 2909b675a1
2 changed files with 138 additions and 280 deletions

View File

@@ -2,23 +2,20 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const multer = require('multer'); const multer = require('multer');
const path = require('path'); const path = require('path');
const fsNative = require('fs');
const fs = require('fs').promises; const fs = require('fs').promises;
const Media = require('../models/Media'); const Media = require('../models/Media');
const adminAuth = require('../middleware/adminAuth'); const adminAuth = require('../middleware/adminAuth');
// ======================= // --- Helpers ---
// Config (LOCAL + PROD) const getUploadsDir = () => {
// ======================= const envDir = (process.env.UPLOADS_DIR || '').trim();
const UPLOAD_DIR = process.env.UPLOAD_DIR if (envDir) return path.isAbsolute(envDir) ? envDir : path.resolve(process.cwd(), envDir);
? path.resolve(process.env.UPLOAD_DIR) return path.resolve(__dirname, '..', 'uploads');
: path.resolve(__dirname, '..', 'uploads'); // local fallback };
const PUBLIC_BASE = process.env.UPLOAD_PUBLIC_URL || '/uploads'; const baseDir = getUploadsDir();
const MEDIA_ROOT = process.env.MEDIA_SUBDIR || 'images'; const urlPrefix = (process.env.UPLOADS_URL_PREFIX || '/uploads').replace(/\/$/, '');
// Ensure upload directories exist
const ensureUploadDir = async (dir) => { const ensureUploadDir = async (dir) => {
try { try {
await fs.access(dir); await fs.access(dir);
@@ -28,41 +25,43 @@ const ensureUploadDir = async (dir) => {
}; };
const sanitizeFolder = (folder) => { const sanitizeFolder = (folder) => {
let f = String(folder || 'general').trim(); const f = String(folder || 'general').trim().toLowerCase();
// allow a-z0-9 _ - and slashes for nested folders return f.replace(/[^a-z0-9/_-]/g, '').replace(/\/+/g, '/').replace(/^\/|\/$/g, '') || 'general';
f = f.replace(/[^a-z0-9/_-]/gi, ''); };
// prevent traversal
while (f.includes('..')) f = f.replace(/\.\./g, ''); const resolveSafePath = (root, relative) => {
f = f.replace(/^\/+|\/+$/g, ''); const safeRel = String(relative || '').replace(/^\/+/, '');
return f || 'general'; const full = path.resolve(root, safeRel);
const rootResolved = path.resolve(root);
if (!full.startsWith(rootResolved + path.sep) && full !== rootResolved) {
throw new Error('Invalid path');
}
return full;
}; };
// Configure multer storage // Configure multer storage
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: async (req, file, cb) => { destination: (req, file, cb) => {
try { const folder = sanitizeFolder(req.body.folder || 'general');
const folderSafe = sanitizeFolder(req.body.folder); const uploadPath = path.join(baseDir, folder);
const uploadPath = path.join(UPLOAD_DIR, MEDIA_ROOT, folderSafe);
await ensureUploadDir(uploadPath); ensureUploadDir(uploadPath)
cb(null, uploadPath); .then(() => cb(null, uploadPath))
} catch (e) { .catch((e) => cb(e));
cb(e);
}
}, },
filename: (req, file, cb) => { filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname); const ext = path.extname(file.originalname).toLowerCase();
const basename = path const basename = path
.basename(file.originalname, ext) .basename(file.originalname, ext)
.toLowerCase() .toLowerCase()
.replace(/[^a-z0-9]/g, '-') .replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-') .replace(/-+/g, '-')
.replace(/^-|-$/g, ''); .replace(/^-|-$/g, '');
cb(null, (basename || 'file') + '-' + uniqueSuffix + ext.toLowerCase()); cb(null, `${basename || 'file'}-${uniqueSuffix}${ext || ''}`);
}, },
}); });
// File filter
const fileFilter = (req, file, cb) => { const fileFilter = (req, file, cb) => {
const allowedMimeTypes = [ const allowedMimeTypes = [
'image/jpeg', 'image/jpeg',
@@ -76,19 +75,14 @@ const fileFilter = (req, file, cb) => {
'application/pdf', 'application/pdf',
]; ];
if (allowedMimeTypes.includes(file.mimetype)) { if (allowedMimeTypes.includes(file.mimetype)) return cb(null, true);
cb(null, true); cb(new Error('Invalid file type. Only images, videos, and PDFs are allowed.'), false);
} else {
cb(new Error('Invalid file type. Only images, videos, and PDFs are allowed.'), false);
}
}; };
const upload = multer({ const upload = multer({
storage, storage,
fileFilter, fileFilter,
limits: { limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileSize: 10 * 1024 * 1024, // 10MB limit
},
}); });
// @route POST /api/media/upload // @route POST /api/media/upload
@@ -97,40 +91,29 @@ const upload = multer({
router.post('/upload', adminAuth, upload.single('file'), async (req, res) => { router.post('/upload', adminAuth, upload.single('file'), async (req, res) => {
try { try {
if (!req.file) { if (!req.file) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'No file uploaded' });
success: false,
message: 'No file uploaded',
});
} }
const folderSafe = sanitizeFolder(req.body.folder); const folder = sanitizeFolder(req.body.folder || 'general');
const { alt, caption, description, tags } = req.body; const { alt, caption, description, tags } = req.body;
// Determine media type
let mediaType = 'other'; let mediaType = 'other';
if (req.file.mimetype.startsWith('image/')) { if (req.file.mimetype.startsWith('image/')) mediaType = 'image';
mediaType = 'image'; else if (req.file.mimetype.startsWith('video/')) mediaType = 'video';
} else if (req.file.mimetype.startsWith('video/')) { else if (req.file.mimetype === 'application/pdf') mediaType = 'document';
mediaType = 'video';
} else if (req.file.mimetype === 'application/pdf') {
mediaType = 'document';
}
const url = `${PUBLIC_BASE}/${MEDIA_ROOT}/${folderSafe}/${req.file.filename}`;
// Create media record
const media = new Media({ const media = new Media({
filename: req.file.filename, filename: req.file.filename,
originalName: req.file.originalname, originalName: req.file.originalname,
url, url: `${urlPrefix}/${folder}/${req.file.filename}`,
mimeType: req.file.mimetype, mimeType: req.file.mimetype,
size: req.file.size, size: req.file.size,
type: mediaType, type: mediaType,
folder: folderSafe, folder,
alt, alt,
caption, caption,
description, description,
tags: tags ? tags.split(',').map((t) => t.trim()) : [], tags: tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : [],
uploadedBy: req.admin.id, uploadedBy: req.admin.id,
}); });
@@ -143,48 +126,34 @@ router.post('/upload', adminAuth, upload.single('file'), async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: error.message || 'Error uploading file' });
success: false,
message: error.message || 'Error uploading file',
});
} }
}); });
// @route POST /api/media/upload-multiple // @route POST /api/media/upload-multiple
// @desc Upload multiple media files
// @access Private (Admin)
router.post('/upload-multiple', adminAuth, upload.array('files', 10), async (req, res) => { router.post('/upload-multiple', adminAuth, upload.array('files', 10), async (req, res) => {
try { try {
if (!req.files || req.files.length === 0) { if (!req.files || req.files.length === 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'No files uploaded' });
success: false,
message: 'No files uploaded',
});
} }
const folderSafe = sanitizeFolder(req.body.folder); const folder = sanitizeFolder(req.body.folder || 'general');
const mediaRecords = []; const mediaRecords = [];
for (const file of req.files) { for (const file of req.files) {
let mediaType = 'other'; let mediaType = 'other';
if (file.mimetype.startsWith('image/')) { if (file.mimetype.startsWith('image/')) mediaType = 'image';
mediaType = 'image'; else if (file.mimetype.startsWith('video/')) mediaType = 'video';
} else if (file.mimetype.startsWith('video/')) { else if (file.mimetype === 'application/pdf') mediaType = 'document';
mediaType = 'video';
} else if (file.mimetype === 'application/pdf') {
mediaType = 'document';
}
const url = `${PUBLIC_BASE}/${MEDIA_ROOT}/${folderSafe}/${file.filename}`;
const media = new Media({ const media = new Media({
filename: file.filename, filename: file.filename,
originalName: file.originalname, originalName: file.originalname,
url, url: `${urlPrefix}/${folder}/${file.filename}`,
mimeType: file.mimetype, mimeType: file.mimetype,
size: file.size, size: file.size,
type: mediaType, type: mediaType,
folder: folderSafe, folder,
uploadedBy: req.admin.id, uploadedBy: req.admin.id,
}); });
@@ -199,16 +168,11 @@ router.post('/upload-multiple', adminAuth, upload.array('files', 10), async (req
}); });
} catch (error) { } catch (error) {
console.error('Multiple upload error:', error); console.error('Multiple upload error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: error.message || 'Error uploading files' });
success: false,
message: error.message || 'Error uploading files',
});
} }
}); });
// @route GET /api/media // @route GET /api/media
// @desc Get all media
// @access Private (Admin)
router.get('/', adminAuth, async (req, res) => { router.get('/', adminAuth, async (req, res) => {
try { try {
const { folder, type, page = 1, limit = 50 } = req.query; const { folder, type, page = 1, limit = 50 } = req.query;
@@ -240,45 +204,29 @@ router.get('/', adminAuth, async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error('Get media error:', error); console.error('Get media error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error fetching media' });
success: false,
message: 'Error fetching media',
});
} }
}); });
// @route GET /api/media/search // @route GET /api/media/search
// @desc Search media
// @access Private (Admin)
router.get('/search', adminAuth, async (req, res) => { router.get('/search', adminAuth, async (req, res) => {
try { try {
const { q, folder, type, limit = 50 } = req.query; const { q, folder, type, limit = 50 } = req.query;
if (!q) { if (!q) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'Search query is required' });
success: false,
message: 'Search query is required',
});
} }
const media = await Media.search(q, { folder, type, limit: parseInt(limit, 10) }); const media = await Media.search(q, { folder, type, limit: parseInt(limit, 10) });
res.json({ res.json({ success: true, data: { media } });
success: true,
data: { media },
});
} catch (error) { } catch (error) {
console.error('Search media error:', error); console.error('Search media error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error searching media' });
success: false,
message: 'Error searching media',
});
} }
}); });
// @route PUT /api/media/:id // @route PUT /api/media/:id
// @desc Update media metadata
// @access Private (Admin)
router.put('/:id', adminAuth, async (req, res) => { router.put('/:id', adminAuth, async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
@@ -293,66 +241,29 @@ router.put('/:id', adminAuth, async (req, res) => {
const media = await Media.findByIdAndUpdate(id, { $set: updateFields }, { new: true, runValidators: true }); const media = await Media.findByIdAndUpdate(id, { $set: updateFields }, { new: true, runValidators: true });
if (!media) { if (!media) return res.status(404).json({ success: false, message: 'Media not found' });
return res.status(404).json({
success: false,
message: 'Media not found',
});
}
res.json({ res.json({ success: true, message: 'Media updated successfully', data: { media } });
success: true,
message: 'Media updated successfully',
data: { media },
});
} catch (error) { } catch (error) {
console.error('Update media error:', error); console.error('Update media error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error updating media' });
success: false,
message: 'Error updating media',
});
} }
}); });
const safeResolveInside = (base, rel) => {
const baseAbs = path.resolve(base);
const targetAbs = path.resolve(baseAbs, rel);
if (!targetAbs.startsWith(baseAbs + path.sep)) return null;
return targetAbs;
};
// @route DELETE /api/media/:id // @route DELETE /api/media/:id
// @desc Delete media file
// @access Private (Admin)
router.delete('/:id', adminAuth, async (req, res) => { router.delete('/:id', adminAuth, async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const media = await Media.findById(id); const media = await Media.findById(id);
if (!media) { if (!media) return res.status(404).json({ success: false, message: 'Media not found' });
return res.status(404).json({
success: false,
message: 'Media not found',
});
}
// Delete file from filesystem (only if inside UPLOAD_DIR) // Delete file from filesystem (based on /uploads/.. url)
try { try {
const url = String(media.url || ''); if (media.url && media.url.startsWith(urlPrefix + '/')) {
let rel = null; const relative = media.url.slice(urlPrefix.length + 1); // remove "/uploads/"
const filePath = resolveSafePath(baseDir, relative);
if (url.startsWith(PUBLIC_BASE + '/')) { await fs.unlink(filePath);
rel = url.slice((PUBLIC_BASE + '/').length);
} else if (url.startsWith('/uploads/')) {
rel = url.slice('/uploads/'.length);
}
if (rel) {
const filePath = safeResolveInside(UPLOAD_DIR, rel);
if (filePath && fsNative.existsSync(filePath)) {
await fs.unlink(filePath);
}
} }
} catch (err) { } catch (err) {
console.error('Error deleting file:', err); console.error('Error deleting file:', err);
@@ -360,36 +271,21 @@ router.delete('/:id', adminAuth, async (req, res) => {
await Media.findByIdAndDelete(id); await Media.findByIdAndDelete(id);
res.json({ res.json({ success: true, message: 'Media deleted successfully' });
success: true,
message: 'Media deleted successfully',
});
} catch (error) { } catch (error) {
console.error('Delete media error:', error); console.error('Delete media error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error deleting media' });
success: false,
message: 'Error deleting media',
});
} }
}); });
// @route GET /api/media/folders/list // @route GET /api/media/folders/list
// @desc Get list of all folders
// @access Private (Admin)
router.get('/folders/list', adminAuth, async (req, res) => { router.get('/folders/list', adminAuth, async (req, res) => {
try { try {
const folders = await Media.distinct('folder'); const folders = await Media.distinct('folder');
res.json({ success: true, data: { folders } });
res.json({
success: true,
data: { folders },
});
} catch (error) { } catch (error) {
console.error('Get folders error:', error); console.error('Get folders error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error fetching folders' });
success: false,
message: 'Error fetching folders',
});
} }
}); });

View File

@@ -6,82 +6,72 @@ const fs = require('fs');
const sharp = require('sharp'); const sharp = require('sharp');
const adminAuth = require('../middleware/adminAuth'); const adminAuth = require('../middleware/adminAuth');
// ======================= // --- Helpers ---
// Config (LOCAL + PROD) const getUploadsDir = () => {
// ======================= const envDir = (process.env.UPLOADS_DIR || '').trim();
const UPLOAD_DIR = process.env.UPLOAD_DIR if (envDir) return path.isAbsolute(envDir) ? envDir : path.resolve(process.cwd(), envDir);
? path.resolve(process.env.UPLOAD_DIR) return path.resolve(__dirname, '..', 'uploads');
: path.resolve(__dirname, '..', 'uploads'); // local fallback };
const PUBLIC_BASE = process.env.UPLOAD_PUBLIC_URL || '/uploads'; const uploadsDir = getUploadsDir();
const urlPrefix = (process.env.UPLOADS_URL_PREFIX || '/uploads').replace(/\/$/, '');
// Ensure uploads directory exists // Ensure uploads directory exists
if (!fs.existsSync(UPLOAD_DIR)) { if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true }); fs.mkdirSync(uploadsDir, { recursive: true });
} }
// Configure multer for file upload // Configure multer for file upload
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: (req, file, cb) => { destination: (req, file, cb) => cb(null, uploadsDir),
cb(null, UPLOAD_DIR);
},
filename: (req, file, cb) => { filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname).toLowerCase() || '.jpg'; const ext = path.extname(file.originalname).toLowerCase();
const name = path const name = path
.basename(file.originalname, ext) .basename(file.originalname, ext)
.replace(/[^a-z0-9]/gi, '-') .replace(/[^a-z0-9]/gi, '-')
.toLowerCase()
.replace(/-+/g, '-') .replace(/-+/g, '-')
.replace(/(^-|-$)/g, ''); .replace(/^-|-$/g, '')
.toLowerCase();
cb(null, `${name || 'image'}-${uniqueSuffix}${ext}`); cb(null, `${name || 'file'}-${uniqueSuffix}${ext || ''}`);
}, },
}); });
const fileFilter = (req, file, cb) => { const fileFilter = (req, file, cb) => {
// Accept images only // Accept images only
if (file.mimetype && file.mimetype.startsWith('image/')) { if (file.mimetype && file.mimetype.startsWith('image/')) return cb(null, true);
cb(null, true); cb(new Error('Only image files are allowed'), false);
} else {
cb(new Error('Only image files are allowed'), false);
}
}; };
const upload = multer({ const upload = multer({
storage, storage,
fileFilter, fileFilter,
limits: { limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileSize: 10 * 1024 * 1024, // 10MB limit
},
}); });
const optimizeImageInPlace = async (filePath, ext) => { async function optimizeImageIfPossible(filePath, extLower) {
const lowerExt = (ext || '').toLowerCase(); if (extLower === '.gif') return;
// GIF: skip optimization (sharp may drop animation) const tmpPath = `${filePath}.tmp`;
if (lowerExt === '.gif') return;
const tmpPath = filePath + '.tmp'; let pipeline = sharp(filePath).resize(1920, 1080, {
fit: 'inside',
withoutEnlargement: true,
});
const pipeline = sharp(filePath) if (extLower === '.png') {
.rotate() pipeline = pipeline.png({ quality: 85, compressionLevel: 9 });
.resize(1920, 1080, { } else if (extLower === '.webp') {
fit: 'inside', pipeline = pipeline.webp({ quality: 85 });
withoutEnlargement: true,
});
if (lowerExt === '.png') {
await pipeline.png({ compressionLevel: 9 }).toFile(tmpPath);
} else if (lowerExt === '.webp') {
await pipeline.webp({ quality: 85 }).toFile(tmpPath);
} else { } else {
// jpg/jpeg + anything else pipeline = pipeline.jpeg({ quality: 85 });
await pipeline.jpeg({ quality: 85 }).toFile(tmpPath);
} }
await pipeline.toFile(tmpPath);
fs.unlinkSync(filePath);
fs.renameSync(tmpPath, filePath); fs.renameSync(tmpPath, filePath);
}; }
// @route POST /api/upload // @route POST /api/upload
// @desc Upload single or multiple images // @desc Upload single or multiple images
@@ -89,40 +79,35 @@ const optimizeImageInPlace = async (filePath, ext) => {
router.post('/', adminAuth, upload.array('images', 10), async (req, res) => { router.post('/', adminAuth, upload.array('images', 10), async (req, res) => {
try { try {
if (!req.files || req.files.length === 0) { if (!req.files || req.files.length === 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'No files uploaded' });
success: false,
message: 'No files uploaded',
});
} }
const uploadedFiles = await Promise.all( const uploadedFiles = [];
req.files.map(async (file) => {
try {
const ext = path.extname(file.filename);
await optimizeImageInPlace(file.path, ext);
return { for (const file of req.files) {
filename: file.filename, try {
originalName: file.originalname, const extLower = path.extname(file.filename).toLowerCase();
url: `${PUBLIC_BASE}/${file.filename}`, await optimizeImageIfPossible(file.path, extLower);
size: fs.statSync(file.path).size,
mimeType: file.mimetype, uploadedFiles.push({
uploadedAt: new Date(), filename: file.filename,
}; originalName: file.originalname,
} catch (error) { url: `${urlPrefix}/${file.filename}`,
console.error('Error processing image:', error); mimeType: file.mimetype,
return { uploadedAt: new Date(),
filename: file.filename, });
originalName: file.originalname, } catch (e) {
url: `${PUBLIC_BASE}/${file.filename}`, console.error('Error processing image:', e);
size: file.size, uploadedFiles.push({
mimeType: file.mimetype, filename: file.filename,
uploadedAt: new Date(), originalName: file.originalname,
error: 'Optimization failed', url: `${urlPrefix}/${file.filename}`,
}; mimeType: file.mimetype,
} uploadedAt: new Date(),
}) error: 'Optimization failed',
); });
}
}
res.json({ res.json({
success: true, success: true,
@@ -131,10 +116,7 @@ router.post('/', adminAuth, upload.array('images', 10), async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: error.message || 'Error uploading files' });
success: false,
message: error.message || 'Error uploading files',
});
} }
}); });
@@ -143,33 +125,26 @@ router.post('/', adminAuth, upload.array('images', 10), async (req, res) => {
// @access Private (Admin) // @access Private (Admin)
router.get('/list', adminAuth, async (req, res) => { router.get('/list', adminAuth, async (req, res) => {
try { try {
const files = fs.readdirSync(UPLOAD_DIR); const files = fs.readdirSync(uploadsDir);
const fileList = files const fileList = files
.filter((file) => !file.startsWith('.')) .filter((f) => !f.startsWith('.'))
.map((filename) => { .map((filename) => {
const filePath = path.join(UPLOAD_DIR, filename); const filePath = path.join(uploadsDir, filename);
const stats = fs.statSync(filePath); const stats = fs.statSync(filePath);
return { return {
filename, filename,
url: `${PUBLIC_BASE}/${filename}`, url: `${urlPrefix}/${filename}`,
size: stats.size, size: stats.size,
uploadedAt: stats.mtime, uploadedAt: stats.mtime,
}; };
}) })
.sort((a, b) => b.uploadedAt - a.uploadedAt); .sort((a, b) => b.uploadedAt - a.uploadedAt);
res.json({ res.json({ success: true, data: { files: fileList, total: fileList.length } });
success: true,
data: { files: fileList, total: fileList.length },
});
} catch (error) { } catch (error) {
console.error('List files error:', error); console.error('List files error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error listing files' });
success: false,
message: 'Error listing files',
});
} }
}); });
@@ -180,35 +155,22 @@ router.delete('/:filename', adminAuth, async (req, res) => {
try { try {
const { filename } = req.params; const { filename } = req.params;
// Security check // Security check: prevent path traversal
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'Invalid filename' });
success: false,
message: 'Invalid filename',
});
} }
const filePath = path.join(UPLOAD_DIR, filename); const filePath = path.join(uploadsDir, filename);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
return res.status(404).json({ return res.status(404).json({ success: false, message: 'File not found' });
success: false,
message: 'File not found',
});
} }
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
res.json({ success: true, message: 'File deleted successfully' });
res.json({
success: true,
message: 'File deleted successfully',
});
} catch (error) { } catch (error) {
console.error('Delete file error:', error); console.error('Delete file error:', error);
res.status(500).json({ res.status(500).json({ success: false, message: 'Error deleting file' });
success: false,
message: 'Error deleting file',
});
} }
}); });