Fix multiple production issues and add password change functionality
- Fixed frontend API URL configuration to use correct port 3002 - Fixed create event functionality by adding proper endpoint and fixing JSON parsing - Fixed email settings save functionality by importing logActivity correctly - Fixed admin settings save functionality by using api client instead of direct fetch - Implemented password change functionality with modal and backend endpoint - Added updated_at column to admin_users table - Fixed all mock data issues - now using real backend data throughout 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -92,8 +92,17 @@ async function initializeDatabase() {
|
||||
table.string('password_hash').notNullable();
|
||||
table.boolean('is_active').defaultTo(true);
|
||||
table.datetime('created_at').defaultTo(db.fn.now());
|
||||
table.datetime('updated_at').defaultTo(db.fn.now());
|
||||
table.datetime('last_login');
|
||||
});
|
||||
} else {
|
||||
// Check if updated_at column exists
|
||||
const hasUpdatedAt = await db.schema.hasColumn('admin_users', 'updated_at');
|
||||
if (!hasUpdatedAt) {
|
||||
await db.schema.table('admin_users', (table) => {
|
||||
table.datetime('updated_at').defaultTo(db.fn.now());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Email configuration table
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { db } = require('../database/db');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// Change password
|
||||
router.post('/change-password', [
|
||||
adminAuth,
|
||||
body('currentPassword').notEmpty().withMessage('Current password is required'),
|
||||
body('newPassword').isLength({ min: 6 }).withMessage('New password must be at least 6 characters')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
// Get user from database
|
||||
const user = await db('admin_users')
|
||||
.where('id', userId)
|
||||
.first();
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const validPassword = await bcrypt.compare(currentPassword, user.password_hash);
|
||||
if (!validPassword) {
|
||||
return res.status(400).json({ error: 'Current password is incorrect' });
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// Update password
|
||||
await db('admin_users')
|
||||
.where('id', userId)
|
||||
.update({
|
||||
password_hash: newPasswordHash,
|
||||
updated_at: new Date()
|
||||
});
|
||||
|
||||
res.json({ message: 'Password changed successfully' });
|
||||
} catch (error) {
|
||||
console.error('Password change error:', error);
|
||||
res.status(500).json({ error: 'Failed to change password' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { db } = require('../database/db');
|
||||
const { db, logActivity } = require('../database/db');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
@@ -83,13 +83,11 @@ router.post('/config', [
|
||||
}
|
||||
|
||||
// Log activity
|
||||
await db('activity_logs').insert({
|
||||
activity_type: 'email_config_updated',
|
||||
actor_type: 'admin',
|
||||
actor_id: req.user.id,
|
||||
actor_name: req.user.username,
|
||||
metadata: JSON.stringify({ smtp_host, from_email })
|
||||
});
|
||||
await logActivity('email_config_updated',
|
||||
{ smtp_host, from_email },
|
||||
null,
|
||||
{ type: 'admin', id: req.user.id, name: req.user.username }
|
||||
);
|
||||
|
||||
res.json({ message: 'Email configuration updated successfully' });
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,120 @@
|
||||
const express = require('express');
|
||||
const { body, query, validationResult } = require('express-validator');
|
||||
const { db } = require('../database/db');
|
||||
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 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,
|
||||
created_at: new Date(),
|
||||
updated_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) => {
|
||||
@@ -164,15 +276,11 @@ router.put('/:id', adminAuth, [
|
||||
});
|
||||
|
||||
// Log activity
|
||||
await db.logActivity({
|
||||
type: 'event_updated',
|
||||
actorType: 'admin',
|
||||
actorId: req.user.id,
|
||||
actorName: req.user.username,
|
||||
eventId: id,
|
||||
eventName: event.event_name,
|
||||
metadata: { changes: Object.keys(updates) }
|
||||
});
|
||||
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) {
|
||||
@@ -199,13 +307,11 @@ router.delete('/:id', adminAuth, async (req, res) => {
|
||||
await db('events').where('id', id).del();
|
||||
|
||||
// Log activity
|
||||
await db.logActivity({
|
||||
type: 'event_deleted',
|
||||
actorType: 'admin',
|
||||
actorId: req.user.id,
|
||||
actorName: req.user.username,
|
||||
metadata: { event_name: event.event_name }
|
||||
});
|
||||
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) {
|
||||
@@ -233,14 +339,11 @@ router.post('/:id/toggle-status', adminAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
// Log activity
|
||||
await db.logActivity({
|
||||
type: newStatus ? 'event_activated' : 'event_deactivated',
|
||||
actorType: 'admin',
|
||||
actorId: req.user.id,
|
||||
actorName: req.user.username,
|
||||
eventId: id,
|
||||
eventName: event.event_name
|
||||
});
|
||||
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`,
|
||||
@@ -276,14 +379,11 @@ router.post('/:id/archive', adminAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
// Log activity
|
||||
await db.logActivity({
|
||||
type: 'event_archived',
|
||||
actorType: 'admin',
|
||||
actorId: req.user.id,
|
||||
actorName: req.user.username,
|
||||
eventId: id,
|
||||
eventName: event.event_name
|
||||
});
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user