From 160f26f104f0ccf62ef235b1f313385addcbf757 Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 3 Jul 2025 16:33:09 +0200 Subject: [PATCH] Add events routes --- backend/src/routes/events.js | 197 +++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 backend/src/routes/events.js diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js new file mode 100644 index 0000000..430ae88 --- /dev/null +++ b/backend/src/routes/events.js @@ -0,0 +1,197 @@ +const express = require('express'); +const { body, validationResult } = require('express-validator'); +const bcrypt = require('bcrypt'); +const crypto = require('crypto'); +const { db } = require('../database/db'); +const { adminAuth } = require('../middleware/auth'); +const fs = require('fs').promises; +const path = require('path'); +const router = express.Router(); + +// Create new event +router.post('/', adminAuth, [ + body('event_type').isIn(['wedding', 'birthday', 'corporate', 'other']), + body('event_name').notEmpty(), + body('event_date').isDate(), + body('host_email').isEmail(), + body('admin_email').isEmail(), + body('password').isLength({ min: 6 }), + body('expiration_days').isInt({ min: 1, max: 365 }).optional() +], 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, + 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 eventPath = path.join(__dirname, '../../../storage/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 + }); + + // 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 + }) + }); + + res.json({ + id: eventId, + slug, + share_link: shareLink, + expires_at + }); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Failed to create event' }); + } +}); + +// Get all events (admin) +router.get('/', adminAuth, async (req, res) => { + try { + const { status = 'all' } = req.query; + + let query = db('events').select('*'); + + if (status === 'active') { + query = query.where('is_active', true); + } else if (status === 'archived') { + query = query.where('is_archived', true); + } + + const events = await query.orderBy('created_at', 'desc'); + + // Add photo counts + for (const event of events) { + const photoCount = await db('photos').where('event_id', event.id).count('id as count').first(); + event.photo_count = photoCount.count; + } + + res.json(events); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch events' }); + } +}); + +// Update event +router.put('/:id', adminAuth, async (req, res) => { + try { + const { id } = req.params; + const updates = req.body; + + // Don't allow updating certain fields + delete updates.id; + delete updates.slug; + delete updates.created_at; + + // If updating password, hash it + if (updates.password) { + updates.password_hash = await bcrypt.hash(updates.password, 10); + delete updates.password; + } + + await db('events').where('id', id).update(updates); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: 'Failed to update event' }); + } +}); + +// Delete event (mark as inactive) +router.delete('/:id', adminAuth, async (req, res) => { + try { + const { id } = req.params; + + await db('events').where('id', id).update({ is_active: false }); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: 'Failed to delete event' }); + } +}); + +// Extend expiration +router.post('/:id/extend', adminAuth, [ + body('days').isInt({ min: 1, max: 365 }) +], async (req, res) => { + try { + const { id } = req.params; + const { days } = req.body; + + const event = await db('events').where('id', id).first(); + if (!event) { + return res.status(404).json({ error: 'Event not found' }); + } + + const newExpiration = new Date(event.expires_at); + newExpiration.setDate(newExpiration.getDate() + days); + + await db('events').where('id', id).update({ + expires_at: newExpiration, + is_active: true // Reactivate if expired + }); + + res.json({ expires_at: newExpiration }); + } catch (error) { + res.status(500).json({ error: 'Failed to extend expiration' }); + } +}); + +module.exports = router;