screenshot: admin github button (#778)

This commit is contained in:
Paul Nothaft
2026-07-10 09:50:18 +02:00
commit e94e440858
1160 changed files with 291466 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
/**
* Script to backfill photo dimensions for photos that are missing them
*/
const path = require('path');
const fs = require('fs');
const sharp = require('sharp');
// Dynamic require for knex to use the app's config
const config = require('../backend/knexfile');
const knex = require('knex')(config);
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../storage');
async function backfillDimensions() {
console.log('Storage path:', storagePath);
const photos = await knex('photos')
.whereNull('width')
.orWhereNull('height')
.select('id', 'path', 'filename', 'media_type');
console.log(`Found ${photos.length} photos without dimensions`);
let updated = 0;
let failed = 0;
for (const photo of photos) {
if (photo.media_type === 'video') continue;
if (!photo.path) {
console.log(`Photo ${photo.id} has no path`);
failed++;
continue;
}
const fullPath = path.join(storagePath, 'events/active', photo.path);
if (!fs.existsSync(fullPath)) {
console.log(`Not found: ${fullPath}`);
failed++;
continue;
}
try {
const metadata = await sharp(fullPath).metadata();
if (metadata.width && metadata.height) {
await knex('photos')
.where('id', photo.id)
.update({ width: metadata.width, height: metadata.height });
updated++;
if (updated % 20 === 0) {
console.log(`Updated ${updated} photos...`);
}
}
} catch (err) {
console.log(`Error processing photo ${photo.id}:`, err.message);
failed++;
}
}
console.log(`\nCompleted: ${updated} updated, ${failed} failed`);
await knex.destroy();
process.exit(0);
}
backfillDimensions().catch(err => {
console.error(err);
process.exit(1);
});
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# PicPeak Backup Script
# Creates backups of database and storage
set -e
# Configuration
BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="picpeak_backup_${TIMESTAMP}"
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
echo "🔄 Starting PicPeak backup..."
# Create backup directory
mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}"
# Backup database
echo "📊 Backing up database..."
if [ -f "./data/photo_sharing.db" ]; then
cp ./data/photo_sharing.db "${BACKUP_DIR}/${BACKUP_NAME}/"
echo -e "${GREEN}✓ SQLite database backed up${NC}"
else
# PostgreSQL backup
docker exec picpeak-postgres pg_dump -U picpeak picpeak > "${BACKUP_DIR}/${BACKUP_NAME}/database.sql" 2>/dev/null || {
echo -e "${RED}⚠ Database backup failed - is PostgreSQL running?${NC}"
}
fi
# Backup storage
echo "📸 Backing up photos..."
if [ -d "./storage" ]; then
tar -czf "${BACKUP_DIR}/${BACKUP_NAME}/storage.tar.gz" ./storage 2>/dev/null || {
echo -e "${RED}⚠ Storage backup failed${NC}"
exit 1
}
echo -e "${GREEN}✓ Storage backed up${NC}"
fi
# Backup environment files
echo "⚙️ Backing up configuration..."
cp .env "${BACKUP_DIR}/${BACKUP_NAME}/.env.backup" 2>/dev/null || true
# Create backup info
echo "📝 Creating backup info..."
cat > "${BACKUP_DIR}/${BACKUP_NAME}/backup_info.txt" << EOF
PicPeak Backup
Created: $(date)
Version: $(grep version backend/package.json | head -1 | awk -F'"' '{print $4}')
Storage Size: $(du -sh ./storage 2>/dev/null | cut -f1 || echo "N/A")
EOF
# Compress entire backup
echo "📦 Compressing backup..."
cd "${BACKUP_DIR}"
tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}"
rm -rf "${BACKUP_NAME}"
# Cleanup old backups (keep last 7)
echo "🧹 Cleaning up old backups..."
ls -t *.tar.gz | tail -n +8 | xargs -r rm
echo -e "${GREEN}✅ Backup completed: ${BACKUP_DIR}/${BACKUP_NAME}.tar.gz${NC}"
echo "💡 To restore: tar -xzf ${BACKUP_NAME}.tar.gz && follow restore instructions"
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# Generate a secure JWT secret for PicPeak
echo "==================================="
echo "JWT Secret Generator for PicPeak"
echo "==================================="
echo ""
# Generate the secret
SECRET=$(openssl rand -hex 32)
echo "Your new JWT secret (64 characters):"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "$SECRET"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "To use this secret:"
echo ""
echo "1. For Docker Compose (.env file):"
echo " JWT_SECRET=$SECRET"
echo ""
echo "2. For environment variable:"
echo " export JWT_SECRET=$SECRET"
echo ""
echo "3. For systemd service:"
echo " Environment=\"JWT_SECRET=$SECRET\""
echo ""
echo "⚠️ IMPORTANT:"
echo " - Keep this secret secure and never commit it to version control"
echo " - Use different secrets for different environments"
echo " - Store production secrets in a secure secret management system"
echo " - Rotate secrets regularly (every 90 days recommended)"
echo ""
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
set -e
echo "Photo Sharing Platform - Docker Installation"
echo "==========================================="
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check prerequisites
echo "Checking prerequisites..."
# Install Docker if not present
if ! command_exists docker; then
echo "Installing Docker..."
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
rm get-docker.sh
fi
# Install Docker Compose if not present
if ! command_exists docker-compose; then
echo "Installing Docker Compose..."
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
fi
# Create necessary directories
echo "Creating directory structure..."
mkdir -p storage/events/{active,archived}
mkdir -p storage/thumbnails
mkdir -p data
mkdir -p logs
mkdir -p nginx/sites-enabled
mkdir -p certbot/{conf,www}
# Set permissions
chmod -R 755 storage
chmod -R 755 data
chmod -R 755 logs
# Copy environment file
if [ ! -f .env ]; then
cp .env.example .env
echo "Created .env file. Please edit it with your configuration."
fi
# Generate secure passwords
echo "Generating secure passwords..."
JWT_SECRET=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 32)
UMAMI_HASH_SALT=$(openssl rand -base64 32)
# Update .env file with generated values
sed -i "s/JWT_SECRET=.*/JWT_SECRET=$JWT_SECRET/" .env
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$DB_PASSWORD/" .env
sed -i "s/UMAMI_HASH_SALT=.*/UMAMI_HASH_SALT=$UMAMI_HASH_SALT/" .env
echo ""
echo "Installation complete!"
echo "Next steps:"
echo "1. Edit .env file with your domain names and SMTP settings"
echo "2. Run: ./scripts/setup-ssl.sh to configure SSL certificates"
echo "3. Run: docker-compose -f docker-compose.prod.yml up -d"
echo "4. Run: docker-compose -f docker-compose.prod.yml exec backend npm run migrate"
+1565
View File
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
# Script to create test galleries for each masonry layout mode
set -e
BASE_URL="${BASE_URL:-http://localhost:7100}"
ADMIN_USER="${ADMIN_USERNAME:-admin}"
ADMIN_PASS="${ADMIN_PASSWORD:-admin}"
TEST_IMAGES_DIR="${1:-./test-images}"
echo "=== Setting up Masonry Layout Test Galleries ==="
echo "Base URL: $BASE_URL"
echo "Test images: $TEST_IMAGES_DIR"
# Login to get admin token
echo ""
echo "Logging in as admin..."
LOGIN_RESPONSE=$(curl -s -X POST "$BASE_URL/api/auth/admin/login" \
-H "Content-Type: application/json" \
-d "{\"username\": \"$ADMIN_USER\", \"password\": \"$ADMIN_PASS\"}")
TOKEN=$(echo "$LOGIN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
if [ -z "$TOKEN" ]; then
echo "Failed to login. Response: $LOGIN_RESPONSE"
exit 1
fi
echo "Login successful!"
# Function to create a gallery with specific masonry mode
create_gallery() {
local name="$1"
local masonry_mode="$2"
local description="$3"
echo ""
echo "Creating gallery: $name (masonry mode: $masonry_mode)"
# Build color_theme JSON with galleryLayout and gallerySettings
local color_theme=$(cat <<EOF
{
"galleryLayout": "masonry",
"gallerySettings": {
"masonryMode": "$masonry_mode",
"masonryGutter": 8,
"masonryRowHeight": 250
},
"primaryColor": "#3B82F6",
"backgroundColor": "#FFFFFF",
"textColor": "#1F2937"
}
EOF
)
# Escape for JSON
local color_theme_escaped=$(echo "$color_theme" | tr -d '\n' | sed 's/"/\\"/g')
local event_date=$(date +%Y-%m-%d)
CREATE_RESPONSE=$(curl -s -X POST "$BASE_URL/api/admin/events" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"event_type\": \"other\",
\"event_name\": \"$name\",
\"event_date\": \"$event_date\",
\"customer_name\": \"Test User\",
\"customer_email\": \"[email protected]\",
\"admin_email\": \"[email protected]\",
\"password\": \"MasonryTest2026!\",
\"welcome_message\": \"$description\",
\"color_theme\": \"$color_theme_escaped\",
\"expiration_days\": 30
}")
local event_id=$(echo "$CREATE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
local slug=$(echo "$CREATE_RESPONSE" | grep -o '"slug":"[^"]*"' | cut -d'"' -f4)
local share_link=$(echo "$CREATE_RESPONSE" | grep -o '"share_link":"[^"]*"' | cut -d'"' -f4)
if [ -z "$event_id" ]; then
echo " Failed to create gallery. Response: $CREATE_RESPONSE"
return 1
fi
echo " Created event ID: $event_id, slug: $slug"
echo " Share link: $share_link"
# Upload test images
echo " Uploading test images..."
for img in "$TEST_IMAGES_DIR"/*.jpg; do
if [ -f "$img" ]; then
local filename=$(basename "$img")
curl -s -X POST "$BASE_URL/api/admin/photos/$event_id/upload" \
-H "Authorization: Bearer $TOKEN" \
-F "photos=@$img" > /dev/null
echo " Uploaded: $filename"
fi
done
echo " Gallery URL: $share_link"
echo "$share_link" >> /tmp/masonry_test_galleries.txt
}
# Clear previous results
> /tmp/masonry_test_galleries.txt
# Create galleries for each masonry mode
create_gallery "Masonry Columns Test" "columns" "Pinterest-style vertical columns with varied heights based on photo aspect ratios"
create_gallery "Masonry Rows Test" "rows" "Custom row-based justified layout that fills each row completely"
create_gallery "Masonry Flickr Test" "flickr" "Flickr's justified-layout algorithm for optimal row arrangement"
create_gallery "Masonry Quilted Test" "quilted" "Mixed sizes layout - landscape photos span 2 columns, portraits span 2 rows"
echo ""
echo "=== All Test Galleries Created ==="
echo ""
echo "Gallery URLs:"
cat /tmp/masonry_test_galleries.txt
echo ""
echo "You can also find these URLs in /tmp/masonry_test_galleries.txt"
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Local-only API docs sync. Generates docs/openapi.{json,yaml} from
# the @openapi JSDoc blocks in backend/src/routes/v1/*, then copies the
# result into the picpeak-docs Nextra site at /Users/paul/Development/picpeak-docs/app/api/.
#
# Writes only — never commits or pushes the docs repo. Review the diff
# in picpeak-docs and commit there manually when ready.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DOCS_REPO="${PICPEAK_DOCS_REPO:-/Users/paul/Development/picpeak-docs}"
SRC_DIR="$REPO_ROOT/docs"
TARGET_DIR="$DOCS_REPO/app/api"
cd "$REPO_ROOT/backend"
# 1. Generate fresh spec from JSDoc.
echo "▶ Generating OpenAPI spec from src/routes/v1/*"
node scripts/generate-openapi.js
# 2. Verify docs repo is reachable. Soft-fail so this doesn't block a
# push when the docs repo isn't on this machine.
if [ ! -d "$DOCS_REPO" ]; then
echo "▶ Docs repo not found at $DOCS_REPO — skipping sync."
echo " (Set PICPEAK_DOCS_REPO to override, or create the path to enable sync.)"
exit 0
fi
if [ ! -d "$TARGET_DIR" ]; then
echo "▶ Target dir $TARGET_DIR doesn't exist — creating."
mkdir -p "$TARGET_DIR"
fi
# 3. Copy spec files into the docs repo. We do NOT git-add or commit
# here — the user reviews and commits picpeak-docs manually.
cp "$SRC_DIR/openapi.json" "$TARGET_DIR/openapi.json"
cp "$SRC_DIR/openapi.yaml" "$TARGET_DIR/openapi.yaml"
echo "▶ Wrote openapi.{json,yaml} to $TARGET_DIR"
# 4. Brief drop-in MDX page that references the spec, so the Nextra
# nav has a stable target. Won't overwrite a hand-edited file —
# only writes if missing.
REF_MDX="$TARGET_DIR/reference.mdx"
if [ ! -f "$REF_MDX" ]; then
cat > "$REF_MDX" <<'EOF'
---
title: API Reference
---
# API Reference
The PicPeak v1 REST API is documented as an OpenAPI 3 spec.
- [Download `openapi.yaml`](./openapi.yaml)
- [Download `openapi.json`](./openapi.json)
- A live, browseable Swagger UI is served by every PicPeak instance at
`/api/docs` (admin login required).
This page is auto-generated from JSDoc annotations on the v1 route files.
Do not hand-edit. The narrative pages (auth, recipes) live alongside.
EOF
echo "▶ Created $REF_MDX (placeholder — replace with your preferred renderer)"
fi
echo "✓ API docs synced. Review changes in $DOCS_REPO before committing."