refactor: rename project from wedding-photo-sharing to PicPeak
Create Release / check-version-change (push) Successful in 2m25s
Automatic Version Bump / version-bump (push) Failing after 8m2s
Create Release / create-release (push) Has been skipped

- Update Docker image names and network configurations
- Rename package.json project names to picpeak-backend/frontend
- Update CI/CD configurations (Drone CI and GitHub Actions)
- Update documentation and setup scripts
- Update application branding in source code
- Change default database name to picpeak
- Update PM2 ecosystem config

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-12 09:22:14 +02:00
parent d065132bb7
commit 288b0c25e6
42 changed files with 1116 additions and 422 deletions
+77 -1
View File
@@ -46,6 +46,12 @@ router.get('/stats', adminAuth, async (req, res) => {
.count('id as count')
.first();
// Get archived events count
const archivedEvents = await db('events')
.where('is_archived', true)
.count('id as count')
.first();
// Calculate trends (compare with previous 30 days)
const previousViews = await db('access_logs')
.where('action', 'view')
@@ -78,7 +84,8 @@ router.get('/stats', adminAuth, async (req, res) => {
totalViews: totalViews.count || 0,
totalDownloads: totalDownloads.count || 0,
viewsTrend: Math.round(viewsTrend * 10) / 10,
downloadsTrend: Math.round(downloadsTrend * 10) / 10
downloadsTrend: Math.round(downloadsTrend * 10) / 10,
archivedEvents: archivedEvents.count || 0
});
} catch (error) {
console.error('Dashboard stats error:', error);
@@ -115,6 +122,75 @@ router.get('/activity', adminAuth, async (req, res) => {
}
});
// Get system health status
router.get('/health', adminAuth, async (req, res) => {
try {
const os = require('os');
// Check database connectivity
let dbStatus = 'healthy';
try {
await db.raw('SELECT 1');
} catch (error) {
dbStatus = 'error';
}
// Check email queue
const [pendingEmails] = await db('email_queue')
.where('status', 'pending')
.count('* as count');
const [failedEmails] = await db('email_queue')
.where('status', 'failed')
.whereRaw('created_at >= datetime("now", "-24 hours")')
.count('* as count');
const emailStatus = failedEmails.count > 10 ? 'warning' : 'healthy';
// Check disk space (simplified)
const storageStatus = 'healthy'; // In production, check actual disk usage
// Memory usage
const memoryUsage = {
total: os.totalmem(),
free: os.freemem(),
used: os.totalmem() - os.freemem(),
percentage: Math.round(((os.totalmem() - os.freemem()) / os.totalmem()) * 100)
};
const memoryStatus = memoryUsage.percentage > 90 ? 'warning' : 'healthy';
// Overall health
const statuses = [dbStatus, emailStatus, storageStatus, memoryStatus];
let overallHealth = 'healthy';
if (statuses.includes('error')) overallHealth = 'error';
else if (statuses.includes('warning')) overallHealth = 'warning';
res.json({
overall: overallHealth,
services: {
database: dbStatus,
email: emailStatus,
storage: storageStatus,
memory: memoryStatus
},
details: {
emailQueue: {
pending: pendingEmails.count,
failed: failedEmails.count
},
memory: memoryUsage
}
});
} catch (error) {
console.error('Health check error:', error);
res.status(500).json({
overall: 'error',
error: 'Failed to check system health'
});
}
});
// Get analytics data for charts
router.get('/analytics', adminAuth, async (req, res) => {
try {
+5 -4
View File
@@ -65,9 +65,9 @@ router.post('/', adminAuth, [
// 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);
// Calculate expiration date (days after event date)
const expires_at = new Date(event_date);
expires_at.setDate(expires_at.getDate() + parseInt(expiration_days, 10));
// Create folder structure
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
@@ -115,7 +115,8 @@ router.post('/', adminAuth, [
event_date: await formatDate(event_date, emailLang),
gallery_link: shareLink,
gallery_password: password,
expiry_date: await formatDate(expires_at, emailLang)
expiry_date: await formatDate(expires_at, emailLang),
welcome_message: welcome_message || ''
})
// scheduled_at will use default value
});
+5 -4
View File
@@ -53,9 +53,9 @@ router.post('/', adminAuth, [
// 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);
// Calculate expiration date (days after event date)
const expires_at = new Date(event_date);
expires_at.setDate(expires_at.getDate() + parseInt(expiration_days, 10));
// Create folder structure
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
@@ -86,7 +86,8 @@ router.post('/', adminAuth, [
event_date: new Date(event_date).toLocaleDateString(),
gallery_link: shareLink,
gallery_password: password,
expiry_date: expires_at.toLocaleDateString()
expiry_date: expires_at.toLocaleDateString(),
welcome_message: welcome_message || ''
});
res.json({
+38
View File
@@ -301,6 +301,44 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => {
}
});
// View single photo (with watermark if enabled)
router.get('/:slug/photo/: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' });
}
const filePath = path.join(getStoragePath(), 'events/active', photo.path);
// Get watermark settings
const watermarkSettings = await watermarkService.getWatermarkSettings();
if (watermarkSettings && watermarkSettings.enabled) {
// Apply watermark and send
const watermarkedBuffer = await watermarkService.applyWatermark(filePath, watermarkSettings);
res.set({
'Content-Type': photo.mime_type || 'image/jpeg',
'Cache-Control': 'public, max-age=3600' // Cache for 1 hour
});
res.send(watermarkedBuffer);
} else {
// Send original file
res.sendFile(filePath);
}
} catch (error) {
console.error('Error serving photo:', error);
res.status(500).json({ error: 'Failed to serve photo' });
}
});
// Get photo stats
router.get('/:slug/stats', verifyGalleryAccess, async (req, res) => {
try {