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);
|
expires_at.setDate(expires_at.getDate() + expiration_days);
|
||||||
|
|
||||||
// Create folder structure
|
// 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, 'collages'), { recursive: true });
|
||||||
await fs.mkdir(path.join(eventPath, 'individual'), { 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);
|
expires_at.setDate(expires_at.getDate() + expiration_days);
|
||||||
|
|
||||||
// Create folder structure
|
// 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, 'collages'), { recursive: true });
|
||||||
await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true });
|
await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true });
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ const archiver = require('archiver');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const router = express.Router();
|
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
|
// Middleware to verify gallery access
|
||||||
async function verifyGalleryAccess(req, res, next) {
|
async function verifyGalleryAccess(req, res, next) {
|
||||||
try {
|
try {
|
||||||
@@ -115,7 +118,7 @@ router.get('/:slug/download/:photoId', verifyGalleryAccess, async (req, res) =>
|
|||||||
photo_id: photoId
|
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);
|
res.download(filePath, photo.filename);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to download photo' });
|
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
|
// Add photos to archive
|
||||||
for (const photo of photos) {
|
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 });
|
archive.file(filePath, { name: photo.path });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ const path = require('path');
|
|||||||
const { db } = require('../database/db');
|
const { db } = require('../database/db');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
const ACTIVE_PATH = path.join(__dirname, '../../../storage/events/active');
|
const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
|
||||||
const ARCHIVE_PATH = path.join(__dirname, '../../../storage/events/archived');
|
const ACTIVE_PATH = () => path.join(getStoragePath(), 'events/active');
|
||||||
|
const ARCHIVE_PATH = () => path.join(getStoragePath(), 'events/archived');
|
||||||
|
|
||||||
async function archiveEvent(event) {
|
async function archiveEvent(event) {
|
||||||
try {
|
try {
|
||||||
const eventPath = path.join(ACTIVE_PATH, event.slug);
|
const eventPath = path.join(ACTIVE_PATH(), event.slug);
|
||||||
const archiveName = `${event.slug}.zip`;
|
const archiveName = `${event.slug}.zip`;
|
||||||
const archivePath = path.join(ARCHIVE_PATH, archiveName);
|
const archivePath = path.join(ARCHIVE_PATH(), archiveName);
|
||||||
|
|
||||||
// Ensure archive directory exists
|
// Ensure archive directory exists
|
||||||
await fs.mkdir(ARCHIVE_PATH, { recursive: true });
|
await fs.mkdir(ARCHIVE_PATH(), { recursive: true });
|
||||||
|
|
||||||
// Create archive
|
// Create archive
|
||||||
const output = require('fs').createWriteStream(archivePath);
|
const output = require('fs').createWriteStream(archivePath);
|
||||||
@@ -32,7 +33,7 @@ async function archiveEvent(event) {
|
|||||||
// Update database
|
// Update database
|
||||||
await db('events').where('id', event.id).update({
|
await db('events').where('id', event.id).update({
|
||||||
is_archived: true,
|
is_archived: true,
|
||||||
archive_path: path.relative(path.join(__dirname, '../../../storage'), archivePath),
|
archive_path: path.relative(getStoragePath(), archivePath),
|
||||||
archived_at: new Date()
|
archived_at: new Date()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ async function archiveEvent(event) {
|
|||||||
const photos = await db('photos').where('event_id', event.id);
|
const photos = await db('photos').where('event_id', event.id);
|
||||||
for (const photo of photos) {
|
for (const photo of photos) {
|
||||||
if (photo.thumbnail_path) {
|
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
|
await fs.unlink(thumbPath).catch(() => {}); // Ignore if already deleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ const { db } = require('../database/db');
|
|||||||
const { generateThumbnail } = require('./imageProcessor');
|
const { generateThumbnail } = require('./imageProcessor');
|
||||||
const logger = require('../utils/logger');
|
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() {
|
function startFileWatcher() {
|
||||||
const watcher = chokidar.watch(WATCH_PATH, {
|
const watcher = chokidar.watch(WATCH_PATH(), {
|
||||||
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
||||||
persistent: true,
|
persistent: true,
|
||||||
awaitWriteFinish: {
|
awaitWriteFinish: {
|
||||||
@@ -37,7 +38,7 @@ function startFileWatcher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function processNewPhoto(filePath) {
|
async function processNewPhoto(filePath) {
|
||||||
const relativePath = path.relative(WATCH_PATH, filePath);
|
const relativePath = path.relative(WATCH_PATH(), filePath);
|
||||||
const pathParts = relativePath.split(path.sep);
|
const pathParts = relativePath.split(path.sep);
|
||||||
|
|
||||||
if (pathParts.length < 2) return; // Not in correct folder structure
|
if (pathParts.length < 2) return; // Not in correct folder structure
|
||||||
@@ -73,7 +74,7 @@ async function processNewPhoto(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removePhoto(filePath) {
|
async function removePhoto(filePath) {
|
||||||
const relativePath = path.relative(WATCH_PATH, filePath);
|
const relativePath = path.relative(WATCH_PATH(), filePath);
|
||||||
|
|
||||||
// Remove from database
|
// Remove from database
|
||||||
await db('photos').where({ path: relativePath }).delete();
|
await db('photos').where({ path: relativePath }).delete();
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ services:
|
|||||||
- SMTP_USER=
|
- SMTP_USER=
|
||||||
- SMTP_PASS=
|
- SMTP_PASS=
|
||||||
- EMAIL_FROM=noreply@photo-sharing.local
|
- EMAIL_FROM=noreply@photo-sharing.local
|
||||||
|
# Storage path
|
||||||
|
- STORAGE_PATH=/app/storage
|
||||||
# Umami Analytics (optional)
|
# Umami Analytics (optional)
|
||||||
- UMAMI_URL=
|
- UMAMI_URL=
|
||||||
- UMAMI_WEBSITE_ID=
|
- UMAMI_WEBSITE_ID=
|
||||||
@@ -92,6 +94,7 @@ services:
|
|||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
|
- STORAGE_PATH=/app/storage
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend:/app
|
- ./backend:/app
|
||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
SettingsPage
|
SettingsPage
|
||||||
} from './pages/admin';
|
} from './pages/admin';
|
||||||
import { AdminLayout } from './components/admin';
|
import { AdminLayout } from './components/admin';
|
||||||
import { PageErrorBoundary, ErrorBoundary, OfflineIndicator, SkipLink } from './components/common';
|
import { PageErrorBoundary, OfflineIndicator, SkipLink } from './components/common';
|
||||||
|
|
||||||
// Create a client
|
// Create a client
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
@@ -69,16 +69,16 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="login" element={<AdminLoginPage />} />
|
<Route path="login" element={<AdminLoginPage />} />
|
||||||
<Route element={<AdminLayout />}>
|
<Route element={<AdminLayout />}>
|
||||||
<Route path="dashboard" element={<ErrorBoundary><AdminDashboard /></ErrorBoundary>} />
|
<Route path="dashboard" element={<AdminDashboard />} />
|
||||||
<Route path="events" element={<ErrorBoundary><EventsListPage /></ErrorBoundary>} />
|
<Route path="events" element={<EventsListPage />} />
|
||||||
<Route path="events/new" element={<ErrorBoundary><CreateEventPage /></ErrorBoundary>} />
|
<Route path="events/new" element={<CreateEventPage />} />
|
||||||
<Route path="events/:id" element={<ErrorBoundary><EventDetailsPage /></ErrorBoundary>} />
|
<Route path="events/:id" element={<EventDetailsPage />} />
|
||||||
<Route path="archives" element={<ArchivesPage />} />
|
<Route path="archives" element={<ArchivesPage />} />
|
||||||
<Route path="email" element={<EmailConfigPage />} />
|
<Route path="email" element={<EmailConfigPage />} />
|
||||||
<Route path="analytics" element={<AnalyticsPage />} />
|
<Route path="analytics" element={<AnalyticsPage />} />
|
||||||
<Route path="branding" element={<BrandingPage />} />
|
<Route path="branding" element={<BrandingPage />} />
|
||||||
<Route path="settings" element={<SettingsPage />} />
|
<Route path="settings" element={<SettingsPage />} />
|
||||||
<Route path="/" element={<Navigate to="/admin/dashboard" replace />} />
|
<Route path="" element={<Navigate to="/admin/dashboard" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</AdminAuthProvider>
|
</AdminAuthProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user