747 lines
23 KiB
JavaScript
747 lines
23 KiB
JavaScript
const express = require('express');
|
|
const mysql = require('mysql2/promise');
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// CORS Middleware - Add this before other middleware
|
|
app.use((req, res, next) => {
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
|
|
|
// Handle preflight requests
|
|
if (req.method === 'OPTIONS') {
|
|
res.sendStatus(200);
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Create uploads directory if it doesn't exist
|
|
const uploadsDir = path.join(__dirname, 'uploads');
|
|
if (!fs.existsSync(uploadsDir)) {
|
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
}
|
|
|
|
// Serve static files from uploads directory
|
|
app.use('/uploads', express.static('uploads'));
|
|
|
|
// MySQL connection pool with optimized settings
|
|
const pool = mysql.createPool({
|
|
host: process.env.DB_HOST,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD,
|
|
database: process.env.DB_NAME,
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0,
|
|
acquireTimeout: 60000,
|
|
timeout: 60000,
|
|
enableKeepAlive: true,
|
|
keepAliveInitialDelay: 0,
|
|
reconnect: true,
|
|
idleTimeout: 300000, // 5 minutes
|
|
maxIdle: 10,
|
|
maxReuse: 1000,
|
|
charset: 'utf8mb4'
|
|
});
|
|
|
|
// Test database connection on startup
|
|
async function testDatabaseConnection() {
|
|
try {
|
|
const connection = await pool.getConnection();
|
|
console.log('✅ Database connected successfully');
|
|
await connection.execute('SELECT 1');
|
|
connection.release();
|
|
} catch (error) {
|
|
console.error('❌ Database connection failed:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Keep connections alive with periodic ping
|
|
setInterval(async () => {
|
|
try {
|
|
const connection = await pool.getConnection();
|
|
await connection.execute('SELECT 1');
|
|
connection.release();
|
|
} catch (error) {
|
|
console.error('Keep-alive ping failed:', error.message);
|
|
}
|
|
}, 240000); // Every 4 minutes
|
|
|
|
// Multer configuration for file uploads
|
|
const storage = multer.diskStorage({
|
|
destination: function (req, file, cb) {
|
|
cb(null, 'uploads/');
|
|
},
|
|
filename: function (req, file, cb) {
|
|
// Create timestamp-based filename
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const randomSuffix = Math.round(Math.random() * 1E9);
|
|
const fileExtension = path.extname(file.originalname);
|
|
const baseName = path.basename(file.originalname, fileExtension);
|
|
|
|
// Format: originalname_timestamp_random.ext
|
|
const filename = `${baseName}_${timestamp}_${randomSuffix}${fileExtension}`;
|
|
cb(null, filename);
|
|
}
|
|
});
|
|
|
|
const upload = multer({
|
|
storage: storage,
|
|
limits: {
|
|
fileSize: 10 * 1024 * 1024 // 10MB limit
|
|
},
|
|
fileFilter: function (req, file, cb) {
|
|
// Accept images only
|
|
if (!file.originalname.match(/\.(jpg|jpeg|png|gif|webp)$/)) {
|
|
return cb(new Error('Only image files are allowed!'), false);
|
|
}
|
|
cb(null, true);
|
|
}
|
|
});
|
|
|
|
// ===============================
|
|
// HEALTH CHECK & STATUS ROUTES
|
|
// ===============================
|
|
|
|
// Health check endpoint
|
|
app.get('/api/health', async (req, res) => {
|
|
try {
|
|
const startTime = Date.now();
|
|
const connection = await pool.getConnection();
|
|
const [rows] = await connection.execute('SELECT 1 as health_check');
|
|
connection.release();
|
|
const dbResponseTime = Date.now() - startTime;
|
|
|
|
res.json({
|
|
success: true,
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
database: {
|
|
status: 'connected',
|
|
responseTime: `${dbResponseTime}ms`
|
|
},
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage()
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
status: 'unhealthy',
|
|
error: error.message,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
});
|
|
|
|
// Database status endpoint
|
|
app.get('/api/db-status', async (req, res) => {
|
|
try {
|
|
const connection = await pool.getConnection();
|
|
const [rows] = await connection.execute('SHOW STATUS LIKE "Threads_connected"');
|
|
connection.release();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
activeConnections: rows[0]?.Value || 'Unknown',
|
|
poolStatus: {
|
|
totalConnections: pool.pool._allConnections.length,
|
|
freeConnections: pool.pool._freeConnections.length,
|
|
acquiringConnections: pool.pool._acquiringConnections.length
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Database status check failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ===============================
|
|
// EVENTS CRUD ROUTES
|
|
// ===============================
|
|
|
|
// GET all events
|
|
app.get('/api/events', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.execute('SELECT * FROM events ORDER BY year DESC, id DESC');
|
|
res.json({
|
|
success: true,
|
|
data: rows
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching events:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching events',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET single event by ID
|
|
app.get('/api/events/:id', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.execute('SELECT * FROM events WHERE id = ?', [req.params.id]);
|
|
if (rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event not found'
|
|
});
|
|
}
|
|
res.json({
|
|
success: true,
|
|
data: rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching event:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching event',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST create new event
|
|
app.post('/api/events', async (req, res) => {
|
|
try {
|
|
const { year, eventdate, eventtitle, eventimageurl, eventdescription } = req.body;
|
|
|
|
if (!year || !eventtitle) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Year and event title are required'
|
|
});
|
|
}
|
|
|
|
const [result] = await pool.execute(
|
|
'INSERT INTO events (year, eventdate, eventtitle, eventimageurl, eventdescription) VALUES (?, ?, ?, ?, ?)',
|
|
[year, eventdate, eventtitle, eventimageurl, eventdescription]
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Event created successfully',
|
|
data: { id: result.insertId }
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating event:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error creating event',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// PUT update event
|
|
app.put('/api/events/:id', async (req, res) => {
|
|
try {
|
|
const { year, eventdate, eventtitle, eventimageurl, eventdescription } = req.body;
|
|
const eventId = req.params.id;
|
|
|
|
if (!year || !eventtitle) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Year and event title are required'
|
|
});
|
|
}
|
|
|
|
const [result] = await pool.execute(
|
|
'UPDATE events SET year = ?, eventdate = ?, eventtitle = ?, eventimageurl = ?, eventdescription = ? WHERE id = ?',
|
|
[year, eventdate, eventtitle, eventimageurl, eventdescription, eventId]
|
|
);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Event updated successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating event:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error updating event',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// DELETE event
|
|
app.delete('/api/events/:id', async (req, res) => {
|
|
try {
|
|
const [result] = await pool.execute('DELETE FROM events WHERE id = ?', [req.params.id]);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Event deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error deleting event:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error deleting event',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ===============================
|
|
// EVENT_IMAGES CRUD ROUTES
|
|
// ===============================
|
|
|
|
// GET all event images
|
|
app.get('/api/event-images', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.execute(`
|
|
SELECT ei.*, e.eventtitle
|
|
FROM event_images ei
|
|
LEFT JOIN events e ON ei.eventid = e.id
|
|
ORDER BY ei.id DESC
|
|
`);
|
|
res.json({
|
|
success: true,
|
|
data: rows
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching event images:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching event images',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET event images by event ID
|
|
app.get('/api/event-images/event/:eventId', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.execute('SELECT * FROM event_images WHERE eventid = ?', [req.params.eventId]);
|
|
res.json({
|
|
success: true,
|
|
data: rows
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching event images:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching event images',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET single event image by ID
|
|
app.get('/api/event-images/:id', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.execute('SELECT * FROM event_images WHERE id = ?', [req.params.id]);
|
|
if (rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event image not found'
|
|
});
|
|
}
|
|
res.json({
|
|
success: true,
|
|
data: rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching event image:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching event image',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST create new event image
|
|
app.post('/api/event-images', async (req, res) => {
|
|
try {
|
|
const { eventid, imageurl } = req.body;
|
|
|
|
if (!eventid || !imageurl) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event ID and image URL are required'
|
|
});
|
|
}
|
|
|
|
// Check if event exists
|
|
const [eventRows] = await pool.execute('SELECT id FROM events WHERE id = ?', [eventid]);
|
|
if (eventRows.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event does not exist'
|
|
});
|
|
}
|
|
|
|
const [result] = await pool.execute(
|
|
'INSERT INTO event_images (eventid, imageurl) VALUES (?, ?)',
|
|
[eventid, imageurl]
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Event image created successfully',
|
|
data: { id: result.insertId }
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating event image:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error creating event image',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post('/api/event-images/bulk', async (req, res) => {
|
|
try {
|
|
const { eventid, imageurl } = req.body;
|
|
|
|
// Input validation
|
|
if (
|
|
!eventid || typeof eventid !== 'number' || isNaN(eventid) ||
|
|
!Array.isArray(imageurl) || imageurl.length === 0 ||
|
|
imageurl.some(url => typeof url !== 'string' || url.trim() === '')
|
|
) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Valid event ID and a non-empty array of image URLs (as strings) are required'
|
|
});
|
|
}
|
|
|
|
// Check if the event exists
|
|
const [eventRows] = await pool.execute(
|
|
'SELECT id FROM events WHERE id = ?',
|
|
[eventid]
|
|
);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event does not exist'
|
|
});
|
|
}
|
|
|
|
// Prepare the SQL placeholders and values
|
|
const placeholders = imageurl.map(() => '(?, ?)').join(', ');
|
|
const values = imageurl.flatMap(url => [eventid, url]);
|
|
|
|
// Debug logs (optional)
|
|
// console.log('SQL:', `INSERT INTO event_images (eventid, imageurl) VALUES ${placeholders}`);
|
|
// console.log('Values:', values);
|
|
|
|
// Execute the bulk insert
|
|
const [result] = await pool.execute(
|
|
`INSERT INTO event_images (eventid, imageurl) VALUES ${placeholders}`,
|
|
values
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Event images inserted successfully',
|
|
insertedCount: imageurl.length,
|
|
data: {
|
|
firstInsertId: result.insertId,
|
|
affectedRows: result.affectedRows
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error inserting event images in bulk:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error inserting event images',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// PUT update event image
|
|
app.put('/api/event-images/:id', async (req, res) => {
|
|
try {
|
|
const { eventid, imageurl } = req.body;
|
|
const imageId = req.params.id;
|
|
|
|
if (!eventid || !imageurl) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event ID and image URL are required'
|
|
});
|
|
}
|
|
|
|
// Check if event exists
|
|
const [eventRows] = await pool.execute('SELECT id FROM events WHERE id = ?', [eventid]);
|
|
if (eventRows.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event does not exist'
|
|
});
|
|
}
|
|
|
|
const [result] = await pool.execute(
|
|
'UPDATE event_images SET eventid = ?, imageurl = ? WHERE id = ?',
|
|
[eventid, imageurl, imageId]
|
|
);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event image not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Event image updated successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating event image:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error updating event image',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// DELETE event image
|
|
app.delete('/api/event-images/:id', async (req, res) => {
|
|
try {
|
|
const [result] = await pool.execute('DELETE FROM event_images WHERE id = ?', [req.params.id]);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Event image not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Event image deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error deleting event image:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error deleting event image',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ===============================
|
|
// FILE UPLOAD ROUTES
|
|
// ===============================
|
|
|
|
// Upload single file
|
|
app.post('/api/upload/single', upload.single('file'), async (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No file uploaded'
|
|
});
|
|
}
|
|
|
|
const filePath = `/uploads/${req.file.filename}`;
|
|
const timestamp = new Date().toISOString();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'File uploaded successfully',
|
|
data: {
|
|
filename: req.file.filename,
|
|
originalname: req.file.originalname,
|
|
path: filePath,
|
|
fullUrl: `${req.protocol}://${req.get('host')}${filePath}`,
|
|
size: req.file.size,
|
|
mimetype: req.file.mimetype,
|
|
uploadedAt: timestamp
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error uploading file:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error uploading file',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Upload multiple files
|
|
app.post('/api/upload/multiple', upload.array('files', 10), async (req, res) => {
|
|
try {
|
|
if (!req.files || req.files.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No files uploaded'
|
|
});
|
|
}
|
|
|
|
const timestamp = new Date().toISOString();
|
|
const uploadedFiles = req.files.map(file => ({
|
|
filename: file.filename,
|
|
originalname: file.originalname,
|
|
path: `/uploads/${file.filename}`,
|
|
fullUrl: `${req.protocol}://${req.get('host')}/uploads/${file.filename}`,
|
|
size: file.size,
|
|
mimetype: file.mimetype,
|
|
uploadedAt: timestamp
|
|
}));
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `${req.files.length} files uploaded successfully`,
|
|
data: uploadedFiles,
|
|
uploadedAt: timestamp
|
|
});
|
|
} catch (error) {
|
|
console.error('Error uploading files:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error uploading files',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Upload and save to event_images table
|
|
app.post('/api/upload/event-images/:eventId', upload.array('files', 10), async (req, res) => {
|
|
try {
|
|
const eventId = req.params.eventId;
|
|
|
|
if (!req.files || req.files.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No files uploaded'
|
|
});
|
|
}
|
|
|
|
// Check if event exists
|
|
const [eventRows] = await pool.execute('SELECT id FROM events WHERE id = ?', [eventId]);
|
|
if (eventRows.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Event does not exist'
|
|
});
|
|
}
|
|
|
|
const timestamp = new Date().toISOString();
|
|
const uploadedFiles = [];
|
|
|
|
// Insert each uploaded file into event_images table
|
|
for (const file of req.files) {
|
|
const filePath = `/uploads/${file.filename}`;
|
|
|
|
const [result] = await pool.execute(
|
|
'INSERT INTO event_images (eventid, imageurl) VALUES (?, ?)',
|
|
[eventId, filePath]
|
|
);
|
|
|
|
uploadedFiles.push({
|
|
id: result.insertId,
|
|
filename: file.filename,
|
|
originalname: file.originalname,
|
|
path: filePath,
|
|
fullUrl: `${req.protocol}://${req.get('host')}${filePath}`,
|
|
size: file.size,
|
|
mimetype: file.mimetype,
|
|
uploadedAt: timestamp,
|
|
eventId: parseInt(eventId)
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `${req.files.length} files uploaded and saved to database`,
|
|
data: uploadedFiles,
|
|
uploadedAt: timestamp
|
|
});
|
|
} catch (error) {
|
|
console.error('Error uploading and saving files:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error uploading and saving files',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((error, req, res, next) => {
|
|
if (error instanceof multer.MulterError) {
|
|
if (error.code === 'LIMIT_FILE_SIZE') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'File too large. Maximum size is 10MB.'
|
|
});
|
|
}
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || 'Something went wrong!'
|
|
});
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, async () => {
|
|
console.log(`🚀 Server is running on port ${PORT}`);
|
|
console.log(`📁 Uploads directory: ${uploadsDir}`);
|
|
|
|
// Test database connection
|
|
await testDatabaseConnection();
|
|
|
|
console.log('🎉 Server is ready to accept requests');
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', async () => {
|
|
console.log('\n🛑 Shutting down gracefully...');
|
|
await pool.end();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', async () => {
|
|
console.log('\n🛑 Shutting down gracefully...');
|
|
await pool.end();
|
|
process.exit(0);
|
|
});
|
|
|
|
module.exports = app; |