fix: enforce gallery access and consolidate gallery workflows (#1357)
Harden gallery authentication and authorization, consolidate gallery workflows, and prevent token-bearing URLs from leaking through nginx request error logs.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { usePhotoSelection } from '../usePhotoSelection';
|
||||
describe('photo selection through changing query results', () => {
|
||||
it('handles empty → populated → reordered → removed → empty without changing hook order', () => {
|
||||
const { result, rerender } = renderHook(({ photos }) => usePhotoSelection(photos, 1), { initialProps: { photos: [] as { id: number }[] } });
|
||||
expect(result.current.currentPhoto).toBeUndefined();
|
||||
rerender({ photos: [{ id: 1 }, { id: 2 }, { id: 3 }] }); expect(result.current.currentPhoto?.id).toBe(2);
|
||||
rerender({ photos: [{ id: 3 }, { id: 2 }, { id: 1 }] }); expect(result.current.currentPhoto?.id).toBe(2);
|
||||
act(() => result.current.setCurrentIndex(2)); expect(result.current.currentPhoto?.id).toBe(1);
|
||||
rerender({ photos: [{ id: 3 }, { id: 2 }] }); expect(result.current.currentPhoto?.id).toBe(2);
|
||||
rerender({ photos: [] }); expect(result.current.currentPhoto).toBeUndefined();
|
||||
rerender({ photos: [{ id: 2 }, { id: 4 }] }); expect(result.current.currentPhoto?.id).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,7 @@ export const useGalleryPhotos = (
|
||||
return useQuery({
|
||||
queryKey: ['gallery-photos', slug, filter, guestId],
|
||||
// Pass guestId so backend can filter per-guest views when needed
|
||||
queryFn: () => galleryService.getGalleryPhotos(slug, filter, guestId),
|
||||
queryFn: ({ signal }) => galleryService.getGalleryPhotos(slug, filter, guestId, signal),
|
||||
enabled,
|
||||
retry: 1,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useCallback, useEffect, useState, type SetStateAction } from 'react';
|
||||
|
||||
/** Keep the selected photo through reordering/refetches; clamp after removal. */
|
||||
export function usePhotoSelection<T extends { id: number }>(photos: T[], initialIndex = 0) {
|
||||
const [selection, setSelection] = useState(() => ({ id: photos[initialIndex]?.id, index: initialIndex }));
|
||||
const found = photos.findIndex(photo => photo.id === selection.id);
|
||||
const currentIndex = found >= 0 ? found : Math.max(0, Math.min(selection.index, photos.length - 1));
|
||||
const currentPhoto = photos[currentIndex];
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPhoto && (currentPhoto.id !== selection.id || currentIndex !== selection.index)) {
|
||||
setSelection({ id: currentPhoto.id, index: currentIndex });
|
||||
}
|
||||
}, [currentPhoto, currentIndex, selection.id, selection.index]);
|
||||
|
||||
const setCurrentIndex = useCallback((next: SetStateAction<number>) => {
|
||||
setSelection(previous => {
|
||||
const previousIndex = photos.findIndex(photo => photo.id === previous.id);
|
||||
const index = previousIndex >= 0 ? previousIndex : Math.max(0, Math.min(previous.index, photos.length - 1));
|
||||
const requested = typeof next === 'function' ? next(index) : next;
|
||||
const clamped = Math.max(0, Math.min(requested, photos.length - 1));
|
||||
return { id: photos[clamped]?.id, index: clamped };
|
||||
});
|
||||
}, [photos]);
|
||||
return { currentPhoto, currentIndex, setCurrentIndex };
|
||||
}
|
||||
Reference in New Issue
Block a user