diff --git a/.env.example b/.env.example index b008cf9..7636892 100644 --- a/.env.example +++ b/.env.example @@ -1,26 +1,24 @@ -# Environment Configuration Template -# Copy this file to .env and adjust values for your environment +# PicPeak Development Environment Configuration +# Copy this file to .env for local development -# Development: Use docker-compose.dev.yml -# Production: Use docker-compose.prod.yml with .env.production.example +# SECURITY WARNING: This configuration is for development only! +# For production, use .env.production.example -# JWT Secret (CRITICAL for production) -# Generate with: openssl rand -base64 32 -JWT_SECRET=dev-secret-change-in-production +# JWT Secret (Change in production!) +# Generate secure secret with: openssl rand -base64 32 +JWT_SECRET=dev-secret-DO-NOT-USE-IN-PRODUCTION -# Application URLs +# Application URLs (Docker Compose development setup) ADMIN_URL=http://localhost:3005 FRONTEND_URL=http://localhost:3005 +BACKEND_URL=http://localhost:3001 -# Database Configuration -# SQLite is used for development by default -# For production PostgreSQL config, see .env.production.example +# Database Configuration (SQLite for development) DATABASE_CLIENT=sqlite3 DATABASE_PATH=./data/photo_sharing.db -# Email Configuration -# Development: Uses Mailhog (included in docker-compose.dev.yml) -# Production: Configure real SMTP server +# Email Configuration (Mailhog for development) +# Access Mailhog UI at: http://localhost:8025 SMTP_HOST=mailhog SMTP_PORT=1025 SMTP_SECURE=false @@ -28,7 +26,22 @@ SMTP_USER= SMTP_PASS= EMAIL_FROM=noreply@localhost -# Optional: Umami Analytics -UMAMI_URL= -UMAMI_WEBSITE_ID= -UMAMI_HASH_SALT= \ No newline at end of file +# Backend Port Configuration +PORT=3001 + +# Optional: Umami Analytics Backend Config +# NOTE: Primary configuration through Admin UI > Settings > Analytics +# These are fallback values for server-side tracking +# UMAMI_URL=https://analytics.example.com +# UMAMI_WEBSITE_ID=your-website-id +# UMAMI_HASH_SALT=your-hash-salt + +# Development Features +NODE_ENV=development +LOG_LEVEL=debug + +# Admin Setup Notes: +# 1. Run 'npm run migrate' in backend folder +# 2. Admin credentials will be auto-generated +# 3. Check ADMIN_CREDENTIALS.txt for login details +# 4. Change password on first login (required) \ No newline at end of file diff --git a/.env.production.example b/.env.production.example index 3812f0a..08246b3 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,44 +1,100 @@ # PicPeak Production Configuration -# Copy this file to .env and update with your values +# Copy this file to .env and update with your production values -# Required: Security -JWT_SECRET=CHANGE_THIS_TO_RANDOM_32_CHAR_STRING +# ============================================ +# CRITICAL SECURITY - MUST CHANGE ALL VALUES! +# ============================================ -# Required: URLs (update with your domain) +# JWT Secret - REQUIRED (minimum 32 characters) +# Generate with: openssl rand -base64 32 +JWT_SECRET=CHANGE-THIS-PRODUCTION-SECRET-USE-OPENSSL-COMMAND + +# Application URLs - REQUIRED (your actual domain) FRONTEND_URL=https://your-domain.com BACKEND_URL=https://your-domain.com ADMIN_URL=https://your-domain.com -# Required: Email Settings -SMTP_HOST=smtp.gmail.com -SMTP_PORT=587 -SMTP_USER=your-email@gmail.com -SMTP_PASS=your-app-password -SMTP_FROM=your-email@gmail.com +# ============================================ +# DATABASE CONFIGURATION - REQUIRED +# ============================================ -# Required: Initial Admin Account -ADMIN_EMAIL=admin@your-domain.com -ADMIN_PASSWORD=change-this-password - -# Database (PostgreSQL recommended for production) +# PostgreSQL Configuration (Recommended for production) DATABASE_CLIENT=pg -DB_HOST=postgres +DB_HOST=postgres # or your database host DB_PORT=5432 DB_NAME=picpeak DB_USER=picpeak -DB_PASSWORD=secure-database-password +DB_PASSWORD=CHANGE-THIS-SECURE-DATABASE-PASSWORD -# Optional: Customization -SITE_NAME=PicPeak -DEFAULT_EXPIRATION_DAYS=30 -SESSION_TIMEOUT_MINUTES=60 +# ============================================ +# EMAIL CONFIGURATION - REQUIRED +# ============================================ -# Optional: Analytics (Umami) -VITE_UMAMI_URL= -VITE_UMAMI_WEBSITE_ID= +# Example: Gmail with App Password +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_SECURE=false +# SMTP_USER=your-email@gmail.com +# SMTP_PASS=your-16-char-app-password +# EMAIL_FROM=Your Name + +# Example: SendGrid +SMTP_HOST=smtp.sendgrid.net +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=apikey +SMTP_PASS=YOUR-SENDGRID-API-KEY +EMAIL_FROM=PicPeak + +# ============================================ +# ADMIN SETUP - AUTO-GENERATED +# ============================================ +# NOTE: Admin credentials are automatically generated during setup +# DO NOT set ADMIN_EMAIL or ADMIN_PASSWORD anymore! +# Run 'npm run migrate' and check ADMIN_CREDENTIALS.txt + +# ============================================ +# OPTIONAL CONFIGURATION +# ============================================ + +# Umami Analytics (Optional - Fallback values) +# Primary config via Admin UI > Settings > Analytics +# UMAMI_URL=https://analytics.your-domain.com +# UMAMI_WEBSITE_ID=your-website-id +# UMAMI_HASH_SALT=your-hash-salt + +# Frontend Analytics (Optional - Fallback values) +# VITE_UMAMI_URL=https://analytics.your-domain.com +# VITE_UMAMI_WEBSITE_ID=your-website-id +# VITE_UMAMI_SHARE_URL=https://analytics.your-domain.com/share/xyz/gallery + +# ============================================ +# PERFORMANCE & SECURITY TUNING +# ============================================ -# Advanced: Performance Tuning NODE_ENV=production +PORT=3001 +LOG_LEVEL=info + +# Security Settings (Defaults are secure) BCRYPT_ROUNDS=12 -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=100 \ No newline at end of file +SESSION_TIMEOUT_MINUTES=60 +RATE_LIMIT_WINDOW_MS=900000 # 15 minutes +RATE_LIMIT_MAX_REQUESTS=100 # per window + +# Connection Pool (Adjust based on load) +DB_POOL_MIN=5 +DB_POOL_MAX=25 + +# ============================================ +# DOCKER COMPOSE SPECIFIC +# ============================================ + +# Traefik Configuration (if using Traefik) +DOMAIN=your-domain.com +LETSENCRYPT_EMAIL=admin@your-domain.com + +# Volume Paths (Docker) +STORAGE_PATH=/app/storage +EVENTS_PATH=/app/storage/events +ARCHIVE_PATH=/app/storage/events/archived \ No newline at end of file diff --git a/.gitea/workflows/mirror-to-github.yml b/.gitea/workflows/mirror-to-github.yml index 193f2ec..ecf2be5 100644 --- a/.gitea/workflows/mirror-to-github.yml +++ b/.gitea/workflows/mirror-to-github.yml @@ -130,7 +130,7 @@ jobs: # Remove sensitive files/directories if they exist echo "Removing sensitive files..." - rm -rf .env* || true + rm -rf .env || true rm -rf backend/.env* || true rm -rf frontend/.env* || true rm -rf docker-compose.prod.yml || true diff --git a/PRODUCTION_DEPLOYMENT.md b/PRODUCTION_DEPLOYMENT.md deleted file mode 100644 index 2f8ab13..0000000 --- a/PRODUCTION_DEPLOYMENT.md +++ /dev/null @@ -1,98 +0,0 @@ -# Production Deployment Guide - -This guide explains how to deploy PicPeak in production behind a reverse proxy like Traefik. - -## Environment Configuration - -### Frontend Configuration - -For production deployment behind a reverse proxy (Traefik, Nginx, etc.), the frontend should use relative URLs to automatically inherit the protocol (HTTPS) and domain. - -1. Copy the production environment template: - ```bash - cp frontend/.env.production.example frontend/.env.production - ``` - -2. Set the API URL to use relative path: - ```env - # frontend/.env.production - VITE_API_URL=/api - ``` - - This ensures all API calls will use the same domain and protocol as the frontend. - -### Backend Configuration - -Ensure your backend `.env` file has the correct URLs: -```env -# backend/.env -FRONTEND_URL=https://yourdomain.com -ADMIN_URL=https://yourdomain.com -``` - -## Docker Compose Production - -When using Docker Compose in production: - -1. Build with production environment: - ```bash - docker-compose -f docker-compose.prod.yml build --build-arg NODE_ENV=production - ``` - -2. The frontend nginx configuration already includes proper proxy settings for: - - `/api` → Backend API - - `/photos` → Protected photo access - - `/thumbnails` → Thumbnail images - - `/uploads` → Public uploads (logos, favicons) - -## Traefik Configuration - -Example Traefik labels for docker-compose: - -```yaml -services: - frontend: - labels: - - "traefik.enable=true" - - "traefik.http.routers.picpeak.rule=Host(`yourdomain.com`)" - - "traefik.http.routers.picpeak.entrypoints=websecure" - - "traefik.http.routers.picpeak.tls.certresolver=letsencrypt" - - "traefik.http.services.picpeak.loadbalancer.server.port=80" -``` - -## Important Notes - -1. **No Hardcoded URLs**: The application uses environment variables with relative URL fallbacks, making it production-ready. - -2. **HTTPS Only**: When `VITE_API_URL=/api`, all requests will use the same protocol as the page (HTTPS in production). - -3. **CORS Configuration**: The backend CORS is configured to accept requests from the URLs specified in `FRONTEND_URL` and `ADMIN_URL`. - -4. **Static Assets**: All static assets (photos, thumbnails, uploads) are served through the nginx proxy, inheriting authentication headers. - -## Verification - -After deployment, verify: - -1. Check browser console for any localhost URLs (there should be none) -2. Verify all API calls use HTTPS -3. Check that images load correctly with authentication -4. Test favicon and logo display - -## Troubleshooting - -If you see console errors about localhost: - -1. Ensure `VITE_API_URL=/api` in frontend environment -2. Clear browser cache -3. Rebuild frontend with production environment: - ```bash - cd frontend - npm run build - ``` - -If images don't load: - -1. Check that nginx proxy locations are configured -2. Verify authentication tokens are being sent -3. Check backend logs for authentication errors \ No newline at end of file diff --git a/PRODUCTION_DEPLOYMENT_GUIDE.md b/PRODUCTION_DEPLOYMENT_GUIDE.md index 7bf26c4..9718db6 100644 --- a/PRODUCTION_DEPLOYMENT_GUIDE.md +++ b/PRODUCTION_DEPLOYMENT_GUIDE.md @@ -1,6 +1,6 @@ # Production Deployment Guide -This guide addresses all known production deployment issues and provides solutions. +This comprehensive guide addresses all production deployment scenarios and common issues. ## Pre-Deployment Checklist @@ -8,43 +8,80 @@ This guide addresses all known production deployment issues and provides solutio Create a `.env` file with ALL required variables: ```bash -# Required +# CRITICAL - Must change these! JWT_SECRET= DB_PASSWORD= + +# Application URLs (your actual domain) ADMIN_URL=https://yourdomain.com FRONTEND_URL=https://yourdomain.com +BACKEND_URL=https://yourdomain.com -# Database +# Database (PostgreSQL) +DATABASE_CLIENT=pg +DB_HOST=postgres # or external host +DB_PORT=5432 DB_USER=picpeak DB_NAME=picpeak -# Email (Optional but recommended) +# Email Configuration (required for notifications) SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com -SMTP_PASS=your-app-password -EMAIL_FROM=noreply@yourdomain.com +SMTP_PASS=your-app-password # Use app-specific password +EMAIL_FROM=PicPeak -# Umami Analytics (Optional) -UMAMI_URL=https://analytics.yourdomain.com -UMAMI_WEBSITE_ID=your-website-id -UMAMI_HASH_SALT= +# Port Configuration +PORT=3001 + +# Performance Tuning +DB_POOL_MIN=5 +DB_POOL_MAX=25 +NODE_ENV=production +LOG_LEVEL=info + +# Optional: Umami Analytics (configured via Admin UI) +# UMAMI_URL=https://analytics.yourdomain.com +# UMAMI_WEBSITE_ID=your-website-id ``` ### 2. Generate Secrets ```bash -# Generate JWT Secret +# Generate JWT Secret (REQUIRED) openssl rand -base64 32 # Generate Database Password openssl rand -base64 24 - -# Generate Umami Hash Salt -openssl rand -hex 32 ``` +## Frontend Configuration + +For production deployment behind a reverse proxy: + +### Frontend Environment +```bash +# frontend/.env.production +VITE_API_URL=/api # Uses relative path for reverse proxy + +# Optional: Umami fallback (primary config via Admin UI) +# VITE_UMAMI_URL=https://analytics.yourdomain.com +# VITE_UMAMI_WEBSITE_ID=your-website-id +``` + +This ensures all API calls use the same domain/protocol as the frontend. + +### Nginx Proxy Configuration + +The frontend nginx configuration already includes proper proxy settings for: +- `/api` → Backend API +- `/photos` → Protected photo access +- `/thumbnails` → Thumbnail images +- `/uploads` → Public uploads (logos, favicons) + +All static assets are served through the nginx proxy, inheriting authentication headers. + ## Deployment Steps ### 1. Initial Setup @@ -96,24 +133,31 @@ docker-compose -f docker-compose.prod.yml up -d docker-compose -f docker-compose.prod.yml logs -f backend ``` -### 4. Create Admin User +### 4. Initial Admin Setup -After deployment, create the first admin user: +The admin user is automatically created during database migration: ```bash -# Enter backend container -docker-compose -f docker-compose.prod.yml exec backend sh +# Run migrations (this creates admin user) +docker-compose -f docker-compose.prod.yml exec backend npm run migrate -# Create admin -node scripts/create-admin.js \ - --username admin \ - --email admin@yourdomain.com \ - --password +# Admin credentials will be displayed in console and saved to ADMIN_CREDENTIALS.txt +# Example output: +# ======================================== +# ✅ Admin user created successfully! +# ======================================== +# Username: admin +# Password: SwiftEagle3847! +# +# ⚠️ IMPORTANT: Change password on first login +# ======================================== -# Exit container -exit +# Retrieve credentials if needed +docker-compose -f docker-compose.prod.yml exec backend cat ADMIN_CREDENTIALS.txt ``` +**Important**: You MUST change the auto-generated password on first login. + ### 5. Configure Email (if using database config) 1. Login to admin panel: https://yourdomain.com/admin @@ -189,6 +233,23 @@ docker-compose -f docker-compose.prod.yml logs backend | grep email ## SSL/HTTPS Setup +### Option 1: Using Traefik (Recommended) + +Add these labels to your docker-compose override: + +```yaml +services: + frontend: + labels: + - "traefik.enable=true" + - "traefik.http.routers.picpeak.rule=Host(`yourdomain.com`)" + - "traefik.http.routers.picpeak.entrypoints=websecure" + - "traefik.http.routers.picpeak.tls.certresolver=letsencrypt" + - "traefik.http.services.picpeak.loadbalancer.server.port=80" +``` + +### Option 2: Using Certbot + 1. Update `nginx/sites-enabled/default` with your domain 2. Run certbot: @@ -294,13 +355,18 @@ docker-compose -f docker-compose.prod.yml up -d - [ ] Strong JWT_SECRET (min 32 chars) - [ ] Strong database password +- [ ] Admin password changed from auto-generated one - [ ] SSL/HTTPS enabled - [ ] Firewall configured (only 80/443 open) - [ ] Regular security updates - [ ] Backup encryption - [ ] Access logs monitored -- [ ] Rate limiting enabled +- [ ] Rate limiting enabled (built-in) - [ ] File upload restrictions configured +- [ ] Password complexity requirements configured (Admin > Settings) +- [ ] Session timeout configured (default 60 min) +- [ ] Umami analytics configured (if using) +- [ ] SMTP credentials secured with app-specific password ## Support diff --git a/PRODUCTION_TODO_LIST.md b/PRODUCTION_TODO_LIST.md deleted file mode 100644 index 367eeae..0000000 --- a/PRODUCTION_TODO_LIST.md +++ /dev/null @@ -1,714 +0,0 @@ -# 🚀 Production Todo List - PicPeak Enhancements - -**Priority:** HIGH - These are production fixes and enhancements -**Estimated Time:** 2-3 days -**Status:** Ready for Implementation - ---- - -## 📋 Action Items Overview - -1. [Password Complexity Settings](#1-password-complexity-settings) -2. [Gallery Login Page - Remove Event Date](#2-gallery-login-page---remove-event-date) -3. [Analytics Umami Configuration Check](#3-analytics-umami-configuration-check) -4. [Analytics Numbers Accuracy Fix](#4-analytics-numbers-accuracy-fix) -5. [Missing Translation Key Fix](#5-missing-translation-key-fix) -6. [Complete Translation Audit](#6-complete-translation-audit) -7. [CMS Page Long German Text Formatting](#7-cms-page-long-german-text-formatting) -8. [Event Creation Date Format Fix](#8-event-creation-date-format-fix) -9. [Language Selector Country Flags Chrome Fix](#9-language-selector-country-flags-chrome-fix) - ---- - -## 1. Password Complexity Settings - -**Problem:** Admin security tab only has password minimum length setting, no complexity requirements. - -**Current State:** -- File: `frontend/src/pages/admin/SettingsPage.tsx` (lines 635-669) -- Backend: `backend/src/utils/passwordValidation.js` has complexity logic but not exposed in settings - -**Implementation:** - -### Frontend Changes: -```typescript -// File: frontend/src/pages/admin/SettingsPage.tsx -// Add to securitySettings state (around line 61): -const [securitySettings, setSecuritySettings] = useState({ - require_password: true, - password_min_length: 8, - password_complexity_level: 'medium', // ADD THIS - enable_2fa: false, - session_timeout_minutes: 60, - max_login_attempts: 5, - enable_recaptcha: false, - recaptcha_site_key: '', - recaptcha_secret_key: '' -}); - -// Add complexity setting UI after password_min_length (around line 660): -
- - -

- {t('settings.security.complexityHelp')} -

-
-``` - -### Translation Updates: -```json -// File: frontend/src/i18n/locales/en.json (add to settings.security): -"passwordComplexity": "Password Complexity Level", -"complexityLow": "Low - Length only", -"complexityMedium": "Medium - Letters and numbers", -"complexityHigh": "High - Letters, numbers, and symbols", -"complexityHelp": "Controls password requirements for new gallery passwords" - -// File: frontend/src/i18n/locales/de.json (add to settings.security): -"passwordComplexity": "Passwort-Komplexitätsstufe", -"complexityLow": "Niedrig - Nur Länge", -"complexityMedium": "Mittel - Buchstaben und Zahlen", -"complexityHigh": "Hoch - Buchstaben, Zahlen und Symbole", -"complexityHelp": "Steuert Passwort-Anforderungen für neue Galerie-Passwörter" -``` - -### Backend Changes: -```javascript -// File: backend/src/utils/passwordValidation.js -// Update PASSWORD_CONFIG based on settings (around line 8): -const getPasswordConfigFromSettings = async () => { - const { db } = require('../database/db'); - const settings = await db('admin_settings').select('key', 'value'); - const settingsMap = settings.reduce((acc, setting) => { - acc[setting.key] = setting.value; - return acc; - }, {}); - - const complexityLevel = settingsMap.security_password_complexity_level || 'medium'; - - return { - ...PASSWORD_CONFIG, - minLength: parseInt(settingsMap.security_password_min_length) || 8, - requireUppercase: complexityLevel !== 'low', - requireLowercase: complexityLevel !== 'low', - requireNumbers: complexityLevel === 'high' || complexityLevel === 'medium', - requireSpecialChars: complexityLevel === 'high', - minStrengthScore: complexityLevel === 'high' ? 3 : (complexityLevel === 'medium' ? 2 : 1) - }; -}; -``` - ---- - -## 2. Gallery Login Page - Remove Event Date - -**Problem:** Gallery login shows event date which is often used as password, creating security risk. - -**Current State:** -- File: `frontend/src/pages/GalleryPage.tsx` (lines 275-285) -- Shows both event name and date with calendar icon - -**Implementation:** - -### Frontend Changes: -```typescript -// File: frontend/src/pages/GalleryPage.tsx -// Replace the date display section (around lines 280-285): - -// REMOVE THIS: -/* -
- - {format(parseISO(galleryInfo!.event_date), 'PP')} -
-*/ - -// REPLACE WITH: -
- {galleryInfo?.event_type ? t(`events.types.${galleryInfo.event_type}`) : ''} -
-``` - -### Additional Layout Improvements: -```typescript -// File: frontend/src/pages/GalleryPage.tsx -// Update the header section for better visual balance (around line 275): -
- {settingsData?.branding_company_name -

- {galleryInfo?.event_name} -

- {/* Event type instead of date */} - {galleryInfo?.event_type && ( -
- - {t(`events.types.${galleryInfo.event_type}`)} - -
- )} -
-``` - ---- - -## 3. Analytics Umami Configuration Check - -**Problem:** Analytics page shows "Umami Analytics Not Configured" even when configured in settings. - -**Current State:** -- File: `frontend/src/pages/admin/AnalyticsPage.tsx` (lines 67-87) -- Check logic may not be working correctly - -**Implementation:** - -### Frontend Fix: -```typescript -// File: frontend/src/pages/admin/AnalyticsPage.tsx -// Fix the Umami configuration check (around lines 67-87): - -// REPLACE the useEffect: -useEffect(() => { - const fetchUmamiConfig = async () => { - try { - const response = await fetch(`${import.meta.env.VITE_API_URL}/api/public/settings`); - const settings = await response.json(); - - // Check if Umami is properly configured - const isConfigured = settings.analytics_umami_enabled && - settings.analytics_umami_url && - settings.analytics_umami_website_id; - - if (isConfigured) { - setUmamiConfig({ - url: settings.analytics_umami_url, - shareUrl: settings.analytics_umami_share_url, - websiteId: settings.analytics_umami_website_id, - enabled: true - }); - } else { - // Fall back to environment variables - const envConfigured = import.meta.env.VITE_UMAMI_URL && - import.meta.env.VITE_UMAMI_WEBSITE_ID; - - setUmamiConfig({ - url: import.meta.env.VITE_UMAMI_URL, - shareUrl: import.meta.env.VITE_UMAMI_SHARE_URL, - websiteId: import.meta.env.VITE_UMAMI_WEBSITE_ID, - enabled: envConfigured - }); - } - } catch (error) { - console.error('Failed to fetch Umami config:', error); - setUmamiConfig({ enabled: false }); - } - }; - - fetchUmamiConfig(); -}, []); - -// Update the configuration notice condition (around line 441): -{!umamiConfig.enabled && ( - -
- -
-

{t('analytics.notConfigured')}

-

- {t('analytics.configureInstructions')} -

-
-
-
-)} -``` - ---- - -## 4. Analytics Numbers Accuracy Fix - -**Problem:** Dashboard shows correct numbers but analytics page shows different numbers. - -**Current State:** -- Dashboard: `frontend/src/services/admin.service.ts` `getDashboardStats()` -- Analytics: `frontend/src/services/admin.service.ts` `getAnalytics()` -- Backend: Different endpoints with potentially different calculation logic - -**Implementation:** - -### Backend Investigation and Fix: -```javascript -// File: backend/src/routes/adminDashboard.js -// Ensure consistent calculation logic in both /stats and /analytics endpoints - -// Update the analytics endpoint (around line 216) to use the same calculation as stats: -router.get('/analytics', adminAuth, async (req, res) => { - try { - const days = sanitizeDays(req.query.days || 7); - - // Use same calculation logic as /stats endpoint - const thirtyDaysAgo = new Date(); - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - days); - - // Get total downloads - SAME logic as /stats - const totalDownloads = await db('access_logs') - .whereIn('action', ['download', 'download_all']) - .where('timestamp', '>=', thirtyDaysAgo.toISOString()) - .count('id as count') - .first(); - - // Get total views - SAME logic as /stats - const totalViews = await db('access_logs') - .where('action', 'view') - .where('timestamp', '>=', thirtyDaysAgo.toISOString()) - .count('id as count') - .first(); - - // ... rest of the analytics logic - - // Add totals to response for verification - res.json({ - chartData: dates, - topGalleries, - devices, - totals: { - totalViews: totalViews.count, - totalDownloads: totalDownloads.count, - period: `${days} days` - } - }); - } catch (error) { - console.error('Analytics error:', error); - res.status(500).json({ error: 'Failed to fetch analytics data' }); - } -}); -``` - -### Frontend Verification: -```typescript -// File: frontend/src/pages/admin/AnalyticsPage.tsx -// Add debug information in development (around line 107): - -const analytics: ComponentAnalyticsData | undefined = React.useMemo(() => { - if (!apiData) return undefined; - - // Calculate totals from chart data - const totalViews = apiData.chartData.reduce((sum, day) => sum + day.views, 0); - const totalDownloads = apiData.chartData.reduce((sum, day) => sum + day.downloads, 0); - - // Debug: Compare with API totals in development - if (process.env.NODE_ENV === 'development' && apiData.totals) { - console.log('Analytics Debug:', { - calculatedViews: totalViews, - apiTotalViews: apiData.totals.totalViews, - calculatedDownloads: totalDownloads, - apiTotalDownloads: apiData.totals.totalDownloads, - period: apiData.totals.period - }); - } - - // ... rest of the calculation -}, [apiData]); -``` - ---- - -## 5. Missing Translation Key Fix - -**Problem:** Missing translation key `admin.activities.analytics_settings_updated` in recent activities. - -**Current State:** -- Key not found in `frontend/src/i18n/locales/en.json` or `de.json` - -**Implementation:** - -### Translation Updates: -```json -// File: frontend/src/i18n/locales/en.json -// Add to admin.activities section (around line 785): -"analytics_settings_updated": "Analytics settings updated" - -// File: frontend/src/i18n/locales/de.json -// Add to admin.activities section (around line 710): -"analytics_settings_updated": "Analytik-Einstellungen aktualisiert" -``` - -### Backend Activity Logging: -```javascript -// File: backend/src/routes/adminSettings.js (or wherever analytics settings are updated) -// Ensure activity is logged with correct key: - -await logActivity(req.admin.id, 'analytics_settings_updated', { - settingsUpdated: Object.keys(updateData).filter(key => key.startsWith('analytics_')), - timestamp: new Date() -}); -``` - ---- - -## 6. Complete Translation Audit - -**Problem:** Need to check all recent activity types for missing translations. - -**Current State:** -- Activity types defined in backend, translations in frontend - -**Implementation:** - -### Audit Script: -```bash -# Create a script to find missing translation keys -# File: scripts/audit-translations.js - -const fs = require('fs'); -const path = require('path'); - -// Read translation files -const enTranslations = JSON.parse(fs.readFileSync('frontend/src/i18n/locales/en.json', 'utf8')); -const deTranslations = JSON.parse(fs.readFileSync('frontend/src/i18n/locales/de.json', 'utf8')); - -// Common activity types that should exist -const requiredActivityKeys = [ - 'event_created', 'event_updated', 'event_deleted', 'event_archived', - 'photos_uploaded', 'photo_deleted', 'photos_bulk_deleted', - 'archive_downloaded', 'archive_deleted', 'archive_restored', - 'email_config_updated', 'email_template_updated', - 'branding_updated', 'theme_updated', 'analytics_settings_updated', - 'general_settings_updated', 'security_settings_updated', - 'category_created', 'category_updated', 'category_deleted', - 'cms_page_updated', 'favicon_uploaded', - 'bulk_download', 'gallery_password_entry', 'expiration_warning_viewed' -]; - -console.log('Missing English translations:'); -requiredActivityKeys.forEach(key => { - if (!enTranslations.admin?.activities?.[key]) { - console.log(`- admin.activities.${key}`); - } -}); - -console.log('\nMissing German translations:'); -requiredActivityKeys.forEach(key => { - if (!deTranslations.admin?.activities?.[key]) { - console.log(`- admin.activities.${key}`); - } -}); -``` - -### Missing Translations to Add: -```json -// File: frontend/src/i18n/locales/en.json -// Add any missing keys to admin.activities: -"analytics_settings_updated": "Analytics settings updated", -"cms_page_updated": "CMS page updated: {{page}}", -"security_settings_updated": "Security settings updated", -"password_reset": "Password reset for: {{eventName}}", -"admin_logout": "Admin {{actorName}} logged out", -"system_activity": "System activity: {{type}}" - -// File: frontend/src/i18n/locales/de.json -// German equivalents: -"analytics_settings_updated": "Analytik-Einstellungen aktualisiert", -"cms_page_updated": "CMS-Seite aktualisiert: {{page}}", -"security_settings_updated": "Sicherheitseinstellungen aktualisiert", -"password_reset": "Passwort zurückgesetzt für: {{eventName}}", -"admin_logout": "Admin {{actorName}} abgemeldet", -"system_activity": "Systemaktivität: {{type}}" -``` - ---- - -## 7. CMS Page Long German Text Formatting - -**Problem:** Long German text like "Datenschutzerklärung" pushes image to left and looks ugly. - -**Current State:** -- File: `frontend/src/pages/admin/CMSPageEnhanced.tsx` (lines 130-170) -- File: `frontend/src/pages/admin/CMSPage.tsx` (lines 90-110) - -**Implementation:** - -### CSS Fix: -```typescript -// File: frontend/src/pages/admin/CMSPageEnhanced.tsx -// Update the page selection buttons (around line 130): - - -``` - -### Alternative - Responsive Layout: -```typescript -// File: frontend/src/pages/admin/CMSPageEnhanced.tsx -// Alternative: Use responsive text sizing - -

- {/* For very long German words, show abbreviated version */} - {t(`legal.${page.slug}`).length > 15 - ? `${t(`legal.${page.slug}`).substring(0, 12)}...` - : t(`legal.${page.slug}`) - } -

-``` - ---- - -## 8. Event Creation Date Format Fix - -**Problem:** Event creation page uses browser English format instead of saved admin settings date format. - -**Current State:** -- Files: `frontend/src/pages/admin/CreateEventPageEnhanced.tsx`, `CreateEventPage.tsx` -- Uses browser locale instead of admin date format settings - -**Implementation:** - -### Hook Enhancement: -```typescript -// File: frontend/src/hooks/useLocalizedDate.ts -// Add admin settings integration: - -import { useTranslation } from 'react-i18next'; -import { format as dateFnsFormat, formatDistanceToNow as dateFnsFormatDistanceToNow } from 'date-fns'; -import { de, enUS, enGB } from 'date-fns/locale'; -import { useQuery } from '@tanstack/react-query'; -import { settingsService } from '../services/settings.service'; - -export const useLocalizedDate = () => { - const { i18n } = useTranslation(); - - // Fetch admin date format settings - const { data: settings } = useQuery({ - queryKey: ['admin-date-settings'], - queryFn: () => settingsService.getAllSettings(), - staleTime: 10 * 60 * 1000, // Cache for 10 minutes - }); - - const getLocale = () => { - // Use admin settings if available, otherwise fall back to i18n language - const savedFormat = settings?.general_date_format; - if (savedFormat?.locale) { - switch (savedFormat.locale) { - case 'en-US': return enUS; - case 'en-GB': return enGB; - case 'de': return de; - default: return i18n.language === 'de' ? de : enUS; - } - } - return i18n.language === 'de' ? de : enUS; - }; - - const getDateFormat = () => { - const savedFormat = settings?.general_date_format?.format; - if (savedFormat) { - // Convert admin format to date-fns format - switch (savedFormat) { - case 'DD/MM/YYYY': return 'dd/MM/yyyy'; - case 'MM/DD/YYYY': return 'MM/dd/yyyy'; - case 'YYYY-MM-DD': return 'yyyy-MM-dd'; - case 'DD.MM.YYYY': return 'dd.MM.yyyy'; - default: return 'dd/MM/yyyy'; - } - } - return i18n.language === 'de' ? 'dd.MM.yyyy' : 'MM/dd/yyyy'; - }; - - const format = (date: Date | string, formatStr?: string) => { - const dateObj = typeof date === 'string' ? new Date(date) : date; - const finalFormat = formatStr || getDateFormat(); - return dateFnsFormat(dateObj, finalFormat, { locale: getLocale() }); - }; - - // ... rest of the hook -}; -``` - -### Event Creation Page Fix: -```typescript -// File: frontend/src/pages/admin/CreateEventPageEnhanced.tsx -// Update the expiration date display (around line 495): - -{formData.event_date && ( -

- {t('events.expiresOn')}: {format(addDays(new Date(formData.event_date), formData.expires_in_days), 'PPP')} -

-)} -``` - ---- - -## 9. Language Selector Country Flags Chrome Fix - -**Problem:** Country flags not showing in Chrome browser on Windows in admin language selector. - -**Current State:** -- File: `frontend/src/components/common/LanguageSelector.tsx` (lines 5-8) -- Uses emoji flags: `🇬🇧`, `🇩🇪` - -**Implementation:** - -### SVG Icon Replacement: -```typescript -// File: frontend/src/components/common/LanguageSelector.tsx -// Replace emoji flags with SVG icons or image flags: - -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Globe } from 'lucide-react'; - -// SVG flag components for better browser compatibility -const FlagGB: React.FC<{ className?: string }> = ({ className = "w-4 h-4" }) => ( - - - - - - - -); - -const FlagDE: React.FC<{ className?: string }> = ({ className = "w-4 h-4" }) => ( - - - - - -); - -const languages = [ - { code: 'en', name: 'English', flag: FlagGB }, - { code: 'de', name: 'Deutsch', flag: FlagDE }, -]; - -export const LanguageSelector: React.FC = () => { - const { i18n } = useTranslation(); - const [isOpen, setIsOpen] = React.useState(false); - - const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0]; - - const handleLanguageChange = (languageCode: string) => { - i18n.changeLanguage(languageCode); - setIsOpen(false); - }; - - return ( -
- - - {isOpen && ( -
- {languages.map((language) => ( - - ))} -
- )} -
- ); -}; -``` - -### Alternative - Image Flags: -```typescript -// Alternative solution using flag images: -const languages = [ - { code: 'en', name: 'English', flag: '/flags/gb.svg' }, - { code: 'de', name: 'Deutsch', flag: '/flags/de.svg' }, -]; - -// Add images to public/flags/ directory -// Use: {language.name} -``` - ---- - -## 🔍 Testing Instructions - -### After implementing each fix: - -1. **Password Complexity**: Test different complexity levels in admin settings -2. **Gallery Login**: Verify event date is hidden on gallery login pages -3. **Analytics Check**: Verify "Not Configured" message appears/disappears correctly -4. **Analytics Numbers**: Compare dashboard vs analytics page numbers -5. **Translations**: Check recent activities display correct translations -6. **CMS Formatting**: Test with long German page names -7. **Date Format**: Test event creation with different admin date settings -8. **Language Flags**: Test language selector in Chrome on Windows - -### Regression Testing: -- [ ] Gallery login still works correctly -- [ ] Analytics page displays correctly when Umami is configured -- [ ] Admin settings save and load correctly -- [ ] Event creation works with all date formats -- [ ] Language switching works in all browsers - ---- - -## 📝 Notes - -- All changes maintain backward compatibility -- No database schema changes required -- Frontend changes are non-breaking -- Can be deployed incrementally -- All text is properly internationalized - -**⚠️ Important**: Test each fix in isolation before combining, especially the analytics changes as they affect production data display. \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index e4b74a7..285e541 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -3,39 +3,56 @@ # Application NODE_ENV=production -PORT=3000 +PORT=3001 # Security -JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long +# Generate with: openssl rand -base64 32 +JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long-example123456 -# URLs -ADMIN_URL=https://yourdomain.com -FRONTEND_URL=https://yourdomain.com +# URLs (adjust for your domain) +ADMIN_URL=https://photos.example.com +FRONTEND_URL=https://photos.example.com # Database Configuration DATABASE_CLIENT=pg -DB_HOST=db +DB_HOST=localhost DB_PORT=5432 DB_USER=picpeak -DB_PASSWORD=your-secure-database-password +DB_PASSWORD=your-secure-database-password-change-this DB_NAME=picpeak -# Email Configuration -SMTP_HOST=smtp.example.com +# Email Configuration (Examples for common providers) +# Gmail example: +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_SECURE=false +# SMTP_USER=your-email@gmail.com +# SMTP_PASS=your-app-specific-password + +# SendGrid example: +SMTP_HOST=smtp.sendgrid.net SMTP_PORT=587 SMTP_SECURE=false -SMTP_USER=your-smtp-username -SMTP_PASS=your-smtp-password -EMAIL_FROM=noreply@yourdomain.com +SMTP_USER=apikey +SMTP_PASS=your-sendgrid-api-key +EMAIL_FROM=noreply@example.com -# Storage Paths (Docker) +# Storage Paths +# Docker deployment: STORAGE_PATH=/app/storage EVENTS_PATH=/app/storage/events ARCHIVE_PATH=/app/storage/events/archived -# Analytics (Optional) -UMAMI_URL=https://analytics.yourdomain.com -UMAMI_WEBSITE_ID=your-website-id +# Local development: +# STORAGE_PATH=./storage +# EVENTS_PATH=./storage/events +# ARCHIVE_PATH=./storage/events/archived + +# Analytics Backend Configuration (OPTIONAL) +# Used for server-side tracking only +# Primary configuration should be done through Admin UI > Settings > Analytics +# UMAMI_URL=https://analytics.example.com +# UMAMI_WEBSITE_ID=b4d3c2a1-5678-90ab-cdef-1234567890ab # Logging LOG_LEVEL=info \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example index b8f3c08..7c0f34a 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,11 +1,16 @@ # Backend API URL -VITE_API_URL=http://localhost:3000 +# For local development: +VITE_API_URL=http://localhost:3001 -# Umami Analytics Configuration -# Get these values from your Umami installation -VITE_UMAMI_URL=https://analytics.yourdomain.com -VITE_UMAMI_WEBSITE_ID=your-website-id-from-umami +# For production behind reverse proxy (Traefik, nginx, etc): +# VITE_API_URL=/api -# Optional: Umami share URL for embedding full dashboard -# This is the public share URL from Umami's share feature -VITE_UMAMI_SHARE_URL=https://analytics.yourdomain.com/share/your-share-id/photo-sharing \ No newline at end of file +# Umami Analytics Configuration (OPTIONAL - Fallback only) +# NOTE: Primary Umami configuration should be done through Admin UI > Settings > Analytics +# These environment variables serve as fallbacks when backend settings are not available +# Useful for: development environments, initial setup, or when backend is unavailable +# +# Example values: +# VITE_UMAMI_URL=https://analytics.example.com +# VITE_UMAMI_WEBSITE_ID=abc123def-4567-89ab-cdef-0123456789ab +# VITE_UMAMI_SHARE_URL=https://analytics.example.com/share/xyz789/wedding-photos \ No newline at end of file diff --git a/frontend/.env.production.example b/frontend/.env.production.example index 11ca0a6..8640bbd 100644 --- a/frontend/.env.production.example +++ b/frontend/.env.production.example @@ -8,7 +8,11 @@ VITE_API_URL=/api # For development or if frontend/backend are on different domains: # VITE_API_URL=https://api.yourdomain.com -# Umami Analytics Configuration (optional) -# VITE_UMAMI_URL=https://analytics.yourdomain.com -# VITE_UMAMI_WEBSITE_ID=your-website-id-from-umami -# VITE_UMAMI_SHARE_URL=https://analytics.yourdomain.com/share/your-share-id/photo-sharing \ No newline at end of file +# Umami Analytics Configuration (OPTIONAL - Fallback only) +# NOTE: Primary Umami configuration should be done through Admin UI > Settings > Analytics +# These environment variables serve as fallbacks when backend settings are not available +# +# Real-world example values: +# VITE_UMAMI_URL=https://analytics.picpeak.com +# VITE_UMAMI_WEBSITE_ID=b4d3c2a1-5678-90ab-cdef-1234567890ab +# VITE_UMAMI_SHARE_URL=https://analytics.picpeak.com/share/Ab3Cd5Fg/picpeak-gallery \ No newline at end of file