Files
picpeak/backend/src/routes/adminEvents.js
T
paul 193cadef27 Fix React error #130 and backend event creation
Frontend fixes:
- Disable verbatimModuleSyntax in TypeScript config to fix module imports
- Add displayName to critical React components for better production debugging
- Configure Vite build with manual chunks for better code splitting
- Enable sourcemaps for production debugging

Backend fixes:
- Remove updated_at field from events table insert (column doesn't exist)
- Fix SQL error that was causing 500 errors on event creation

These changes resolve:
- React error #130 that occurred during login and event creation
- 500 Internal Server Error when creating new events
- Better error tracking in production builds

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-07 13:30:53 +02:00

395 lines
11 KiB
JavaScript

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;