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:
@@ -63,17 +63,26 @@ async function processNewPhoto(filePath) {
|
|||||||
// Calculate relative thumbnail path
|
// Calculate relative thumbnail path
|
||||||
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
|
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
|
||||||
|
|
||||||
// Add to database
|
// Check if photo already exists
|
||||||
await db('photos').insert({
|
const existingPhoto = await db('photos')
|
||||||
event_id: event.id,
|
.where({ event_id: event.id, filename: path.basename(filePath) })
|
||||||
filename: path.basename(filePath),
|
.first();
|
||||||
path: relativePath,
|
|
||||||
thumbnail_path: relativeThumbPath,
|
|
||||||
type: photoType,
|
|
||||||
size_bytes: stats.size
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
async function removePhoto(filePath) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface AuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageEleme
|
|||||||
src: string;
|
src: string;
|
||||||
fallbackSrc?: string;
|
fallbackSrc?: string;
|
||||||
useWatermark?: boolean;
|
useWatermark?: boolean;
|
||||||
|
isGallery?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
||||||
@@ -12,6 +13,7 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
fallbackSrc,
|
fallbackSrc,
|
||||||
alt,
|
alt,
|
||||||
useWatermark = false,
|
useWatermark = false,
|
||||||
|
isGallery = false,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [imageSrc, setImageSrc] = useState<string>('');
|
const [imageSrc, setImageSrc] = useState<string>('');
|
||||||
@@ -21,7 +23,20 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let objectUrl: string | null = null;
|
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) {
|
if (!src) {
|
||||||
setImageSrc(fallbackSrc || '');
|
setImageSrc(fallbackSrc || '');
|
||||||
@@ -89,7 +104,7 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [src, fallbackSrc, useWatermark]);
|
}, [src, fallbackSrc, useWatermark, isGallery]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
|||||||
alt={photo.filename}
|
alt={photo.filename}
|
||||||
className="w-full h-full object-cover rounded-lg transition-transform duration-200 group-hover:scale-105"
|
className="w-full h-full object-cover rounded-lg transition-transform duration-200 group-hover:scale-105"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
isGallery={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overlay on hover */}
|
{/* Overlay on hover */}
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
}}
|
}}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
useWatermark={true}
|
useWatermark={true}
|
||||||
|
isGallery={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user