Compare commits

...

3 Commits

Author SHA1 Message Date
Gitea Actions Bot e343106af5 chore: bump version to 1.0.4
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2025-07-13 19:28:44 +00:00
paul 2f848eb602 fix: make create-admin script executable
Test and Lint / backend-test (push) Successful in 1m7s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m7s
Version and Release / version-bump (push) Successful in 37s
Version and Release / trigger-drone (push) Successful in 4s
2025-07-13 21:24:40 +02:00
paul 1c7fa781ad fix: configure PostgreSQL for production and clean up deployment
- Fix database configuration to use PostgreSQL in production
- Add knexfile.js to support both SQLite (dev) and PostgreSQL (prod)
- Create admin user creation script (scripts/create-admin.js)
- Clean up docker-compose files:
  - Remove redundant docker-compose.yml and docker-compose.local.yml
  - Create docker-compose.dev.yml for development
  - Update docker-compose.prod.yml with proper DB configuration
- Clean up environment files:
  - Update .env.example for development
  - Update .env.production.example with proper settings
  - Remove redundant .env.local
- Update backend .env.example with database configuration options
- Create comprehensive DEPLOYMENT.md with admin setup instructions
- Fix production database name consistency (picpeak instead of photoapp)
2025-07-13 21:24:40 +02:00
15 changed files with 583 additions and 697 deletions
+28 -20
View File
@@ -1,26 +1,34 @@
# JWT Secret for authentication
# IMPORTANT: Generate a secure random secret with: openssl rand -hex 32
# NEVER use the default value or commit the actual secret to version control
JWT_SECRET=CHANGE_ME_TO_A_64_CHARACTER_SECURE_RANDOM_STRING_GENERATED_BY_OPENSSL
# Environment Configuration Template
# Copy this file to .env and adjust values for your environment
# URLs
ADMIN_URL=https://admin.photos.yourdomain.com
FRONTEND_URL=https://photos.yourdomain.com
# Development: Use docker-compose.dev.yml
# Production: Use docker-compose.prod.yml with .env.production.example
# Database (for PostgreSQL in production)
DB_USER=photoapp
DB_PASSWORD=secure-password-here
DB_NAME=photo_sharing
# JWT Secret (CRITICAL for production)
# Generate with: openssl rand -base64 32
JWT_SECRET=dev-secret-change-in-production
# Application URLs
ADMIN_URL=http://localhost:3005
FRONTEND_URL=http://localhost:3005
# Database Configuration
# SQLite is used for development by default
# For production PostgreSQL config, see .env.production.example
DATABASE_CLIENT=sqlite3
DATABASE_PATH=./data/photo_sharing.db
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
# Development: Uses Mailhog (included in docker-compose.dev.yml)
# Production: Configure real SMTP server
SMTP_HOST=mailhog
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
EMAIL_FROM=noreply@yourdomain.com
SMTP_USER=
SMTP_PASS=
EMAIL_FROM=noreply@localhost
# Umami Analytics
UMAMI_URL=https://analytics.yourdomain.com
UMAMI_WEBSITE_ID=your-website-id
UMAMI_HASH_SALT=random-salt-here
# Optional: Umami Analytics
UMAMI_URL=
UMAMI_WEBSITE_ID=
UMAMI_HASH_SALT=
+18 -46
View File
@@ -1,60 +1,32 @@
# Production Environment Configuration Template
# Copy this file to .env and fill in your values
# Application URLs
FRONTEND_HOST=photos.yourdomain.com
BACKEND_HOST=api.photos.yourdomain.com
ADMIN_URL=https://admin.photos.yourdomain.com
FRONTEND_URL=https://photos.yourdomain.com
ADMIN_URL=https://yourdomain.com
FRONTEND_URL=https://yourdomain.com
# Database Configuration
DB_NAME=photo_sharing
DB_USER=photoapp
DB_PASSWORD=your-secure-password-here
# Security - CRITICAL: Generate a secure random JWT secret
# You can generate one with: openssl rand -base64 32
JWT_SECRET=your-secure-random-jwt-secret-here
# JWT Configuration
JWT_SECRET=your-jwt-secret-here
# Database Configuration (PostgreSQL)
DB_USER=picpeak
DB_PASSWORD=your-secure-database-password
DB_NAME=picpeak
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_SECURE=true
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
EMAIL_FROM=noreply@yourdomain.com
# Umami Analytics
# Umami Analytics (Optional)
UMAMI_URL=https://analytics.yourdomain.com
UMAMI_HOST=analytics.yourdomain.com
UMAMI_WEBSITE_ID=your-website-id
UMAMI_HASH_SALT=your-random-salt
UMAMI_DB_PASSWORD=umami-db-password
UMAMI_HASH_SALT=your-random-hash-salt
# Traefik Configuration
TRAEFIK_HOST=traefik.yourdomain.com
ACME_EMAIL=admin@yourdomain.com
TRAEFIK_DASHBOARD_AUTH=admin:$2y$10$... # Use htpasswd to generate
# Docker Registry (optional)
REGISTRY_URL=registry.yourdomain.com
VERSION=latest
# Monitoring
DOMAIN=yourdomain.com
GRAFANA_USER=admin
GRAFANA_PASSWORD=your-grafana-password
# OAuth Configuration (optional)
OAUTH_AUTH_URL=https://auth.yourdomain.com/oauth2/auth
OAUTH_TOKEN_URL=https://auth.yourdomain.com/oauth2/token
OAUTH_USER_URL=https://auth.yourdomain.com/oauth2/userinfo
OAUTH_CLIENT_ID=photo-sharing
OAUTH_CLIENT_SECRET=your-oauth-secret
OAUTH_SECRET=your-random-secret
COOKIE_DOMAIN=.yourdomain.com
OAUTH_WHITELIST=admin@yourdomain.com
# Backup Configuration (optional)
S3_BACKUP_BUCKET=your-backup-bucket
# Drone CI Configuration
DRONE_RPC_SECRET=your-drone-secret
DRONE_GITHUB_CLIENT_ID=your-github-client-id
DRONE_GITHUB_CLIENT_SECRET=your-github-client-secret
# First Admin User (for initial setup)
# Run: docker-compose exec backend node scripts/create-admin.js --email admin@yourdomain.com
ADMIN_EMAIL=admin@yourdomain.com
+287 -424
View File
@@ -1,483 +1,346 @@
# Photo Sharing Platform - Production Deployment Guide
# PicPeak Deployment Guide
This guide covers deploying the photo sharing platform using Docker Swarm, Traefik, and Drone CI/CD.
This guide covers deploying PicPeak for development and production environments.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Infrastructure Setup](#infrastructure-setup)
- [Docker Swarm Setup](#docker-swarm-setup)
- [Traefik Setup](#traefik-setup)
- [Application Deployment](#application-deployment)
- [CI/CD with Drone](#cicd-with-drone)
- [Monitoring](#monitoring)
- [Backup and Recovery](#backup-and-recovery)
- [Quick Start (Development)](#quick-start-development)
- [Production Deployment](#production-deployment)
- [Admin User Setup](#admin-user-setup)
- [Configuration Reference](#configuration-reference)
- [Troubleshooting](#troubleshooting)
## Prerequisites
## Quick Start (Development)
### Hardware Requirements
- **Manager Node**: 2 CPU cores, 4GB RAM, 50GB storage
- **Worker Nodes**: 2 CPU cores, 2GB RAM, 20GB storage
- **Storage**: SSD recommended for database and photo storage
### Software Requirements
- Ubuntu 20.04+ or similar Linux distribution
- Docker Engine 20.10+
- Docker Compose 2.0+
- Git
- SSL certificates (automated with Let's Encrypt)
### Network Requirements
- Ports 80, 443 open for web traffic
- Port 2377 for Swarm management
- Ports 7946, 4789 for Swarm networking
- Static IP or reliable dynamic DNS
## Infrastructure Setup
### 1. Install Docker
### 1. Clone and Setup
```bash
# Install Docker
curl -fsSL https://get.docker.com | sh
git clone https://github.com/yourusername/picpeak.git
cd picpeak
# Add user to docker group
sudo usermod -aG docker $USER
# Enable Docker service
sudo systemctl enable docker
sudo systemctl start docker
```
### 2. Configure Firewall
```bash
# Allow Docker Swarm ports
sudo ufw allow 2377/tcp
sudo ufw allow 7946/tcp
sudo ufw allow 7946/udp
sudo ufw allow 4789/udp
# Allow web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
```
## Docker Swarm Setup
### 1. Initialize Swarm
On the manager node:
```bash
cd deploy/scripts
sudo ./init-swarm.sh
```
This script will:
- Initialize Docker Swarm
- Create overlay networks
- Label nodes for service placement
- Create required directories
### 2. Join Worker Nodes
On each worker node, run the join command displayed by the init script:
```bash
docker swarm join --token SWMTKN-1-xxx... manager-ip:2377
```
### 3. Verify Swarm
```bash
docker node ls
```
## Application Configuration
### 1. Environment Setup
```bash
# Copy environment template
cp .env.production.example .env.production
cp .env.example .env
# Edit with your values
nano .env.production
```
Required configurations:
- Domain names for frontend, backend, and services
- SMTP credentials for email
- Database passwords
- JWT secrets
### 2. Create Docker Secrets
```bash
cd deploy/scripts
./create-secrets.sh
```
This will create all required secrets in Docker Swarm. Save the generated passwords!
## Traefik Setup
### 1. Deploy Traefik
```bash
cd deploy/traefik
# Create traefik network
docker network create --driver overlay traefik-public
# Deploy Traefik stack
docker stack deploy -c docker-compose.traefik.yml traefik
```
### 2. Verify Traefik
```bash
# Check service status
docker service ls | grep traefik
# View logs
docker service logs traefik_traefik
```
Access Traefik dashboard at: `https://traefik.yourdomain.com/dashboard/`
## Application Deployment
### 1. Build Images (if using local registry)
```bash
# Build frontend
cd frontend
docker build -t photo-sharing-frontend:latest .
# Build backend
cd ../backend
docker build -t photo-sharing-backend:latest .
```
### 2. Deploy Application Stack
```bash
cd deploy/scripts
./deploy.sh
```
Options:
- `--env FILE`: Specify environment file
- `--registry URL`: Docker registry URL
- `--version VERSION`: Image version to deploy
### 3. Verify Deployment
```bash
# Check all services
docker service ls
# Check specific service
docker service ps photo-sharing_backend
# View logs
docker service logs photo-sharing_backend -f
```
### 4. Run Database Migrations
The deploy script automatically runs migrations, but you can run manually:
```bash
docker exec $(docker ps -q -f name=photo-sharing_backend) npm run migrate
```
## CI/CD with Drone
### 1. Drone Server Setup
Deploy Drone server on your CI infrastructure:
```bash
docker run \
--volume=/var/lib/drone:/data \
--env=DRONE_GITHUB_CLIENT_ID=your-id \
--env=DRONE_GITHUB_CLIENT_SECRET=your-secret \
--env=DRONE_RPC_SECRET=your-rpc-secret \
--env=DRONE_SERVER_HOST=drone.yourdomain.com \
--env=DRONE_SERVER_PROTO=https \
--publish=80:80 \
--publish=443:443 \
--restart=always \
--detach=true \
--name=drone \
drone/drone:2
```
### 2. Drone Runner Setup
On build servers:
```bash
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-e DRONE_RPC_PROTO=https \
-e DRONE_RPC_HOST=drone.yourdomain.com \
-e DRONE_RPC_SECRET=your-rpc-secret \
-e DRONE_RUNNER_CAPACITY=2 \
-e DRONE_RUNNER_NAME=runner-1 \
-p 3000:3000 \
--restart always \
--name runner \
drone/drone-runner-docker:1
```
### 3. Repository Setup
1. Enable repository in Drone UI
2. Add secrets in Drone:
- `docker_username`
- `docker_password`
- `docker_registry`
- `staging_swarm_host`
- `staging_swarm_user`
- `staging_swarm_key`
- `prod_swarm_host`
- `prod_swarm_user`
- `prod_swarm_key`
- `slack_webhook`
### 4. Deployment Workflow
- Push to `develop` → Deploy to staging
- Create tag → Deploy to production
- Automatic rollback on failure
## Monitoring
### 1. Deploy Monitoring Stack
```bash
cd deploy/monitoring
# Deploy monitoring services
docker stack deploy -c docker-compose.monitoring.yml monitoring
# Start development environment
docker-compose -f docker-compose.dev.yml up -d
```
### 2. Access Services
- Grafana: `https://grafana.yourdomain.com`
- Prometheus: `https://prometheus.yourdomain.com`
- Alertmanager: `https://alerts.yourdomain.com`
- Frontend: http://localhost:3005
- Backend API: http://localhost:3001
- MailHog (email testing): http://localhost:8025
### 3. Configure Alerts
### 3. Create Admin User
Create alert rules in `deploy/monitoring/alerts/`:
```bash
docker-compose -f docker-compose.dev.yml exec backend node scripts/create-admin.js \
--email admin@localhost \
--username admin \
--password admin123
```
## Production Deployment
### Prerequisites
- Docker and Docker Compose installed
- Domain with DNS configured
- SSL/TLS handled by reverse proxy (Traefik, Nginx, etc.)
### 1. Environment Setup
```bash
# Copy production template
cp .env.production.example .env
# Generate secure secrets
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
echo "DB_PASSWORD=$(openssl rand -base64 24)" >> .env
```
Edit `.env` with your configuration:
```env
# Your domain
ADMIN_URL=https://yourdomain.com
FRONTEND_URL=https://yourdomain.com
# Database (PostgreSQL)
DB_USER=picpeak
DB_NAME=picpeak
# DB_PASSWORD already generated above
# Email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=true
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
EMAIL_FROM=noreply@yourdomain.com
```
### 2. Frontend Configuration
```bash
# Configure frontend for production
echo "VITE_API_URL=/api" > frontend/.env.production
```
### 3. Deploy with Docker Compose
```bash
# Build and start services
docker-compose -f docker-compose.prod.yml up -d
# Check status
docker-compose -f docker-compose.prod.yml ps
# View logs
docker-compose -f docker-compose.prod.yml logs -f
```
### 4. Deploy with Traefik
If using Traefik, create `docker-compose.override.yml`:
```yaml
groups:
- name: photo-sharing
rules:
- alert: ServiceDown
expr: up{job="photo-sharing-backend"} == 0
for: 5m
annotations:
summary: "Photo sharing backend is down"
version: '3.8'
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"
networks:
- traefik
- picpeak
networks:
traefik:
external: true
```
## Backup and Recovery
## Admin User Setup
### 1. Automated Backups
### Create First Admin
Set up cron job for automated backups:
After deployment, create your admin user:
```bash
# Edit crontab
crontab -e
# Production
docker-compose -f docker-compose.prod.yml exec backend node scripts/create-admin.js \
--email admin@yourdomain.com \
--username admin \
--password yourSecurePassword
# Add daily backup at 2 AM
0 2 * * * /opt/photo-sharing/deploy/scripts/backup.sh
# Auto-generate password
docker-compose -f docker-compose.prod.yml exec backend node scripts/create-admin.js \
--email admin@yourdomain.com
```
### 2. Manual Backup
The script will display:
- ✅ Admin user created successfully!
- Email: admin@yourdomain.com
- Username: admin
- Login URL: https://yourdomain.com/admin/login
- Password: (save this if auto-generated!)
### Managing Admin Users
```bash
cd deploy/scripts
./backup.sh
# List admin users
docker-compose -f docker-compose.prod.yml exec backend \
psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \
-c "SELECT id, username, email, is_active, last_login FROM admin_users;"
# Deactivate user
docker-compose -f docker-compose.prod.yml exec backend \
psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \
-c "UPDATE admin_users SET is_active = false WHERE email = 'user@example.com';"
```
### 3. Restore from Backup
## Configuration Reference
### Database Configuration
PicPeak automatically detects the environment and uses:
- **Development**: SQLite (`./data/photo_sharing.db`)
- **Production**: PostgreSQL (configured via environment variables)
### Environment Variables
#### Required for Production
| Variable | Description | Example |
|----------|-------------|---------|
| `JWT_SECRET` | JWT signing key | `openssl rand -base64 32` |
| `DB_PASSWORD` | PostgreSQL password | `openssl rand -base64 24` |
| `ADMIN_URL` | Admin panel URL | `https://yourdomain.com` |
| `FRONTEND_URL` | Frontend URL | `https://yourdomain.com` |
| `EMAIL_FROM` | Sender email | `noreply@yourdomain.com` |
#### Email Configuration
| Variable | Description | Example |
|----------|-------------|---------|
| `SMTP_HOST` | SMTP server | `smtp.gmail.com` |
| `SMTP_PORT` | SMTP port | `587` |
| `SMTP_SECURE` | Use TLS | `true` |
| `SMTP_USER` | SMTP username | `your-email@gmail.com` |
| `SMTP_PASS` | SMTP password | App-specific password |
### Storage Paths
- Photos: `./storage/events/active/`
- Archives: `./storage/events/archived/`
- Thumbnails: `./storage/thumbnails/`
- Uploads: `./storage/uploads/`
## Backup and Restore
### Backup Database
```bash
# Extract backup
tar -xzf backup-20240615-020000.tar.gz
# PostgreSQL backup
docker-compose -f docker-compose.prod.yml exec db \
pg_dump -U picpeak picpeak > backup-$(date +%Y%m%d).sql
# Restore database
docker exec -i $(docker ps -q -f name=photo-sharing_db) \
psql -U postgres photo_sharing < backup-20240615-020000/database.sql
# Restore photos
tar -xzf backup-20240615-020000/photos.tar.gz -C /opt/photo-sharing/
# Restore volumes
docker run --rm \
-v photo-sharing_app-data:/data \
-v $(pwd)/backup-20240615-020000:/backup \
alpine tar -xzf /backup/volume-photo-sharing_app-data.tar.gz -C /data
# Backup storage
tar -czf storage-backup-$(date +%Y%m%d).tar.gz ./storage
```
## Maintenance
### 1. Scaling Services
### Restore Database
```bash
# Scale backend to 5 replicas
docker service scale photo-sharing_backend=5
# PostgreSQL restore
docker-compose -f docker-compose.prod.yml exec -T db \
psql -U picpeak picpeak < backup-20240115.sql
# Scale frontend to 3 replicas
docker service scale photo-sharing_frontend=3
# Restore storage
tar -xzf storage-backup-20240115.tar.gz
```
### 2. Rolling Updates
```bash
# Update backend image
docker service update \
--image registry.yourdomain.com/photo-sharing-backend:v2.0 \
photo-sharing_backend
```
### 3. Drain Node for Maintenance
```bash
# Drain node
docker node update --availability drain worker-1
# Perform maintenance...
# Activate node
docker node update --availability active worker-1
```
## Troubleshooting
### Common Issues
#### 1. Service Won't Start
```bash
# Check service status
docker service ps photo-sharing_backend --no-trunc
# View detailed logs
docker service logs photo-sharing_backend --details
```
#### 2. Database Connection Issues
```bash
# Check database logs
docker service logs photo-sharing_db
# Test connection
docker exec $(docker ps -q -f name=photo-sharing_db) \
pg_isready -U postgres
```
#### 3. Traefik Certificate Issues
```bash
# Check Traefik logs
docker service logs traefik_traefik | grep acme
# Remove and regenerate certificates
rm -rf /opt/traefik/letsencrypt/acme.json
docker service update --force traefik_traefik
```
#### 4. Storage Issues
```bash
# Check disk usage
df -h
# Clean up Docker
docker system prune -a
```
### Debug Mode
Enable debug logging:
```bash
# Update service with debug logging
docker service update \
--env-add LOG_LEVEL=debug \
photo-sharing_backend
```
## Monitoring
### Health Checks
```bash
# Check all endpoints
curl -f https://photos.yourdomain.com/health
curl -f https://api.photos.yourdomain.com/api/health
curl -f https://traefik.yourdomain.com/ping
# Backend health
curl https://yourdomain.com/api/health
# Frontend health
curl https://yourdomain.com/health
```
## Security Best Practices
### Logs
1. **Regular Updates**
- Keep Docker and system packages updated
- Update application dependencies regularly
- Monitor security advisories
```bash
# All services
docker-compose -f docker-compose.prod.yml logs -f
2. **Access Control**
- Use strong passwords for all services
- Enable 2FA where possible
- Restrict SSH access to specific IPs
- Use Docker secrets for sensitive data
# Specific service
docker-compose -f docker-compose.prod.yml logs -f backend
3. **Network Security**
- Use internal networks for service communication
- Enable firewall rules
- Use TLS for all external communication
- Regular security scans with Trivy
# Last 100 lines
docker-compose -f docker-compose.prod.yml logs --tail=100 backend
```
4. **Backup Security**
- Encrypt backups at rest
- Test restore procedures regularly
- Store backups in multiple locations
- Rotate old backups
## Troubleshooting
## Performance Tuning
### Backend Won't Start
1. **Database Optimization**
```sql
-- Add indexes for common queries
CREATE INDEX idx_photos_event_id ON photos(event_id);
CREATE INDEX idx_access_logs_event_id ON access_logs(event_id);
1. Check database connection:
```bash
docker-compose -f docker-compose.prod.yml logs db
```
2. **Image Optimization**
- Use CDN for static assets
- Enable aggressive caching
- Optimize image sizes before upload
3. **Service Limits**
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
2. Verify environment variables:
```bash
docker-compose -f docker-compose.prod.yml exec backend env | grep DB_
```
## Support
### Can't Login as Admin
For issues and questions:
- Check logs: `docker service logs <service_name>`
- Review documentation: [README.md](README.md)
- Check monitoring dashboards
- Contact: admin@yourdomain.com
1. Verify admin user exists:
```bash
docker-compose -f docker-compose.prod.yml exec backend \
psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \
-c "SELECT * FROM admin_users;"
```
2. Reset admin password:
```bash
# Create new admin with different email
docker-compose -f docker-compose.prod.yml exec backend \
node scripts/create-admin.js --email newadmin@yourdomain.com
```
### Photos Not Loading
1. Check file permissions:
```bash
ls -la ./storage/events/active/
```
2. Verify nginx proxy configuration:
```bash
docker-compose -f docker-compose.prod.yml exec frontend \
cat /etc/nginx/conf.d/default.conf
```
### Email Not Sending
1. Check email configuration:
```bash
docker-compose -f docker-compose.prod.yml exec backend env | grep SMTP_
```
2. View email queue:
```bash
docker-compose -f docker-compose.prod.yml exec backend \
psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \
-c "SELECT * FROM email_queue WHERE status = 'failed';"
```
## Maintenance
### Update Application
```bash
# Pull latest changes
git pull
# Rebuild images
docker-compose -f docker-compose.prod.yml build
# Restart services
docker-compose -f docker-compose.prod.yml up -d
```
### Clean Up
```bash
# Remove unused images
docker image prune -a
# Clean up logs
docker-compose -f docker-compose.prod.yml logs --tail=0 -f
# Remove old archives
find ./storage/events/archived -name "*.zip" -mtime +90 -delete
```
## Security Checklist
- [ ] Generated secure `JWT_SECRET`
- [ ] Generated secure `DB_PASSWORD`
- [ ] HTTPS enabled via reverse proxy
- [ ] Changed default admin credentials
- [ ] Configured real SMTP server
- [ ] Set file permissions: `chmod 600 .env`
- [ ] Firewall configured
- [ ] Regular backups scheduled
- [ ] Monitoring enabled
+10 -1
View File
@@ -24,10 +24,19 @@ ARCHIVE_PATH=./storage/events/archived
# Logging
LOG_LEVEL=info
# Database (for production, consider PostgreSQL)
# Database Configuration
# Development: Use SQLite
DATABASE_CLIENT=sqlite3
DATABASE_PATH=./data/photo_sharing.db
# Production: Use PostgreSQL
# DATABASE_CLIENT=pg
# DB_HOST=localhost
# DB_PORT=5432
# DB_USER=picpeak
# DB_PASSWORD=your-secure-password
# DB_NAME=picpeak
# Umami Analytics (optional)
UMAMI_URL=
UMAMI_WEBSITE_ID=
+46
View File
@@ -0,0 +1,46 @@
require('dotenv').config();
const path = require('path');
// Database configuration for different environments
const config = {
development: {
client: process.env.DATABASE_CLIENT || 'sqlite3',
connection: process.env.DATABASE_CLIENT === 'pg' ? {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'photo_sharing'
} : {
filename: path.join(__dirname, process.env.DATABASE_PATH || './data/photo_sharing.db')
},
useNullAsDefault: process.env.DATABASE_CLIENT !== 'pg',
migrations: {
directory: './migrations'
},
seeds: {
directory: './seeds'
}
},
production: {
client: process.env.DATABASE_CLIENT || 'pg',
connection: {
host: process.env.DB_HOST || 'postgres',
port: process.env.DB_PORT || 5432,
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'photo_sharing'
},
pool: {
min: 2,
max: 10
},
migrations: {
directory: './migrations'
}
}
};
module.exports = config[process.env.NODE_ENV || 'development'];
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-backend",
"version": "1.0.3",
"version": "1.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-backend",
"version": "1.0.3",
"version": "1.0.4",
"dependencies": {
"adm-zip": "^0.5.16",
"archiver": "^5.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picpeak-backend",
"version": "1.0.3",
"version": "1.0.4",
"description": "Backend for PicPeak event photo sharing platform",
"main": "server.js",
"scripts": {
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env node
/**
* Script to create an admin user
* Usage: node scripts/create-admin.js --email admin@example.com --username admin --password yourpassword
*
* If no password is provided, a random one will be generated and displayed
*/
require('dotenv').config();
const bcrypt = require('bcryptjs');
const { db } = require('../src/database/db');
const crypto = require('crypto');
// Parse command line arguments
const args = process.argv.slice(2);
const getArg = (name) => {
const index = args.findIndex(arg => arg === `--${name}`);
return index !== -1 && args[index + 1] ? args[index + 1] : null;
};
const email = getArg('email');
const username = getArg('username') || email?.split('@')[0] || 'admin';
let password = getArg('password');
// Validate email
if (!email) {
console.error('Error: Email is required. Use --email admin@example.com');
process.exit(1);
}
// Generate password if not provided
if (!password) {
password = crypto.randomBytes(12).toString('base64').slice(0, 16);
console.log(`Generated password: ${password}`);
console.log('Please save this password securely!');
}
async function createAdmin() {
try {
// Check if user already exists
const existingUser = await db('admin_users')
.where('email', email)
.orWhere('username', username)
.first();
if (existingUser) {
console.error(`Error: User with email "${email}" or username "${username}" already exists`);
process.exit(1);
}
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create admin user
await db('admin_users').insert({
username,
email,
password_hash: passwordHash,
is_active: true,
created_at: new Date(),
updated_at: new Date()
});
console.log(`✅ Admin user created successfully!`);
console.log(` Email: ${email}`);
console.log(` Username: ${username}`);
console.log(` Login URL: ${process.env.ADMIN_URL || 'http://localhost:3000'}/admin/login`);
process.exit(0);
} catch (error) {
console.error('Error creating admin user:', error.message);
process.exit(1);
}
}
createAdmin();
+39 -40
View File
@@ -1,13 +1,7 @@
const knex = require('knex');
const path = require('path');
const knexConfig = require('../../knexfile');
const db = knex({
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '../../data/photo_sharing.db')
},
useNullAsDefault: true
});
const db = knex(knexConfig);
async function initializeDatabase() {
// Events table
@@ -35,37 +29,42 @@ async function initializeDatabase() {
} else {
// Check if color_theme needs to be updated to TEXT type
// This is needed for larger theme configurations
try {
await db.raw(`
CREATE TABLE IF NOT EXISTS events_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
event_type TEXT NOT NULL,
event_name TEXT NOT NULL,
event_date DATE NOT NULL,
host_email TEXT NOT NULL,
admin_email TEXT NOT NULL,
password_hash TEXT NOT NULL,
welcome_message TEXT,
color_theme TEXT,
share_link TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME NOT NULL,
is_active BOOLEAN DEFAULT 1,
is_archived BOOLEAN DEFAULT 0,
archive_path TEXT,
archived_at DATETIME,
allow_user_uploads BOOLEAN DEFAULT 0,
upload_category_id INTEGER
)
`);
await db.raw(`INSERT INTO events_new SELECT * FROM events`);
await db.raw(`DROP TABLE events`);
await db.raw(`ALTER TABLE events_new RENAME TO events`);
} catch (error) {
// If the migration fails, it might already have been applied
console.log('Color theme migration may have already been applied');
const isPostgres = knexConfig.client === 'pg';
if (!isPostgres) {
// SQLite-specific migration
try {
await db.raw(`
CREATE TABLE IF NOT EXISTS events_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
event_type TEXT NOT NULL,
event_name TEXT NOT NULL,
event_date DATE NOT NULL,
host_email TEXT NOT NULL,
admin_email TEXT NOT NULL,
password_hash TEXT NOT NULL,
welcome_message TEXT,
color_theme TEXT,
share_link TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME NOT NULL,
is_active BOOLEAN DEFAULT 1,
is_archived BOOLEAN DEFAULT 0,
archive_path TEXT,
archived_at DATETIME,
allow_user_uploads BOOLEAN DEFAULT 0,
upload_category_id INTEGER
)
`);
await db.raw(`INSERT INTO events_new SELECT * FROM events`);
await db.raw(`DROP TABLE events`);
await db.raw(`ALTER TABLE events_new RENAME TO events`);
} catch (error) {
// If the migration fails, it might already have been applied
console.log('Color theme migration may have already been applied');
}
}
}
@@ -225,4 +224,4 @@ async function logActivity(activityType, metadata = {}, eventId = null, actor =
}
}
module.exports = { db, initializeDatabase, logActivity };
module.exports = { db, initializeDatabase, logActivity };
+61
View File
@@ -0,0 +1,61 @@
# Development Docker Compose Configuration
version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3000"
environment:
- NODE_ENV=development
- PORT=3000
- JWT_SECRET=dev-secret-change-in-production
- ADMIN_URL=http://localhost:3005
- FRONTEND_URL=http://localhost:3005
- DATABASE_CLIENT=sqlite3
- DATABASE_PATH=./data/photo_sharing.db
# Email - uses Mailhog
- SMTP_HOST=mailhog
- SMTP_PORT=1025
- SMTP_SECURE=false
- EMAIL_FROM=noreply@photo-sharing.local
volumes:
- ./backend:/app
- /app/node_modules
- ./storage:/app/storage
- ./data:/app/data
- ./logs:/app/logs
depends_on:
- mailhog
command: sh -c "npm install && npm run dev"
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3005:80"
environment:
- NODE_ENV=development
volumes:
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
-109
View File
@@ -1,109 +0,0 @@
version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "3001:3000"
environment:
- NODE_ENV=development
- PORT=3000
- JWT_SECRET=b375996f704bb1541ad9297e7710a215fce0b59ecf1c43236aeea32ec01e7b27
- ADMIN_URL=http://localhost:3005
- FRONTEND_URL=http://localhost:3005
# Email - uses Mailhog
- SMTP_HOST=mailhog
- SMTP_PORT=1025
- SMTP_SECURE=false
- SMTP_USER=
- SMTP_PASS=
- EMAIL_FROM=noreply@photo-sharing.local
# Storage path
- STORAGE_PATH=/app/storage
# Umami Analytics (optional)
- UMAMI_URL=
- UMAMI_WEBSITE_ID=
volumes:
- ./backend:/app
- /app/node_modules
- ./storage:/app/storage
- ./data:/app/data
- ./logs:/app/logs
depends_on:
- mailhog
command: sh -c "npm install && npm run dev"
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
args:
- VITE_API_URL=http://localhost:3001
- VITE_UMAMI_URL=
- VITE_UMAMI_WEBSITE_ID=
ports:
- "3005:80"
environment:
- NODE_ENV=development
volumes:
- ./frontend/dist:/usr/share/nginx/html
- ./frontend/nginx.dev.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Development frontend with hot reload
frontend-dev:
image: node:18-alpine
working_dir: /app
ports:
- "3002:5173"
environment:
- NODE_ENV=development
- VITE_API_URL=http://localhost:3001
- VITE_UMAMI_URL=
- VITE_UMAMI_WEBSITE_ID=
volumes:
- ./frontend:/app
- /app/node_modules
command: sh -c "npm install --legacy-peer-deps && npm run dev -- --host"
depends_on:
- backend
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
# Optional: File watcher service
file-watcher:
build:
context: ./backend
dockerfile: Dockerfile.dev
environment:
- NODE_ENV=development
- STORAGE_PATH=/app/storage
volumes:
- ./backend:/app
- /app/node_modules
- ./storage:/app/storage
- ./data:/app/data
command: node src/services/fileWatcher.js
depends_on:
- backend
volumes:
node_modules_backend:
node_modules_frontend:
+11 -2
View File
@@ -14,12 +14,21 @@ services:
- JWT_SECRET=${JWT_SECRET}
- ADMIN_URL=${ADMIN_URL}
- FRONTEND_URL=${FRONTEND_URL}
# Database
- DATABASE_CLIENT=pg
- DB_HOST=db
- DB_PORT=5432
- DB_USER=${DB_USER:-picpeak}
- DB_PASSWORD=${DB_PASSWORD}
- DB_NAME=${DB_NAME:-picpeak}
# Email
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_SECURE=${SMTP_SECURE}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- EMAIL_FROM=${EMAIL_FROM}
# Analytics
- UMAMI_URL=${UMAMI_URL}
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
volumes:
@@ -70,7 +79,7 @@ services:
image: postgres:14-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=${DB_USER:-photoapp}
- POSTGRES_USER=${DB_USER:-picpeak}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME:-picpeak}
volumes:
@@ -82,7 +91,7 @@ services:
image: ghcr.io/umami-software/umami:postgresql-latest
restart: unless-stopped
environment:
DATABASE_URL: postgresql://${DB_USER:-photoapp}:${DB_PASSWORD}@db:5432/umami
DATABASE_URL: postgresql://${DB_USER:-picpeak}:${DB_PASSWORD}@db:5432/umami
DATABASE_TYPE: postgresql
HASH_SALT: ${UMAMI_HASH_SALT}
depends_on:
-49
View File
@@ -1,49 +0,0 @@
# docker-compose.yml - Development configuration
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3000"
environment:
- NODE_ENV=development
- PORT=3000
- JWT_SECRET=b55e4b3e9f212e1d1836c2ee2ec5ac47672350acdf1391c894d3433646579ad0
- ADMIN_URL=http://localhost:3001
- FRONTEND_URL=http://localhost:3005
- SMTP_HOST=mailhog
- SMTP_PORT=1025
- SMTP_SECURE=false
- SMTP_USER=
- SMTP_PASS=
- EMAIL_FROM=noreply@localhost
volumes:
- ./backend:/app
- /app/node_modules
- ./storage:/app/storage
- ./data:/app/data
- ./logs:/app/logs
depends_on:
- mailhog
command: node server.js
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3005:80"
environment:
- REACT_APP_API_URL=http://localhost:3001
volumes:
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025"
- "8025:8025"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-frontend",
"version": "1.0.3",
"version": "1.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-frontend",
"version": "1.0.3",
"version": "1.0.4",
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"@tiptap/extension-link": "^2.25.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "picpeak-frontend",
"private": true,
"version": "1.0.3",
"version": "1.0.4",
"type": "module",
"scripts": {
"dev": "vite",