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:
@@ -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 });
|
||||
|
||||
|
||||
@@ -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,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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user