From 2e7cba9e8a8f258e6d00f9a045d8ffe830cd91d0 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 7 Jul 2025 13:20:40 +0200 Subject: [PATCH] Fix storage path issues and React error #130 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend fixes: - Add STORAGE_PATH environment variable support - Fix absolute path references in all backend services - Update Docker configuration with correct storage path Frontend fixes: - Remove individual ErrorBoundary wrappers to fix React error #130 - Remove unused ErrorBoundary import - Simplify route structure to prevent component mounting issues This resolves: - 500 errors when creating events due to storage permission issues - React error #130 that occurred during event creation - Consistent storage path handling across all services 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/routes/adminEvents.js | 3 ++- backend/src/routes/events.js | 3 ++- backend/src/routes/gallery.js | 7 +++++-- backend/src/services/archiveService.js | 15 ++++++++------- backend/src/services/fileWatcher.js | 9 +++++---- docker-compose.local.yml | 3 +++ frontend/src/App.tsx | 12 ++++++------ 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index e4b26ac..4eed43c 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -60,7 +60,8 @@ router.post('/', adminAuth, [ expires_at.setDate(expires_at.getDate() + expiration_days); // Create folder structure - const eventPath = path.join(__dirname, '../../../storage/events/active', slug); + const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); + const eventPath = path.join(storagePath, 'events/active', slug); await fs.mkdir(path.join(eventPath, 'collages'), { recursive: true }); await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js index 430ae88..4970ff9 100644 --- a/backend/src/routes/events.js +++ b/backend/src/routes/events.js @@ -58,7 +58,8 @@ router.post('/', adminAuth, [ expires_at.setDate(expires_at.getDate() + expiration_days); // Create folder structure - const eventPath = path.join(__dirname, '../../../storage/events/active', slug); + const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); + const eventPath = path.join(storagePath, 'events/active', slug); await fs.mkdir(path.join(eventPath, 'collages'), { recursive: true }); await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index c100015..210f337 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -5,6 +5,9 @@ const archiver = require('archiver'); const path = require('path'); const router = express.Router(); +// Get storage path from environment or default +const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); + // Middleware to verify gallery access async function verifyGalleryAccess(req, res, next) { try { @@ -115,7 +118,7 @@ router.get('/:slug/download/:photoId', verifyGalleryAccess, async (req, res) => photo_id: photoId }); - const filePath = path.join(__dirname, '../../../storage/events/active', req.event.slug, photo.path); + const filePath = path.join(getStoragePath(), 'events/active', req.event.slug, photo.path); res.download(filePath, photo.filename); } catch (error) { res.status(500).json({ error: 'Failed to download photo' }); @@ -143,7 +146,7 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => { // Add photos to archive for (const photo of photos) { - const filePath = path.join(__dirname, '../../../storage/events/active', req.event.slug, photo.path); + const filePath = path.join(getStoragePath(), 'events/active', req.event.slug, photo.path); archive.file(filePath, { name: photo.path }); } diff --git a/backend/src/services/archiveService.js b/backend/src/services/archiveService.js index b00c34d..abdc692 100644 --- a/backend/src/services/archiveService.js +++ b/backend/src/services/archiveService.js @@ -4,17 +4,18 @@ const path = require('path'); const { db } = require('../database/db'); const logger = require('../utils/logger'); -const ACTIVE_PATH = path.join(__dirname, '../../../storage/events/active'); -const ARCHIVE_PATH = path.join(__dirname, '../../../storage/events/archived'); +const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); +const ACTIVE_PATH = () => path.join(getStoragePath(), 'events/active'); +const ARCHIVE_PATH = () => path.join(getStoragePath(), 'events/archived'); async function archiveEvent(event) { try { - const eventPath = path.join(ACTIVE_PATH, event.slug); + const eventPath = path.join(ACTIVE_PATH(), event.slug); const archiveName = `${event.slug}.zip`; - const archivePath = path.join(ARCHIVE_PATH, archiveName); + const archivePath = path.join(ARCHIVE_PATH(), archiveName); // Ensure archive directory exists - await fs.mkdir(ARCHIVE_PATH, { recursive: true }); + await fs.mkdir(ARCHIVE_PATH(), { recursive: true }); // Create archive const output = require('fs').createWriteStream(archivePath); @@ -32,7 +33,7 @@ async function archiveEvent(event) { // Update database await db('events').where('id', event.id).update({ is_archived: true, - archive_path: path.relative(path.join(__dirname, '../../../storage'), archivePath), + archive_path: path.relative(getStoragePath(), archivePath), archived_at: new Date() }); @@ -43,7 +44,7 @@ async function archiveEvent(event) { const photos = await db('photos').where('event_id', event.id); for (const photo of photos) { if (photo.thumbnail_path) { - const thumbPath = path.join(__dirname, '../../../storage', photo.thumbnail_path); + const thumbPath = path.join(getStoragePath(), photo.thumbnail_path); await fs.unlink(thumbPath).catch(() => {}); // Ignore if already deleted } } diff --git a/backend/src/services/fileWatcher.js b/backend/src/services/fileWatcher.js index a9e064d..a7dfa36 100644 --- a/backend/src/services/fileWatcher.js +++ b/backend/src/services/fileWatcher.js @@ -5,10 +5,11 @@ const { db } = require('../database/db'); const { generateThumbnail } = require('./imageProcessor'); const logger = require('../utils/logger'); -const WATCH_PATH = path.join(__dirname, '../../../storage/events/active'); +const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); +const WATCH_PATH = () => path.join(getStoragePath(), 'events/active'); function startFileWatcher() { - const watcher = chokidar.watch(WATCH_PATH, { + const watcher = chokidar.watch(WATCH_PATH(), { ignored: /(^|[\/\\])\../, // ignore dotfiles persistent: true, awaitWriteFinish: { @@ -37,7 +38,7 @@ function startFileWatcher() { } async function processNewPhoto(filePath) { - const relativePath = path.relative(WATCH_PATH, filePath); + const relativePath = path.relative(WATCH_PATH(), filePath); const pathParts = relativePath.split(path.sep); if (pathParts.length < 2) return; // Not in correct folder structure @@ -73,7 +74,7 @@ async function processNewPhoto(filePath) { } async function removePhoto(filePath) { - const relativePath = path.relative(WATCH_PATH, filePath); + const relativePath = path.relative(WATCH_PATH(), filePath); // Remove from database await db('photos').where({ path: relativePath }).delete(); diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 7bd10ed..909e67c 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -20,6 +20,8 @@ services: - SMTP_USER= - SMTP_PASS= - EMAIL_FROM=noreply@photo-sharing.local + # Storage path + - STORAGE_PATH=/app/storage # Umami Analytics (optional) - UMAMI_URL= - UMAMI_WEBSITE_ID= @@ -92,6 +94,7 @@ services: dockerfile: Dockerfile.dev environment: - NODE_ENV=development + - STORAGE_PATH=/app/storage volumes: - ./backend:/app - /app/node_modules diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d42146e..25cfa94 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,7 +21,7 @@ import { SettingsPage } from './pages/admin'; import { AdminLayout } from './components/admin'; -import { PageErrorBoundary, ErrorBoundary, OfflineIndicator, SkipLink } from './components/common'; +import { PageErrorBoundary, OfflineIndicator, SkipLink } from './components/common'; // Create a client const queryClient = new QueryClient({ @@ -69,16 +69,16 @@ function App() { } /> }> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> } /> } /> } /> } /> } /> - } /> + } />