#!/bin/bash # Complete setup script to create ALL remaining files echo "=========================================" echo "PicPeak Platform Setup" echo "=========================================" echo "" # Function to create directory if it doesn't exist create_dir() { if [ ! -d "$1" ]; then mkdir -p "$1" echo "Created directory: $1" fi } # Create all necessary directories echo "Creating directory structure..." create_dir "backend/src/services" create_dir "backend/src/utils" create_dir "backend/src/routes" create_dir "backend/migrations" create_dir "backend/scripts" create_dir "backend/__tests__" create_dir "frontend/public" create_dir "frontend/src/components" create_dir "frontend/src/contexts" create_dir "frontend/src/hooks" create_dir "frontend/src/pages/admin" create_dir "frontend/src/services" create_dir "frontend/src/config" create_dir "nginx/sites-enabled" create_dir "scripts" create_dir "storage/events/active" create_dir "storage/events/archived" create_dir "storage/thumbnails" create_dir "data" create_dir "logs" create_dir "certbot/conf" create_dir "certbot/www" # Create .gitkeep files to preserve empty directories touch storage/events/active/.gitkeep touch storage/events/archived/.gitkeep touch storage/thumbnails/.gitkeep touch data/.gitkeep touch logs/.gitkeep echo "" echo "Creating backend utilities..." # Create helpers utility cat > backend/src/utils/helpers.js << 'EOF' const crypto = require('crypto'); const path = require('path'); function generateToken(length = 32) { return crypto.randomBytes(length).toString('hex'); } function sanitizeFilename(filename) { const basename = path.basename(filename); return basename.replace(/[^a-zA-Z0-9._-]/g, '_'); } function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } function generateSlug(text) { return text .toString() .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } function daysBetween(date1, date2) { const oneDay = 24 * 60 * 60 * 1000; const firstDate = new Date(date1); const secondDate = new Date(date2); const diffDays = Math.round(Math.abs((firstDate - secondDate) / oneDay)); return diffDays; } function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } function paginate(totalItems, currentPage = 1, pageSize = 20) { const totalPages = Math.ceil(totalItems / pageSize); const offset = (currentPage - 1) * pageSize; return { totalItems, currentPage, pageSize, totalPages, offset, hasNext: currentPage < totalPages, hasPrev: currentPage > 1 }; } function asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; } function getClientIp(req) { return req.headers['x-forwarded-for']?.split(',')[0] || req.headers['x-real-ip'] || req.connection.remoteAddress; } module.exports = { generateToken, sanitizeFilename, formatBytes, generateSlug, daysBetween, isValidEmail, paginate, asyncHandler, getClientIp }; EOF echo "Creating remaining backend routes..." # Create admin routes cat > backend/src/routes/admin.js << 'EOF' const express = require('express'); const bcrypt = require('bcrypt'); const { adminAuth } = require('../middleware/auth'); const { db } = require('../database/db'); const router = express.Router(); // Dashboard stats router.get('/stats', adminAuth, async (req, res) => { try { const totalEvents = await db('events').count('id as count').first(); const activeEvents = await db('events').where('is_active', true).count('id as count').first(); const archivedEvents = await db('events').where('is_archived', true).count('id as count').first(); const totalPhotos = await db('photos').count('id as count').first(); const upcomingExpirations = await db('events') .where('is_active', true) .where('expires_at', '<=', new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)) .orderBy('expires_at', 'asc') .limit(5); const recentActivity = await db('access_logs') .join('events', 'access_logs.event_id', 'events.id') .select('access_logs.*', 'events.event_name') .orderBy('access_logs.timestamp', 'desc') .limit(10); res.json({ total_events: totalEvents.count, active_events: activeEvents.count, archived_events: archivedEvents.count, total_photos: totalPhotos.count, upcoming_expirations: upcomingExpirations, recent_activity: recentActivity }); } catch (error) { res.status(500).json({ error: 'Failed to fetch stats' }); } }); // Email queue management router.get('/emails', adminAuth, async (req, res) => { try { const emails = await db('email_queue') .join('events', 'email_queue.event_id', 'events.id') .select('email_queue.*', 'events.event_name') .orderBy('email_queue.scheduled_at', 'desc') .limit(50); res.json(emails); } catch (error) { res.status(500).json({ error: 'Failed to fetch emails' }); } }); // Retry failed email router.post('/emails/:id/retry', adminAuth, async (req, res) => { try { const { id } = req.params; await db('email_queue').where('id', id).update({ status: 'pending', retry_count: 0, error_message: null }); res.json({ success: true }); } catch (error) { res.status(500).json({ error: 'Failed to retry email' }); } }); // Archive management router.get('/archives', adminAuth, async (req, res) => { try { const archives = await db('events') .where('is_archived', true) .select('id', 'event_name', 'event_date', 'archive_path', 'archived_at') .orderBy('archived_at', 'desc'); res.json(archives); } catch (error) { res.status(500).json({ error: 'Failed to fetch archives' }); } }); // Create admin user router.post('/users', adminAuth, async (req, res) => { try { const { username, email, password } = req.body; const existing = await db('admin_users') .where('username', username) .orWhere('email', email) .first(); if (existing) { return res.status(400).json({ error: 'User already exists' }); } const password_hash = await bcrypt.hash(password, 10); const [userId] = await db('admin_users').insert({ username, email, password_hash }); res.json({ id: userId, username, email }); } catch (error) { res.status(500).json({ error: 'Failed to create user' }); } }); module.exports = router; EOF echo "Creating deployment scripts..." # Create backup script cat > scripts/backup.sh << 'EOF' #!/bin/bash BACKUP_DIR="/backup/photo-sharing" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_NAME="backup_${TIMESTAMP}" mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}" echo "Starting backup..." if [ -f data/photo_sharing.db ]; then echo "Backing up SQLite database..." cp data/photo_sharing.db "${BACKUP_DIR}/${BACKUP_NAME}/" else echo "Backing up PostgreSQL database..." docker-compose -f docker-compose.prod.yml exec -T db pg_dump -U photoapp photo_sharing > "${BACKUP_DIR}/${BACKUP_NAME}/database.sql" fi echo "Backing up active events..." tar -czf "${BACKUP_DIR}/${BACKUP_NAME}/active_events.tar.gz" -C storage/events active/ cp .env "${BACKUP_DIR}/${BACKUP_NAME}/" cat > "${BACKUP_DIR}/${BACKUP_NAME}/backup_info.txt" << EOFINFO Backup created: $(date) Database: $([ -f data/photo_sharing.db ] && echo "photo_sharing.db" || echo "database.sql") Active events: active_events.tar.gz Configuration: .env EOFINFO cd "${BACKUP_DIR}" tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}/" rm -rf "${BACKUP_NAME}/" find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +30 -delete echo "Backup completed: ${BACKUP_DIR}/${BACKUP_NAME}.tar.gz" EOF chmod +x scripts/backup.sh # Create monitoring script cat > scripts/monitoring.sh << 'EOF' #!/bin/bash check_service() { SERVICE=$1 if docker-compose -f docker-compose.prod.yml ps | grep -q "${SERVICE}.*Up"; then echo "✓ ${SERVICE} is running" return 0 else echo "✗ ${SERVICE} is down!" return 1 fi } echo "Service Health Check" echo "===================" SERVICES_OK=true check_service "backend" || SERVICES_OK=false check_service "frontend" || SERVICES_OK=false check_service "nginx" || SERVICES_OK=false echo "" echo "Disk Usage:" df -h | grep -E '^/dev/' | awk '{print $6 ": " $5 " used"}' FAILED_EMAILS=$(docker-compose -f docker-compose.prod.yml exec -T backend sqlite3 data/photo_sharing.db "SELECT COUNT(*) FROM email_queue WHERE status='failed' AND retry_count >= 3;" 2>/dev/null || echo "0") if [ "$FAILED_EMAILS" -gt 0 ]; then echo "" echo "⚠️ Warning: $FAILED_EMAILS failed emails in queue" fi echo "" echo "Upcoming Expirations:" docker-compose -f docker-compose.prod.yml exec -T backend sqlite3 data/photo_sharing.db "SELECT event_name, date(expires_at) as expires FROM events WHERE is_active=1 AND expires_at <= datetime('now', '+7 days') ORDER BY expires_at;" 2>/dev/null || echo "No database connection" if [ "$SERVICES_OK" = false ]; then echo "" echo "⚠️ Some services are down! Run 'docker-compose -f docker-compose.prod.yml up -d' to restart." exit 1 fi EOF chmod +x scripts/monitoring.sh # Create SSL setup script cat > scripts/setup-ssl.sh << 'EOF' #!/bin/bash set -e echo "SSL Certificate Setup" echo "====================" if [ ! -f .env ]; then echo "Error: .env file not found. Please run install.sh first." exit 1 fi source .env ADMIN_DOMAIN=$(echo $ADMIN_URL | sed 's|https://||') FRONTEND_DOMAIN=$(echo $FRONTEND_URL | sed 's|https://||') if [ -z "$ADMIN_DOMAIN" ] || [ -z "$FRONTEND_DOMAIN" ]; then echo "Error: Please set ADMIN_URL and FRONTEND_URL in .env file" exit 1 fi sed -i "s/admin.photos.yourdomain.com/$ADMIN_DOMAIN/g" nginx/sites-enabled/default.conf sed -i "s/photos.yourdomain.com/$FRONTEND_DOMAIN/g" nginx/sites-enabled/default.conf read -p "Enter email for Let's Encrypt notifications: " EMAIL docker-compose -f docker-compose.prod.yml up -d nginx sleep 5 echo "Obtaining SSL certificates for $ADMIN_DOMAIN and $FRONTEND_DOMAIN..." docker-compose -f docker-compose.prod.yml run --rm certbot certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email $EMAIL \ --agree-tos \ --no-eff-email \ -d $ADMIN_DOMAIN \ -d $FRONTEND_DOMAIN echo "SSL certificates obtained successfully!" EOF chmod +x scripts/setup-ssl.sh # Create update script cat > scripts/update.sh << 'EOF' #!/bin/bash echo "Photo Sharing Platform - Update" echo "==============================" echo "Creating backup before update..." ./scripts/backup.sh echo "Pulling latest changes..." git pull origin main echo "Rebuilding services..." docker-compose -f docker-compose.prod.yml build echo "Restarting services..." docker-compose -f docker-compose.prod.yml down docker-compose -f docker-compose.prod.yml up -d echo "Running database migrations..." docker-compose -f docker-compose.prod.yml exec backend npm run migrate echo "Update completed successfully!" EOF chmod +x scripts/update.sh echo "" echo "Creating frontend files..." # Create frontend Dockerfile cat > frontend/Dockerfile << 'EOF' FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] EOF # Create frontend nginx.conf cat > frontend/nginx.conf << 'EOF' server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } location /photos { proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } EOF # Create minimal frontend files to get started cat > frontend/public/index.html << 'EOF' Photo Gallery
EOF # Create basic frontend files cat > frontend/src/index.js << 'EOF' import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( ); EOF cat > frontend/src/App.js << 'EOF' import React from 'react'; function App() { return (

Photo Sharing Platform

Setup in progress. Please complete the frontend implementation.

); } export default App; EOF cat > frontend/src/index.css << 'EOF' @tailwind base; @tailwind components; @tailwind utilities; body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } EOF # Create Tailwind config cat > frontend/tailwind.config.js << 'EOF' module.exports = { content: [ "./src/**/*.{js,jsx,ts,tsx}", ], theme: { extend: { colors: { wedding: { primary: '#d4a574', secondary: '#f3e5d0', accent: '#8b7355' } } }, }, plugins: [], } EOF # Create postcss config cat > frontend/postcss.config.js << 'EOF' module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } EOF # Create nginx site config cat > nginx/sites-enabled/default.conf << 'EOF' # Redirect HTTP to HTTPS server { listen 80; server_name admin.photos.yourdomain.com photos.yourdomain.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$server_name$request_uri; } } # Admin backend server { listen 443 ssl http2; server_name admin.photos.yourdomain.com; ssl_certificate /etc/letsencrypt/live/admin.photos.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/admin.photos.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers off; client_max_body_size 100M; location / { limit_req zone=general burst=20 nodelay; proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } location /api/auth { limit_req zone=auth burst=5 nodelay; proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Public frontend server { listen 443 ssl http2; server_name photos.yourdomain.com; ssl_certificate /etc/letsencrypt/live/photos.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/photos.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers off; location / { limit_req zone=general burst=20 nodelay; proxy_pass http://frontend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } location /api { limit_req zone=general burst=20 nodelay; proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } location /photos { proxy_pass http://backend:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Cache images proxy_cache_valid 200 30d; add_header Cache-Control "public, max-age=2592000"; } } EOF # Create DEPLOYMENT.md cat > DEPLOYMENT.md << 'EOF' # Production Deployment Guide ## System Requirements - Ubuntu 20.04+ or similar Linux distribution - 2GB RAM minimum (4GB recommended) - 20GB storage minimum - Docker and Docker Compose - Valid domain names with DNS configured ## Step-by-Step Deployment ### 1. Server Preparation ```bash # Update system sudo apt update && sudo apt upgrade -y # Install required packages sudo apt install -y git curl ufw # Configure firewall sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable ``` ### 2. Clone and Install ```bash # Clone repository cd /opt sudo git clone https://github.com/yourusername/photo-sharing-platform.git cd photo-sharing-platform # Run installation script sudo ./scripts/install.sh ``` ### 3. Configuration Edit `.env` file: ```bash sudo nano .env ``` Required settings: ```env # URLs (use your actual domains) ADMIN_URL=https://admin.photos.yourdomain.com FRONTEND_URL=https://photos.yourdomain.com # Email Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password EMAIL_FROM=noreply@yourdomain.com ``` ### 4. SSL Certificate Setup ```bash # Configure SSL sudo ./scripts/setup-ssl.sh ``` ### 5. Start Services ```bash # Build and start all services docker-compose -f docker-compose.prod.yml build docker-compose -f docker-compose.prod.yml up -d # Initialize database docker-compose -f docker-compose.prod.yml exec backend npm run migrate ``` ### 6. Verify Deployment 1. Check service status: ```bash docker-compose -f docker-compose.prod.yml ps ``` 2. View logs: ```bash docker-compose -f docker-compose.prod.yml logs -f ``` 3. Access sites: - Admin panel: https://admin.photos.yourdomain.com - Public gallery: https://photos.yourdomain.com ## Post-Deployment ### Configure Automatic Backups ```bash # Add to crontab sudo crontab -e # Add this line for daily backups at 2 AM 0 2 * * * /opt/photo-sharing-platform/scripts/backup.sh ``` ### Set Up Monitoring ```bash # Add health check to crontab */5 * * * * /opt/photo-sharing-platform/scripts/monitoring.sh ``` ## Security Recommendations 1. Change default admin password immediately 2. Configure firewall rules 3. Enable automatic security updates 4. Monitor access logs regularly ## Troubleshooting ### Services won't start ```bash # Check logs docker-compose -f docker-compose.prod.yml logs backend docker-compose -f docker-compose.prod.yml logs frontend # Restart services docker-compose -f docker-compose.prod.yml restart ``` ### Email not sending 1. Check SMTP settings in `.env` 2. View email queue in admin panel 3. Check logs: `docker-compose logs backend | grep email` ### SSL certificate issues ```bash # Renew certificates docker-compose -f docker-compose.prod.yml run --rm certbot renew ``` EOF # Set all script permissions chmod +x scripts/*.sh echo "" echo "=========================================" echo "✅ Setup Complete!" echo "=========================================" echo "" echo "All core files have been created. The platform structure is ready." echo "" echo "Next steps:" echo "1. Install dependencies:" echo " cd backend && npm install" echo " cd ../frontend && npm install" echo "" echo "2. Create a .env file from .env.example:" echo " cp .env.example .env" echo " nano .env # Edit with your settings" echo "" echo "3. Start development environment:" echo " docker-compose up" echo "" echo "4. For production deployment:" echo " Follow the instructions in DEPLOYMENT.md" echo "" echo "Note: The frontend is a basic skeleton. You'll need to implement:" echo "- Authentication context (AuthContext.js)" echo "- Page components (Login, Gallery, Admin pages)" echo "- API service layer" echo "- UI components" echo "" echo "All backend functionality is complete and ready to use!" echo "" echo "Default admin credentials: admin / admin123 (change immediately!)"