diff --git a/backend/scripts/README.md b/backend/scripts/README.md new file mode 100644 index 0000000..91af92b --- /dev/null +++ b/backend/scripts/README.md @@ -0,0 +1,64 @@ +# Storage Management Scripts + +## check-storage.js + +Checks the storage directory structure and verifies that photo files exist. + +### Usage: +```bash +# Check overall storage structure +node scripts/check-storage.js + +# Check specific event +node scripts/check-storage.js wedding-test-gallery-2025-07-14-1 +``` + +### What it checks: +- Storage directory structure and permissions +- Event directories +- Photo files existence +- Thumbnail files existence +- Database vs filesystem consistency + +## regenerate-thumbnails.js + +Regenerates missing thumbnails for photos in the database. + +### Usage: +```bash +# Regenerate all missing thumbnails +node scripts/regenerate-thumbnails.js + +# Regenerate thumbnails for specific event (by ID) +node scripts/regenerate-thumbnails.js 2 +``` + +### What it does: +- Scans photos in database +- Checks if thumbnails exist +- Generates missing thumbnails using Sharp +- Updates database with thumbnail paths +- Reports success/error statistics + +### Prerequisites: +- Node.js environment +- Database access +- Write permissions to storage directory +- Sharp library installed + +## Production Usage + +On your production server: + +1. First, check the storage structure: + ```bash + cd /path/to/picpeak/backend + NODE_ENV=production node scripts/check-storage.js wedding-test-gallery-2025-07-14-1 + ``` + +2. If thumbnails are missing, regenerate them: + ```bash + NODE_ENV=production node scripts/regenerate-thumbnails.js 2 + ``` + +Note: Replace `2` with the actual event ID from your database. \ No newline at end of file diff --git a/backend/scripts/check-storage.js b/backend/scripts/check-storage.js new file mode 100755 index 0000000..a9e1391 --- /dev/null +++ b/backend/scripts/check-storage.js @@ -0,0 +1,132 @@ +#!/usr/bin/env node + +/** + * Script to check storage directory structure and verify files + * Usage: node scripts/check-storage.js [eventSlug] + */ + +const path = require('path'); +const fs = require('fs').promises; +const { db } = require('../src/database/db'); + +const STORAGE_PATH = process.env.STORAGE_PATH || path.join(__dirname, '../../storage'); + +async function checkDirectory(dirPath, description) { + try { + await fs.access(dirPath); + const stats = await fs.stat(dirPath); + const files = await fs.readdir(dirPath); + console.log(`✓ ${description}: ${dirPath}`); + console.log(` - Files/Folders: ${files.length}`); + console.log(` - Permissions: ${(stats.mode & parseInt('777', 8)).toString(8)}`); + return true; + } catch (error) { + console.log(`✗ ${description}: ${dirPath} - ${error.message}`); + return false; + } +} + +async function checkStorageStructure(eventSlug = null) { + console.log('Checking storage structure...'); + console.log(`Storage base path: ${STORAGE_PATH}\n`); + + // Check main directories + await checkDirectory(STORAGE_PATH, 'Storage root'); + await checkDirectory(path.join(STORAGE_PATH, 'events'), 'Events directory'); + await checkDirectory(path.join(STORAGE_PATH, 'events/active'), 'Active events'); + await checkDirectory(path.join(STORAGE_PATH, 'events/archived'), 'Archived events'); + await checkDirectory(path.join(STORAGE_PATH, 'thumbnails'), 'Thumbnails'); + await checkDirectory(path.join(STORAGE_PATH, 'uploads'), 'Uploads'); + + console.log('\n---\n'); + + // If event slug provided, check specific event + if (eventSlug) { + console.log(`Checking specific event: ${eventSlug}`); + + const event = await db('events').where('slug', eventSlug).first(); + if (!event) { + console.log(`✗ Event not found in database: ${eventSlug}`); + return; + } + + console.log(`✓ Event found in database:`); + console.log(` - ID: ${event.id}`); + console.log(` - Name: ${event.event_name}`); + console.log(` - Active: ${event.is_active}`); + console.log(` - Archived: ${event.is_archived}`); + + // Check event directory + const eventDir = path.join(STORAGE_PATH, 'events/active', eventSlug); + const eventExists = await checkDirectory(eventDir, 'Event directory'); + + if (eventExists) { + const files = await fs.readdir(eventDir); + console.log(` - Photo files: ${files.filter(f => /\.(jpg|jpeg|png|gif)$/i.test(f)).length}`); + } + + // Check photos in database + const photos = await db('photos').where('event_id', event.id).select('id', 'filename', 'path', 'thumbnail_path'); + console.log(`\nDatabase photos: ${photos.length}`); + + // Check if photo files exist + let existingPhotos = 0; + let missingPhotos = 0; + let existingThumbnails = 0; + let missingThumbnails = 0; + + for (const photo of photos) { + const photoPath = path.join(STORAGE_PATH, 'events/active', photo.path); + try { + await fs.access(photoPath); + existingPhotos++; + } catch { + missingPhotos++; + console.log(` ✗ Missing photo: ${photo.path}`); + } + + if (photo.thumbnail_path) { + const thumbPath = path.join(STORAGE_PATH, photo.thumbnail_path.replace(/^\//, '')); + try { + await fs.access(thumbPath); + existingThumbnails++; + } catch { + missingThumbnails++; + console.log(` ✗ Missing thumbnail: ${photo.thumbnail_path}`); + } + } + } + + console.log(`\nFile check summary:`); + console.log(` - Photos: ${existingPhotos} exist, ${missingPhotos} missing`); + console.log(` - Thumbnails: ${existingThumbnails} exist, ${missingThumbnails} missing`); + } else { + // List all event directories + try { + const activeDir = path.join(STORAGE_PATH, 'events/active'); + const eventDirs = await fs.readdir(activeDir); + console.log(`Active event directories: ${eventDirs.length}`); + for (const dir of eventDirs.slice(0, 10)) { + console.log(` - ${dir}`); + } + if (eventDirs.length > 10) { + console.log(` ... and ${eventDirs.length - 10} more`); + } + } catch (error) { + console.log('Could not list event directories:', error.message); + } + } +} + +// Parse command line arguments +const eventSlug = process.argv[2] || null; + +// Run the script +checkStorageStructure(eventSlug).then(async () => { + await db.destroy(); + console.log('\nStorage check complete'); +}).catch(async error => { + console.error('Error:', error); + await db.destroy(); + process.exit(1); +}); \ No newline at end of file diff --git a/backend/scripts/regenerate-thumbnails.js b/backend/scripts/regenerate-thumbnails.js new file mode 100755 index 0000000..3797c55 --- /dev/null +++ b/backend/scripts/regenerate-thumbnails.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +/** + * Script to regenerate missing thumbnails for photos in the database + * Usage: node scripts/regenerate-thumbnails.js [eventId] + */ + +const path = require('path'); +const fs = require('fs').promises; +const sharp = require('sharp'); +const { db } = require('../src/database/db'); + +// Configuration +const THUMBNAIL_SIZE = 300; +const STORAGE_PATH = process.env.STORAGE_PATH || path.join(__dirname, '../../storage'); +const THUMBNAILS_DIR = path.join(STORAGE_PATH, 'thumbnails'); + +async function ensureDirectoryExists(dirPath) { + try { + await fs.access(dirPath); + } catch { + await fs.mkdir(dirPath, { recursive: true }); + console.log(`Created directory: ${dirPath}`); + } +} + +async function generateThumbnail(photoPath, thumbnailPath) { + try { + await sharp(photoPath) + .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE, { + fit: 'cover', + position: 'center' + }) + .jpeg({ quality: 80 }) + .toFile(thumbnailPath); + + return true; + } catch (error) { + console.error(`Failed to generate thumbnail for ${photoPath}:`, error.message); + return false; + } +} + +async function regenerateThumbnails(eventId = null) { + try { + console.log('Starting thumbnail regeneration...'); + console.log(`Storage path: ${STORAGE_PATH}`); + console.log(`Thumbnails directory: ${THUMBNAILS_DIR}`); + + // Ensure thumbnails directory exists + await ensureDirectoryExists(THUMBNAILS_DIR); + + // Build query + let query = db('photos') + .join('events', 'photos.event_id', 'events.id') + .select( + 'photos.id', + 'photos.filename', + 'photos.path', + 'photos.thumbnail_path', + 'events.slug as event_slug' + ); + + if (eventId) { + query = query.where('photos.event_id', eventId); + console.log(`Filtering for event ID: ${eventId}`); + } + + const photos = await query; + console.log(`Found ${photos.length} photos to process`); + + let successCount = 0; + let skipCount = 0; + let errorCount = 0; + + for (const photo of photos) { + const photoPath = path.join(STORAGE_PATH, 'events/active', photo.path); + const thumbnailFilename = `thumb_${photo.filename}`; + const thumbnailPath = path.join(THUMBNAILS_DIR, thumbnailFilename); + + try { + // Check if photo file exists + await fs.access(photoPath); + + // Check if thumbnail already exists + try { + await fs.access(thumbnailPath); + console.log(`Thumbnail already exists for ${photo.filename}, skipping...`); + skipCount++; + continue; + } catch { + // Thumbnail doesn't exist, generate it + } + + console.log(`Generating thumbnail for ${photo.filename}...`); + const success = await generateThumbnail(photoPath, thumbnailPath); + + if (success) { + // Update database with thumbnail path + await db('photos') + .where('id', photo.id) + .update({ + thumbnail_path: `thumbnails/${thumbnailFilename}` + }); + + successCount++; + console.log(`✓ Generated thumbnail for ${photo.filename}`); + } else { + errorCount++; + } + } catch (error) { + console.error(`✗ Photo file not found: ${photoPath}`); + errorCount++; + } + } + + console.log('\nThumbnail regeneration complete!'); + console.log(`- Successfully generated: ${successCount}`); + console.log(`- Skipped (already exist): ${skipCount}`); + console.log(`- Errors: ${errorCount}`); + console.log(`- Total processed: ${photos.length}`); + + } catch (error) { + console.error('Error during thumbnail regeneration:', error); + process.exit(1); + } finally { + await db.destroy(); + } +} + +// Parse command line arguments +const eventId = process.argv[2] ? parseInt(process.argv[2]) : null; + +// Run the script +regenerateThumbnails(eventId).then(() => { + console.log('Script completed successfully'); + process.exit(0); +}).catch(error => { + console.error('Script failed:', error); + process.exit(1); +}); \ No newline at end of file