Replace all mock data with real backend integration

- Add database tables for email configs, settings, and activity logs
- Create backend endpoints for dashboard stats, analytics, archives, email config, and settings
- Create frontend service layer (admin, archive, email, settings services)
- Update AdminDashboard to use real statistics and activity data
- Update AnalyticsPage to fetch real analytics from backend
- Update ArchivesPage with pagination and real archive operations
- Update EmailConfigPage to manage real SMTP config and templates
- Remove all mock data and replace with API calls throughout admin interface

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-06 22:46:24 +02:00
parent 3470120a0d
commit 932e5e137c
15 changed files with 1998 additions and 356 deletions
+275
View File
@@ -0,0 +1,275 @@
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs').promises;
const { body, validationResult } = require('express-validator');
const { db } = require('../database/db');
const { adminAuth } = require('../middleware/auth');
const router = express.Router();
// Configure multer for logo uploads
const storage = multer.diskStorage({
destination: async (req, file, cb) => {
const uploadDir = path.join(__dirname, '../../storage/uploads/logos');
await fs.mkdir(uploadDir, { recursive: true });
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
cb(null, `logo-${Date.now()}${ext}`);
}
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif|svg/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Only image files are allowed'));
}
}
});
// Get all settings
router.get('/', adminAuth, async (req, res) => {
try {
const settings = await db('app_settings').select('*');
// Convert to object format
const settingsObject = {};
settings.forEach(setting => {
settingsObject[setting.setting_key] = setting.setting_value
? JSON.parse(setting.setting_value)
: null;
});
res.json(settingsObject);
} catch (error) {
console.error('Settings fetch error:', error);
res.status(500).json({ error: 'Failed to fetch settings' });
}
});
// Get settings by type
router.get('/:type', adminAuth, async (req, res) => {
try {
const { type } = req.params;
const settings = await db('app_settings')
.where('setting_type', type)
.select('*');
// Convert to object format
const settingsObject = {};
settings.forEach(setting => {
settingsObject[setting.setting_key] = setting.setting_value
? JSON.parse(setting.setting_value)
: null;
});
res.json(settingsObject);
} catch (error) {
console.error('Settings fetch error:', error);
res.status(500).json({ error: 'Failed to fetch settings' });
}
});
// Update branding settings
router.put('/branding', adminAuth, async (req, res) => {
try {
const {
company_name,
company_tagline,
support_email,
footer_text,
watermark_enabled
} = req.body;
const brandingSettings = {
company_name,
company_tagline,
support_email,
footer_text,
watermark_enabled
};
// Update or insert each setting
for (const [key, value] of Object.entries(brandingSettings)) {
await db('app_settings')
.insert({
setting_key: `branding_${key}`,
setting_value: JSON.stringify(value),
setting_type: 'branding',
updated_at: new Date()
})
.onConflict('setting_key')
.merge({
setting_value: JSON.stringify(value),
updated_at: new Date()
});
}
// Log activity
await db('activity_logs').insert({
activity_type: 'branding_updated',
actor_type: 'admin',
actor_id: req.user.id,
actor_name: req.user.username,
metadata: JSON.stringify({ company_name })
});
res.json({ message: 'Branding settings updated successfully' });
} catch (error) {
console.error('Branding update error:', error);
res.status(500).json({ error: 'Failed to update branding settings' });
}
});
// Upload logo
router.post('/logo', adminAuth, upload.single('logo'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No logo file uploaded' });
}
// Get old logo to delete
const oldLogoSetting = await db('app_settings')
.where('setting_key', 'branding_logo_path')
.first();
if (oldLogoSetting && oldLogoSetting.setting_value) {
const oldPath = JSON.parse(oldLogoSetting.setting_value);
try {
await fs.unlink(oldPath);
} catch (error) {
console.error('Failed to delete old logo:', error);
}
}
// Save new logo path
const logoPath = req.file.path;
const publicPath = `/uploads/logos/${req.file.filename}`;
await db('app_settings')
.insert({
setting_key: 'branding_logo_path',
setting_value: JSON.stringify(logoPath),
setting_type: 'branding',
updated_at: new Date()
})
.onConflict('setting_key')
.merge({
setting_value: JSON.stringify(logoPath),
updated_at: new Date()
});
// Save public URL
await db('app_settings')
.insert({
setting_key: 'branding_logo_url',
setting_value: JSON.stringify(publicPath),
setting_type: 'branding',
updated_at: new Date()
})
.onConflict('setting_key')
.merge({
setting_value: JSON.stringify(publicPath),
updated_at: new Date()
});
res.json({
message: 'Logo uploaded successfully',
logo_url: publicPath
});
} catch (error) {
console.error('Logo upload error:', error);
res.status(500).json({ error: 'Failed to upload logo' });
}
});
// Update theme settings
router.put('/theme', adminAuth, async (req, res) => {
try {
const themeSettings = req.body;
// Save theme settings
await db('app_settings')
.insert({
setting_key: 'theme_config',
setting_value: JSON.stringify(themeSettings),
setting_type: 'theme',
updated_at: new Date()
})
.onConflict('setting_key')
.merge({
setting_value: JSON.stringify(themeSettings),
updated_at: new Date()
});
// Log activity
await db('activity_logs').insert({
activity_type: 'theme_updated',
actor_type: 'admin',
actor_id: req.user.id,
actor_name: req.user.username,
metadata: JSON.stringify({ theme_name: themeSettings.name || 'custom' })
});
res.json({ message: 'Theme settings updated successfully' });
} catch (error) {
console.error('Theme update error:', error);
res.status(500).json({ error: 'Failed to update theme settings' });
}
});
// Get storage info
router.get('/storage/info', adminAuth, async (req, res) => {
try {
// Get total storage used
const totalStorage = await db('photos')
.sum('size_bytes as total')
.first();
// Get storage by event
const storageByEvent = await db('photos')
.select('events.event_name', 'events.id')
.sum('photos.size_bytes as size')
.join('events', 'photos.event_id', 'events.id')
.groupBy('events.id')
.orderBy('size', 'desc')
.limit(10);
// Get archive storage
const archives = await db('events')
.where('is_archived', true)
.whereNotNull('archive_path')
.select('archive_path');
let archiveStorage = 0;
for (const archive of archives) {
try {
const stats = await fs.stat(archive.archive_path);
archiveStorage += stats.size;
} catch (error) {
console.error('Archive file not found:', archive.archive_path);
}
}
res.json({
total_used: totalStorage.total || 0,
archive_storage: archiveStorage,
storage_by_event: storageByEvent,
storage_limit: 10 * 1024 * 1024 * 1024 // 10GB default
});
} catch (error) {
console.error('Storage info error:', error);
res.status(500).json({ error: 'Failed to fetch storage information' });
}
});
module.exports = router;