diff --git a/src/app/p/[photoId]/layout.tsx b/src/app/p/[photoId]/layout.tsx index 4cd5f51b..fcf5a252 100644 --- a/src/app/p/[photoId]/layout.tsx +++ b/src/app/p/[photoId]/layout.tsx @@ -11,9 +11,13 @@ import { absolutePathForPhotoImage, } from '@/site/paths'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; -import { getPhotosNearIdCachedCached } from '@/photo/cache'; +import { getPhotosNearIdCached } from '@/photo/cache'; import { IS_PRODUCTION, STATICALLY_OPTIMIZED_PAGES } from '@/site/config'; import { GENERATE_STATIC_PARAMS_LIMIT, getPhotoIds } from '@/photo/db'; +import { cache } from 'react'; + +const getPhotosNearIdCachedCached = cache((photoId: string, limit: number) => + getPhotosNearIdCached(photoId, { limit })); export let generateStaticParams: (() => Promise<{ photoId: string }[]>) | undefined = undefined; diff --git a/src/photo/actions.ts b/src/photo/actions.ts index e5e22502..08064ce0 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -21,7 +21,7 @@ import { deleteStorageUrl, } from '@/services/storage'; import { - getPhotosCachedCached, + getPhotosCached, getPhotosTagHiddenMetaCached, revalidateAdminPaths, revalidateAllKeysAndPaths, @@ -219,8 +219,8 @@ export const getPhotosAction = async (options: GetPhotosOptions) => export const getPhotosCachedAction = async (options: GetPhotosOptions) => (options.hidden === 'include' || options.hidden === 'only') ? safelyRunAdminServerAction(() => - getPhotosCachedCached(options)) - : getPhotosCachedCached(options); + getPhotosCached (options)) + : getPhotosCached(options); // Public actions diff --git a/src/photo/cache.ts b/src/photo/cache.ts index e347abad..20ec6552 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -35,7 +35,6 @@ import { PREFIX_TAG, pathForPhoto, } from '@/site/paths'; -import { cache } from 'react'; // Table key const KEY_PHOTOS = 'photos'; @@ -137,9 +136,8 @@ export const getPhotosCached = ( getPhotos, [KEY_PHOTOS, ...getPhotosCacheKeys(...args)], )(...args).then(parseCachedPhotosDates); -export const getPhotosCachedCached = cache(getPhotosCached); -const getPhotosNearIdCached = ( +export const getPhotosNearIdCached = ( ...args: Parameters ) => unstable_cache( getPhotosNearId, @@ -148,7 +146,6 @@ const getPhotosNearIdCached = ( photos: parseCachedPhotosDates(photos), photo: photo ? parseCachedPhotoDates(photo) : undefined, })); -export const getPhotosNearIdCachedCached = cache(getPhotosNearIdCached); export const getPhotosDateRangeCached = unstable_cache( diff --git a/src/photo/db.ts b/src/photo/db.ts index bccc0ae7..e664ddf2 100644 --- a/src/photo/db.ts +++ b/src/photo/db.ts @@ -358,26 +358,20 @@ const safelyQueryPhotos = async ( return result; }; -export const getPhotos = async (options: GetPhotosOptions = {}) => { +const getWheresFromOptions = (options: GetPhotosOptions) => { const { - sortBy = PRIORITY_ORDER_ENABLED ? 'priority' : 'takenAt', - limit = PHOTO_DEFAULT_LIMIT, - offset = 0, - query: queryOption, + hidden = 'exclude', + takenBefore, + takenAfterInclusive, + query, tag, camera, simulation, - takenBefore, - takenAfterInclusive, - hidden = 'exclude', } = options; - let sql = ['SELECT * FROM photos']; - let values = [] as (string | number)[]; - let valueIndex = 1; - - // WHERE - let wheres = [] as string[]; + const wheres = [] as string[]; + const values = [] as (string | number)[]; + let valuesIndex = 1; switch (hidden) { case 'exclude': @@ -388,35 +382,56 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { break; } if (takenBefore) { - wheres.push(`taken_at > $${valueIndex++}`); + wheres.push(`taken_at > $${valuesIndex++}`); values.push(takenBefore.toISOString()); } if (takenAfterInclusive) { - wheres.push(`taken_at <= $${valueIndex++}`); + wheres.push(`taken_at <= $${valuesIndex++}`); values.push(takenAfterInclusive.toISOString()); } - if (queryOption) { + if (query) { // eslint-disable-next-line max-len - wheres.push(`CONCAT(title, ' ', caption, ' ', semantic_description) ILIKE $${valueIndex++}`); - values.push(`%${queryOption.toLocaleLowerCase()}%`); + wheres.push(`CONCAT(title, ' ', caption, ' ', semantic_description) ILIKE $${valuesIndex++}`); + values.push(`%${query.toLocaleLowerCase()}%`); } if (tag) { - wheres.push(`$${valueIndex++}=ANY(tags)`); + wheres.push(`$${valuesIndex++}=ANY(tags)`); values.push(tag); } if (camera) { - wheres.push(`LOWER(REPLACE(make, ' ', '-'))=$${valueIndex++}`); - wheres.push(`LOWER(REPLACE(model, ' ', '-'))=$${valueIndex++}`); + wheres.push(`LOWER(REPLACE(make, ' ', '-'))=$${valuesIndex++}`); + wheres.push(`LOWER(REPLACE(model, ' ', '-'))=$${valuesIndex++}`); values.push(parameterize(camera.make, true)); values.push(parameterize(camera.model, true)); } if (simulation) { - wheres.push(`film_simulation=$${valueIndex++}`); + wheres.push(`film_simulation=$${valuesIndex++}`); values.push(simulation); } - if (wheres.length > 0) { - sql.push(`WHERE ${wheres.join(' AND ')}`); - } + + return { + wheres: wheres.length > 0 + ? `WHERE ${wheres.join(' AND ')}` + : '', + values, + lastValuesIndex: valuesIndex, + }; +}; + +export const getPhotos = async (options: GetPhotosOptions = {}) => { + const { + sortBy = PRIORITY_ORDER_ENABLED ? 'priority' : 'takenAt', + limit = PHOTO_DEFAULT_LIMIT, + offset = 0, + } = options; + + let sql = ['SELECT * FROM photos']; + + const { wheres, values, lastValuesIndex } = getWheresFromOptions(options); + + let valuesIndex = lastValuesIndex; + + if (wheres) { sql.push(wheres); } // ORDER BY switch (sortBy) { @@ -432,7 +447,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { } // LIMIT + OFFSET - sql.push(`LIMIT $${valueIndex++} OFFSET $${valueIndex++}`); + sql.push(`LIMIT $${valuesIndex++} OFFSET $${valuesIndex++}`); values.push(limit, offset); return safelyQueryPhotos(async () => { @@ -442,40 +457,43 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { }; export const getPhotosNearId = async ( - id: string, - limit: number, - onlyHidden?: boolean, -) => { + photoId: string, + options: GetPhotosOptions, +) => safelyQueryPhotos(async () => { + const { limit } = options; + const orderBy = PRIORITY_ORDER_ENABLED ? 'ORDER BY priority_order ASC, taken_at DESC' : 'ORDER BY taken_at DESC'; - return safelyQueryPhotos(async () => { - return query( - ` - WITH twi AS ( - SELECT *, row_number() - OVER (${orderBy}) as row_number - FROM photos - WHERE hidden is ${onlyHidden ? 'TRUE' : 'NOT TRUE'} - ), - current AS (SELECT row_number FROM twi WHERE id = $1) - SELECT twi.* - FROM twi, current - WHERE twi.row_number >= current.row_number - 1 - LIMIT $2 - `, - [id, limit] - ); - }, `getPhotosNearId: ${id}`) - .then(({ rows }) => { - const photos = rows.map(parsePhotoFromDb); - return { - photos, - photo: photos.find(photo => photo.id === id), - }; - }); -}; + const { wheres, values, lastValuesIndex } = getWheresFromOptions(options); + + let valuesIndex = lastValuesIndex; + + return query( + ` + WITH twi AS ( + SELECT *, row_number() + OVER (${orderBy}) as row_number + FROM photos + ${wheres} + ), + current AS (SELECT row_number FROM twi WHERE id = $${valuesIndex++}) + SELECT twi.* + FROM twi, current + WHERE twi.row_number >= current.row_number - 1 + LIMIT $${valuesIndex++} + `, + [...values, photoId, limit] + ); +}, `getPhotosNearId: ${photoId}`) + .then(({ rows }) => { + const photos = rows.map(parsePhotoFromDb); + return { + photos, + photo: photos.find(photo => photo.id === photoId), + }; + }); export const getPhotoIds = async ({ limit }: { limit?: number }) => { return safelyQueryPhotos(() => limit diff --git a/src/tag/data.ts b/src/tag/data.ts index c760d128..e915a6e4 100644 --- a/src/tag/data.ts +++ b/src/tag/data.ts @@ -1,5 +1,5 @@ import { - getPhotosCachedCached, + getPhotosCached, getPhotosTagMetaCached, } from '@/photo/cache'; @@ -11,7 +11,7 @@ export const getPhotosTagDataCached = ({ limit?: number, }) => Promise.all([ - getPhotosCachedCached({ tag, limit }), + getPhotosCached({ tag, limit }), getPhotosTagMetaCached(tag), ]);