From 0faf9b32816f5f94aa584d2336cdb1e0b7082239 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Wed, 29 Apr 2026 22:24:15 +0200 Subject: [PATCH] docs: move documentation to docs.picpeak.app, drop in-repo copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full documentation now lives at https://docs.picpeak.app — built from the picpeak-docs Nextra repo. The README, SIMPLE_SETUP, and the v1 OpenAPI generation flow all point there now. Removed (now living at docs.picpeak.app): - DEPLOYMENT_GUIDE.md (root-level — content covered by docs.picpeak.app/deployment) - docs/ADMIN_SETUP_GUIDE.md - docs/JWT_SECRET_MIGRATION.md - docs/SECURITY_BEST_PRACTICES.md - docs/admin-api-quickstart.md → docs.picpeak.app/api - docs/nginx-fix.md → docs.picpeak.app/deployment/reverse-proxy - docs/openapi.json, docs/openapi.yaml → still generated locally as a build artifact (now gitignored), synced into picpeak-docs by scripts/sync-api-docs.sh - docs/picpeak-admin-api.openapi.yaml → ditto Kept: - docs/*.png (logo + screenshots — README still img-tags these) Updated: - README.md — replaced six in-repo doc links with docs.picpeak.app pointers, restructured the Documentation section as a curated link list to the new site - SIMPLE_SETUP.md — single deployment-guide link redirected - .gitignore — docs/openapi.{json,yaml} are now build artifacts, not tracked - backend/src/routes/v1/events.js — comment clarifies the OpenAPI flow --- .gitignore | 6 + DEPLOYMENT_GUIDE.md | 799 ---------------------------- README.md | 21 +- SIMPLE_SETUP.md | 4 +- backend/src/routes/v1/events.js | 3 +- docs/ADMIN_SETUP_GUIDE.md | 166 ------ docs/JWT_SECRET_MIGRATION.md | 109 ---- docs/SECURITY_BEST_PRACTICES.md | 179 ------- docs/admin-api-quickstart.md | 147 ----- docs/nginx-fix.md | 59 -- docs/openapi.json | 416 --------------- docs/openapi.yaml | 270 ---------- docs/picpeak-admin-api.openapi.yaml | 584 -------------------- 13 files changed, 24 insertions(+), 2739 deletions(-) delete mode 100644 DEPLOYMENT_GUIDE.md delete mode 100644 docs/ADMIN_SETUP_GUIDE.md delete mode 100644 docs/JWT_SECRET_MIGRATION.md delete mode 100644 docs/SECURITY_BEST_PRACTICES.md delete mode 100644 docs/admin-api-quickstart.md delete mode 100644 docs/nginx-fix.md delete mode 100644 docs/openapi.json delete mode 100644 docs/openapi.yaml delete mode 100644 docs/picpeak-admin-api.openapi.yaml diff --git a/.gitignore b/.gitignore index 0ce28735..52f31cf6 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,12 @@ docs/FRONTEND_ARCHITECTURE.md docs/DEVELOPER_ONBOARDING.md docs/ENVIRONMENT_VARIABLES.md +# Build artifact: OpenAPI spec generated locally + synced into the +# picpeak-docs repo. Never tracked here — the docs site at +# docs.picpeak.app is the source of truth. +docs/openapi.json +docs/openapi.yaml + # Local backup directory (from testing) backup/ diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md deleted file mode 100644 index a68d0e1e..00000000 --- a/DEPLOYMENT_GUIDE.md +++ /dev/null @@ -1,799 +0,0 @@ -# 🚀 PicPeak Deployment Guide - -This guide covers multiple deployment options for PicPeak, from simple local setups to production-ready configurations. - -## 📋 Table of Contents - -- [Quick Start](#-quick-start) -- [Prerequisites](#prerequisites) -- [Configuration](#-configuration) -- [Deployment](#-deployment) -- [First Login](#-first-login) -- [Release Channels](#-release-channels) -- [Reverse Proxy Setup](#-reverse-proxy-setup) -- [External Media Library](#external-media-library) -- [Maintenance](#-maintenance) -- [Troubleshooting](#-troubleshooting) - -## 🚀 Quick Start - -### Option 1: Automated Setup Script (Easiest) - -For the simplest installation, use our unified setup script: - -```bash -curl -fsSL https://raw.githubusercontent.com/the-luap/picpeak/main/scripts/picpeak-setup.sh -o picpeak-setup.sh && \ -chmod +x picpeak-setup.sh && \ -sudo ./picpeak-setup.sh -``` - -This script handles Docker/Native installation choice, OS detection, dependencies, database setup, and optional SSL. - -👉 **See [SIMPLE_SETUP.md](./SIMPLE_SETUP.md) for detailed instructions.** - -### Option 2: Docker with Pre-built Images (Recommended) - -```bash -# Clone repository for configuration files -git clone https://github.com/the-luap/picpeak.git -cd picpeak - -# Copy and configure environment -cp .env.example .env -nano .env # Edit with your values - -# Create required directories -mkdir -p events/active events/archived data logs backup storage -chmod -R 755 events data logs backup storage - -# Deploy using pre-built images -docker compose -f docker-compose.production.yml up -d - -# Check logs -docker compose -f docker-compose.production.yml logs -f -``` - -**Available image tags:** -| Channel | Tags | Description | -|---------|------|-------------| -| Stable | `stable`, `latest`, `v2.3.0` | Production-ready releases | -| Beta | `beta`, `v2.3.0-beta.1` | Early access to new features | -| Branch | `main`, `beta` | Latest from each branch | - -To select a channel, set `PICPEAK_CHANNEL` in your `.env` file (see [Release Channels](#release-channels) section) - -### Option 3: Build from Source - -```bash -git clone https://github.com/the-luap/picpeak.git -cd picpeak -cp .env.example .env -nano .env # Edit with your values - -mkdir -p events/active events/archived data logs backup storage -chmod -R 755 events data logs backup storage - -docker compose build -docker compose up -d -``` - -## Prerequisites - -- Docker and Docker Compose installed -- Domain name (for production) -- SMTP server credentials for emails -- At least 2GB RAM and 20GB storage - -## 🔧 Configuration - -### Essential Environment Variables - -Generate secure values: -```bash -# JWT Secret -openssl rand -base64 64 - -# Database Password (avoid $ character - see warning below) -openssl rand -base64 32 | tr -d '$' - -# Redis Password (avoid $ character - see warning below) -openssl rand -base64 32 | tr -d '$' -``` - -⚠️ **PASSWORD WARNING**: Docker Compose interprets `$` as variable substitution. Either: -- Avoid `$` in passwords (recommended - use the commands above) -- Escape `$` as `$$` (e.g., `Pass$$word` instead of `Pass$word`) -- Quote the entire value: `DB_PASSWORD='Pass$word'` (less reliable) - -### Public Landing Page - -- `npm run migrate` now seeds three general settings: `general_public_site_enabled`, `general_public_site_html`, and `general_public_site_custom_css` so existing installs stay disabled by default. -- Configure the feature from **Admin → CMS Pages**. The landing page panel exposes the toggle, HTML editor, optional CSS overrides, preview, and a reset-to-default action. -- All HTML and CSS submitted through the UI is sanitized server-side. Scripts, inline event handlers, disallowed attributes, `@import` rules, and `javascript:` URLs are stripped before content is cached or rendered. -- Resetting via the UI (or calling `POST /api/admin/settings/public-site/reset`) restores the bundled template and clears custom CSS. -- The landing page response is cached in-memory. Override the default 60s cache window by setting `PUBLIC_SITE_CACHE_TTL_MS` (milliseconds) in your environment if you need faster cache busting. -- When the toggle is off PicPeak continues to serve the SPA/login redirect at `/`, preserving legacy behaviour until you explicitly enable the feature. - -### Backend Configuration (.env) -Update `.env` with: -- `JWT_SECRET` - Authentication secret (REQUIRED - generate a secure random value) -- `DB_PASSWORD` - PostgreSQL password -- `REDIS_PASSWORD` - Redis password -- `SMTP_*` - Email configuration -- **URL Configuration** (for backend CORS): - - `FRONTEND_URL` - Frontend origin (use full URL with scheme, no trailing slash) - - Example (Docker): `http://localhost:3000` -- `ADMIN_URL` - Admin origin (same as `FRONTEND_URL` for Docker; full URL, no trailing slash) - - Example (Docker): `http://localhost:3000` - - Notes: - - Do not include trailing `/` (e.g., use `http://host:3000`, not `http://host:3000/`). - - Always include the scheme (`http://` or `https://`). - - The backend compares origins strictly for CORS; malformed values will cause login requests to fail with 500. - -#### Authentication Security -- Configure login attempt thresholds from **Admin → Settings → Security**. Defaults are 5 failed attempts per IP within 15 minutes, resulting in a 30 minute lockout. - -#### External Database Example -To use an external PostgreSQL instead of the bundled container, set the following in `.env` and ensure the `postgres` service is disabled or removed: - -```env -DB_HOST=db.example.com -DB_PORT=5432 -DB_USER=picpeak -DB_PASSWORD=change_me -DB_NAME=picpeak_prod -``` - -Compose uses these values via `env_file: .env`. The backend service also defaults `DB_HOST=${DB_HOST:-postgres}` so if you don’t set `DB_HOST` it will use the bundled `postgres` container. - -### Frontend Configuration (frontend/.env) -Create `frontend/.env` from `frontend/.env.example`: -```bash -cp frontend/.env.example frontend/.env -``` - -Update `frontend/.env` with: -- `VITE_API_URL` - Backend API URL - - Docker (pre-built images) and production behind reverse proxy: `/api` (recommended; avoids CORS and matches the frontend Nginx proxy in the image) - - Local dev (Vite): `http://localhost:3001` or `/api` if proxying through a dev proxy - -Note: When using pre-built frontend images, runtime container env does not change the already-built JS. Prefer the default `/api` and let the frontend Nginx proxy forward to the backend. - -⚠️ **IMPORTANT PORT CONFIGURATION**: -- The frontend runs on port **3000** in Docker (exposed via nginx) -- The backend API runs on port **3001** -- The frontend `.env` file MUST point to the correct backend port (3001) -- Default `.env.example` is configured for Docker deployment - -### Email Configuration Examples - -#### Gmail -```env -SMTP_HOST=smtp.gmail.com -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=your-email@gmail.com -SMTP_PASS=your-app-specific-password -``` - -#### SendGrid -```env -SMTP_HOST=smtp.sendgrid.net -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=apikey -SMTP_PASS=your-sendgrid-api-key -``` - -## 📦 Deployment - -### Using Pre-built Images (Fastest) - -```bash -# Pull latest images from GitHub Container Registry -docker pull ghcr.io/the-luap/picpeak/backend:latest -docker pull ghcr.io/the-luap/picpeak/frontend:latest - -# Start services using production compose file -docker compose -f docker-compose.production.yml up -d - -# View running containers -docker compose ps -``` - -### Building from Source (For Customization) - -```bash -# Build images locally -docker compose build - -# Or build with no cache for clean build -docker compose build --no-cache - -# Start all services -docker compose up -d - -# View running containers -docker compose ps -``` - -### Access Points - -By default, services are exposed on: -- Frontend (UI + Admin): http://localhost:3000 (admin at `/admin`) -- Backend/API: http://localhost:3001 (API only; no UI routes) -- PostgreSQL: localhost:5432 (if needed) -- Redis: localhost:6379 (if needed) - -### Initial Admin Setup - -When deploying for the first time, an admin account is automatically created with a secure, randomly generated password. This password is displayed in the Docker logs during initialization and **must be changed** on first login. - -#### Finding the Auto-Generated Admin Password - -The admin password is automatically generated during the first startup and displayed in the backend container logs. Here's how to find it: - -**Option 1: Search Docker logs for admin password** (recommended) -```bash -# Find the auto-generated admin password in logs -docker compose logs backend | grep "Admin password" -``` - -You should see output like: -``` -✅ Admin password generated: BraveTiger6231! -``` - -**Option 2: View the complete initialization logs** -```bash -# View the complete admin setup logs -docker compose logs backend | grep -A 10 "Admin user created" -``` - -**Option 3: Check the saved credentials file** -```bash -# The password is also saved in the backend container -docker exec picpeak-backend cat data/ADMIN_CREDENTIALS.txt -``` - -**Option 4: Use the helper script** -```bash -# Show current admin username and email (password is hidden) -docker exec picpeak-backend node scripts/show-admin-credentials.js - -# Reset the admin password to a new random password (displays new password in console) -docker exec picpeak-backend node scripts/show-admin-credentials.js --reset -``` - -> **Note:** When using `--reset`, the new password will be displayed in the console output. Save it immediately - it will not be shown again! - -#### Important Security Notes - -- **Login requires the email address**, not username -- When resetting password, the new password is displayed once in the console - save it immediately -- **Password change is MANDATORY** on first login - the system will force you to change it -- If you lose the password before first login, use the `--reset` option to generate a new one -- New password requirements: minimum 12 characters, mixed case, numbers, and special characters - -## 🔐 First Login - -After deployment, you must complete the first login process which includes mandatory password change for security. - -### Step 1: Locate Your Admin Password - -1. **Find the auto-generated password** from the credentials file: - ```bash - # Docker deployment - docker compose exec backend cat /app/data/ADMIN_CREDENTIALS.txt - - # Or directly from the host (if you have access) - cat data/ADMIN_CREDENTIALS.txt - ``` - -2. **Note the admin email** (default: `admin@example.com` unless customized) - -### Step 2: Access Admin Panel - -1. Navigate to your frontend domain and open the admin section: - - `http://your-domain.com/admin` (behind reverse proxy) - - `http://localhost:3000/admin` (Docker local) - - The backend at `:3001` serves API only and does not serve the admin UI. -2. Login using: - - **Email**: `admin@example.com` (or your custom admin email) - - **Password**: The auto-generated password from the logs - -### Step 3: Mandatory Password Change - -Upon first login, the system will **automatically redirect** you to change your password: - -1. **You cannot skip this step** - it's enforced for security -2. Enter the current auto-generated password -3. Create a new secure password meeting these requirements: - - Minimum 12 characters - - At least one uppercase letter - - At least one lowercase letter - - At least one number - - At least one special character (!@#$%^&*) - -### Security Best Practices for New Password - -- **Use a unique password** not used elsewhere -- **Consider a password manager** for generation and storage -- **Include mixed characters**: `MySecureP@ssw0rd2024!` -- **Avoid personal information** (names, dates, etc.) -- **Save securely** - you cannot recover this password easily - -### If You Lose Access - -If you lose your admin credentials after the first login, you'll need to manually reset the password in the database or create a new admin user through the database. - -**Note**: The credentials file (`ADMIN_CREDENTIALS.txt`) is only created during initial deployment and contains the first admin password. After changing the password, this file becomes outdated but is kept for reference. If you need to regenerate the password and file during a reinstall, re-run the installer with the `--force-admin-password-reset` flag: - -```bash -# Native reinstall example -sudo ./picpeak-setup.sh --native --force-admin-password-reset - -# Docker reinstall example -sudo ./picpeak-setup.sh --docker --force-admin-password-reset -``` - -The flag calls `scripts/reset-admin-password.js` in non-interactive mode, writes a fresh random password into `data/ADMIN_CREDENTIALS.txt`, and prints the new credentials at the end of the installer run. - -#### Configuring Admin Email - -By default, the admin email is `admin@example.com`. To use a different email address, set it in your `.env` file before first deployment: - -```env -# .env -ADMIN_EMAIL=your-email@yourdomain.com -``` - -**Note**: This only works on first deployment. To change the admin email after deployment, you'll need to update it in the database or create a new admin user through the admin panel. - -## 🔄 Release Channels - -PicPeak offers two release channels for different needs: - -### Stable Channel (Recommended) -- Production-ready releases -- Thoroughly tested before release -- Docker tags: `stable`, `latest`, or specific version like `v2.3.0` - -### Beta Channel -- Early access to new features -- May contain bugs or incomplete functionality -- Docker tags: `beta` or specific version like `v2.3.0-beta.1` - -### Configuring Your Channel - -Set the `PICPEAK_CHANNEL` environment variable in your `.env` file: - -```bash -# For stable releases (default) -PICPEAK_CHANNEL=stable - -# For beta releases -PICPEAK_CHANNEL=beta - -# For a specific version -PICPEAK_CHANNEL=v2.3.0 -``` - -The `docker-compose.production.yml` uses this variable for both backend and frontend images: -```yaml -image: ghcr.io/the-luap/picpeak/backend:${PICPEAK_CHANNEL:-stable} -``` - -### Switching Channels - -To switch between channels: - -```bash -# Edit your .env file -nano .env -# Change PICPEAK_CHANNEL=stable to PICPEAK_CHANNEL=beta (or vice versa) - -# Pull the new images and restart -docker compose -f docker-compose.production.yml pull -docker compose -f docker-compose.production.yml up -d -``` - -### Update Notifications - -The admin dashboard automatically notifies you when updates are available for your channel. This feature: -- Checks GitHub releases hourly (cached to avoid rate limits) -- Shows updates relevant to your current channel (stable or beta) -- Can be disabled by setting `UPDATE_CHECK_ENABLED=false` in your `.env` - -## 🔒 Reverse Proxy Setup - -For production deployments, you should use a reverse proxy for SSL/HTTPS. The application exposes ports directly, allowing you to use any reverse proxy solution. - -### Routing Schema - -PicPeak consists of two services that need to be routed correctly: - -| Path | Service | Port | Description | -|------|---------|------|-------------| -| `/api/*` | Backend | 3001 | All API endpoints | -| `/photos/*` | Backend | 3001 | Protected photo files | -| `/thumbnails/*` | Backend | 3001 | Protected thumbnail files | -| `/uploads/*` | Backend | 3001 | Upload files | -| `/*` (everything else) | Frontend | 3000 | React SPA (including `/admin/*`, `/gallery/*`) | - -> **Important:** The `/admin/*` routes are served by the frontend (React SPA), NOT the backend. The backend only handles `/api/admin/*` requests. - -### Option 1: Nginx - -Install nginx and create `/etc/nginx/sites-available/picpeak`: - -```nginx -server { - listen 80; - server_name your-domain.com; - return 301 https://$server_name$request_uri; -} - -server { - listen 443 ssl http2; - server_name your-domain.com; - - ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; - - # Backend: API endpoints - location /api/ { - proxy_pass http://localhost:3001; - 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; - } - - # Backend: Protected media files - location ~ ^/(photos|thumbnails|uploads)/ { - proxy_pass http://localhost:3001; - 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; - } - - # Frontend: Everything else (React SPA) - location / { - proxy_pass http://localhost:3000; - 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; - } -} -``` - -Enable the site: -```bash -sudo ln -s /etc/nginx/sites-available/picpeak /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl reload nginx -``` - -### Option 2: Traefik - -Add labels to `docker-compose.override.yml`: - -```yaml -version: '3.8' - -services: - frontend: - labels: - - "traefik.enable=true" - - "traefik.http.routers.picpeak.rule=Host(`your-domain.com`)" - - "traefik.http.routers.picpeak.entrypoints=websecure" - - "traefik.http.routers.picpeak.tls.certresolver=letsencrypt" - - "traefik.http.services.picpeak.loadbalancer.server.port=80" - - backend: - labels: - - "traefik.enable=true" - # API endpoints - - "traefik.http.routers.picpeak-api.rule=Host(`your-domain.com`) && PathPrefix(`/api`)" - - "traefik.http.routers.picpeak-api.entrypoints=websecure" - - "traefik.http.routers.picpeak-api.tls.certresolver=letsencrypt" - - "traefik.http.services.picpeak-api.loadbalancer.server.port=3001" - # Protected media files - - "traefik.http.routers.picpeak-media.rule=Host(`your-domain.com`) && (PathPrefix(`/photos`) || PathPrefix(`/thumbnails`) || PathPrefix(`/uploads`))" - - "traefik.http.routers.picpeak-media.entrypoints=websecure" - - "traefik.http.routers.picpeak-media.tls.certresolver=letsencrypt" - - "traefik.http.services.picpeak-media.loadbalancer.server.port=3001" -``` - -### Option 3: Caddy - -Create a `Caddyfile`: - -```caddyfile -your-domain.com { - # Backend: API endpoints - handle /api/* { - reverse_proxy localhost:3001 - } - - # Backend: Protected media files - handle /photos/* { - reverse_proxy localhost:3001 - } - - handle /thumbnails/* { - reverse_proxy localhost:3001 - } - - handle /uploads/* { - reverse_proxy localhost:3001 - } - - # Frontend: Everything else (React SPA including /admin/*, /gallery/*) - handle { - reverse_proxy localhost:3000 - } -} -``` - -### SSL Certificates - -For any reverse proxy, you can use Let's Encrypt: - -```bash -# With Certbot -sudo certbot certonly --webroot -w /var/www/certbot -d your-domain.com - -# Or use your reverse proxy's built-in ACME support -``` - -## 🔧 Maintenance - -### Viewing Logs - -```bash -# All services -docker compose logs -f - -# Specific service -docker compose logs -f backend -docker compose logs -f frontend -``` - -### Backup - -#### Manual Backup -```bash -# Database backup -docker exec picpeak-postgres pg_dump -U picpeak picpeak_prod > backup/db_$(date +%Y%m%d_%H%M%S).sql - -# Files backup -tar -czf backup/photos_$(date +%Y%m%d_%H%M%S).tar.gz events/ -``` - -#### Automated Backup -The application includes a built-in backup service. Configure it in the admin panel: -1. Login to admin panel -2. Go to Settings → Backup -3. Configure destination and schedule -4. Enable backup service - -### Updates - -#### Method 1: Using Pre-built Images (Recommended) - -```bash -# Pull latest changes (for configuration updates) -git pull - -# Pull latest images from GitHub Container Registry -docker compose -f docker-compose.production.yml pull - -# Restart with new images -docker compose -f docker-compose.production.yml down -docker compose -f docker-compose.production.yml up -d - -# Verify services are healthy -docker compose -f docker-compose.production.yml ps -``` - -#### Method 2: Building from Source - -```bash -# Pull latest changes -git pull - -# Rebuild and restart -docker compose down -docker compose build --no-cache -docker compose up -d - -# Verify services are healthy -docker compose ps -``` - -#### Specific Version or Channel Updates - -To use a specific version or switch channels, update your `.env` file: - -```bash -# Edit .env to change the channel or pin to a specific version -nano .env - -# Options for PICPEAK_CHANNEL: -# - stable (recommended, production-ready) -# - beta (early access to new features) -# - v2.3.0 (pin to specific stable version) -# - v2.3.0-beta.1 (pin to specific beta version) - -# Then pull and restart -docker compose -f docker-compose.production.yml pull -docker compose -f docker-compose.production.yml up -d -``` - -The admin dashboard will notify you when updates are available for your configured channel. - -### Database Migrations - -Migrations run automatically on startup, but you can run them manually: - -```bash -docker exec picpeak-backend npm run migrate -``` - -## 🚨 Troubleshooting - -### Common Issues - -#### 502 Bad Gateway / Login Failures -**This is the most common deployment issue!** Usually caused by misconfigured URLs or network problems: - -1. **CORS Configuration Errors**: - ```bash - # WRONG - Missing port will cause CORS errors - FRONTEND_URL=http://10.0.252.12 - - # CORRECT - Include the port you're accessing from - FRONTEND_URL=http://10.0.252.12:3000 - ``` - - The backend validates Origin headers against `FRONTEND_URL` for CORS. If they don't match exactly, you'll get 500 errors on login. - -2. **After Container Restarts**: - - Nginx may have cached old container IPs - - Solution: `docker restart picpeak-frontend` - - Always wait 30-60 seconds for health checks - -3. **Backend Not Starting After Migrations**: - - The logs may only show migrations completed - - Check if server is actually running: `docker exec picpeak-backend ps aux | grep node` - - Should see `node server.js` process - -4. **Login After Fresh Install**: - - Check backend logs for auto-generated admin password: `docker compose logs backend | grep "Admin password"` - - Email: `admin@example.com` (or your custom admin email from .env) - - Password: Auto-generated and shown in logs (e.g., `BraveTiger6231!`) - - Remember: Password MUST be changed on first login - -5. **Complete Fix Sequence**: - ```bash - # 1. Fix your .env file URLs - # 2. Full restart - docker-compose down - docker-compose up -d - - # 3. Wait for healthy status - sleep 60 - docker ps # All should show (healthy) - - # 4. Test backend directly - curl http://localhost:3001/health - - # 5. Test through frontend - curl http://localhost:3000/api/public/settings - ``` - -#### Port Already in Use -```bash -# Check what's using the port -sudo lsof -i :3000 -sudo lsof -i :3001 - -# Change ports in .env -FRONTEND_PORT=3002 -BACKEND_PORT=3003 -``` - -#### Docker Compose Variable Substitution Errors -If you see warnings like: -``` -WARN[0000] The "fgbf" variable is not set. Defaulting to a blank string. -``` - -This means your password contains `$` which Docker Compose interprets as a variable. Solutions: -1. **Best**: Generate passwords without `$`: `openssl rand -base64 32 | tr -d '$'` -2. **Alternative**: Escape `$` as `$$` in your .env file -3. **Example**: `DB_PASSWORD=Pass@#$$fgbf` instead of `DB_PASSWORD=Pass@#$fgbf` - -#### Permission Errors -```bash -# Fix ownership -sudo chown -R 1000:1000 events data logs backup storage -chmod -R 755 events data logs backup storage -``` - -#### Database Connection Issues -```bash -# Check if database is running -docker compose ps -docker compose logs postgres - -# Test connection -docker exec picpeak-postgres pg_isready -``` - -#### Email Not Sending -- Verify SMTP settings in .env -- Check email queue: `docker exec picpeak-backend psql -U picpeak -d picpeak_prod -c "SELECT * FROM email_queue ORDER BY created_at DESC LIMIT 10;"` -- For Gmail, use app-specific password -- Check logs: `docker compose logs backend | grep email` - -### Health Checks - -```bash -# Backend health -curl http://localhost:3001/api/health - -# Frontend health -curl http://localhost:3000 - -# Database health -docker exec picpeak-postgres pg_isready -``` - -### Useful Commands - -```bash -# Enter backend container -docker exec -it picpeak-backend sh - -# Enter database -docker exec -it picpeak-postgres psql -U picpeak picpeak_prod - -# Reset admin password -docker exec picpeak-backend node scripts/show-admin-credentials.js --reset - -# Check disk usage -df -h -du -sh events/ storage/ backup/ - -# View running processes -docker compose top -``` - -## Security Recommendations - -1. **Use HTTPS**: Always use a reverse proxy with SSL in production -2. **Firewall**: Only expose necessary ports (80, 443) -3. **Secure passwords**: Use strong, unique passwords for all services -4. **Regular updates**: Keep Docker images and system packages updated -5. **Backup strategy**: Set up automated backups and test restoration -6. **Monitor logs**: Regularly check logs for suspicious activity -7. **Rate limiting**: The app includes built-in rate limiting, configure as needed - -## Support - -For issues and questions: -- Check logs first: `docker compose logs` -- Review documentation in the repository -- Check existing issues on GitHub -- Create a new issue with: - - Error messages - - Log output - - Environment details (without secrets) - - Steps to reproduce diff --git a/README.md b/README.md index d8645fde..6765002f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![React](https://img.shields.io/badge/react-%2320232a.svg?style=flat&logo=react&logoColor=%2361DAFB)](https://reactjs.org/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-theluap-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/theluap) - [Homepage](https://www.picpeak.app) · [Live Demo](https://demo.picpeak.app) · [Documentation](DEPLOYMENT_GUIDE.md) · [Support the project ☕](https://buymeacoffee.com/theluap) + [Homepage](https://www.picpeak.app) · [Live Demo](https://demo.picpeak.app) · [Documentation](https://docs.picpeak.app) · [Support the project ☕](https://buymeacoffee.com/theluap) **PicPeak** is a powerful, self-hosted open-source alternative to commercial photo-sharing platforms like PicDrop.com and Scrapbook.de. Designed specifically for photographers and event organizers, PicPeak makes it simple to share beautiful, time-limited photo galleries with clients while maintaining full control over your data and branding. @@ -143,10 +143,17 @@ UPDATE_CHECK_ENABLED=false ## 📖 Documentation -- 📘 [**Deployment Guide**](DEPLOYMENT_GUIDE.md) - Detailed installation instructions - - Includes the new [External Media Library](DEPLOYMENT_GUIDE.md#external-media-library) reference mode -- 📚 [**Admin API (OpenAPI)**](docs/picpeak-admin-api.openapi.yaml) - Machine-readable documentation for event automation endpoints -- 🛠️ [**Admin API Quickstart**](docs/admin-api-quickstart.md) - Step-by-step authentication and testing guide for the documented endpoints +Full documentation lives at **[docs.picpeak.app](https://docs.picpeak.app)** — deployment, admin settings reference, API docs, webhooks, archive lifecycle, branding, and everything else. Some quick links: + +- 🚀 [**Deployment**](https://docs.picpeak.app/deployment) - Docker, environment variables, reverse proxy, SSL +- ⚙️ [**Admin Settings**](https://docs.picpeak.app/guides/admin-settings) - Every tab in the Settings panel +- 🎯 [**Creating Events**](https://docs.picpeak.app/guides/creating-events) - Full event field reference +- 💾 [**Backup & Restore**](https://docs.picpeak.app/guides/backup-restore) - Local, S3, rsync destinations +- 🔌 [**API Reference**](https://docs.picpeak.app/api) - REST endpoints, OpenAPI spec, webhooks +- 🪝 [**Webhooks**](https://docs.picpeak.app/features/webhooks) - Event payloads, signing, filters, templates + +Project meta: + - 🤝 [**Contributing**](CONTRIBUTING.md) - How to contribute - 📜 [**License**](LICENSE) - MIT License - 🔒 [**Security**](SECURITY.md) - Security policies @@ -447,7 +454,7 @@ PicPeak is released under the [MIT License](LICENSE). Use it freely for personal ## 🚀 Ready to Get Started? 1. ⭐ **Star this repository** to show your support -2. 📖 Read the [Deployment Guide](DEPLOYMENT_GUIDE.md) +2. 📖 Read the [docs at docs.picpeak.app](https://docs.picpeak.app) 3. 🐛 Report issues or request features 4. 🤝 Join our community and contribute! @@ -459,6 +466,6 @@ PicPeak is released under the [MIT License](LICENSE). Use it freely for personal HomepageLive DemoGitHub • - Documentation • + DocumentationSupport

diff --git a/SIMPLE_SETUP.md b/SIMPLE_SETUP.md index 19c6a713..498535ee 100644 --- a/SIMPLE_SETUP.md +++ b/SIMPLE_SETUP.md @@ -468,8 +468,8 @@ sudo -u picpeak node scripts/reset-admin-password.js - Installation: `/tmp/picpeak-setup-*.log` 2. **Documentation:** - - [Full Documentation](https://github.com/the-luap/picpeak) - - [Deployment Guide](./DEPLOYMENT_GUIDE.md) + - [Full Documentation](https://docs.picpeak.app) + - [Deployment Guide](https://docs.picpeak.app/deployment) 3. **Support:** - [GitHub Issues](https://github.com/the-luap/picpeak/issues) diff --git a/backend/src/routes/v1/events.js b/backend/src/routes/v1/events.js index c8aab9d7..06ad6fcb 100644 --- a/backend/src/routes/v1/events.js +++ b/backend/src/routes/v1/events.js @@ -6,7 +6,8 @@ * are admin-only via the UI for v1. Mounts under /api/v1 with apiTokenAuth. * * Each route is annotated with @openapi JSDoc that swagger-jsdoc picks - * up to generate docs/openapi.yaml — the source of truth for picpeak-docs. + * up to generate docs/openapi.yaml (gitignored), which is then synced + * into the picpeak-docs site at docs.picpeak.app. */ const express = require('express'); diff --git a/docs/ADMIN_SETUP_GUIDE.md b/docs/ADMIN_SETUP_GUIDE.md deleted file mode 100644 index 73f30867..00000000 --- a/docs/ADMIN_SETUP_GUIDE.md +++ /dev/null @@ -1,166 +0,0 @@ -# Admin Setup Guide - Secure Password System - -## Overview - -PicPeak now uses a secure admin setup process that eliminates the default password vulnerability. When you first set up the application, a secure password is automatically generated for the admin account. - -## Initial Setup Process - -### 1. First Installation - -When you run the database migrations for the first time: - -```bash -cd backend -npm run migrate -``` - -The system will: -- Create an admin user with username `admin` -- Generate a secure, random password (e.g., `SwiftEagle3847!`) -- Display the credentials in the console -- Save the credentials to `ADMIN_CREDENTIALS.txt` - -### 2. Retrieving Your Credentials - -After setup, you can find your admin credentials in: -- **Console output** - Displayed immediately after setup -- **ADMIN_CREDENTIALS.txt** - File in the project root - -**Example output:** -``` -======================================== -✅ Admin user created successfully! -======================================== -Username: admin -Password: SwiftEagle3847! - -⚠️ IMPORTANT: -1. Save these credentials securely -2. You will be required to change the password on first login -3. Credentials are also saved in: ADMIN_CREDENTIALS.txt -======================================== -``` - -### 3. First Login - -1. Navigate to the admin panel: `http://localhost:3001/admin` -2. Login with: - - Username: `admin` - - Password: (from ADMIN_CREDENTIALS.txt) -3. You will be prompted to change your password immediately - -### 4. Password Requirements - -When changing your password, it must meet these requirements: -- Minimum 12 characters long -- Contains uppercase letters (A-Z) -- Contains lowercase letters (a-z) -- Contains numbers (0-9) -- Contains special characters (!@#$%^&*()_+-=[]{}|;:,.<>?) -- Not a common password - -## Security Features - -### Generated Passwords -- Uses cryptographically secure random generation -- Human-readable format: `AdjectiveNoun####!` -- Example: `BrightMountain7823$` - -### Password Storage -- Passwords are hashed using bcrypt with 12 rounds -- Original password is never stored in the database -- Credentials file should be deleted after noting the password - -### Forced Password Change -- Admin must change password on first login -- System tracks `must_change_password` flag -- Cannot access admin features until password is changed - -## Troubleshooting - -### Lost Admin Password - -If you lose the admin password before first login: - -1. Delete the admin user from the database: - ```sql - DELETE FROM admin_users WHERE username = 'admin'; - ``` - -2. Run migrations again: - ```bash - npm run migrate - ``` - -3. New credentials will be generated - -### Password Change Issues - -If you can't change your password: -- Ensure new password meets all requirements -- Check for detailed error messages -- Password strength validator provides specific feedback - -### Can't Find Credentials File - -If ADMIN_CREDENTIALS.txt is missing: -- Check the console output from when you ran migrations -- File is created in the backend directory root -- File might have been deleted for security (as recommended) -- Regenerate it by running `node scripts/reset-admin-password.js --force --credentials-file data/ADMIN_CREDENTIALS.txt` -- When using the unified `picpeak-setup.sh` installer for a reinstall, append `--force-admin-password-reset` to have the script perform the reset automatically - -## Best Practices - -1. **Immediate Action** - - Change the generated password on first login - - Use a password manager to store credentials - - Delete ADMIN_CREDENTIALS.txt after noting the password - -2. **Password Security** - - Use unique passwords for each environment - - Rotate passwords regularly (every 90 days) - - Never share admin credentials - -3. **Multiple Admins** - - Create separate admin accounts for each person - - Avoid sharing the main admin account - - Use role-based access control when available - -## Migration from Old System - -If upgrading from the old system with hardcoded `admin123`: - -1. The system will detect existing admin user -2. You must manually reset the password: - ```bash - # Run the password reset script - node scripts/reset-admin-password.js - ``` - -3. Follow the new secure password process - -## Environment-Specific Setup - -### Development -- Generated passwords are suitable for development -- Consider using simpler passwords for convenience -- Always use strong passwords in staging/production - -### Production -- Generate new admin account for production -- Use extremely strong passwords (20+ characters) -- Enable two-factor authentication when available -- Regularly audit admin access logs - -## Security Checklist - -- [ ] Retrieved generated password from ADMIN_CREDENTIALS.txt -- [ ] Logged in successfully with generated password -- [ ] Changed password to a strong, unique password -- [ ] Deleted ADMIN_CREDENTIALS.txt file -- [ ] Stored new password in password manager -- [ ] Tested login with new password -- [ ] Set up additional admin accounts if needed -- [ ] Configured password policies for organization diff --git a/docs/JWT_SECRET_MIGRATION.md b/docs/JWT_SECRET_MIGRATION.md deleted file mode 100644 index c73e46bd..00000000 --- a/docs/JWT_SECRET_MIGRATION.md +++ /dev/null @@ -1,109 +0,0 @@ -# JWT_SECRET Security Fix - Migration Guide - -## Overview - -A critical security vulnerability has been fixed where the application would fall back to a hardcoded JWT secret (`'your-secret-key'`) if the `JWT_SECRET` environment variable was not set. This has been addressed by: - -1. Adding startup validation that requires `JWT_SECRET` to be set -2. Removing all hardcoded fallback values -3. Ensuring the secret meets minimum security requirements - -## Changes Made - -### 1. Added Environment Validation (`backend/src/config/validateEnv.js`) -- The server now validates critical environment variables at startup -- If `JWT_SECRET` is missing or set to the insecure default, the server will refuse to start -- Warns if `JWT_SECRET` is less than 32 characters (recommended minimum) - -### 2. Updated Server Startup (`backend/server.js`) -- Added validation call immediately after loading environment variables -- Ensures all routes and middleware have access to validated configuration - -### 3. Removed Hardcoded Fallbacks (`backend/src/routes/protectedImages.js`) -- Removed `|| 'your-secret-key'` fallback from lines 15 and 27 -- Functions now rely on the validated `JWT_SECRET` from environment - -## Migration Steps for Production - -### Before Deployment - -1. **Verify JWT_SECRET is set in production**: - ```bash - # Check if JWT_SECRET is set - echo $JWT_SECRET - ``` - -2. **Ensure JWT_SECRET is secure**: - - Must NOT be `'your-secret-key'` - - Should be at least 32 characters long - - Should be randomly generated - -3. **Generate a secure JWT_SECRET if needed**: - ```bash - # Generate a secure 64-character secret - openssl rand -hex 32 - ``` - -### Deployment Process - -1. **Update environment variables** (if needed): - ```bash - # Example for .env file - JWT_SECRET=your-secure-64-character-random-string-here - ``` - -2. **Deploy the updated code** - -3. **Monitor startup logs** to ensure no validation errors: - ``` - ✓ Environment validation passed - ✓ Server running on port 3000 - ``` - -### Rollback Plan - -If the deployment fails due to missing `JWT_SECRET`: - -1. **Quick Fix** (temporary): - - Set `JWT_SECRET` environment variable to a secure value - - Restart the application - -2. **Full Rollback** (if needed): - - Revert to previous version - - Set `JWT_SECRET` properly before attempting deployment again - -## Verification - -After deployment, verify the fix is working: - -1. **Check server logs** for successful startup -2. **Test authentication** to ensure JWT tokens are working -3. **Verify image protection** routes are functioning - -## Security Considerations - -- **Never** commit JWT_SECRET to version control -- **Rotate** JWT_SECRET periodically -- **Use different** secrets for different environments (dev, staging, production) -- **Monitor** for authentication failures that might indicate token issues - -## Troubleshooting - -### Server won't start -- **Error**: "Missing required environment variable: JWT_SECRET" -- **Solution**: Set the JWT_SECRET environment variable - -### JWT_SECRET rejection -- **Error**: "JWT_SECRET is set to the insecure default value" -- **Solution**: Change JWT_SECRET from 'your-secret-key' to a secure value - -### Authentication failures after deployment -- **Cause**: Existing tokens were signed with old secret -- **Solution**: Users will need to re-authenticate to get new tokens - -## Support - -If you encounter issues during migration: -1. Check the server logs for specific error messages -2. Verify environment variables are properly set -3. Ensure the JWT_SECRET value doesn't contain special characters that might need escaping \ No newline at end of file diff --git a/docs/SECURITY_BEST_PRACTICES.md b/docs/SECURITY_BEST_PRACTICES.md deleted file mode 100644 index 2f67d5f7..00000000 --- a/docs/SECURITY_BEST_PRACTICES.md +++ /dev/null @@ -1,179 +0,0 @@ -# Security Best Practices for PicPeak - -## JWT Secret Management - -### Generating Secure Secrets - -Always generate cryptographically secure random secrets for JWT signing: - -```bash -# Generate a 64-character hex string (256 bits) -openssl rand -hex 32 - -# Alternative: Generate a base64 string -openssl rand -base64 32 - -# Alternative: Using Node.js -node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -``` - -### Environment-Specific Secrets - -**NEVER use the same JWT secret across different environments!** - -- **Development**: Use the secure secret in `docker-compose.yml` -- **Staging**: Generate a unique secret for staging -- **Production**: Generate a unique secret for production - -### Secret Requirements - -1. **Minimum Length**: 32 characters (enforced by application) -2. **Recommended Length**: 64 characters (256 bits) -3. **Character Set**: Use hex or base64 encoding -4. **Uniqueness**: Each environment must have a unique secret - -### What NOT to Do - -❌ **Never commit real secrets to version control** -```bash -# Bad - real secret in code -JWT_SECRET=my-actual-production-secret -``` - -❌ **Never use predictable or weak secrets** -```bash -# Bad examples -JWT_SECRET=secret123 -JWT_SECRET=mycompanyname -JWT_SECRET=password -JWT_SECRET=your-secret-key -``` - -❌ **Never share secrets between environments** -```bash -# Bad - same secret everywhere -DEV_JWT_SECRET=same-secret -PROD_JWT_SECRET=same-secret -``` - -### Secure Secret Storage - -#### For Local Development -- Docker Compose files can contain development secrets -- These should still be secure random values - -#### For Production -1. **Environment Variables** - ```bash - # Set via secure environment - export JWT_SECRET=$(openssl rand -hex 32) - ``` - -2. **Secret Management Services** - - AWS Secrets Manager - - HashiCorp Vault - - Azure Key Vault - - Kubernetes Secrets - -3. **CI/CD Integration** - - Store secrets in CI/CD platform's secret storage - - Never log or echo secrets in build scripts - -### Secret Rotation - -Implement a secret rotation strategy: - -1. **Regular Rotation**: Rotate secrets every 90 days -2. **Incident Response**: Rotate immediately if compromised -3. **Graceful Rotation**: Support multiple valid secrets during transition - -### Monitoring and Alerts - -1. **Startup Validation**: Application refuses to start without proper JWT_SECRET -2. **Length Warnings**: Warnings for secrets shorter than 32 characters -3. **Default Detection**: Critical error if default secret is detected - -## Additional Security Measures - -### Password Requirements -- Minimum 12 characters -- Mix of uppercase, lowercase, numbers, and special characters -- Check against common password lists -- Implement password strength meter - -### Session Security -- Implement token expiration (24 hours for admin, configurable for galleries) -- Add refresh token mechanism -- Implement token revocation -- Use secure session storage (Redis in production) - -### API Security -- Rate limiting on all endpoints -- Extra strict limits on authentication endpoints -- CSRF protection for state-changing operations -- Input validation on all user inputs - -### File Upload Security -- Validate file types by content, not just extension -- Implement virus scanning -- Limit file sizes -- Sanitize filenames -- Store files outside web root - -### Database Security -- Use parameterized queries (Knex.js handles this) -- Validate and sanitize all inputs -- Implement query timeouts -- Use least-privilege database users - -### HTTPS and Headers -- Always use HTTPS in production -- Implement security headers: - - Strict-Transport-Security - - X-Frame-Options - - X-Content-Type-Options - - Content-Security-Policy - - X-XSS-Protection - -### Logging and Monitoring -- Log authentication attempts -- Monitor for suspicious patterns -- Never log sensitive data (passwords, tokens) -- Implement audit trails for admin actions - -## Security Checklist for Deployment - -- [ ] Generate unique JWT_SECRET for environment -- [ ] Verify JWT_SECRET meets minimum requirements -- [ ] Store secrets securely (not in code) -- [ ] Enable HTTPS -- [ ] Configure security headers -- [ ] Set up rate limiting -- [ ] Enable audit logging -- [ ] Test authentication flows -- [ ] Verify file upload restrictions -- [ ] Check database query security - -## Incident Response - -If a security incident occurs: - -1. **Immediate Actions** - - Rotate all secrets - - Review access logs - - Disable compromised accounts - -2. **Investigation** - - Analyze logs for unauthorized access - - Check for data exfiltration - - Review code changes - -3. **Recovery** - - Deploy security patches - - Force password resets if needed - - Notify affected users - -4. **Prevention** - - Update security practices - - Implement additional monitoring - - Conduct security audit \ No newline at end of file diff --git a/docs/admin-api-quickstart.md b/docs/admin-api-quickstart.md deleted file mode 100644 index eb37a087..00000000 --- a/docs/admin-api-quickstart.md +++ /dev/null @@ -1,147 +0,0 @@ -# PicPeak Admin API Quickstart - -This guide explains how to authenticate against the PicPeak Admin API, use the OpenAPI documentation, and exercise the three automation endpoints (`create event`, `photo upload`, `resend email`) that now ship with machine-readable docs. - -> **Prerequisites** -> -> - PicPeak backend running (Docker or local `node backend/server.js`) -> - An admin account (see `data/ADMIN_CREDENTIALS.txt` for the seeded defaults) -> - API base URL (defaults to `http://localhost:3001/api`) - ---- - -## 1. Obtain an Admin API Token - -1. Determine whether reCAPTCHA is enabled in **Admin → Settings → Security**. If disabled (the default), you can skip the `recaptchaToken` field shown below. -2. Authenticate with your admin username/email and password: - -```bash -curl --fail --silent --show-error \ - -X POST "http://localhost:3001/api/auth/admin/login" \ - -H "Content-Type: application/json" \ - -d '{ - "username": "admin", - "password": "BoldTiger5872%", - "recaptchaToken": "" - }' | jq -``` - -Successful responses look like: - -```json -{ - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "user": { - "id": 1, - "username": "admin", - "email": "admin@example.com", - "mustChangePassword": false - } -} -``` - -- PicPeak also sets the `admin_token` cookie; however, when scripting you typically pass the token in an `Authorization: Bearer ` header. -- Tokens expire after 24 hours. Log in again to refresh them. - ---- - -## 2. Use the OpenAPI Documentation - -The machine-readable spec lives at `docs/picpeak-admin-api.openapi.yaml`. You can: - -- Preview it interactively with Redocly: - - ```bash - npx --yes @redocly/cli preview-docs docs/picpeak-admin-api.openapi.yaml - ``` - -- Import it into Postman, Insomnia, or VS Code REST client. -- Validate changes as part of CI with: - - ```bash - npx --yes @apidevtools/swagger-cli@4.0.4 validate docs/picpeak-admin-api.openapi.yaml - ``` - -Keep this file in sync whenever the backend endpoints evolve. - ---- - -## 3. Call the Key Admin Endpoints - -Below are minimal `curl` examples that rely on the bearer token captured earlier. - -### 3.1 Create an Event - -```bash -API_URL="http://localhost:3001/api" -TOKEN="REPLACE_WITH_JWT" - -curl --fail --silent --show-error \ - -X POST "$API_URL/admin/events" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "event_type": "wedding", - "event_name": "Emily & Jordan Celebration", - "event_date": "2025-06-07", - "customer_name": "Emily Carter", - "customer_email": "emily@example.com", - "admin_email": "studio@example.com", - "require_password": true, - "password": "Shutter123", - "expiration_days": 45 - }' | jq -``` - -### 3.2 Upload Photos to the Event - -```bash -EVENT_ID=512 - -curl --fail --silent --show-error \ - -X POST "$API_URL/admin/events/$EVENT_ID/upload" \ - -H "Authorization: Bearer $TOKEN" \ - -F "photos=@/path/to/DSC_2031.jpg" \ - -F "photos=@/path/to/DSC_2032.jpg" \ - -F "category_id=individual" | jq -``` - -- Files must be JPEG/PNG/WebP, each ≤ 50 MB. -- The per-request file count respects the `general_max_files_per_upload` admin setting (default 500). - -### 3.3 Resend the Gallery Email - -```bash -curl --fail --silent --show-error \ - -X POST "$API_URL/admin/events/$EVENT_ID/resend-email" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"password": "Shutter123"}' | jq -``` - -Omit `"password"` to send the standard security message instead. - ---- - -## 4. Quick Testing Checklist - -- ✅ Login succeeds and returns a token (HTTP 200). -- ✅ Creating an event returns `id`, `slug`, and `share_link`. -- ✅ Uploading more files than allowed returns HTTP 400 with a helpful message. -- ✅ Resending email for a missing event returns HTTP 404. -- ✅ `swagger-cli validate` passes after any spec edits. - -Automate these checks using your preferred test harness or CI pipeline to catch regressions early. - ---- - -## 5. Migrating From `host_*` - -- Run backend migrations to add the new `customer_name` / `customer_email` columns: `npm --prefix backend run migrate` (or your existing deployment flow). The migration copies legacy data automatically, so upgrades remain seamless. -- All admin APIs now require the `customer_*` fields. Older `host_*` payloads are rejected, which makes downstream client issues obvious during testing instead of silently dropping data. -- API responses still mirror `customer_*` even if migrations have not run yet (the server falls back to legacy columns until the upgrade is complete), so existing frontends can move over incrementally. -- Once every consumer writes and reads the new fields, you can safely plan the removal of the legacy `host_*` columns in a future release. - ---- - -Need deeper integration examples or language-specific SDKs? Import the OpenAPI spec into code generators such as `openapi-generator` or `orval` to scaffold API clients quickly. diff --git a/docs/nginx-fix.md b/docs/nginx-fix.md deleted file mode 100644 index a3407cff..00000000 --- a/docs/nginx-fix.md +++ /dev/null @@ -1,59 +0,0 @@ -# Nginx Configuration Fix for Photo Authentication - -If photos and thumbnails are not loading in gallery view but work in admin, it's likely that the Authorization header is being stripped by nginx or another reverse proxy. - -## Common Issue - -The `Authorization` header is often not passed through by default in nginx proxy configurations. - -## Fix - -Add these lines to your nginx configuration for the PicPeak location block: - -```nginx -location / { - proxy_pass http://localhost:3001; - - # Important: Pass the Authorization header - proxy_pass_header Authorization; - proxy_set_header Authorization $http_authorization; - - # Other standard proxy headers - 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; -} -``` - -## Alternative Fix Using Traefik - -If using Traefik, ensure headers are passed: - -```yaml -services: - picpeak: - labels: - - "traefik.http.middlewares.picpeak-headers.headers.customrequestheaders.Authorization=" -``` - -## Testing - -1. Check if Authorization header is reaching the backend: - ```bash - curl -H "Authorization: Bearer YOUR_TOKEN" https://picpeak.yourdomain.com/thumbnails/test.jpg -v - ``` - -2. Check nginx logs to see if the header is present: - ```bash - tail -f /var/log/nginx/access.log - ``` - -## Docker Compose Fix - -If using docker-compose with nginx proxy, add: - -```yaml -environment: - - NGINX_PROXY_PASS_HEADER=Authorization -``` \ No newline at end of file diff --git a/docs/openapi.json b/docs/openapi.json deleted file mode 100644 index f340e68e..00000000 --- a/docs/openapi.json +++ /dev/null @@ -1,416 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "PicPeak API", - "version": "v1", - "description": "Public REST API for PicPeak — create gallery events, upload photos, fetch share links. Authenticate with a Bearer token issued via the admin **Settings → API Tokens** tab." - }, - "servers": [ - { - "url": "/api/v1", - "description": "Same-origin (production)" - } - ], - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "pp_live_*", - "description": "Long-lived API token. Issue via Settings → API Tokens. Token format: `pp_live_`. Scopes: `read`, `write`, `admin`." - } - }, - "schemas": { - "EventSummary": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "slug": { - "type": "string" - }, - "event_name": { - "type": "string" - }, - "event_type": { - "type": "string" - }, - "event_date": { - "type": "string", - "format": "date", - "nullable": true - }, - "expires_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "is_active": { - "type": "boolean" - }, - "is_archived": { - "type": "boolean" - }, - "is_draft": { - "type": "boolean" - }, - "created_at": { - "type": "string", - "format": "date-time" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/events": { - "post": { - "tags": [ - "Events" - ], - "summary": "Create a gallery event", - "description": "Returns the new event's id, slug, and absolute share URL.", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "event_name", - "event_type" - ], - "properties": { - "event_name": { - "type": "string" - }, - "event_type": { - "type": "string", - "enum": [ - "wedding", - "birthday", - "corporate", - "other", - "family" - ] - }, - "event_date": { - "type": "string", - "format": "date", - "nullable": true - }, - "customer_name": { - "type": "string", - "nullable": true - }, - "customer_email": { - "type": "string", - "format": "email", - "nullable": true - }, - "customer_phone": { - "type": "string", - "nullable": true, - "description": "Only persisted when the global phone-field setting is enabled." - }, - "admin_email": { - "type": "string", - "format": "email", - "nullable": true - }, - "require_password": { - "type": "boolean", - "default": true - }, - "password": { - "type": "string", - "nullable": true, - "description": "Required when require_password is true." - }, - "expires_at": { - "type": "string", - "format": "date-time", - "nullable": true - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Event created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "slug": { - "type": "string" - }, - "share_url": { - "type": "string", - "format": "uri" - }, - "share_token": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "Validation error" - }, - "401": { - "description": "Missing/invalid token" - }, - "403": { - "description": "Token lacks admin scope" - } - } - }, - "get": { - "tags": [ - "Events" - ], - "summary": "List gallery events (paginated)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "minimum": 1, - "default": 1 - } - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - } - ], - "responses": { - "200": { - "description": "Paginated list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "events": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EventSummary" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - } - } - } - } - } - } - } - } - } - } - }, - "/events/{id}": { - "get": { - "tags": [ - "Events" - ], - "summary": "Get a single event", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Event details" - }, - "404": { - "description": "Not found" - } - } - } - }, - "/events/{id}/photos": { - "post": { - "tags": [ - "Photos" - ], - "summary": "Upload a single photo to an event", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "photo" - ], - "properties": { - "photo": { - "type": "string", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Photo uploaded", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "filename": { - "type": "string" - }, - "path": { - "type": "string" - }, - "thumbnail_path": { - "type": "string", - "nullable": true - }, - "size_bytes": { - "type": "integer" - } - } - } - } - } - }, - "400": { - "description": "No file or invalid type" - }, - "404": { - "description": "Event not found" - } - } - } - }, - "/events/{id}/share-link": { - "get": { - "tags": [ - "Events" - ], - "summary": "Get the absolute share URL for an event", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Share URL", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "slug": { - "type": "string" - }, - "share_token": { - "type": "string" - }, - "share_url": { - "type": "string", - "format": "uri" - } - } - } - } - } - }, - "404": { - "description": "Not found" - } - } - } - } - }, - "tags": [] -} \ No newline at end of file diff --git a/docs/openapi.yaml b/docs/openapi.yaml deleted file mode 100644 index ddf8a8ea..00000000 --- a/docs/openapi.yaml +++ /dev/null @@ -1,270 +0,0 @@ -openapi: 3.0.3 -info: - title: PicPeak API - version: v1 - description: >- - Public REST API for PicPeak — create gallery events, upload photos, fetch share links. - Authenticate with a Bearer token issued via the admin **Settings → API Tokens** tab. -servers: - - url: /api/v1 - description: Same-origin (production) -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: pp_live_* - description: >- - Long-lived API token. Issue via Settings → API Tokens. Token format: `pp_live_`. - Scopes: `read`, `write`, `admin`. - schemas: - EventSummary: - type: object - properties: - id: - type: integer - slug: - type: string - event_name: - type: string - event_type: - type: string - event_date: - type: string - format: date - nullable: true - expires_at: - type: string - format: date-time - nullable: true - is_active: - type: boolean - is_archived: - type: boolean - is_draft: - type: boolean - created_at: - type: string - format: date-time -security: - - bearerAuth: [] -paths: - /events: - post: - tags: - - Events - summary: Create a gallery event - description: Returns the new event's id, slug, and absolute share URL. - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - event_name - - event_type - properties: - event_name: - type: string - event_type: - type: string - enum: - - wedding - - birthday - - corporate - - other - - family - event_date: - type: string - format: date - nullable: true - customer_name: - type: string - nullable: true - customer_email: - type: string - format: email - nullable: true - customer_phone: - type: string - nullable: true - description: Only persisted when the global phone-field setting is enabled. - admin_email: - type: string - format: email - nullable: true - require_password: - type: boolean - default: true - password: - type: string - nullable: true - description: Required when require_password is true. - expires_at: - type: string - format: date-time - nullable: true - responses: - '201': - description: Event created - content: - application/json: - schema: - type: object - properties: - id: - type: integer - slug: - type: string - share_url: - type: string - format: uri - share_token: - type: string - '400': - description: Validation error - '401': - description: Missing/invalid token - '403': - description: Token lacks admin scope - get: - tags: - - Events - summary: List gallery events (paginated) - security: - - bearerAuth: [] - parameters: - - in: query - name: page - schema: - type: integer - minimum: 1 - default: 1 - - in: query - name: limit - schema: - type: integer - minimum: 1 - maximum: 100 - default: 25 - responses: - '200': - description: Paginated list - content: - application/json: - schema: - type: object - properties: - events: - type: array - items: - $ref: '#/components/schemas/EventSummary' - pagination: - type: object - properties: - page: - type: integer - limit: - type: integer - total: - type: integer - /events/{id}: - get: - tags: - - Events - summary: Get a single event - security: - - bearerAuth: [] - parameters: - - in: path - name: id - required: true - schema: - type: integer - responses: - '200': - description: Event details - '404': - description: Not found - /events/{id}/photos: - post: - tags: - - Photos - summary: Upload a single photo to an event - security: - - bearerAuth: [] - parameters: - - in: path - name: id - required: true - schema: - type: integer - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: - - photo - properties: - photo: - type: string - format: binary - responses: - '201': - description: Photo uploaded - content: - application/json: - schema: - type: object - properties: - id: - type: integer - filename: - type: string - path: - type: string - thumbnail_path: - type: string - nullable: true - size_bytes: - type: integer - '400': - description: No file or invalid type - '404': - description: Event not found - /events/{id}/share-link: - get: - tags: - - Events - summary: Get the absolute share URL for an event - security: - - bearerAuth: [] - parameters: - - in: path - name: id - required: true - schema: - type: integer - responses: - '200': - description: Share URL - content: - application/json: - schema: - type: object - properties: - slug: - type: string - share_token: - type: string - share_url: - type: string - format: uri - '404': - description: Not found -tags: [] diff --git a/docs/picpeak-admin-api.openapi.yaml b/docs/picpeak-admin-api.openapi.yaml deleted file mode 100644 index 185e289f..00000000 --- a/docs/picpeak-admin-api.openapi.yaml +++ /dev/null @@ -1,584 +0,0 @@ -openapi: 3.1.0 -info: - title: PicPeak Admin API - version: 1.1.11 - summary: High-level administrative endpoints for creating events, uploading photos, and resending gallery access emails. - description: | - This document describes the core administrative endpoints that power PicPeak automations. - It focuses on the three workflows requested by integrators: - - 1. Creating events with customer access credentials. - 2. Uploading photos in bulk to an event gallery. - 3. Resending the customer-facing gallery email. - - The specification follows the latest [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) best practices - and is intended to be kept in sync with backend changes. - contact: - name: PicPeak Maintainers - url: https://github.com/the-luap/picpeak -servers: - - url: https://api.picpeak.example.com/api - description: Example production deployment - - url: http://localhost:3001/api - description: Local development -tags: - - name: Admin Events - description: Administrative endpoints for managing event galleries. -components: - securitySchemes: - CookieAuth: - type: apiKey - in: cookie - name: admin_token - description: > - Session cookie issued by the admin authentication flow. When present, the backend mirrors - it into the `Authorization` header automatically. - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: > - JSON Web Token created by the admin login endpoint. You can also pass the token explicitly - as `Authorization: Bearer ` instead of using the admin cookie. - parameters: - EventId: - name: eventId - in: path - description: Numeric identifier of the event. - required: true - schema: - type: integer - minimum: 1 - example: 341 - schemas: - ErrorResponse: - type: object - properties: - error: - type: string - description: Human readable error message. - details: - type: string - nullable: true - description: Additional context (when available). - required: - - error - example: - error: Invalid token - ValidationErrorItem: - type: object - properties: - type: - type: string - nullable: true - description: Validation error type reported by express-validator. - msg: - type: string - path: - type: string - description: Dot-delimited path to the invalid field. - value: - description: Value that failed validation. - location: - type: string - description: Location of the invalid value (always `body` for these endpoints). - required: - - msg - - path - - location - example: - type: field - msg: Event date must be a valid ISO 8601 date - path: event_date - value: 2025/05/01 - location: body - ValidationErrorResponse: - type: object - properties: - errors: - type: array - items: - $ref: '#/components/schemas/ValidationErrorItem' - required: - - errors - example: - errors: - - type: field - msg: Customer email must be a valid address - path: customer_email - value: example@invalid - location: body - CreateEventRequest: - type: object - required: - - event_type - - event_name - - event_date - - customer_name - - customer_email - - admin_email - properties: - event_type: - type: string - description: Type of event. Controls default theme and copy in the UI. - enum: [wedding, birthday, corporate, other] - event_name: - type: string - minLength: 1 - description: Display name for the gallery shown to end customers. - event_date: - type: string - format: date - description: Event date (YYYY-MM-DD). Used to calculate the default expiration. - customer_name: - type: string - minLength: 1 - description: Name of the customer receiving gallery access. - customer_email: - type: string - format: email - description: Email address of the customer who will receive the gallery link. - admin_email: - type: string - format: email - description: Admin contact email included in notification messages. - require_password: - type: boolean - default: true - description: When true, the gallery requires `password`; when false a random placeholder is stored. - password: - type: string - minLength: 6 - description: > - Gallery password issued to the customer. Required when `require_password` is `true`. - Left unset to auto-generate a placeholder when password protection is disabled. - expiration_days: - type: integer - minimum: 1 - maximum: 365 - default: 30 - description: Number of days after the event date before the gallery expires. - welcome_message: - type: string - description: Optional welcome message displayed in the gallery. - color_theme: - type: string - nullable: true - description: Optional theme identifier or CSS color settings. - allow_user_uploads: - type: boolean - default: false - description: Allow gallery guests to upload their own photos. - upload_category_id: - type: integer - nullable: true - description: ID of the default category for user uploads. - allow_downloads: - type: boolean - default: true - description: Allow guests to download photos. - disable_right_click: - type: boolean - default: false - description: Disable right-click in the gallery view. - watermark_downloads: - type: boolean - default: false - description: Enable watermarking on downloaded images. - watermark_text: - type: string - nullable: true - description: Custom watermark text when `watermark_downloads` is true. - feedback_enabled: - type: boolean - default: false - description: Enable the feedback module for this gallery. - allow_ratings: - type: boolean - default: true - allow_likes: - type: boolean - default: true - allow_comments: - type: boolean - default: true - allow_favorites: - type: boolean - default: true - require_name_email: - type: boolean - default: false - description: Require guests to provide name and email when leaving feedback. - moderate_comments: - type: boolean - default: true - description: Hold guest comments for moderation. - show_feedback_to_guests: - type: boolean - default: true - description: Display aggregated feedback metrics back to guests. - example: - event_type: wedding - event_name: Emily & Jordan Celebration - event_date: 2025-06-07 - customer_name: Emily Carter - customer_email: emily@example.com - admin_email: studio@example.com - require_password: true - password: Shutter123 - expiration_days: 45 - welcome_message: > - We loved capturing your day! Use the password below to view and download your photos. - allow_user_uploads: false - allow_downloads: true - feedback_enabled: true - allow_comments: true - show_feedback_to_guests: true - EventSummary: - type: object - properties: - id: - type: integer - description: Database identifier of the newly created event. - slug: - type: string - description: Unique slug used to build the gallery URL. - event_name: - type: string - event_type: - type: string - enum: [wedding, birthday, corporate, other] - customer_name: - type: string - nullable: true - description: Name of the customer associated with the event. - customer_email: - type: string - format: email - nullable: true - description: Email address of the customer associated with the event. - require_password: - type: boolean - share_link: - type: string - description: Absolute or relative URL guests can use to reach the gallery. - expires_at: - type: string - format: date-time - description: ISO 8601 timestamp when the gallery expires. - created_at: - type: string - format: date-time - description: ISO 8601 timestamp when the event was created. - required: - - id - - slug - - event_name - - event_type - - require_password - - share_link - - expires_at - - created_at - example: - id: 512 - slug: wedding-emily-jordan-2025-06-07 - event_name: Emily & Jordan Celebration - event_type: wedding - customer_name: Emily Carter - customer_email: emily@example.com - require_password: true - share_link: https://app.picpeak.io/gallery/wedding-emily-jordan-2025-06-07/2f3c8a4d90bb11ef9b2e0242ac120002 - expires_at: 2025-07-22T00:00:00.000Z - created_at: 2025-05-01T14:32:45.000Z - UploadPhotosResponse: - type: object - properties: - message: - type: string - photos: - type: array - items: - $ref: '#/components/schemas/UploadedPhotoSummary' - description: Metadata for each photo that was persisted successfully. - totalFiles: - type: integer - minimum: 0 - description: Total number of files included in the request (valid + invalid). - successCount: - type: integer - minimum: 0 - failureCount: - type: integer - minimum: 0 - errors: - type: array - items: - $ref: '#/components/schemas/UploadFailure' - description: Present when some files failed validation or processing. - required: - - message - - photos - - totalFiles - - successCount - - failureCount - example: - message: Uploaded 18 of 20 photos. 2 failed. - photos: - - id: 9821 - filename: DSC_2031.jpg - size: 4812096 - category_id: 2 - - id: 9822 - filename: DSC_2032.jpg - size: 5216743 - category_id: 2 - totalFiles: 20 - successCount: 18 - failureCount: 2 - errors: - - filename: DSC_2020.raw - error: Only JPEG, PNG and WebP images are allowed - - filename: portrait.png - error: File is empty - UploadedPhotoSummary: - type: object - properties: - id: - type: integer - filename: - type: string - size: - type: integer - description: File size in bytes. - category_id: - type: integer - nullable: true - required: - - id - - filename - - size - example: - id: 9821 - filename: DSC_2031.jpg - size: 4812096 - category_id: 2 - UploadFailure: - type: object - properties: - filename: - type: string - error: - type: string - required: - - filename - - error - example: - filename: DSC_2031.gif - error: Only JPEG, PNG and WebP images are allowed - ResendEmailRequest: - type: object - properties: - password: - type: string - minLength: 1 - description: > - Optional plain-text password to include in the email. When omitted a security notice - placeholder is inserted because the stored hash cannot be reversed. - example: - password: Shutter123 - ResendEmailResponse: - type: object - properties: - success: - type: boolean - message: - type: string - required: - - success - - message - example: - success: true - message: Creation email has been queued for sending -paths: - /admin/events: - post: - tags: [Admin Events] - operationId: createAdminEvent - summary: Create a new event - description: > - Creates a new event, provisions storage folders, stores the gallery password, and queues - the initial gallery email for the customer. Requires admin authentication. - security: - - CookieAuth: [] - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateEventRequest' - examples: - weddingExample: - summary: Wedding with password protection - value: - event_type: wedding - event_name: Emily & Jordan Celebration - event_date: 2025-06-07 - customer_name: Emily Carter - customer_email: emily@example.com - admin_email: studio@example.com - require_password: true - password: Shutter123 - expiration_days: 45 - welcome_message: > - We loved capturing your day! Use the password below to view and download your photos. - allow_user_uploads: false - allow_downloads: true - feedback_enabled: true - allow_comments: true - show_feedback_to_guests: true - responses: - '200': - description: Event created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/EventSummary' - '400': - description: Validation failed. At least one field is invalid or missing. - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorResponse' - '401': - description: Authentication required or token invalid. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Unexpected server error while creating the event. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - /admin/events/{eventId}/upload: - post: - tags: [Admin Events] - operationId: uploadEventPhotos - summary: Upload photos to an event gallery - description: | - Uploads one or more photos to the specified event. Files are validated, moved into the - event storage directory, and thumbnails are generated asynchronously. - - The maximum number of files per upload is controlled via the `general_max_files_per_upload` - setting (default 500, capped at 2000). Files exceeding 50 MB are rejected. - security: - - CookieAuth: [] - - BearerAuth: [] - parameters: - - $ref: '#/components/parameters/EventId' - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - photos: - type: array - description: > - One or more image files (JPEG, PNG, WebP). Each file must be <= 50 MB. - items: - type: string - format: binary - category_id: - oneOf: - - type: integer - - type: string - description: > - Optional category assignment. Accepts numeric IDs or the string values `collage` - and `individual` for backward compatibility. - required: - - photos - encoding: - photos: - style: form - explode: false - responses: - '200': - description: Upload completed. Failed files (if any) are listed in the response. - content: - application/json: - schema: - $ref: '#/components/schemas/UploadPhotosResponse' - '400': - description: Request failed validation (invalid files, too many files, etc.). - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '401': - description: Authentication required or token invalid. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: The referenced event does not exist. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Unexpected server error while processing uploads. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - /admin/events/{eventId}/resend-email: - post: - tags: [Admin Events] - operationId: resendEventEmail - summary: Resend the gallery access email to the customer - description: > - Queues the standard `gallery_created` email for the event's customer. Useful when resending - credentials to the customer or communicating an updated password. Requires admin authentication. - security: - - CookieAuth: [] - - BearerAuth: [] - parameters: - - $ref: '#/components/parameters/EventId' - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ResendEmailRequest' - example: - password: NewSecurePassword! - responses: - '200': - description: Email successfully queued for delivery. - content: - application/json: - schema: - $ref: '#/components/schemas/ResendEmailResponse' - '401': - description: Authentication required or token invalid. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: Event not found. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Unexpected server error while queuing the email. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse'