Fix storage path issues and React error #130

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 <noreply@anthropic.com>
This commit is contained in:
2025-07-07 13:20:40 +02:00
parent 2e10374e2c
commit 2e7cba9e8a
7 changed files with 31 additions and 21 deletions
+2 -1
View File
@@ -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 });
+2 -1
View File
@@ -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 });
+5 -2
View File
@@ -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 });
}
+8 -7
View File
@@ -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
}
}
+5 -4
View File
@@ -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();
+3
View File
@@ -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
+6 -6
View File
@@ -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() {
<Routes>
<Route path="login" element={<AdminLoginPage />} />
<Route element={<AdminLayout />}>
<Route path="dashboard" element={<ErrorBoundary><AdminDashboard /></ErrorBoundary>} />
<Route path="events" element={<ErrorBoundary><EventsListPage /></ErrorBoundary>} />
<Route path="events/new" element={<ErrorBoundary><CreateEventPage /></ErrorBoundary>} />
<Route path="events/:id" element={<ErrorBoundary><EventDetailsPage /></ErrorBoundary>} />
<Route path="dashboard" element={<AdminDashboard />} />
<Route path="events" element={<EventsListPage />} />
<Route path="events/new" element={<CreateEventPage />} />
<Route path="events/:id" element={<EventDetailsPage />} />
<Route path="archives" element={<ArchivesPage />} />
<Route path="email" element={<EmailConfigPage />} />
<Route path="analytics" element={<AnalyticsPage />} />
<Route path="branding" element={<BrandingPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="/" element={<Navigate to="/admin/dashboard" replace />} />
<Route path="" element={<Navigate to="/admin/dashboard" replace />} />
</Route>
</Routes>
</AdminAuthProvider>