Compare commits

...

25 Commits

Author SHA1 Message Date
Gitea Actions Bot cdff4ebb8a chore: bump version to 1.0.74 (backend + frontend)
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2025-07-21 17:40:15 +00:00
paul 59f958b085 chore: consolidate and update deployment documentation
Mirror to GitHub / mirror (push) Successful in 28s
Test and Lint / backend-test (push) Successful in 1m4s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m20s
Version and Release / version-bump (push) Successful in 42s
Version and Release / trigger-drone (push) Successful in 4s
- Remove completed PRODUCTION_TODO_LIST.md
- Consolidate deployment guides: keep comprehensive PRODUCTION_DEPLOYMENT_GUIDE.md, remove redundant PRODUCTION_DEPLOYMENT.md
- Update all .env.example files to reflect current system:
  - Remove deprecated ADMIN_EMAIL/ADMIN_PASSWORD (now auto-generated)
  - Add proper documentation for all environment variables
  - Clarify that Umami config is optional (primary via Admin UI)
  - Add realistic examples for SMTP providers
  - Update ports to match actual defaults (3001)
- Update PRODUCTION_DEPLOYMENT_GUIDE.md:
  - Document auto-generated admin credentials process
  - Add Traefik configuration section
  - Update security checklist with current features
  - Fix outdated environment variables
  - Add nginx proxy configuration details

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 19:35:22 +02:00
paul 097d7a0b65 git statusMerge branch 'main' of https://gitea.nothaft.cloud/paul/picpeak
Mirror to GitHub / mirror (push) Successful in 27s
Test and Lint / backend-test (push) Successful in 1m15s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m22s
Update workflows
2025-07-21 17:31:57 +02:00
paul 1273777541 feat: exclude Claude contributor from GitHub mirror workflow 2025-07-21 17:31:39 +02:00
Gitea Actions Bot b6a960879f chore: bump frontend version to 1.0.73
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-07-21 15:28:33 +00:00
paul 4bd153104b Merge branch 'main' of https://gitea.nothaft.cloud/paul/picpeak
Mirror to GitHub / mirror (push) Successful in 35s
Test and Lint / backend-test (push) Successful in 1m30s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m30s
Version and Release / version-bump (push) Successful in 45s
Version and Release / trigger-drone (push) Successful in 3s
2025-07-21 17:23:02 +02:00
paul e099fcf600 fix: use admin API for Umami config in analytics page
- Changed from public settings endpoint to admin settings endpoint
- Fixed "Unexpected token '<'" JSON parse error
- Properly transforms settings array to key-value map
- Uses correct setting keys (analytics_umami_*)
- Maintains fallback to environment variables

The analytics page now correctly fetches Umami configuration using
the authenticated admin API instead of the public endpoint, which
was returning errors and causing JSON parse failures.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 16:53:31 +02:00
Gitea Actions Bot 89fdf401c0 chore: bump frontend version to 1.0.72
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is passing
2025-07-20 20:32:41 +00:00
paul 9cc46a1819 feat: completely rewrite GitHub mirror to create new history from target commit
Mirror to GitHub / mirror (push) Successful in 1m49s
continuous-integration/drone/push Build is failing
Test and Lint / backend-test (push) Successful in 2m12s
Test and Lint / frontend-test (push) Successful in 2m45s
BREAKING: This completely replaces the previous approach and will DELETE
all existing history on GitHub, creating entirely new commit SHAs.

Key changes:
- Use orphan branch instead of cherry-pick to break history connection
- Create initial commit from target commit tree using git read-tree
- Apply subsequent changes as completely new commits with new SHAs
- Force push will COMPLETELY REPLACE GitHub history
- No trace of commits before 7aca927937 will remain on GitHub

This ensures GitHub shows only history from the target commit onwards
with no connection to previous commits or their metadata.
2025-07-20 22:30:57 +02:00
paul f7c0e5f51a fix: improve version bump workflow with better conflict resolution
Mirror to GitHub / mirror (push) Has been cancelled
Test and Lint / backend-test (push) Has been cancelled
Test and Lint / frontend-test (push) Has been cancelled
continuous-integration/drone/push Build is passing
- Added pre-fetch and check before committing to ensure we're up-to-date
- Improved retry logic with clearer output and better error handling
- Added explicit fetch before each retry attempt
- Use for-loop instead of while for clearer retry counting
- Better fallback from rebase to merge on conflicts
- Added set -e to fail fast on errors
- More verbose logging for debugging

This should resolve the persistent "non-fast-forward" errors by:
1. Checking if we're behind before even committing
2. Pulling changes if needed
3. Retrying with proper synchronization
4. Providing clear debug output

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 22:28:05 +02:00
paul 200581e73c CRITICAL FIX: Remove 403 from auth redirect logic to restore login
Mirror to GitHub / mirror (push) Successful in 1m2s
Test and Lint / frontend-test (push) Has been cancelled
Test and Lint / backend-test (push) Has started running
continuous-integration/drone/push Build is passing
Version and Release / version-bump (push) Successful in 3m36s
Version and Release / trigger-drone (push) Successful in 6s
BREAKING ISSUE FIXED:
- 403 errors were triggering redirects, preventing login page from loading
- Public endpoints returning 403 were causing redirect loops

Changes:
- Removed 403 status from automatic redirect logic
- Only 401 (Unauthorized) now triggers login redirect
- 403 (Forbidden) errors are passed through without redirect

This fixes the critical issue where users couldn't access the login page
because public API calls were returning 403 and triggering redirects.

403 errors should be handled differently than 401:
- 401 = Missing/invalid auth (redirect to login)
- 403 = Forbidden (could be rate limit, IP block, etc - don't redirect)

🚨 Emergency fix for production

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 22:24:25 +02:00
paul 505acf833e fix: resolve SIGPIPE error in GitHub mirror workflow file cleanup
Mirror to GitHub / mirror (push) Successful in 1m23s
continuous-integration/drone/push Build is passing
Test and Lint / backend-test (push) Successful in 2m6s
Test and Lint / frontend-test (push) Has started running
- Replace problematic 'find | head -20' commands that caused exit code 141
- Use 'ls -la | head -10 || true' for safer file listing
- Add better progress logging during sensitive file removal
- Add error handling with '|| true' to prevent pipe failures

The find command was outputting more than head could handle, causing
SIGPIPE when head closed the pipe early. This fix uses ls which is
more predictable and adds proper error handling.
2025-07-20 22:17:47 +02:00
paul 1ac5b0447a fix: resolve GitHub mirror workflow cherry-pick failure with merge commits
Mirror to GitHub / mirror (push) Failing after 1m27s
Test and Lint / frontend-test (push) Has been cancelled
Test and Lint / backend-test (push) Has started running
- Add --no-merges flag to exclude merge commits during cherry-pick
- Improve error handling for cherry-pick conflicts with auto-resolution
- Add reporting of skipped merge commits for transparency
- Enhance logging to show detailed progress during commit application

Fixes the workflow failure caused by trying to cherry-pick merge commits
which require special handling that was causing exit code 128.
2025-07-20 22:13:55 +02:00
paul 15a2fc2d5f fix: resolve CI/CD version bump race condition
Mirror to GitHub / mirror (push) Failing after 27s
Test and Lint / backend-test (push) Successful in 2m19s
Test and Lint / frontend-test (push) Has been cancelled
continuous-integration/drone/push Build is passing
- Added pull before push to handle concurrent workflow executions
- Implemented retry logic with 3 attempts for push operations
- Added fallback from rebase to merge if conflicts occur
- Added proper error handling and logging for debugging

This fixes the "non-fast-forward" error that occurs when multiple
workflows run simultaneously and try to push version bumps.

The workflow now:
1. Pulls latest changes before pushing
2. Retries up to 3 times with 5-second delays
3. Falls back to merge if rebase fails
4. Provides clear error messages for debugging

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 22:10:04 +02:00
paul a1cf6a1156 feat: update GitHub mirror workflow to start history from specific commit
Mirror to GitHub / mirror (push) Failing after 34s
Test and Lint / backend-test (push) Has started running
Test and Lint / frontend-test (push) Has been cancelled
continuous-integration/drone/push Build is failing
- Start history from commit 7aca927937 instead of orphan branch
- Use cherry-pick to preserve meaningful commit history
- Automatically exclude files that only existed before target commit
- Add comprehensive error handling and logging
- Maintain clean linear history for GitHub repository
2025-07-20 22:08:53 +02:00
paul 481545c37b feat: enhance mirror-to-github workflow with commit-based history filtering
Mirror to GitHub / mirror (push) Successful in 29s
Test and Lint / backend-test (push) Successful in 1m17s
continuous-integration/drone/push Build is failing
Test and Lint / frontend-test (push) Successful in 2m29s
- Replace orphan branch approach with commit-based filtering from cfa29ad5cb
- Add automatic removal of sensitive files (env, logs, gitea configs)
- Implement robust git operations with fallback mechanisms
- Add comprehensive debugging and error handling
- Ensure same security exclusions as manual process
2025-07-20 22:00:07 +02:00
paul a67df87013 fix: handle auth errors and JSON parsing in admin panel
Mirror to GitHub / mirror (push) Successful in 32s
Test and Lint / frontend-test (push) Has been cancelled
Test and Lint / backend-test (push) Has started running
continuous-integration/drone/push Build is passing
Version and Release / version-bump (push) Failing after 1m17s
Version and Release / trigger-drone (push) Failing after 11m37s
- Added proper HTTP status check before JSON parsing in AnalyticsPage
  * Prevents "Unexpected token '<'" error when API returns HTML error pages
  * Throws proper error for non-OK responses

- Enhanced API error handling to treat 403 as auth failure
  * Both 401 and 403 now trigger redirect to login page
  * Clears expired admin tokens automatically
  * Prevents users from staying on admin pages with expired sessions

These fixes resolve:
1. JSON parse errors when fetching Umami config
2. 403 Forbidden errors not redirecting to login
3. Backend version display issues due to auth failures

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 21:58:53 +02:00
paul d2dbe2ea2f Merge branch 'main' of https://gitea.nothaft.cloud/paul/picpeak
Mirror to GitHub / mirror (push) Successful in 39s
Test and Lint / backend-test (push) Successful in 1m19s
Test and Lint / frontend-test (push) Successful in 2m18s
Version and Release / version-bump (push) Successful in 36s
Version and Release / trigger-drone (push) Has been skipped
continuous-integration/drone/push Build is passing
2025-07-20 21:19:38 +02:00
paul 98ea5e7202 chore: add production todo list and update CI/CD configs
- Added PRODUCTION_TODO_LIST.md with 9 completed production fixes
- Updated .gitea/workflows/mirror-to-github.yml
- Updated .gitignore

This commit includes all the production fixes implemented:
1. Password complexity settings
2. Gallery login security improvements
3. Analytics configuration fixes
4. Translation additions
5. UI/UX improvements
6. Date format consistency
7. Chrome compatibility fixes

All tasks have been completed and tested for production deployment.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 21:19:18 +02:00
Gitea Actions Bot 6b5c08e99b chore: bump version to 1.0.71 (backend + frontend)
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
2025-07-20 19:07:39 +00:00
paul 11ecad136b Merge branch 'main' of https://gitea.nothaft.cloud/paul/picpeak
Mirror to GitHub / mirror (push) Successful in 41s
Test and Lint / backend-test (push) Successful in 1m21s
Test and Lint / frontend-test (push) Successful in 2m23s
Version and Release / version-bump (push) Successful in 52s
Version and Release / trigger-drone (push) Successful in 5s
continuous-integration/drone/push Build is failing
2025-07-20 21:01:27 +02:00
paul 3a4dccd9f0 fix: resolve production UI and API issues
- Fixed backend version endpoint by adding retry logic import
- Gallery login page improvements:
  * Increased title size from text-xl to text-2xl (responsive scaling)
  * Title now uses event's custom primary color (var(--color-primary))
  * Removed event category badge from login page
- Fixed Umami analytics configuration check:
  * Added proper enabled state tracking
  * Warning now only shows when Umami is explicitly not configured
  * Checks both admin settings and environment variables properly

These changes improve user experience and fix false warnings in production.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 21:00:43 +02:00
Gitea Actions Bot 0a5e55ca96 chore: bump backend version to 1.0.70
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2025-07-20 19:00:09 +00:00
paul a72741c0d9 Merge branch 'main' of https://gitea.nothaft.cloud/paul/picpeak
Version and Release / trigger-drone (push) Blocked by required conditions
Mirror to GitHub / mirror (push) Successful in 40s
Test and Lint / backend-test (push) Successful in 1m28s
Test and Lint / frontend-test (push) Successful in 2m48s
Version and Release / version-bump (push) Successful in 1m4s
2025-07-20 20:52:38 +02:00
paul e7ed7006fd fix: critical database connection pool exhaustion issues
- Disabled duplicate email service (emailService.js) that was creating redundant connections
- Increased connection pool size from 10 to 25 for production environment
- Extended session timeout cache from 5 to 30 minutes to reduce DB queries
- Added connection retry logic with exponential backoff for transient failures
- Fixed password validation to use retry wrapper and correct setting key
- Updated public settings and gallery middleware to handle connection failures gracefully

These changes address the "Connection terminated unexpectedly" errors in production by:
1. Reducing unnecessary database connections
2. Increasing available connection pool capacity
3. Implementing automatic retry for transient connection failures
4. Caching frequently accessed data for longer periods

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 20:51:44 +02:00
25 changed files with 574 additions and 289 deletions
+31 -18
View File
@@ -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=
# 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)
+83 -27
View File
@@ -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 <your-email@gmail.com>
# 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 <noreply@your-domain.com>
# ============================================
# 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
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
+154 -33
View File
@@ -10,10 +10,10 @@ jobs:
mirror:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- name: Checkout repository with full history
uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history needed for mirroring
fetch-depth: 0 # Full history needed for finding the commit
- name: Setup Git
run: |
@@ -28,40 +28,153 @@ jobs:
git status
echo "Remote info:"
git remote -v
echo "Checking target commit exists:"
git show --oneline 7aca927937 || echo "Target commit not found!"
- name: Create filtered branch
- name: Create completely new history from specific commit
run: |
TARGET_COMMIT="7aca927937"
# Verify the target commit exists
if ! git cat-file -e $TARGET_COMMIT^{commit}; then
echo "ERROR: Target commit $TARGET_COMMIT does not exist!"
exit 1
fi
echo "✅ Target commit found: $(git log --oneline -1 $TARGET_COMMIT)"
# Clean up any existing github-mirror branch
git branch -D github-mirror || true
# Create a new branch for GitHub
# Create a completely new orphan branch (no history)
git checkout --orphan github-mirror
# Remove sensitive files/directories
# Example: Remove .env files, private configs, etc.
git rm -r --cached .env* || true
git rm -r --cached backend/.env* || true
git rm -r --cached frontend/.env* || true
git rm -r --cached docker-compose.prod.yml || true
git rm -r --cached .claudedocs/ || true
git rm -r --cached backend/data/ || true
git rm -r --cached backend/storage/ || true
git rm -r --cached .gitea/ || true
git rm -r --cached scripts/install-gitea-runner.sh || true
git rm -r --cached .drone* || true
git rm -r --cached .github-mirror-exclude || true
git rm -r --cached .gitattributes-github || true
git rm -r --cached photo-sharing-prd.md || true
git rm -r --cached CLAUDE.md || true
git rm -r --cached PRODUCTION_DEPLOYMENT_GUIDE.md || true
git rm -r --cached logs/ || true
git rm -r --cached frontend/.claudedocs/ || true
git rm -r --cached test-maintenance.sh || true
git rm -r --cached storage/ || true
# Clear the staging area completely
git rm -rf . || true
# Get the file tree from the target commit and create initial commit
echo "Creating new history starting from $TARGET_COMMIT..."
git read-tree $TARGET_COMMIT
git commit -m "Initial commit - imported from $(git log --oneline -1 $TARGET_COMMIT)"
echo "✅ Created new initial commit: $(git log --oneline -1)"
# Now get all commits after the target commit and apply their changes
COMMITS_AFTER_TARGET=$(git rev-list --reverse --no-merges $TARGET_COMMIT..main)
if [ -n "$COMMITS_AFTER_TARGET" ]; then
echo "📋 Applying changes from commits after $TARGET_COMMIT (excluding Claude commits):"
for commit in $COMMITS_AFTER_TARGET; do
# Get the commit author name
COMMIT_AUTHOR_NAME=$(git log --format="%an" -n 1 $commit)
# Skip commits by Claude
if [ "$COMMIT_AUTHOR_NAME" = "Claude" ]; then
echo "⚠️ Skipping commit by Claude: $(git log --oneline -1 $commit)"
continue
fi
echo "Processing: $(git log --oneline -1 $commit)"
# Get the commit message and author info
COMMIT_MSG=$(git log --format="%B" -n 1 $commit)
COMMIT_AUTHOR=$(git log --format="%an <%ae>" -n 1 $commit)
COMMIT_DATE=$(git log --format="%ad" -n 1 $commit)
# Apply the changes from this commit
if git diff-tree --no-commit-id --name-only -r $commit | xargs -I {} git show $commit:{} > /dev/null 2>&1; then
# Apply file changes
git checkout $commit -- . || true
# Stage all changes
git add -A
# Only commit if there are changes
if ! git diff --cached --quiet; then
# Create new commit with original metadata but new SHA
GIT_AUTHOR_NAME=$(echo "$COMMIT_AUTHOR" | cut -d'<' -f1 | xargs)
GIT_AUTHOR_EMAIL=$(echo "$COMMIT_AUTHOR" | cut -d'<' -f2 | cut -d'>' -f1)
GIT_AUTHOR_DATE="$COMMIT_DATE"
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
git commit -m "$COMMIT_MSG"
echo "✅ Applied changes as new commit: $(git log --oneline -1)"
else
echo "⚠️ No changes to commit for $commit"
fi
else
echo "⚠️ Skipping problematic commit $commit"
fi
done
echo "✅ Finished creating new history"
else
echo "✅ No commits after target commit - history starts fresh"
fi
echo ""
echo "=== New History Summary ==="
echo "Total commits in new history: $(git rev-list --count github-mirror)"
echo "History starts with: $(git log --oneline --reverse | head -1)"
echo "Latest commit: $(git log --oneline -1)"
# Commit the changes
git commit -m "Remove sensitive files for GitHub mirror" || true
- name: Remove sensitive files and directories
run: |
# Switch to the github-mirror branch
git checkout github-mirror
echo "Current files before cleanup:"
ls -la | head -10 || true
echo "..."
# Remove sensitive files/directories if they exist
echo "Removing sensitive files..."
rm -rf .env || true
rm -rf backend/.env* || true
rm -rf frontend/.env* || true
rm -rf docker-compose.prod.yml || true
rm -rf .claudedocs/ || true
rm -rf backend/data/ || true
rm -rf backend/storage/ || true
rm -rf .gitea/ || true
rm -rf scripts/install-gitea-runner.sh || true
rm -rf .drone* || true
rm -rf .github-mirror-exclude || true
rm -rf .gitattributes-github || true
rm -rf photo-sharing-prd.md || true
rm -rf CLAUDE.md || true
rm -rf PRODUCTION_DEPLOYMENT_GUIDE.md || true
rm -rf logs/ || true
rm -rf frontend/.claudedocs/ || true
rm -rf test-maintenance.sh || true
rm -rf storage/ || true
echo "Sensitive files removal completed"
# Add and commit the cleanup if there are changes
git add -A
if ! git diff --cached --quiet; then
git commit -m "chore: remove sensitive files for GitHub mirror"
echo "✅ Committed cleanup of sensitive files"
else
echo "✅ No sensitive files to remove"
fi
echo "Final file structure (top level):"
ls -la | head -10 || true
- name: Verify completely new history
run: |
git checkout github-mirror
echo "=== Final History Verification ==="
echo "Total commits in new github-mirror branch: $(git rev-list --count github-mirror)"
echo ""
echo "Complete commit history (should start from target commit content):"
git log --oneline --reverse
echo ""
echo "⚠️ Note: This is a completely NEW history with new commit SHAs"
echo "🔍 Original target commit content preserved but with new commit ID"
- name: Check GitHub token
env:
@@ -74,10 +187,13 @@ jobs:
echo "GitHub token is available (length: ${#GITHUBTOKEN})"
fi
- name: Push to GitHub
- name: Force push completely new history to GitHub
env:
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
run: |
# Switch to github-mirror branch
git checkout github-mirror
# Remove existing github remote if it exists
git remote remove github || true
@@ -88,12 +204,17 @@ jobs:
echo "GitHub remote added:"
git remote -v
# Force push the filtered branch to GitHub main
echo "Pushing to GitHub..."
# Force push the completely new history to GitHub main
echo "🔥 FORCE PUSHING completely new history to GitHub..."
echo "⚠️ This will COMPLETELY REPLACE all history on GitHub!"
git push github github-mirror:main --force
echo "Push completed successfully!"
echo "✅ Force push completed - GitHub now has completely new history!"
- name: Workflow completed
run: |
echo "✅ Mirror to GitHub workflow completed successfully!"
echo "Check https://github.com/the-luap/picpeak to verify the mirror."
echo "🔥 COMPLETE HISTORY REPLACEMENT: GitHub now has entirely new history"
echo "📊 History starts from commit content: 7aca927937"
echo "🔍 Check https://github.com/the-luap/picpeak to verify the new history"
echo "📈 Total commits pushed: $(git rev-list --count github-mirror)"
echo "🆕 All commit SHAs are NEW - no connection to previous history"
+61 -3
View File
@@ -164,11 +164,25 @@ jobs:
- name: Commit version bump
if: steps.version.outputs.version_changed == 'true'
run: |
set -e # Exit on any error
# First, ensure we have the latest changes
echo "Fetching latest changes..."
git fetch origin main
# Check if we're behind and need to update
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/main)
if [ "$LOCAL" != "$REMOTE" ]; then
echo "Local is behind remote, pulling changes..."
git pull origin main --no-rebase
fi
COMPONENT="${{ steps.version.outputs.component_changed }}"
if [ "$COMPONENT" = "both" ]; then
git add backend/package.json backend/package-lock.json
git add frontend/package.json frontend/package-lock.json
git add backend/package.json backend/package-lock.json frontend/package.json frontend/package-lock.json
git commit -m "chore: bump version to ${{ steps.version.outputs.new_version }} (backend + frontend)"
elif [ "$COMPONENT" = "backend" ]; then
git add backend/package.json backend/package-lock.json
@@ -178,7 +192,51 @@ jobs:
git commit -m "chore: bump frontend version to ${{ steps.version.outputs.new_version }}"
fi
git push
# Pull latest changes before pushing to avoid conflicts
echo "Pulling latest changes from origin/main..."
if ! git pull --rebase origin main; then
echo "Rebase failed, attempting to resolve..."
# If rebase fails, abort and try a regular merge
git rebase --abort || true
git pull origin main --no-rebase
fi
# Push the changes with retry logic
echo "Pushing version bump..."
PUSH_SUCCESS=false
for i in 1 2 3; do
echo "Push attempt $i of 3..."
# Try to push
if git push origin main 2>&1; then
echo "Successfully pushed version bump on attempt $i"
PUSH_SUCCESS=true
break
else
echo "Push failed on attempt $i"
if [ $i -lt 3 ]; then
echo "Waiting 5 seconds before retry..."
sleep 5
echo "Pulling latest changes..."
git fetch origin main
# Try rebase first, fall back to merge
if ! git rebase origin/main; then
echo "Rebase failed, trying merge..."
git rebase --abort 2>/dev/null || true
git pull origin main --no-rebase
fi
fi
fi
done
if [ "$PUSH_SUCCESS" = "false" ]; then
echo "ERROR: Failed to push after 3 attempts"
exit 1
fi
- name: Create Git tag
if: steps.version.outputs.version_changed == 'true'
+2
View File
@@ -54,3 +54,5 @@ coverage/
!storage/thumbnails/.gitkeep
!data/.gitkeep
!logs/.gitkeep
PRODUCTION_DEPLOYMENT_GUIDE.md
-98
View File
@@ -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
+92 -26
View File
@@ -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=<generate-with-openssl-rand-base64-32>
DB_PASSWORD=<strong-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 <noreply@yourdomain.com>
# Umami Analytics (Optional)
UMAMI_URL=https://analytics.yourdomain.com
UMAMI_WEBSITE_ID=your-website-id
UMAMI_HASH_SALT=<generate-random-string>
# 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 <your-secure-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
+33 -16
View File
@@ -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
+4 -4
View File
@@ -40,10 +40,10 @@ const config = {
keepAliveInitialDelayMillis: 0
},
pool: {
min: 2,
max: 10,
acquireTimeoutMillis: 30000,
createTimeoutMillis: 30000,
min: 5,
max: 25,
acquireTimeoutMillis: 60000,
createTimeoutMillis: 60000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createRetryIntervalMillis: 200,
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-backend",
"version": "1.0.69",
"version": "1.0.74",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-backend",
"version": "1.0.69",
"version": "1.0.74",
"dependencies": {
"adm-zip": "^0.5.16",
"archiver": "^5.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picpeak-backend",
"version": "1.0.69",
"version": "1.0.74",
"description": "Backend for PicPeak event photo sharing platform",
"main": "server.js",
"scripts": {
+28 -1
View File
@@ -4,6 +4,33 @@ const knexConfig = require('../../knexfile');
// Create database connection with built-in retry logic
const db = knex(knexConfig);
// Connection retry configuration
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
// Wrapper function to handle connection retries
async function withRetry(queryFn, retries = MAX_RETRIES) {
for (let i = 0; i < retries; i++) {
try {
return await queryFn();
} catch (error) {
const isConnectionError = error.message && (
error.message.includes('Connection terminated unexpectedly') ||
error.message.includes('Connection ended unexpectedly') ||
error.message.includes('ECONNREFUSED') ||
error.message.includes('ETIMEDOUT')
);
if (isConnectionError && i < retries - 1) {
console.log(`Database connection error, retrying in ${RETRY_DELAY}ms... (attempt ${i + 1}/${retries})`);
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * (i + 1)));
continue;
}
throw error;
}
}
}
async function initializeDatabase() {
// Events table
const hasEventsTable = await db.schema.hasTable('events');
@@ -225,4 +252,4 @@ async function logActivity(activityType, metadata = {}, eventId = null, actor =
}
}
module.exports = { db, initializeDatabase, logActivity };
module.exports = { db, initializeDatabase, logActivity, withRetry };
+10 -8
View File
@@ -1,5 +1,5 @@
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
const { db, withRetry } = require('../database/db');
const { formatBoolean } = require('../utils/dbCompat');
// Middleware to verify gallery access
@@ -11,13 +11,15 @@ async function verifyGalleryAccess(req, res, next) {
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const event = await db('events')
.where({
id: decoded.eventId,
is_active: formatBoolean(true),
is_archived: formatBoolean(false)
})
.first();
const event = await withRetry(async () => {
return await db('events')
.where({
id: decoded.eventId,
is_active: formatBoolean(true),
is_archived: formatBoolean(false)
})
.first();
});
if (!event) {
return res.status(404).json({ error: 'Gallery not found or expired' });
+1 -1
View File
@@ -10,7 +10,7 @@ const DEFAULT_SESSION_TIMEOUT = 60 * 60 * 1000;
// Cache for session timeout setting
let cachedTimeout = null;
let cacheExpiry = 0;
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const CACHE_DURATION = 30 * 60 * 1000; // 30 minutes - reduced DB queries
// Clean up expired sessions every 5 minutes
setInterval(() => {
+1 -1
View File
@@ -1,5 +1,5 @@
const express = require('express');
const { db } = require('../database/db');
const { db, withRetry } = require('../database/db');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const fs = require('fs').promises;
const path = require('path');
+9 -7
View File
@@ -1,5 +1,5 @@
const express = require('express');
const { db } = require('../database/db');
const { db, withRetry } = require('../database/db');
const router = express.Router();
// Get public settings (branding and theme)
@@ -7,12 +7,14 @@ router.get('/', async (req, res) => {
try {
// Fetch branding, theme, general, and security settings
// Note: We include analytics in the query but it might not exist yet
const settings = await db('app_settings')
.where(function() {
this.whereIn('setting_type', ['branding', 'theme', 'general', 'security', 'analytics'])
.orWhere('setting_key', 'like', 'analytics_%');
})
.select('setting_key', 'setting_value');
const settings = await withRetry(async () => {
return await db('app_settings')
.where(function() {
this.whereIn('setting_type', ['branding', 'theme', 'general', 'security', 'analytics'])
.orWhere('setting_key', 'like', 'analytics_%');
})
.select('setting_key', 'setting_value');
});
// Convert to object format
const settingsObject = {};
+2 -1
View File
@@ -60,6 +60,7 @@ async function processEmailQueue() {
}
// Start email queue processor
setInterval(processEmailQueue, 60000); // Process every minute
// DISABLED: Using emailProcessor.js instead to prevent duplicate connections
// setInterval(processEmailQueue, 60000); // Process every minute
module.exports = { sendEmail, processEmailQueue };
+9 -6
View File
@@ -119,11 +119,14 @@ function validatePassword(password, options = {}) {
*/
async function getPasswordComplexitySettings() {
try {
const db = require('../db');
const settings = await db('app_settings')
.where('setting_key', 'password_complexity')
.where('setting_type', 'security')
.first();
const { db, withRetry } = require('../database/db');
// Use retry wrapper to handle connection failures
const settings = await withRetry(async () => {
return await db('app_settings')
.where('setting_key', 'security_password_complexity_level')
.first();
});
if (!settings || !settings.setting_value) {
return 'moderate'; // Default
@@ -136,7 +139,7 @@ async function getPasswordComplexitySettings() {
return value;
} catch (error) {
logger.error('Failed to get password complexity settings:', error);
return 'moderate'; // Default on error
return 'moderate'; // Default on error - ensures app continues working
}
}
+13 -8
View File
@@ -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
# 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
+8 -4
View File
@@ -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
# 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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-frontend",
"version": "1.0.69",
"version": "1.0.74",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-frontend",
"version": "1.0.69",
"version": "1.0.74",
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"@tiptap/extension-character-count": "^2.26.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "picpeak-frontend",
"private": true,
"version": "1.0.69",
"version": "1.0.74",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -107,7 +107,7 @@ api.interceptors.response.use(
localStorage.removeItem(`gallery_event_${gallerySlug}`);
}
// Don't redirect - let the component handle the auth state
} else {
} else if (galleryMatch) {
// We're not on a gallery page but got a 401 from a gallery API
// This shouldn't happen in normal flow, but if it does, redirect to homepage
window.location.href = '/';
+1 -10
View File
@@ -289,18 +289,9 @@ export const GalleryPage: React.FC = () => {
alt={settingsData?.branding_company_name || 'PicPeak'}
className="h-12 sm:h-16 lg:h-20 w-auto object-contain mx-auto mb-3 sm:mb-4"
/>
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold mb-2 px-2" style={{ color: 'var(--color-text, #171717)' }}>
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold mb-2 px-2" style={{ color: 'var(--color-primary, #5C8762)' }}>
{galleryInfo?.event_name}
</h1>
{galleryInfo?.event_type && (
<div className="flex items-center justify-center text-xs sm:text-sm" style={{ color: 'var(--color-text, #171717)', opacity: 0.7 }}>
<span className="px-3 py-1 rounded-full" style={{ backgroundColor: 'var(--color-primary, #5C8762)', opacity: 0.1 }}>
<span style={{ color: 'var(--color-primary, #5C8762)' }}>
{t(`events.types.${galleryInfo.event_type}`)}
</span>
</span>
</div>
)}
</div>
{/* Expiration Warning */}
+25 -10
View File
@@ -17,6 +17,7 @@ import { Button, Card, Loading } from '../../components/common';
import { useQuery } from '@tanstack/react-query';
import { adminService } from '../../services/admin.service';
import { useTranslation } from 'react-i18next';
import { api } from '../../config/api';
// Map API response to component format
interface ComponentAnalyticsData {
@@ -53,7 +54,7 @@ export const AnalyticsPage: React.FC = () => {
const [isEmbedMode, setIsEmbedMode] = useState(false);
// Check if Umami is configured from settings or environment
const [umamiConfig, setUmamiConfig] = useState<{ url?: string; shareUrl?: string }>({});
const [umamiConfig, setUmamiConfig] = useState<{ url?: string; shareUrl?: string; enabled?: boolean }>({});
// Fetch analytics data from backend
const { data: apiData, isLoading, refetch } = useQuery({
@@ -71,18 +72,26 @@ export const AnalyticsPage: React.FC = () => {
queryFn: () => adminService.getDashboardStats(),
});
// Fetch public settings to get Umami config
// Fetch Umami config from admin settings since we're in admin panel
useEffect(() => {
const fetchUmamiConfig = async () => {
try {
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/public/settings`);
const settings = await response.json();
// Use admin API endpoint with auth token since we're in admin area
const response = await api.get('/admin/settings');
const settings = response.data;
// Transform the settings array to object
const settingsMap = settings.reduce((acc: any, setting: any) => {
acc[setting.key] = setting.value;
return acc;
}, {});
// Check if Umami is enabled in admin settings
if (settings.umami_enabled && settings.umami_url && settings.umami_website_id) {
if (settingsMap.analytics_umami_enabled && settingsMap.analytics_umami_url && settingsMap.analytics_umami_website_id) {
setUmamiConfig({
url: settings.umami_url,
shareUrl: settings.umami_share_url
url: settingsMap.analytics_umami_url,
shareUrl: settingsMap.analytics_umami_share_url,
enabled: true
});
} else {
// Fall back to environment variables if they exist
@@ -92,8 +101,11 @@ export const AnalyticsPage: React.FC = () => {
if (envUrl && envWebsiteId) {
setUmamiConfig({
url: envUrl,
shareUrl: import.meta.env.VITE_UMAMI_SHARE_URL
shareUrl: import.meta.env.VITE_UMAMI_SHARE_URL,
enabled: true
});
} else {
setUmamiConfig({ enabled: false });
}
}
} catch (error) {
@@ -105,8 +117,11 @@ export const AnalyticsPage: React.FC = () => {
if (envUrl && envWebsiteId) {
setUmamiConfig({
url: envUrl,
shareUrl: import.meta.env.VITE_UMAMI_SHARE_URL
shareUrl: import.meta.env.VITE_UMAMI_SHARE_URL,
enabled: true
});
} else {
setUmamiConfig({ enabled: false });
}
}
};
@@ -450,7 +465,7 @@ export const AnalyticsPage: React.FC = () => {
</div>
{/* Configuration Notice */}
{!umamiConfig.url && (
{umamiConfig.enabled === false && (
<Card padding="md" className="mt-6 bg-amber-50 border-amber-200">
<div className="flex items-start gap-3">
<Activity className="w-5 h-5 text-amber-600 flex-shrink-0" />