upcoming events dynamic
This commit is contained in:
parent
b42f8a9e69
commit
ead2eef1b5
13
.env.example
13
.env.example
@ -1,13 +0,0 @@
|
||||
# Database Configuration
|
||||
DB_HOST=82.25.95.117
|
||||
DB_USER=user_tamilculturewaterloo
|
||||
DB_PASSWORD=Metatron@2025
|
||||
DB_NAME=user_tamilculturewaterloo
|
||||
|
||||
# Server Configuration
|
||||
PORT=3006
|
||||
|
||||
DB_CONNECTION_LIMIT=20
|
||||
DB_ACQUIRE_TIMEOUT=60000
|
||||
DB_TIMEOUT=60000
|
||||
DB_IDLE_TIMEOUT=300000
|
||||
362
server.js
362
server.js
@ -157,6 +157,120 @@ async function ensureEventImagesSortOrderColumn() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Test database connection on startup
|
||||
async function testDatabaseConnection() {
|
||||
try {
|
||||
@ -166,6 +280,7 @@ async function testDatabaseConnection() {
|
||||
connection.release();
|
||||
await ensureAdminUsersTable();
|
||||
await ensureEventImagesSortOrderColumn();
|
||||
await ensureUpcomingEventsTable();
|
||||
} catch (error) {
|
||||
console.error('❌ Database connection failed:', error.message);
|
||||
process.exit(1);
|
||||
@ -548,6 +663,253 @@ app.delete('/api/events/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ===============================
|
||||
// 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 DESC');
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ===============================
|
||||
// EVENT_IMAGES CRUD ROUTES
|
||||
// ===============================
|
||||
|
||||
20
setup-db.js
20
setup-db.js
@ -52,6 +52,26 @@ async function setup() {
|
||||
`);
|
||||
console.log('Verified event_images table exists.');
|
||||
|
||||
// Create upcoming_events table
|
||||
await pool.execute(`
|
||||
CREATE TABLE IF NOT EXISTS upcoming_events (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
slug VARCHAR(255),
|
||||
date VARCHAR(255),
|
||||
time VARCHAR(255),
|
||||
location VARCHAR(255),
|
||||
image TEXT,
|
||||
link TEXT,
|
||||
btn_text VARCHAR(255) DEFAULT 'Details Coming Soon',
|
||||
description TEXT,
|
||||
admission VARCHAR(255),
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('Verified upcoming_events table exists.');
|
||||
|
||||
// Check if admin user exists
|
||||
const [rows] = await pool.execute('SELECT * FROM admin_users WHERE email = ?', ['admin@example.com']);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user