Add image upload

This commit is contained in:
2025-02-26 16:15:18 -05:00
parent fbb200c4ee
commit 42af434bd7
11 changed files with 680 additions and 44 deletions

View File

@@ -2,6 +2,61 @@ const express = require('express');
const router = express.Router();
const { Client } = require('ssh2');
const mysql = require('mysql2/promise');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// Create uploads directory if it doesn't exist
const uploadsDir = path.join('/var/www/html/inventory/uploads/products');
fs.mkdirSync(uploadsDir, { recursive: true });
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: function (req, file, cb) {
console.log(`Saving to: ${uploadsDir}`);
cb(null, uploadsDir);
},
filename: function (req, file, cb) {
// Create unique filename with original extension
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
// Make sure we preserve the original file extension
let fileExt = path.extname(file.originalname).toLowerCase();
// Ensure there is a proper extension based on mimetype if none exists
if (!fileExt) {
switch (file.mimetype) {
case 'image/jpeg': fileExt = '.jpg'; break;
case 'image/png': fileExt = '.png'; break;
case 'image/gif': fileExt = '.gif'; break;
case 'image/webp': fileExt = '.webp'; break;
default: fileExt = '.jpg'; // Default to jpg
}
}
const fileName = `${req.body.upc || 'product'}-${uniqueSuffix}${fileExt}`;
console.log(`Generated filename: ${fileName} with mimetype: ${file.mimetype}`);
cb(null, fileName);
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB max file size
},
fileFilter: function (req, file, cb) {
// Accept only image files
const filetypes = /jpeg|jpg|png|gif|webp/;
const mimetype = filetypes.test(file.mimetype);
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb(new Error('Only image files are allowed'));
}
});
// Helper function to setup SSH tunnel
async function setupSshTunnel() {
@@ -47,6 +102,89 @@ async function setupSshTunnel() {
});
}
// Image upload endpoint
router.post('/upload-image', upload.single('image'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No image file provided' });
}
// Log file information
console.log('File uploaded:', {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
});
// Ensure the file exists
const filePath = path.join(uploadsDir, req.file.filename);
if (!fs.existsSync(filePath)) {
return res.status(500).json({ error: 'File was not saved correctly' });
}
// Log file access permissions
fs.access(filePath, fs.constants.R_OK, (err) => {
if (err) {
console.error('File permission issue:', err);
} else {
console.log('File is readable');
}
});
// Create URL for the uploaded file - using an absolute URL with domain
// This will generate a URL like: https://inventory.acot.site/uploads/products/filename.jpg
const baseUrl = 'https://inventory.acot.site';
const imageUrl = `${baseUrl}/uploads/products/${req.file.filename}`;
// Return success response with image URL
res.status(200).json({
success: true,
imageUrl,
fileName: req.file.filename,
mimetype: req.file.mimetype,
fullPath: filePath,
message: 'Image uploaded successfully'
});
} catch (error) {
console.error('Error uploading image:', error);
res.status(500).json({ error: error.message || 'Failed to upload image' });
}
});
// Image deletion endpoint
router.delete('/delete-image', (req, res) => {
try {
const { filename } = req.body;
if (!filename) {
return res.status(400).json({ error: 'Filename is required' });
}
const filePath = path.join(uploadsDir, filename);
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'File not found' });
}
// Delete the file
fs.unlinkSync(filePath);
// Return success response
res.status(200).json({
success: true,
message: 'Image deleted successfully'
});
} catch (error) {
console.error('Error deleting image:', error);
res.status(500).json({ error: error.message || 'Failed to delete image' });
}
});
// Get all options for import fields
router.get('/field-options', async (req, res) => {
let ssh;
@@ -267,4 +405,90 @@ router.get('/sublines/:lineId', async (req, res) => {
}
});
// Add a simple endpoint to check file existence and permissions
router.get('/check-file/:filename', (req, res) => {
const { filename } = req.params;
// Prevent directory traversal
if (filename.includes('..') || filename.includes('/')) {
return res.status(400).json({ error: 'Invalid filename' });
}
const filePath = path.join(uploadsDir, filename);
try {
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({
error: 'File not found',
path: filePath,
exists: false,
readable: false
});
}
// Check if file is readable
fs.accessSync(filePath, fs.constants.R_OK);
// Get file stats
const stats = fs.statSync(filePath);
return res.json({
filename,
path: filePath,
exists: true,
readable: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
permissions: stats.mode.toString(8)
});
} catch (error) {
return res.status(500).json({
error: error.message,
path: filePath,
exists: fs.existsSync(filePath),
readable: false
});
}
});
// List all files in uploads directory
router.get('/list-uploads', (req, res) => {
try {
if (!fs.existsSync(uploadsDir)) {
return res.status(404).json({ error: 'Uploads directory not found', path: uploadsDir });
}
const files = fs.readdirSync(uploadsDir);
const fileDetails = files.map(file => {
const filePath = path.join(uploadsDir, file);
try {
const stats = fs.statSync(filePath);
return {
filename: file,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
permissions: stats.mode.toString(8)
};
} catch (error) {
return { filename: file, error: error.message };
}
});
return res.json({
directory: uploadsDir,
count: files.length,
files: fileDetails
});
} catch (error) {
return res.status(500).json({ error: error.message, path: uploadsDir });
}
});
module.exports = router;