// Trigger restart 2 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 bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { authenticateToken, requireAdmin } = require('./middleware/auth'); const JWT_SECRET = process.env.JWT_SECRET || 'metatron-admin-secret-key'; process.env.JWT_SECRET = JWT_SECRET; const app = express(); const PORT = process.env.PORT; // CORS Middleware - Add this before other middleware app.use((req, res, next) => { const origin = req.headers.origin; if (origin) { res.header('Access-Control-Allow-Origin', origin); res.header('Access-Control-Allow-Credentials', 'true'); res.header('Vary', 'Origin'); } else { 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') { return res.sendStatus(200); } next(); }); app.options('*', (req, res) => { res.sendStatus(200); }); // 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, enableKeepAlive: true, keepAliveInitialDelay: 10000, idleTimeout: 60000, // Close idle connections after 60s to prevent reuse of dead connections charset: 'utf8mb4' }); async function ensureAdminUsersTable() { try { await pool.execute(` CREATE TABLE IF NOT EXISTS admin_users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role VARCHAR(50) DEFAULT 'admin', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); const [columns] = await pool.execute('SHOW COLUMNS FROM admin_users'); const columnNames = columns.map(column => column.Field); if (!columnNames.includes('name')) { await pool.execute('ALTER TABLE admin_users ADD COLUMN name VARCHAR(255)'); } if (!columnNames.includes('email')) { await pool.execute('ALTER TABLE admin_users ADD COLUMN email VARCHAR(255) NOT NULL UNIQUE'); } if (!columnNames.includes('password_hash')) { await pool.execute('ALTER TABLE admin_users ADD COLUMN password_hash VARCHAR(255) NOT NULL'); } if (!columnNames.includes('role')) { await pool.execute("ALTER TABLE admin_users ADD COLUMN role VARCHAR(50) DEFAULT 'admin'"); } const [existingAdminRows] = await pool.execute('SELECT id FROM admin_users WHERE email = ?', ['admin@example.com']); if (existingAdminRows.length === 0) { const defaultHash = await bcrypt.hash('adminpassword', 10); await pool.execute( 'INSERT INTO admin_users (name, email, password_hash, role) VALUES (?, ?, ?, ?)', ['Admin', 'admin@example.com', defaultHash, 'admin'] ); console.log('✅ Default admin user created'); } } catch (error) { console.error('❌ Failed to ensure admin_users table:', error.message); throw error; } } async function ensureEventImagesSortOrderColumn() { try { const [columns] = await pool.execute("SHOW COLUMNS FROM event_images"); const columnNames = columns.map(col => col.Field); if (!columnNames.includes("sort_order")) { console.log("Adding sort_order column to event_images..."); await pool.execute(` ALTER TABLE event_images ADD COLUMN sort_order INT NOT NULL DEFAULT 0 `); await pool.execute(` UPDATE event_images SET sort_order = id `); console.log("sort_order column added successfully."); } else { console.log("sort_order column already exists."); } } catch (error) { console.error("Failed to ensure sort_order column:", error.message); throw error; } } async function ensureUpcomingEventsTable() { try { await pool.execute(` CREATE TABLE IF NOT EXISTS upcoming_events ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), eventtitle VARCHAR(255), slug VARCHAR(255), date VARCHAR(255), eventdate VARCHAR(255), time VARCHAR(255), location VARCHAR(255), image TEXT, eventimageurl TEXT, link TEXT, btn_text VARCHAR(255) DEFAULT 'Details Coming Soon', btnText VARCHAR(255) DEFAULT 'Details Coming Soon', description TEXT, eventdescription TEXT, admission VARCHAR(255), sort_order INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); const [columns] = await pool.execute("SHOW COLUMNS FROM upcoming_events"); const colNames = columns.map(col => col.Field); if (colNames.includes('year')) { try { await pool.execute("ALTER TABLE upcoming_events MODIFY COLUMN year INT NULL DEFAULT 2026"); } catch (err) { console.warn('Could not modify year column:', err.message); } } const columnsToAdd = [ { name: 'title', type: 'VARCHAR(255)' }, { name: 'date', type: 'VARCHAR(255)' }, { name: 'image', type: 'TEXT' }, { name: 'description', type: 'TEXT' }, { name: 'btn_text', type: "VARCHAR(255) DEFAULT 'Details Coming Soon'" }, { name: 'admission', type: 'VARCHAR(255)' }, { name: 'sort_order', type: 'INT DEFAULT 0' } ]; for (const col of columnsToAdd) { if (!colNames.includes(col.name)) { await pool.execute(`ALTER TABLE upcoming_events ADD COLUMN ${col.name} ${col.type}`); } } console.log('✅ Verified upcoming_events table'); const [existing] = await pool.execute( 'SELECT id FROM upcoming_events WHERE title = ? OR eventtitle = ?', ['KW Multicultural Festival', 'KW Multicultural Festival'] ); if (existing.length === 0) { const hasYearCol = colNames.includes('year'); if (hasYearCol) { await pool.execute( `INSERT INTO upcoming_events (year, title, eventtitle, slug, date, eventdate, time, location, image, eventimageurl, link, btn_text, btnText, description, eventdescription) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ 2026, 'KW Multicultural Festival', 'KW Multicultural Festival', 'kw-multicultural-festival-2026', 'Jun 20, 2026 and Jun 21, 2026', 'Jun 20, 2026 and Jun 21, 2026', 'Details will be announced', 'Victoria Park, Kitchener', '/assets/img/event/upcoming-event/multicultural-festival.webp', '/assets/img/event/upcoming-event/multicultural-festival.webp', '/upcoming-event/kw-multicultural-festival-2026', 'Details Coming Soon', 'Details Coming Soon', 'KW Multicultural Festival will take place on Jun 20-21, 2026 at Victoria Park, Kitchener. More details will be updated soon.', 'KW Multicultural Festival will take place on Jun 20-21, 2026 at Victoria Park, Kitchener. More details will be updated soon.' ] ); } else { await pool.execute( `INSERT INTO upcoming_events (title, eventtitle, slug, date, eventdate, time, location, image, eventimageurl, link, btn_text, btnText, description, eventdescription) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ 'KW Multicultural Festival', 'KW Multicultural Festival', 'kw-multicultural-festival-2026', 'Jun 20, 2026 and Jun 21, 2026', 'Jun 20, 2026 and Jun 21, 2026', 'Details will be announced', 'Victoria Park, Kitchener', '/assets/img/event/upcoming-event/multicultural-festival.webp', '/assets/img/event/upcoming-event/multicultural-festival.webp', '/upcoming-event/kw-multicultural-festival-2026', 'Details Coming Soon', 'Details Coming Soon', 'KW Multicultural Festival will take place on Jun 20-21, 2026 at Victoria Park, Kitchener. More details will be updated soon.', 'KW Multicultural Festival will take place on Jun 20-21, 2026 at Victoria Park, Kitchener. More details will be updated soon.' ] ); } console.log('✅ Seeded initial upcoming event: KW Multicultural Festival'); } } catch (error) { console.error('❌ Failed to ensure upcoming_events table:', error.message); throw error; } } async function ensureBlogsTable() { try { await pool.execute(` CREATE TABLE IF NOT EXISTS blogs ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, slug VARCHAR(255) UNIQUE NOT NULL, image TEXT, description TEXT, author VARCHAR(255) DEFAULT 'Admin', profile VARCHAR(255) DEFAULT '/assets/images/profile-1.jpeg', date VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); console.log('✅ Verified blogs table'); // Seed initial blogs if empty const [blogRows] = await pool.execute('SELECT id FROM blogs LIMIT 1'); if (blogRows.length === 0) { const initialBlogs = [ { title: 'Save soil, save world Projects in 2020', slug: 'save-soil-save-world', image: '/assets/img/all-images/blog/blog-img4.png', description: '

At Eventify 2024, you\'ll join an exclusive gathering of business leaders and innovators shaping the future of their industries. This one-day conference offers dynamic sessions on leadership, technology, and strategy to help you stay ahead in today\'s competitive market. Whether you\'re looking to unlock new opportunities or build lasting eventify partnerships, Eventify is where you need to be.

', author: 'Beverly', profile: '/assets/images/profile-1.jpeg', date: '26 Jan 2025' }, { title: 'Civil Litigation paper’s Of Conference', slug: 'civil-litigation-papers', image: '/assets/img/all-images/blog/blog-img5.png', description: '

Fuel your business growth with actionable insights from world-class experts at Eventify 2024. This premier event brings together forward-thinking professionals to explore the latest trends, technologies, and strategies for success. From keynote speeches to interactive workshops, Eventify provides you with the tools you need.

', author: 'Gisselle', profile: '/assets/images/profile-2.jpeg', date: '26 Jan 2025' }, { title: 'Greetings and Opening Event of health', slug: 'greetings-and-opening-event', image: '/assets/img/all-images/blog/blog-img6.png', description: '

Join us at Eventify 2024, where innovation meets opportunity. This conference is the ultimate destination for business leaders seeking to push the boundaries of what\'s possible. With sessions on disruptive technologies, leadership trends, and market strategies, you\'ll walk away with the knowledge and connections to lead.

', author: 'Mertie', profile: '/assets/images/profile-3.jpeg', date: '26 Jan 2025' } ]; for (const blog of initialBlogs) { await pool.execute( 'INSERT IGNORE INTO blogs (title, slug, image, description, author, profile, date) VALUES (?, ?, ?, ?, ?, ?, ?)', [blog.title, blog.slug, blog.image, blog.description, blog.author, blog.profile, blog.date] ); } console.log('✅ Seeded initial blogs successfully.'); } } catch (error) { console.error('❌ Failed to ensure blogs table:', error.message); throw error; } } // 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(); await ensureAdminUsersTable(); await ensureEventImagesSortOrderColumn(); await ensureUpcomingEventsTable(); await ensureBlogsTable(); } catch (error) { console.error('❌ Database connection failed:', error.message); process.exit(1); } } // (Removed custom keep-alive ping) // 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|svg)$/i)) { return cb(new Error('Only image files (jpg, jpeg, png, gif, webp, svg) are allowed!'), false); } cb(null, true); } }); // =============================== // =============================== // AUTHENTICATION ROUTES // =============================== // POST login app.post('/api/auth/login', async (req, res) => { try { const { email, password } = req.body; const normalizedEmail = typeof email === 'string' ? email.trim().toLowerCase() : ''; if (!normalizedEmail || !password) { return res.status(400).json({ success: false, message: 'Email and password are required' }); } const [rows] = await pool.execute('SELECT * FROM admin_users WHERE LOWER(email) = ?', [normalizedEmail]); if (rows.length === 0) { return res.status(401).json({ success: false, message: 'Invalid credentials' }); } const user = rows[0]; const isMatch = await bcrypt.compare(password, user.password_hash); if (!isMatch) { return res.status(401).json({ success: false, message: 'Invalid credentials' }); } const token = jwt.sign({ id: user.id, email: user.email || normalizedEmail, role: user.role || 'admin' }, JWT_SECRET, { expiresIn: '1d' }); res.json({ success: true, message: 'Login successful', data: { token, user: { id: user.id, email: user.email, role: user.role } } }); } catch (error) { console.error('Login error:', error); res.status(500).json({ success: false, message: 'Server error during login' }); } }); // POST register app.post('/api/auth/register', async (req, res) => { try { const { name, email, password } = req.body; if (!name || !email || !password) { return res.status(400).json({ success: false, message: 'Name, email, and password are required' }); } const trimmedName = name.trim(); const trimmedEmail = typeof email === 'string' ? email.trim().toLowerCase() : ''; if (!trimmedName || !trimmedEmail || !password) { return res.status(400).json({ success: false, message: 'Name, email, and password are required' }); } const [existingRows] = await pool.execute('SELECT id FROM admin_users WHERE LOWER(email) = ?', [trimmedEmail]); if (existingRows.length > 0) { return res.status(409).json({ success: false, message: 'Email already exists' }); } const hashedPassword = await bcrypt.hash(password, 10); const [columns] = await pool.execute('SHOW COLUMNS FROM admin_users'); const columnNames = columns.map(column => column.Field); const hasNameColumn = columnNames.includes('name'); const insertFields = hasNameColumn ? ['name', 'email', 'password_hash', 'role'] : ['email', 'password_hash', 'role']; const insertValues = hasNameColumn ? [trimmedName, trimmedEmail, hashedPassword, 'admin'] : [trimmedEmail, hashedPassword, 'admin']; const placeholders = insertFields.map(() => '?').join(', '); const [result] = await pool.execute( `INSERT INTO admin_users (${insertFields.join(', ')}) VALUES (${placeholders})`, insertValues ); const userId = result.insertId; const token = jwt.sign( { id: userId, email: trimmedEmail, role: 'admin' }, JWT_SECRET, { expiresIn: '1d' } ); res.json({ success: true, message: 'User registered successfully', data: { token, user: { id: userId, name: trimmedName, email: trimmedEmail, role: 'admin' } } }); } catch (error) { console.error('Registration error:', error); if (error && error.code === 'ER_NO_SUCH_TABLE') { try { await ensureAdminUsersTable(); return res.status(500).json({ success: false, message: 'Registration failed' }); } catch (retryError) { console.error('Retry registration setup failed:', retryError); } } res.status(500).json({ success: false, message: 'Registration failed' }); } }); app.get('/api/debug/tables', async (req, res) => { try { const [tables] = await pool.execute("SHOW TABLES"); res.json(tables); } catch (err) { console.error(err); res.status(500).json({ error: err.message }); } }); // =============================== // 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 }); } }); // =============================== // UPCOMING EVENTS CRUD ROUTES // =============================== // Helper function to slugify string function slugify(text) { if (!text) return ''; return text .toString() .toLowerCase() .trim() .replace(/\s+/g, '-') // Replace spaces with - .replace(/[^\w\-]+/g, '') // Remove all non-word chars .replace(/\-\-+/g, '-'); // Replace multiple - with single - } // GET all upcoming events app.get('/api/upcoming-events', async (req, res) => { try { const [rows] = await pool.execute('SELECT * FROM upcoming_events ORDER BY sort_order ASC, id ASC'); res.json({ success: true, data: rows }); } catch (error) { console.error('Error fetching upcoming events:', error); res.status(500).json({ success: false, message: 'Error fetching upcoming events', error: error.message }); } }); // GET single upcoming event by ID app.get('/api/upcoming-events/:id', async (req, res) => { try { const [rows] = await pool.execute('SELECT * FROM upcoming_events WHERE id = ?', [req.params.id]); if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Upcoming event not found' }); } res.json({ success: true, data: rows[0] }); } catch (error) { console.error('Error fetching upcoming event:', error); res.status(500).json({ success: false, message: 'Error fetching upcoming event', error: error.message }); } }); // GET single upcoming event by slug or ID app.get('/api/upcoming-events/slug/:slug', async (req, res) => { try { const rawSlug = req.params.slug ? req.params.slug.trim() : ''; const cleanedSlug = slugify(rawSlug); // 1. First priority: exact slug or ID match let [rows] = await pool.execute( 'SELECT * FROM upcoming_events WHERE slug = ? OR slug = ? OR id = ?', [rawSlug, cleanedSlug, rawSlug] ); // 2. Second priority: title slug match fallback if (rows.length === 0 && cleanedSlug) { [rows] = await pool.execute( 'SELECT * FROM upcoming_events WHERE LOWER(REPLACE(title, " ", "-")) = ? OR LOWER(REPLACE(eventtitle, " ", "-")) = ?', [cleanedSlug, cleanedSlug] ); } if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Upcoming event not found' }); } res.json({ success: true, data: rows[0] }); } catch (error) { console.error('Error fetching upcoming event by slug:', error); res.status(500).json({ success: false, message: 'Error fetching upcoming event', error: error.message }); } }); // POST create new upcoming event app.post('/api/upcoming-events', async (req, res) => { try { const { title, eventtitle, slug, date, eventdate, time, location, venue, image, image_url, eventimageurl, link, btn_text, btnText, description, desc, eventdescription, admission } = req.body; const inputTitle = title || eventtitle; if (!inputTitle || String(inputTitle).trim() === '') { return res.status(400).json({ success: false, message: 'Event title is required' }); } const finalTitle = String(inputTitle).trim(); const generatedSlug = slugify(finalTitle); const finalSlug = (slug && String(slug).trim()) ? slugify(String(slug).trim()) : generatedSlug; const finalLocation = location || venue || ''; const finalImage = image || image_url || eventimageurl || ''; const finalBtnText = btn_text || btnText || 'Details Coming Soon'; const finalDesc = description || desc || eventdescription || ''; const finalLink = (link && String(link).trim() && !String(link).startsWith('/upcoming-event/')) ? String(link).trim() : `/upcoming-event/${finalSlug}`; const finalDate = date || eventdate || ''; const finalTime = time || ''; const finalAdmission = admission || null; const [result] = await pool.execute( `INSERT INTO upcoming_events (title, eventtitle, slug, date, eventdate, time, location, image, eventimageurl, link, btn_text, btnText, description, eventdescription, admission) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [finalTitle, finalTitle, finalSlug, finalDate, finalDate, finalTime, finalLocation, finalImage, finalImage, finalLink, finalBtnText, finalBtnText, finalDesc, finalDesc, finalAdmission] ); res.status(201).json({ success: true, message: 'Upcoming event created successfully', data: { id: result.insertId, title: finalTitle, slug: finalSlug, link: finalLink } }); } catch (error) { console.error('Error creating upcoming event:', error); res.status(500).json({ success: false, message: 'Error creating upcoming event', error: error.message }); } }); // PUT update upcoming event (supports full or partial updates) app.put('/api/upcoming-events/:id', async (req, res) => { try { const eventId = req.params.id; // Fetch existing record const [rows] = await pool.execute('SELECT * FROM upcoming_events WHERE id = ?', [eventId]); if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Upcoming event not found' }); } const existing = rows[0]; const { title, eventtitle, slug, date, eventdate, time, location, venue, image, image_url, eventimageurl, link, btn_text, btnText, description, desc, eventdescription, admission, sort_order } = req.body || {}; const inputTitle = title || eventtitle || existing.title || existing.eventtitle; if (!inputTitle || String(inputTitle).trim() === '') { return res.status(400).json({ success: false, message: 'Event title is required' }); } const finalTitle = String(inputTitle).trim(); const generatedSlug = slugify(finalTitle); let finalSlug = generatedSlug; if (slug && String(slug).trim()) { const userSlug = slugify(String(slug).trim()); const oldTitleSlug = slugify(existing.title || existing.eventtitle || ''); if (userSlug !== oldTitleSlug) { finalSlug = userSlug; } } const finalLocation = location !== undefined ? location : (venue !== undefined ? venue : (existing.location || '')); const finalImage = image !== undefined ? image : (image_url !== undefined ? image_url : (eventimageurl !== undefined ? eventimageurl : (existing.image || existing.eventimageurl || ''))); const finalBtnText = btn_text !== undefined ? btn_text : (btnText !== undefined ? btnText : (existing.btn_text || existing.btnText || 'Details Coming Soon')); const finalDesc = description !== undefined ? description : (desc !== undefined ? desc : (eventdescription !== undefined ? eventdescription : (existing.description || existing.eventdescription || ''))); const finalLink = (link && String(link).trim() && !String(link).startsWith('/upcoming-event/')) ? String(link).trim() : `/upcoming-event/${finalSlug}`; const finalDate = date !== undefined ? date : (eventdate !== undefined ? eventdate : (existing.date || existing.eventdate || '')); const finalTime = time !== undefined ? time : (existing.time || ''); const finalAdmission = admission !== undefined ? admission : existing.admission; const finalSortOrder = sort_order !== undefined ? Number(sort_order) : (existing.sort_order || 0); await pool.execute( `UPDATE upcoming_events SET title = ?, eventtitle = ?, slug = ?, date = ?, eventdate = ?, time = ?, location = ?, image = ?, eventimageurl = ?, link = ?, btn_text = ?, btnText = ?, description = ?, eventdescription = ?, admission = ?, sort_order = ? WHERE id = ?`, [finalTitle, finalTitle, finalSlug, finalDate, finalDate, finalTime, finalLocation, finalImage, finalImage, finalLink, finalBtnText, finalBtnText, finalDesc, finalDesc, finalAdmission, finalSortOrder, eventId] ); res.json({ success: true, message: 'Upcoming event updated successfully' }); } catch (error) { console.error('Error updating upcoming event:', error); res.status(500).json({ success: false, message: 'Error updating upcoming event', error: error.message }); } }); // DELETE upcoming event app.delete('/api/upcoming-events/:id', async (req, res) => { try { const [result] = await pool.execute('DELETE FROM upcoming_events WHERE id = ?', [req.params.id]); if (result.affectedRows === 0) { return res.status(404).json({ success: false, message: 'Upcoming event not found' }); } res.json({ success: true, message: 'Upcoming event deleted successfully' }); } catch (error) { console.error('Error deleting upcoming event:', error); res.status(500).json({ success: false, message: 'Error deleting upcoming event', error: error.message }); } }); // =============================== // BLOGS CRUD ROUTES // =============================== // GET all blogs app.get('/api/blogs', async (req, res) => { try { const [rows] = await pool.execute('SELECT * FROM blogs ORDER BY id DESC'); res.json({ success: true, data: rows }); } catch (error) { console.error('Error fetching blogs:', error); res.status(500).json({ success: false, message: 'Error fetching blogs', error: error.message }); } }); // GET single blog by ID app.get('/api/blogs/:id', async (req, res) => { try { const [rows] = await pool.execute('SELECT * FROM blogs WHERE id = ?', [req.params.id]); if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Blog not found' }); } res.json({ success: true, data: rows[0] }); } catch (error) { console.error('Error fetching blog:', error); res.status(500).json({ success: false, message: 'Error fetching blog', error: error.message }); } }); // GET single blog by slug app.get('/api/blogs/slug/:slug', async (req, res) => { try { const rawSlug = req.params.slug ? req.params.slug.trim() : ''; const cleanedSlug = slugify(rawSlug); let [rows] = await pool.execute( 'SELECT * FROM blogs WHERE slug = ? OR id = ?', [rawSlug, rawSlug] ); if (rows.length === 0 && cleanedSlug) { [rows] = await pool.execute( 'SELECT * FROM blogs WHERE slug = ? OR LOWER(REPLACE(title, " ", "-")) = ?', [cleanedSlug, cleanedSlug] ); } if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Blog not found' }); } res.json({ success: true, data: rows[0] }); } catch (error) { console.error('Error fetching blog by slug:', error); res.status(500).json({ success: false, message: 'Error fetching blog', error: error.message }); } }); // POST create new blog app.post('/api/blogs', async (req, res) => { try { const { title, slug, image, description, author, profile, date } = req.body; if (!title || String(title).trim() === '') { return res.status(400).json({ success: false, message: 'Blog title is required' }); } const finalTitle = String(title).trim(); const generatedSlug = slugify(finalTitle); const finalSlug = (slug && String(slug).trim()) ? slugify(String(slug).trim()) : generatedSlug; const finalImage = image || ''; const finalDesc = description || ''; const finalAuthor = author || 'Admin'; const finalProfile = profile || '/assets/images/profile-1.jpeg'; // Format current date as "DD MMM YYYY" or similar if no date passed let finalDate = date; if (!finalDate) { const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const now = new Date(); finalDate = `${now.getDate()} ${months[now.getMonth()]} ${now.getFullYear()}`; } const [result] = await pool.execute( 'INSERT INTO blogs (title, slug, image, description, author, profile, date) VALUES (?, ?, ?, ?, ?, ?, ?)', [finalTitle, finalSlug, finalImage, finalDesc, finalAuthor, finalProfile, finalDate] ); res.status(201).json({ success: true, message: 'Blog created successfully', data: { id: result.insertId, slug: finalSlug } }); } catch (error) { console.error('Error creating blog:', error); if (error && error.code === 'ER_DUP_ENTRY') { return res.status(409).json({ success: false, message: 'A blog with this title or slug already exists' }); } res.status(500).json({ success: false, message: 'Error creating blog', error: error.message }); } }); // PUT update blog app.put('/api/blogs/:id', async (req, res) => { try { const blogId = req.params.id; // Fetch existing record const [rows] = await pool.execute('SELECT * FROM blogs WHERE id = ?', [blogId]); if (rows.length === 0) { return res.status(404).json({ success: false, message: 'Blog not found' }); } const existing = rows[0]; const { title, slug, image, description, author, profile, date } = req.body || {}; const inputTitle = title || existing.title; if (!inputTitle || String(inputTitle).trim() === '') { return res.status(400).json({ success: false, message: 'Blog title is required' }); } const finalTitle = String(inputTitle).trim(); const generatedSlug = slugify(finalTitle); let finalSlug = generatedSlug; if (slug && String(slug).trim()) { const userSlug = slugify(String(slug).trim()); const oldTitleSlug = slugify(existing.title || ''); if (userSlug !== oldTitleSlug) { finalSlug = userSlug; } } const finalImage = image !== undefined ? image : existing.image; const finalDesc = description !== undefined ? description : existing.description; const finalAuthor = author !== undefined ? author : existing.author; const finalProfile = profile !== undefined ? profile : existing.profile; const finalDate = date !== undefined ? date : existing.date; await pool.execute( 'UPDATE blogs SET title = ?, slug = ?, image = ?, description = ?, author = ?, profile = ?, date = ? WHERE id = ?', [finalTitle, finalSlug, finalImage, finalDesc, finalAuthor, finalProfile, finalDate, blogId] ); res.json({ success: true, message: 'Blog updated successfully' }); } catch (error) { console.error('Error updating blog:', error); res.status(500).json({ success: false, message: 'Error updating blog', error: error.message }); } }); // DELETE blog app.delete('/api/blogs/:id', async (req, res) => { try { const [result] = await pool.execute('DELETE FROM blogs WHERE id = ?', [req.params.id]); if (result.affectedRows === 0) { return res.status(404).json({ success: false, message: 'Blog not found' }); } res.json({ success: true, message: 'Blog deleted successfully' }); } catch (error) { console.error('Error deleting blog:', error); res.status(500).json({ success: false, message: 'Error deleting blog', 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 = ? ORDER BY sort_order ASC, id ASC', [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 }); } }); // Bulk reorder images app.put('/api/event-images/reorder', async (req, res) => { try { console.log("Request body:", req.body); const { images } = req.body; // Array of { id, sort_order } console.log(`Reorder request received for ${images?.length} images`); if (!Array.isArray(images) || images.length === 0) { return res.status(400).json({ success: false, message: 'Images array is required for reordering' }); } // Using a transaction for bulk update const connection = await pool.getConnection(); try { await connection.beginTransaction(); for (const item of images) { await connection.execute( 'UPDATE event_images SET sort_order = ? WHERE id = ?', [item.sort_order, item.id] ); } await connection.commit(); res.json({ success: true, message: 'Images reordered successfully' }); } catch (err) { console.error('Transaction failed, rolling back:', err); await connection.rollback(); throw err; } finally { connection.release(); } } catch (error) { console.error("===== REORDER ERROR ====="); console.error(error); console.error("Message:", error.message); console.error("Code:", error.code); console.error("========================="); res.status(500).json({ success: false, message: error.message, code: error.code }); } }); 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', 50), 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', 50), 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!' }); }); app.get('/api/debug/tables', async (req, res) => { try { const [tables] = await pool.execute('SHOW TABLES'); res.json(tables); } catch (error) { console.error(error); res.status(500).json({ success: false, message: error.message }); } }); // 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;