From f306a2539d2115722c1c375edb64e60196338054 Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 3 Jul 2025 16:33:49 +0200 Subject: [PATCH] Add gallery routes --- backend/src/routes/gallery.js | 199 ++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 backend/src/routes/gallery.js diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js new file mode 100644 index 0000000..c100015 --- /dev/null +++ b/backend/src/routes/gallery.js @@ -0,0 +1,199 @@ +const express = require('express'); +const jwt = require('jsonwebtoken'); +const { db } = require('../database/db'); +const archiver = require('archiver'); +const path = require('path'); +const router = express.Router(); + +// Middleware to verify gallery access +async function verifyGalleryAccess(req, res, next) { + try { + const token = req.headers.authorization?.split(' ')[1]; + if (!token) { + return res.status(401).json({ error: 'No token provided' }); + } + + const decoded = jwt.verify(token, process.env.JWT_SECRET); + const event = await db('events').where({ id: decoded.eventId, is_active: true }).first(); + + if (!event) { + return res.status(404).json({ error: 'Gallery not found or expired' }); + } + + req.event = event; + next(); + } catch (error) { + res.status(401).json({ error: 'Invalid token' }); + } +} + +// Get gallery info +router.get('/:slug/info', async (req, res) => { + try { + const { slug } = req.params; + + const event = await db('events') + .where({ slug }) + .select('event_name', 'event_type', 'event_date', 'expires_at', 'is_active') + .first(); + + if (!event) { + return res.status(404).json({ error: 'Gallery not found' }); + } + + res.json({ + ...event, + is_expired: !event.is_active || new Date(event.expires_at) < new Date() + }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch gallery info' }); + } +}); + +// Get all photos +router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => { + try { + const photos = await db('photos') + .where('event_id', req.event.id) + .orderBy('uploaded_at', 'desc'); + + // Log view + await db('access_logs').insert({ + event_id: req.event.id, + ip_address: req.ip, + user_agent: req.headers['user-agent'], + action: 'view' + }); + + res.json({ + event: { + id: req.event.id, + event_name: req.event.event_name, + event_type: req.event.event_type, + event_date: req.event.event_date, + welcome_message: req.event.welcome_message, + color_theme: req.event.color_theme, + expires_at: req.event.expires_at + }, + photos: photos.map(photo => ({ + id: photo.id, + filename: photo.filename, + url: `/photos/${req.event.slug}/${photo.path}`, + thumbnail_url: photo.thumbnail_path ? `/photos/${photo.thumbnail_path}` : null, + type: photo.type, + size: photo.size_bytes, + uploaded_at: photo.uploaded_at + })) + }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch photos' }); + } +}); + +// Download single photo +router.get('/:slug/download/:photoId', verifyGalleryAccess, async (req, res) => { + try { + const { photoId } = req.params; + + const photo = await db('photos') + .where({ id: photoId, event_id: req.event.id }) + .first(); + + if (!photo) { + return res.status(404).json({ error: 'Photo not found' }); + } + + // Update download count + await db('photos').where('id', photoId).increment('download_count', 1); + + // Log download + await db('access_logs').insert({ + event_id: req.event.id, + ip_address: req.ip, + user_agent: req.headers['user-agent'], + action: 'download', + photo_id: photoId + }); + + const filePath = path.join(__dirname, '../../../storage/events/active', req.event.slug, photo.path); + res.download(filePath, photo.filename); + } catch (error) { + res.status(500).json({ error: 'Failed to download photo' }); + } +}); + +// Download all photos as ZIP +router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => { + try { + const photos = await db('photos').where('event_id', req.event.id); + + if (photos.length === 0) { + return res.status(404).json({ error: 'No photos found' }); + } + + res.setHeader('Content-Type', 'application/zip'); + res.setHeader('Content-Disposition', `attachment; filename="${req.event.slug}.zip"`); + + const archive = archiver('zip', { zlib: { level: 5 } }); + archive.on('error', (err) => { + throw err; + }); + + archive.pipe(res); + + // Add photos to archive + for (const photo of photos) { + const filePath = path.join(__dirname, '../../../storage/events/active', req.event.slug, photo.path); + archive.file(filePath, { name: photo.path }); + } + + await archive.finalize(); + + // Log bulk download + await db('access_logs').insert({ + event_id: req.event.id, + ip_address: req.ip, + user_agent: req.headers['user-agent'], + action: 'download_all' + }); + } catch (error) { + res.status(500).json({ error: 'Failed to create download archive' }); + } +}); + +// Get photo stats +router.get('/:slug/stats', verifyGalleryAccess, async (req, res) => { + try { + const totalPhotos = await db('photos') + .where('event_id', req.event.id) + .count('id as count') + .first(); + + const totalViews = await db('access_logs') + .where('event_id', req.event.id) + .where('action', 'view') + .count('id as count') + .first(); + + const totalDownloads = await db('photos') + .where('event_id', req.event.id) + .sum('download_count as total') + .first(); + + const uniqueVisitors = await db('access_logs') + .where('event_id', req.event.id) + .countDistinct('ip_address as count') + .first(); + + res.json({ + total_photos: totalPhotos.count, + total_views: totalViews.count, + total_downloads: totalDownloads.total || 0, + unique_visitors: uniqueVisitors.count + }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch stats' }); + } +}); + +module.exports = router;