diff --git a/src/app/admin/tags/[tag]/edit/page.tsx b/src/app/admin/tags/[tag]/edit/page.tsx index 2cfb7f5e..670c64bc 100644 --- a/src/app/admin/tags/[tag]/edit/page.tsx +++ b/src/app/admin/tags/[tag]/edit/page.tsx @@ -1,6 +1,6 @@ import AdminChildPage from '@/components/AdminChildPage'; import { redirect } from 'next/navigation'; -import { getPhotosCached, getPhotosTagCountCached } from '@/photo/cache'; +import { getPhotosCached } from '@/photo/cache'; import TagForm from '@/tag/TagForm'; import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/site/paths'; import PhotoTag from '@/tag/PhotoTag'; @@ -8,6 +8,7 @@ import { photoLabelForCount } from '@/photo'; import PhotoLightbox from '@/photo/PhotoLightbox'; import FavsTag from '@/tag/FavsTag'; import { isTagFavs } from '@/tag'; +import { getPhotosTagMeta } from '@/services/vercel-postgres'; const MAX_PHOTO_TO_SHOW = 6; @@ -21,10 +22,10 @@ export default async function PhotoPageEdit({ const tag = decodeURIComponent(tagFromParams); const [ - count, + { count }, photos, ] = await Promise.all([ - getPhotosTagCountCached(tag), + getPhotosTagMeta(tag), getPhotosCached({ tag, limit: MAX_PHOTO_TO_SHOW }), ]); diff --git a/src/app/tag/[tag]/[photoId]/layout.tsx b/src/app/tag/[tag]/[photoId]/layout.tsx index 1d3695f2..eb834147 100644 --- a/src/app/tag/[tag]/[photoId]/layout.tsx +++ b/src/app/tag/[tag]/[photoId]/layout.tsx @@ -58,8 +58,7 @@ export default async function PhotoTagPage({ const [ photos, - count, - dateRange, + { count, dateRange }, ] = await getPhotosTagDataCached({ tag }); return <> diff --git a/src/app/tag/[tag]/page.tsx b/src/app/tag/[tag]/page.tsx index bdb72d70..95a33ff4 100644 --- a/src/app/tag/[tag]/page.tsx +++ b/src/app/tag/[tag]/page.tsx @@ -19,8 +19,7 @@ export async function generateMetadata({ const [ photos, - count, - dateRange, + { count, dateRange }, ] = await getPhotosTagDataCached({ tag, limit: GRID_THUMBNAILS_TO_SHOW_MAX, diff --git a/src/photo/PhotoEditPageClient.tsx b/src/photo/PhotoEditPageClient.tsx index 0969d8c0..fb86a83b 100644 --- a/src/photo/PhotoEditPageClient.tsx +++ b/src/photo/PhotoEditPageClient.tsx @@ -10,14 +10,14 @@ import { useFormState } from 'react-dom'; import { areSimpleObjectsEqual } from '@/utility/object'; import IconGrSync from '@/site/IconGrSync'; import { getExifDataAction } from './actions'; -import { Tags } from '@/tag'; +import { TagsWithMeta } from '@/tag'; export default function PhotoEditPageClient({ photo, uniqueTags, }: { photo: Photo - uniqueTags?: Tags + uniqueTags?: TagsWithMeta }) { const seedExifData = { url: photo.url }; diff --git a/src/photo/PhotoGridSidebar.tsx b/src/photo/PhotoGridSidebar.tsx index fa2dddf7..93a40327 100644 --- a/src/photo/PhotoGridSidebar.tsx +++ b/src/photo/PhotoGridSidebar.tsx @@ -5,7 +5,7 @@ import PhotoTag from '@/tag/PhotoTag'; import { FaTag } from 'react-icons/fa'; import { IoMdCamera } from 'react-icons/io'; import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.'; -import { TAG_FAVS, Tags } from '@/tag'; +import { TAG_FAVS, TagsWithMeta } from '@/tag'; import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation'; @@ -18,7 +18,7 @@ export default function PhotoGridSidebar({ photosCount, photosDateRange, }: { - tags: Tags + tags: TagsWithMeta cameras: Cameras simulations: FilmSimulations photosCount: number diff --git a/src/photo/cache.ts b/src/photo/cache.ts index aa37e9f1..6476c8d7 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -11,10 +11,9 @@ import { getPhotosCount, getPhotosCameraCount, getPhotosCountIncludingHidden, - getPhotosTagCount, getUniqueCameras, getUniqueTags, - getPhotosTagDateRange, + getPhotosTagMeta, getPhotosCameraDateRange, getUniqueTagsHidden, getUniqueFilmSimulations, @@ -162,12 +161,6 @@ export const getPhotosCountIncludingHiddenCached = [KEY_PHOTOS, KEY_COUNT, KEY_HIDDEN], ); -export const getPhotosTagCountCached = - unstable_cache( - getPhotosTagCount, - [KEY_PHOTOS, KEY_TAGS], - ); - export const getPhotosCameraCountCached = ( ...args: Parameters ) => @@ -182,9 +175,9 @@ export const getPhotosFilmSimulationCountCached = [KEY_PHOTOS, KEY_FILM_SIMULATIONS, KEY_COUNT], ); -export const getPhotosTagDateRangeCached = +export const getPhotosTagMetaCached = unstable_cache( - getPhotosTagDateRange, + getPhotosTagMeta, [KEY_PHOTOS, KEY_TAGS, KEY_DATE_RANGE], ); diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index 3c33b47e..66765790 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -23,7 +23,7 @@ import { toastSuccess, toastWarning } from '@/toast'; import { getDimensionsFromSize } from '@/utility/size'; import ImageBlurFallback from '@/components/ImageBlurFallback'; import { BLUR_ENABLED } from '@/site/config'; -import { Tags, sortTagsObjectWithoutFavs } from '@/tag'; +import { TagsWithMeta, sortTagsObjectWithoutFavs } from '@/tag'; import { formatCount, formatCountDescriptive } from '@/utility/string'; const THUMBNAIL_SIZE = 300; @@ -38,7 +38,7 @@ export default function PhotoForm({ initialPhotoForm: Partial updatedExifData?: Partial type?: 'create' | 'edit' - uniqueTags?: Tags + uniqueTags?: TagsWithMeta debugBlur?: boolean }) { const [formData, setFormData] = diff --git a/src/services/vercel-postgres.ts b/src/services/vercel-postgres.ts index 9e27e656..5f0c7e24 100644 --- a/src/services/vercel-postgres.ts +++ b/src/services/vercel-postgres.ts @@ -9,7 +9,7 @@ import { } from '@/photo'; import { Camera, Cameras, createCameraKey } from '@/camera'; import { parameterize } from '@/utility/string'; -import { Tags } from '@/tag'; +import { TagsWithMeta } from '@/tag'; import { FilmSimulation, FilmSimulations } from '@/simulation'; import { PRIORITY_ORDER_ENABLED } from '@/site/config'; import { screenForPPR } from '@/utility/ppr'; @@ -165,12 +165,6 @@ const sqlGetPhotosCountIncludingHidden = async () => sql` SELECT COUNT(*) FROM photos `.then(({ rows }) => parseInt(rows[0].count, 10)); -const sqlGetPhotosTagCount = async (tag: string) => sql` - SELECT COUNT(*) FROM photos - WHERE ${tag}=ANY(tags) AND - hidden IS NOT TRUE -`.then(({ rows }) => parseInt(rows[0].count, 10)); - const sqlGetPhotosCameraCount = async (camera: Camera) => sql` SELECT COUNT(*) FROM photos WHERE @@ -195,14 +189,17 @@ const sqlGetPhotosDateRange = async () => sql` ? rows[0] as PhotoDateRange : undefined); -const sqlGetPhotosTagDateRange = async (tag: string) => sql` - SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end +const sqlGetPhotosTagMeta = async (tag: string) => sql` + SELECT COUNT(*), MIN(taken_at_naive) as start, MAX(taken_at_naive) as end FROM photos WHERE ${tag}=ANY(tags) AND hidden IS NOT TRUE -`.then(({ rows }) => rows[0]?.start && rows[0]?.end - ? rows[0] as PhotoDateRange - : undefined); +`.then(({ rows }) => ({ + count: parseInt(rows[0].count, 10), + ...rows[0]?.start && rows[0]?.end + ? { dateRange: rows[0] as PhotoDateRange } + : undefined, + })); const sqlGetPhotosCameraDateRange = async (camera: Camera) => sql` SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end @@ -232,7 +229,7 @@ const sqlGetUniqueTags = async () => sql` WHERE hidden IS NOT TRUE GROUP BY tag ORDER BY tag ASC -`.then(({ rows }): Tags => rows.map(({ tag, count }) => ({ +`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({ tag: tag as string, count: parseInt(count, 10), }))); @@ -242,7 +239,7 @@ const sqlGetUniqueTagsHidden = async () => sql` FROM photos GROUP BY tag ORDER BY tag ASC -`.then(({ rows }): Tags => rows.map(({ tag, count }) => ({ +`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({ tag: tag as string, count: parseInt(count, 10), }))); @@ -461,15 +458,10 @@ export const getUniqueTags = () => safelyQueryPhotos(sqlGetUniqueTags, 'getUniqueTags'); export const getUniqueTagsHidden = () => safelyQueryPhotos(sqlGetUniqueTagsHidden, 'getUniqueTagsHidden'); -export const getPhotosTagDateRange = (tag: string) => +export const getPhotosTagMeta = (tag: string) => safelyQueryPhotos( - () => sqlGetPhotosTagDateRange(tag), - 'getPhotosTagDateRange', - ); -export const getPhotosTagCount = (tag: string) => - safelyQueryPhotos( - () => sqlGetPhotosTagCount(tag), - 'getPhotosTagCount', + () => sqlGetPhotosTagMeta(tag), + 'getPhotosTagMeta', ); // CAMERAS diff --git a/src/tag/data.ts b/src/tag/data.ts index c5ddf555..49b803fc 100644 --- a/src/tag/data.ts +++ b/src/tag/data.ts @@ -1,7 +1,6 @@ import { getPhotosCached, - getPhotosTagCountCached, - getPhotosTagDateRangeCached, + getPhotosTagMetaCached, } from '@/photo/cache'; import { PaginationSearchParams, @@ -18,8 +17,7 @@ export const getPhotosTagDataCached = ({ }) => Promise.all([ getPhotosCached({ tag, limit }), - getPhotosTagCountCached(tag), - getPhotosTagDateRangeCached(tag), + getPhotosTagMetaCached(tag), ]); export const getPhotosTagDataCachedWithPagination = async ({ @@ -33,7 +31,7 @@ export const getPhotosTagDataCachedWithPagination = async ({ }) => { const { offset, limit } = getPaginationFromSearchParams(searchParams); - const [photos, count, dateRange] = + const [photos, { count, dateRange }] = await getPhotosTagDataCached({ tag, limit: limitProp ?? limit, diff --git a/src/tag/index.ts b/src/tag/index.ts index 60f9ed86..ea0b5d28 100644 --- a/src/tag/index.ts +++ b/src/tag/index.ts @@ -13,10 +13,13 @@ import { capitalizeWords, convertStringToArray } from '@/utility/string'; export const TAG_FAVS = 'favs'; -export type Tags = { +export type TagWithMeta = { tag: string count: number -}[] + dataRange?: PhotoDateRange +} + +export type TagsWithMeta = Omit[] export const formatTag = (tag?: string) => capitalizeWords(tag?.replaceAll('-', ' ')); @@ -41,7 +44,7 @@ export const sortTags = ( .sort((a, b) => isTagFavs(a) ? -1 : a.localeCompare(b)); export const sortTagsObject = ( - tags: Tags, + tags: TagsWithMeta, tagToHide?: string, ) => tags .filter(({ tag }) => tag!== tagToHide) @@ -50,7 +53,7 @@ export const sortTagsObject = ( export const sortTagsWithoutFavs = (tags: string[]) => sortTags(tags, TAG_FAVS); -export const sortTagsObjectWithoutFavs = (tags: Tags) => +export const sortTagsObjectWithoutFavs = (tags: TagsWithMeta) => sortTagsObject(tags, TAG_FAVS); export const descriptionForTaggedPhotos = (