screenshot: admin github button (#778)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
Executable
+69
@@ -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"
|
||||
Executable
+34
@@ -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 ""
|
||||
Executable
+73
@@ -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"
|
||||
Executable
+1565
File diff suppressed because it is too large
Load Diff
Executable
+121
@@ -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"
|
||||
Executable
+65
@@ -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."
|
||||
Reference in New Issue
Block a user