const express = require('express'); const { body, query, validationResult } = require('express-validator'); const { db, logActivity } = require('../database/db'); const { adminAuth } = require('../middleware/auth'); const router = express.Router(); const bcrypt = require('bcrypt'); const crypto = require('crypto'); const fs = require('fs').promises; const path = require('path'); // Create new event router.post('/', adminAuth, [ body('event_type').isIn(['wedding', 'birthday', 'corporate', 'other']), body('event_name').notEmpty().trim(), body('event_date').isDate(), body('host_email').isEmail().normalizeEmail(), body('admin_email').isEmail().normalizeEmail(), body('password').isLength({ min: 6 }), body('expiration_days').isInt({ min: 1, max: 365 }).optional(), body('welcome_message').optional().trim(), body('color_theme').optional().trim() ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { event_type, event_name, event_date, host_email, admin_email, password, welcome_message = '', color_theme = 'default', expiration_days = 30 } = req.body; // Generate unique slug const baseSlug = `${event_type}-${event_name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${event_date}`; let slug = baseSlug; let counter = 1; while (await db('events').where({ slug }).first()) { slug = `${baseSlug}-${counter}`; counter++; } // Generate share link const shareToken = crypto.randomBytes(16).toString('hex'); const shareLink = `${process.env.FRONTEND_URL}/gallery/${slug}/${shareToken}`; // Hash password const password_hash = await bcrypt.hash(password, 10); // Calculate expiration date const expires_at = new Date(); expires_at.setDate(expires_at.getDate() + expiration_days); // Create folder structure const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); const eventPath = path.join(storagePath, 'events/active', slug); await fs.mkdir(path.join(eventPath, 'collages'), { recursive: true }); await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); // Insert into database const [eventId] = await db('events').insert({ slug, event_type, event_name, event_date, host_email, admin_email, password_hash, welcome_message, color_theme, share_link: shareLink, expires_at, created_at: new Date() }); // Log activity await logActivity('event_created', { event_type, expires_at }, eventId, { type: 'admin', id: req.user.id, name: req.user.username } ); // Queue creation email await db('email_queue').insert({ event_id: eventId, recipient_email: host_email, email_type: 'creation', email_data: JSON.stringify({ event_name, share_link: shareLink, password, expires_at: expires_at.toISOString() }), created_at: new Date() }); res.json({ id: eventId, slug, event_name, event_type, share_link: shareLink, expires_at }); } catch (error) { console.error('Error creating event:', error); res.status(500).json({ error: 'Failed to create event' }); } }); // Get all events with pagination and filters router.get('/', adminAuth, async (req, res) => { try { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 20; const offset = (page - 1) * limit; const search = req.query.search || ''; const status = req.query.status || 'all'; const sortBy = req.query.sortBy || 'created_at'; const sortOrder = req.query.sortOrder || 'desc'; // Build query let query = db('events'); // Apply search filter if (search) { query = query.where((builder) => { builder.where('event_name', 'like', `%${search}%`) .orWhere('admin_email', 'like', `%${search}%`) .orWhere('slug', 'like', `%${search}%`); }); } // Apply status filter if (status === 'active') { query = query.where('is_active', true).where('is_archived', false); } else if (status === 'archived') { query = query.where('is_archived', true); } else if (status === 'inactive') { query = query.where('is_active', false).where('is_archived', false); } else if (status === 'expiring') { const sevenDaysFromNow = new Date(); sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7); query = query .where('is_active', true) .where('is_archived', false) .where('expires_at', '<=', sevenDaysFromNow.toISOString()) .where('expires_at', '>', new Date().toISOString()); } // Get total count for pagination const countQuery = query.clone(); const [{ count }] = await countQuery.count('* as count'); // Apply sorting and pagination const events = await query .orderBy(sortBy, sortOrder) .limit(limit) .offset(offset); // Get photo counts for each event const eventIds = events.map(e => e.id); const photoCounts = await db('photos') .whereIn('event_id', eventIds) .groupBy('event_id') .select('event_id') .count('* as count'); // Map photo counts to events const photoCountMap = photoCounts.reduce((acc, { event_id, count }) => { acc[event_id] = parseInt(count); return acc; }, {}); // Add photo counts to events const eventsWithCounts = events.map(event => ({ ...event, photo_count: photoCountMap[event.id] || 0 })); res.json({ events: eventsWithCounts, pagination: { page, limit, total: parseInt(count), totalPages: Math.ceil(count / limit) } }); } catch (error) { console.error('Error fetching events:', error); res.status(500).json({ error: 'Failed to fetch events' }); } }); // Get single event details router.get('/:id', adminAuth, async (req, res) => { try { const { id } = req.params; const event = await db('events') .where('id', id) .first(); if (!event) { return res.status(404).json({ error: 'Event not found' }); } // Get photo count const [{ count: photoCount }] = await db('photos') .where('event_id', id) .count('* as count'); // Get total size const [{ totalSize }] = await db('photos') .where('event_id', id) .sum('size_bytes as totalSize'); // Get recent photos const recentPhotos = await db('photos') .where('event_id', id) .orderBy('uploaded_at', 'desc') .limit(10) .select('filename', 'type', 'size_bytes', 'uploaded_at'); res.json({ ...event, photo_count: parseInt(photoCount) || 0, total_size: parseInt(totalSize) || 0, recent_photos: recentPhotos }); } catch (error) { console.error('Error fetching event:', error); res.status(500).json({ error: 'Failed to fetch event details' }); } }); // Update event router.put('/:id', adminAuth, [ body('event_name').optional().trim().notEmpty(), body('admin_email').optional().isEmail(), body('is_active').optional().isBoolean(), body('expires_at').optional().isISO8601(), body('welcome_message').optional().trim(), body('color_theme').optional().trim() ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { id } = req.params; const updates = req.body; // Check if event exists const event = await db('events').where('id', id).first(); if (!event) { return res.status(404).json({ error: 'Event not found' }); } // Update event await db('events') .where('id', id) .update({ ...updates, updated_at: new Date() }); // Log activity await logActivity('event_updated', { changes: Object.keys(updates), eventName: event.event_name }, id, { type: 'admin', id: req.user.id, name: req.user.username } ); res.json({ message: 'Event updated successfully' }); } catch (error) { console.error('Error updating event:', error); res.status(500).json({ error: 'Failed to update event' }); } }); // Delete event router.delete('/:id', adminAuth, async (req, res) => { try { const { id } = req.params; // Check if event exists const event = await db('events').where('id', id).first(); if (!event) { return res.status(404).json({ error: 'Event not found' }); } // Delete associated photos await db('photos').where('event_id', id).del(); // Delete event await db('events').where('id', id).del(); // Log activity await logActivity('event_deleted', { event_name: event.event_name }, null, { type: 'admin', id: req.user.id, name: req.user.username } ); res.json({ message: 'Event deleted successfully' }); } catch (error) { console.error('Error deleting event:', error); res.status(500).json({ error: 'Failed to delete event' }); } }); // Toggle event status router.post('/:id/toggle-status', adminAuth, async (req, res) => { try { const { id } = req.params; const event = await db('events').where('id', id).first(); if (!event) { return res.status(404).json({ error: 'Event not found' }); } const newStatus = !event.is_active; await db('events') .where('id', id) .update({ is_active: newStatus, updated_at: new Date() }); // Log activity await logActivity(newStatus ? 'event_activated' : 'event_deactivated', { eventName: event.event_name }, id, { type: 'admin', id: req.user.id, name: req.user.username } ); res.json({ message: `Event ${newStatus ? 'activated' : 'deactivated'} successfully`, is_active: newStatus }); } catch (error) { console.error('Error toggling event status:', error); res.status(500).json({ error: 'Failed to toggle event status' }); } }); // Archive event router.post('/:id/archive', adminAuth, async (req, res) => { try { const { id } = req.params; const event = await db('events').where('id', id).first(); if (!event) { return res.status(404).json({ error: 'Event not found' }); } if (event.is_archived) { return res.status(400).json({ error: 'Event is already archived' }); } await db('events') .where('id', id) .update({ is_archived: true, is_active: false, archived_at: new Date(), updated_at: new Date() }); // Log activity await logActivity('event_archived', { eventName: event.event_name }, id, { type: 'admin', id: req.user.id, name: req.user.username } ); res.json({ message: 'Event archived successfully' }); } catch (error) { console.error('Error archiving event:', error); res.status(500).json({ error: 'Failed to archive event' }); } }); module.exports = router;