const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); // Create reusable uploads directory if it doesn't exist const uploadsDir = path.join('/var/www/html/inventory/uploads/reusable'); fs.mkdirSync(uploadsDir, { recursive: true }); // Configure multer for file uploads const storage = multer.diskStorage({ destination: function (req, file, cb) { console.log(`Saving reusable image 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 = `reusable-${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')); } }); // Get all reusable images router.get('/', async (req, res) => { try { const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } const result = await pool.query(` SELECT * FROM reusable_images ORDER BY created_at DESC `); res.json(result.rows); } catch (error) { console.error('Error fetching reusable images:', error); res.status(500).json({ error: 'Failed to fetch reusable images', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Get images by company or global images router.get('/by-company/:companyId', async (req, res) => { try { const { companyId } = req.params; const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } // Get images that are either global or belong to this company const result = await pool.query(` SELECT * FROM reusable_images WHERE is_global = true OR company = $1 ORDER BY created_at DESC `, [companyId]); res.json(result.rows); } catch (error) { console.error('Error fetching reusable images by company:', error); res.status(500).json({ error: 'Failed to fetch reusable images by company', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Get global images only router.get('/global', async (req, res) => { try { const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } const result = await pool.query(` SELECT * FROM reusable_images WHERE is_global = true ORDER BY created_at DESC `); res.json(result.rows); } catch (error) { console.error('Error fetching global reusable images:', error); res.status(500).json({ error: 'Failed to fetch global reusable images', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Get a single image by ID router.get('/:id', async (req, res) => { try { const { id } = req.params; const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } const result = await pool.query(` SELECT * FROM reusable_images WHERE id = $1 `, [id]); if (result.rows.length === 0) { return res.status(404).json({ error: 'Reusable image not found' }); } res.json(result.rows[0]); } catch (error) { console.error('Error fetching reusable image:', error); res.status(500).json({ error: 'Failed to fetch reusable image', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Upload a new reusable image router.post('/upload', upload.single('image'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ error: 'No image file provided' }); } const { name, is_global, company } = req.body; // Validate required fields if (!name) { return res.status(400).json({ error: 'Image name is required' }); } // Convert is_global from string to boolean const isGlobal = is_global === 'true' || is_global === true; // Validate company is provided for non-global images if (!isGlobal && !company) { return res.status(400).json({ error: 'Company is required for non-global images' }); } // Log file information console.log('Reusable image 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' }); } // Create URL for the uploaded file const baseUrl = 'https://inventory.acot.site'; const imageUrl = `${baseUrl}/uploads/reusable/${req.file.filename}`; const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } // Insert record into database const result = await pool.query(` INSERT INTO reusable_images ( name, filename, file_path, image_url, is_global, company, mime_type, file_size ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING * `, [ name, req.file.filename, filePath, imageUrl, isGlobal, isGlobal ? null : company, req.file.mimetype, req.file.size ]); // Return success response with image data res.status(201).json({ success: true, image: result.rows[0], message: 'Image uploaded successfully' }); } catch (error) { console.error('Error uploading reusable image:', error); res.status(500).json({ error: error.message || 'Failed to upload image' }); } }); // Update image details (name, is_global, company) router.put('/:id', async (req, res) => { try { const { id } = req.params; const { name, is_global, company } = req.body; // Validate required fields if (!name) { return res.status(400).json({ error: 'Image name is required' }); } // Convert is_global from string to boolean if necessary const isGlobal = typeof is_global === 'string' ? is_global === 'true' : !!is_global; // Validate company is provided for non-global images if (!isGlobal && !company) { return res.status(400).json({ error: 'Company is required for non-global images' }); } const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } // Check if the image exists const checkResult = await pool.query('SELECT * FROM reusable_images WHERE id = $1', [id]); if (checkResult.rows.length === 0) { return res.status(404).json({ error: 'Reusable image not found' }); } const result = await pool.query(` UPDATE reusable_images SET name = $1, is_global = $2, company = $3 WHERE id = $4 RETURNING * `, [ name, isGlobal, isGlobal ? null : company, id ]); res.json(result.rows[0]); } catch (error) { console.error('Error updating reusable image:', error); res.status(500).json({ error: 'Failed to update reusable image', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Delete a reusable image router.delete('/:id', async (req, res) => { try { const { id } = req.params; const pool = req.app.locals.pool; if (!pool) { throw new Error('Database pool not initialized'); } // Get the image data first to get the filename const imageResult = await pool.query('SELECT * FROM reusable_images WHERE id = $1', [id]); if (imageResult.rows.length === 0) { return res.status(404).json({ error: 'Reusable image not found' }); } const image = imageResult.rows[0]; // Delete from database await pool.query('DELETE FROM reusable_images WHERE id = $1', [id]); // Delete the file from filesystem const filePath = path.join(uploadsDir, image.filename); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } res.json({ message: 'Reusable image deleted successfully', image }); } catch (error) { console.error('Error deleting reusable image:', error); res.status(500).json({ error: 'Failed to delete reusable image', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Check if file exists 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 }); } }); // Error handling middleware router.use((err, req, res, next) => { console.error('Reusable images route error:', err); res.status(500).json({ error: 'Internal server error', details: err.message }); }); module.exports = router;