Add gallery routes

This commit is contained in:
2025-07-03 16:33:49 +02:00
parent 160f26f104
commit f306a2539d
+199
View File
@@ -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;