Fix critical issues: gallery authentication and photo duplication

- Fix gallery-specific authentication for images
  - Update AuthenticatedImage component to use gallery-specific tokens
  - Add isGallery prop to distinguish between admin and gallery contexts
  - Update PhotoGrid and PhotoLightbox to pass isGallery prop

- Fix photo duplication issue in fileWatcher service
  - Add check to prevent duplicate photo entries when backend restarts
  - File watcher now verifies if photo exists before inserting
  - Cleaned up 176 duplicate photos from database

These fixes resolve:
1. Gallery images not loading due to auth token errors
2. Photo count increasing without new uploads due to duplicates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-09 08:28:15 +02:00
parent cf32b01356
commit ec3d5a0f80
4 changed files with 38 additions and 12 deletions
+19 -10
View File
@@ -63,17 +63,26 @@ async function processNewPhoto(filePath) {
// Calculate relative thumbnail path
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
// Add to database
await db('photos').insert({
event_id: event.id,
filename: path.basename(filePath),
path: relativePath,
thumbnail_path: relativeThumbPath,
type: photoType,
size_bytes: stats.size
});
// Check if photo already exists
const existingPhoto = await db('photos')
.where({ event_id: event.id, filename: path.basename(filePath) })
.first();
logger.info(`Added new photo: ${relativePath}`);
if (!existingPhoto) {
// Add to database
await db('photos').insert({
event_id: event.id,
filename: path.basename(filePath),
path: relativePath,
thumbnail_path: relativeThumbPath,
type: photoType,
size_bytes: stats.size
});
logger.info(`Added new photo: ${relativePath}`);
} else {
logger.debug(`Photo already exists: ${relativePath}`);
}
}
async function removePhoto(filePath) {
@@ -5,6 +5,7 @@ interface AuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageEleme
src: string;
fallbackSrc?: string;
useWatermark?: boolean;
isGallery?: boolean;
}
export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
@@ -12,6 +13,7 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
fallbackSrc,
alt,
useWatermark = false,
isGallery = false,
...props
}) => {
const [imageSrc, setImageSrc] = useState<string>('');
@@ -21,7 +23,20 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
useEffect(() => {
let objectUrl: string | null = null;
const token = getAuthToken();
// Determine which token to use based on context
let token: string | undefined;
if (isGallery) {
// For gallery images, get the gallery-specific token
const pathParts = window.location.pathname.split('/');
if (pathParts[1] === 'gallery' && pathParts[2]) {
const gallerySlug = pathParts[2];
token = localStorage.getItem(`gallery_token_${gallerySlug}`) || undefined;
}
} else {
// For admin images, use the admin token
token = getAuthToken(true);
}
if (!src) {
setImageSrc(fallbackSrc || '');
@@ -89,7 +104,7 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
URL.revokeObjectURL(objectUrl);
}
};
}, [src, fallbackSrc, useWatermark]);
}, [src, fallbackSrc, useWatermark, isGallery]);
if (isLoading) {
return (
@@ -237,6 +237,7 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
alt={photo.filename}
className="w-full h-full object-cover rounded-lg transition-transform duration-200 group-hover:scale-105"
loading="lazy"
isGallery={true}
/>
{/* Overlay on hover */}
@@ -252,6 +252,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
}}
draggable={false}
useWatermark={true}
isGallery={true}
/>
</div>