Merge pull request #97 from sambecker/meta-queries
Streamline photo meta queries
This commit is contained in:
commit
3b06fd3a16
@ -1,6 +1,6 @@
|
||||
import { getStorageUploadUrlsNoStore } from '@/services/storage/cache';
|
||||
import {
|
||||
getPhotosCountIncludingHiddenCached,
|
||||
getPhotosMetaCached,
|
||||
getPhotosMostRecentUpdateCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/photo/cache';
|
||||
@ -18,7 +18,9 @@ export default async function AdminNav() {
|
||||
countTags,
|
||||
mostRecentPhotoUpdateTime,
|
||||
] = await Promise.all([
|
||||
getPhotosCountIncludingHiddenCached().catch(() => 0),
|
||||
getPhotosMetaCached({ hidden: 'include' })
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
getStorageUploadUrlsNoStore()
|
||||
.then(urls => urls.length)
|
||||
.catch(e => {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import PhotoUpload from '@/photo/PhotoUpload';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { getPhotosCountIncludingHiddenCached } from '@/photo/cache';
|
||||
import AdminUploadsTable from '@/admin/AdminUploadsTable';
|
||||
import { PRO_MODE_ENABLED } from '@/site/config';
|
||||
import { getStoragePhotoUrlsNoStore } from '@/services/storage/cache';
|
||||
import { getPhotos } from '@/photo/db';
|
||||
import { getPhotos } from '@/photo/db/query';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import AdminPhotosTable from '@/admin/AdminPhotosTable';
|
||||
import AdminPhotosTableInfinite from
|
||||
'@/admin/AdminPhotosTableInfinite';
|
||||
import { getPhotosMetaCached } from '@/photo/cache';
|
||||
|
||||
const DEBUG_PHOTO_BLOBS = false;
|
||||
|
||||
@ -27,7 +27,9 @@ export default async function AdminPhotosPage() {
|
||||
sortBy: 'createdAt',
|
||||
limit: INFINITE_SCROLL_INITIAL_ADMIN_PHOTOS,
|
||||
}).catch(() => []),
|
||||
getPhotosCountIncludingHiddenCached().catch(() => 0),
|
||||
getPhotosMetaCached({ hidden: 'include'})
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
DEBUG_PHOTO_BLOBS
|
||||
? getStoragePhotoUrlsNoStore()
|
||||
: [],
|
||||
|
||||
@ -4,7 +4,7 @@ import { getPhotosCached } from '@/photo/cache';
|
||||
import TagForm from '@/tag/TagForm';
|
||||
import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/site/paths';
|
||||
import PhotoLightbox from '@/photo/PhotoLightbox';
|
||||
import { getPhotosTagMeta } from '@/photo/db';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
import AdminTagBadge from '@/admin/AdminTagBadge';
|
||||
|
||||
const MAX_PHOTO_TO_SHOW = 6;
|
||||
@ -22,7 +22,7 @@ export default async function PhotoPageEdit({
|
||||
{ count },
|
||||
photos,
|
||||
] = await Promise.all([
|
||||
getPhotosTagMeta(tag),
|
||||
getPhotosMeta({ tag }),
|
||||
getPhotosCached({ tag, limit: MAX_PHOTO_TO_SHOW }),
|
||||
]);
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { ReactNode, cache } from 'react';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import {
|
||||
getPhotosFilmSimulationMetaCached,
|
||||
getPhotosMetaCached,
|
||||
getPhotosNearIdCached,
|
||||
} from '@/photo/cache';
|
||||
|
||||
@ -70,8 +70,7 @@ export default async function PhotoFilmSimulationPage({
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const { count, dateRange } =
|
||||
await getPhotosFilmSimulationMetaCached(simulation);
|
||||
const { count, dateRange } = await getPhotosMetaCached({ simulation });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
||||
@ -6,7 +6,7 @@ import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||
import { Metadata } from 'next/types';
|
||||
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
||||
import { getPhotoSidebarData } from '@/photo/data';
|
||||
import { getPhotos } from '@/photo/db';
|
||||
import { getPhotos } from '@/photo/db/query';
|
||||
import { cache } from 'react';
|
||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
||||
import { PATH_GRID } from '@/site/paths';
|
||||
|
||||
@ -2,7 +2,8 @@ import {
|
||||
INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
||||
INFINITE_SCROLL_GRID_PHOTO_MULTIPLE,
|
||||
} from '@/photo';
|
||||
import { getPhotosCached, getPhotosCountCached } from '@/photo/cache';
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
import StaggeredOgPhotos from '@/photo/StaggeredOgPhotos';
|
||||
import StaggeredOgPhotosInfinite from '@/photo/StaggeredOgPhotosInfinite';
|
||||
|
||||
@ -12,7 +13,9 @@ export default async function GridPage() {
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL }),
|
||||
getPhotosCountCached(),
|
||||
getPhotosMeta()
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -5,7 +5,8 @@ import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
|
||||
import { IS_PRODUCTION, STATICALLY_OPTIMIZED_OG_IMAGES } from '@/site/config';
|
||||
import { GENERATE_STATIC_PARAMS_LIMIT, getPhotoIds } from '@/photo/db';
|
||||
import { getPhotoIds } from '@/photo/db/query';
|
||||
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
|
||||
import { isNextImageReadyBasedOnPhotos } from '@/photo';
|
||||
|
||||
export let generateStaticParams:
|
||||
|
||||
@ -13,7 +13,8 @@ import {
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { IS_PRODUCTION, STATICALLY_OPTIMIZED_PAGES } from '@/site/config';
|
||||
import { GENERATE_STATIC_PARAMS_LIMIT, getPhotoIds } from '@/photo/db';
|
||||
import { getPhotoIds } from '@/photo/db/query';
|
||||
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
|
||||
import { ReactNode, cache } from 'react';
|
||||
|
||||
const getPhotosNearIdCachedCached = cache((photoId: string) =>
|
||||
|
||||
@ -8,7 +8,7 @@ import { Metadata } from 'next/types';
|
||||
import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response';
|
||||
import PhotosLarge from '@/photo/PhotosLarge';
|
||||
import { cache } from 'react';
|
||||
import { getPhotos, getPhotosCount } from '@/photo/db';
|
||||
import { getPhotos, getPhotosMeta } from '@/photo/db/query';
|
||||
import PhotosLargeInfinite from '@/photo/PhotosLargeInfinite';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
@ -32,7 +32,8 @@ export default async function HomePage() {
|
||||
limit: INFINITE_SCROLL_LARGE_PHOTO_INITIAL,
|
||||
})
|
||||
.catch(() => []),
|
||||
getPhotosCount()
|
||||
getPhotosMeta()
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
]);
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosCameraMetaCached,
|
||||
getPhotosMetaCached,
|
||||
getPhotosNearIdCached,
|
||||
} from '@/photo/cache';
|
||||
import {
|
||||
@ -79,7 +79,7 @@ export default async function PhotoCameraPage({
|
||||
|
||||
const camera = cameraFromPhoto(photo, { make, model });
|
||||
|
||||
const { count, dateRange } = await getPhotosCameraMetaCached(camera);
|
||||
const { count, dateRange } = await getPhotosMetaCached({ camera });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
||||
@ -11,11 +11,9 @@ import {
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosNearIdCached,
|
||||
getPhotosTagMetaCached,
|
||||
} from '@/photo/cache';
|
||||
import { getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { ReactNode, cache } from 'react';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
|
||||
const getPhotosNearIdCachedCached = cache((photoId: string, tag: string) =>
|
||||
getPhotosNearIdCached(
|
||||
@ -66,7 +64,7 @@ export default async function PhotoTagPage({
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const { count, dateRange } = await getPhotosTagMetaCached(tag);
|
||||
const { count, dateRange } = await getPhotosMeta({ tag });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
||||
@ -6,8 +6,8 @@ import {
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosNearIdCached,
|
||||
getPhotosTagHiddenMetaCached,
|
||||
} from '@/photo/cache';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
import { PATH_ROOT, absolutePathForPhoto } from '@/site/paths';
|
||||
import { TAG_HIDDEN } from '@/tag';
|
||||
import { Metadata } from 'next';
|
||||
@ -59,7 +59,7 @@ export default async function PhotoTagHiddenPage({
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const { count, dateRange } = await getPhotosTagHiddenMetaCached();
|
||||
const { count, dateRange } = await getPhotosMeta({ hidden: 'only' });
|
||||
|
||||
return (
|
||||
<PhotoDetailPage {...{
|
||||
|
||||
@ -3,17 +3,18 @@ import Banner from '@/components/Banner';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import { getPhotosNoStore } from '@/photo/cache';
|
||||
import { getPhotosTagHiddenMeta } from '@/photo/db';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
import { absolutePathForTag } from '@/site/paths';
|
||||
import { TAG_HIDDEN, descriptionForTaggedPhotos, titleForTag } from '@/tag';
|
||||
import HiddenHeader from '@/tag/HiddenHeader';
|
||||
import { Metadata } from 'next';
|
||||
import { cache } from 'react';
|
||||
|
||||
const getPhotosTagHiddenMetaCached = cache(getPhotosTagHiddenMeta);
|
||||
const getPhotosHiddenMetaCached = cache(() =>
|
||||
getPhotosMeta({ hidden: 'only' }));
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const { count, dateRange } = await getPhotosTagHiddenMetaCached();
|
||||
const { count, dateRange } = await getPhotosHiddenMetaCached();
|
||||
|
||||
if (count === 0) { return {}; }
|
||||
|
||||
@ -47,7 +48,7 @@ export default async function HiddenTagPage() {
|
||||
{ count, dateRange },
|
||||
] = await Promise.all([
|
||||
getPhotosNoStore({ hidden: 'only' }),
|
||||
getPhotosTagHiddenMetaCached(),
|
||||
getPhotosHiddenMetaCached(),
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { cameraFromPhoto, getCameraFromParams } from '.';
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosCameraMetaCached,
|
||||
getPhotosMetaCached,
|
||||
} from '@/photo/cache';
|
||||
|
||||
export const getPhotosCameraDataCached = async (
|
||||
@ -12,7 +12,7 @@ export const getPhotosCameraDataCached = async (
|
||||
const camera = getCameraFromParams({ make, model });
|
||||
return Promise.all([
|
||||
getPhotosCached({ camera, limit }),
|
||||
getPhotosCameraMetaCached(camera),
|
||||
getPhotosMetaCached({ camera }),
|
||||
])
|
||||
.then(([photos, meta]) => [
|
||||
photos,
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
'use server';
|
||||
|
||||
import {
|
||||
GetPhotosOptions,
|
||||
sqlDeletePhoto,
|
||||
sqlInsertPhoto,
|
||||
sqlDeletePhotoTagGlobally,
|
||||
sqlUpdatePhoto,
|
||||
sqlRenamePhotoTagGlobally,
|
||||
deletePhoto,
|
||||
insertPhoto,
|
||||
deletePhotoTagGlobally,
|
||||
updatePhoto,
|
||||
renamePhotoTagGlobally,
|
||||
getPhoto,
|
||||
getPhotos,
|
||||
} from '@/photo/db';
|
||||
} from '@/photo/db/query';
|
||||
import { GetPhotosOptions } from './db';
|
||||
import {
|
||||
PhotoFormData,
|
||||
convertFormDataToPhotoDbInsert,
|
||||
@ -22,7 +22,7 @@ import {
|
||||
} from '@/services/storage';
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosTagHiddenMetaCached,
|
||||
getPhotosMetaCached,
|
||||
revalidateAdminPaths,
|
||||
revalidateAllKeysAndPaths,
|
||||
revalidatePhoto,
|
||||
@ -53,7 +53,7 @@ export const createPhotoAction = async (formData: FormData) =>
|
||||
|
||||
if (updatedUrl) {
|
||||
photo.url = updatedUrl;
|
||||
await sqlInsertPhoto(photo);
|
||||
await insertPhoto(photo);
|
||||
revalidateAllKeysAndPaths();
|
||||
redirect(PATH_ADMIN_PHOTOS);
|
||||
}
|
||||
@ -71,7 +71,7 @@ export const updatePhotoAction = async (formData: FormData) =>
|
||||
if (url) { photo.url = url; }
|
||||
}
|
||||
|
||||
await sqlUpdatePhoto(photo);
|
||||
await updatePhoto(photo);
|
||||
|
||||
revalidatePhoto(photo.id);
|
||||
|
||||
@ -89,7 +89,7 @@ export const toggleFavoritePhotoAction = async (
|
||||
photo.tags = tags.some(tag => tag === TAG_FAVS)
|
||||
? tags.filter(tag => !isTagFavs(tag))
|
||||
: [...tags, TAG_FAVS];
|
||||
await sqlUpdatePhoto(convertPhotoToPhotoDbInsert(photo));
|
||||
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
|
||||
revalidateAllKeysAndPaths();
|
||||
if (shouldRedirect) {
|
||||
redirect(pathForPhoto(photoId));
|
||||
@ -103,7 +103,7 @@ export const deletePhotoAction = async (
|
||||
shouldRedirect?: boolean,
|
||||
) =>
|
||||
runAuthenticatedAdminServerAction(async () => {
|
||||
await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl));
|
||||
await deletePhoto(photoId).then(() => deleteStorageUrl(photoUrl));
|
||||
revalidateAllKeysAndPaths();
|
||||
if (shouldRedirect) {
|
||||
redirect(PATH_ROOT);
|
||||
@ -122,7 +122,7 @@ export const deletePhotoTagGloballyAction = async (formData: FormData) =>
|
||||
runAuthenticatedAdminServerAction(async () => {
|
||||
const tag = formData.get('tag') as string;
|
||||
|
||||
await sqlDeletePhotoTagGlobally(tag);
|
||||
await deletePhotoTagGlobally(tag);
|
||||
|
||||
revalidatePhotosKey();
|
||||
revalidateAdminPaths();
|
||||
@ -134,7 +134,7 @@ export const renamePhotoTagGloballyAction = async (formData: FormData) =>
|
||||
const updatedTag = formData.get('updatedTag') as string;
|
||||
|
||||
if (tag && updatedTag && tag !== updatedTag) {
|
||||
await sqlRenamePhotoTagGlobally(tag, updatedTag);
|
||||
await renamePhotoTagGlobally(tag, updatedTag);
|
||||
revalidatePhotosKey();
|
||||
revalidateTagsKey();
|
||||
redirect(PATH_ADMIN_TAGS);
|
||||
@ -185,7 +185,7 @@ export const syncPhotoExifDataAction = async (formData: FormData) =>
|
||||
...convertPhotoToFormData(photo),
|
||||
...photoFormExif,
|
||||
});
|
||||
await sqlUpdatePhoto(photoFormDbInsert);
|
||||
await updatePhoto(photoFormDbInsert);
|
||||
revalidatePhotosKey();
|
||||
}
|
||||
}
|
||||
@ -205,21 +205,20 @@ export const streamAiImageQueryAction = async (
|
||||
export const getImageBlurAction = async (url: string) =>
|
||||
runAuthenticatedAdminServerAction(() => blurImageFromUrl(url));
|
||||
|
||||
export const getPhotosTagHiddenMetaCachedAction = async () =>
|
||||
runAuthenticatedAdminServerAction(getPhotosTagHiddenMetaCached);
|
||||
export const getPhotosHiddenMetaCachedAction = async () =>
|
||||
runAuthenticatedAdminServerAction(() =>
|
||||
getPhotosMetaCached({ hidden: 'only' }));
|
||||
|
||||
// Public/Private actions
|
||||
|
||||
export const getPhotosAction = async (options: GetPhotosOptions) =>
|
||||
(options.hidden === 'include' || options.hidden === 'only')
|
||||
? runAuthenticatedAdminServerAction(() =>
|
||||
getPhotos(options))
|
||||
? runAuthenticatedAdminServerAction(() => getPhotos(options))
|
||||
: getPhotos(options);
|
||||
|
||||
export const getPhotosCachedAction = async (options: GetPhotosOptions) =>
|
||||
(options.hidden === 'include' || options.hidden === 'only')
|
||||
? runAuthenticatedAdminServerAction(() =>
|
||||
getPhotosCached (options))
|
||||
? runAuthenticatedAdminServerAction(() => getPhotosCached (options))
|
||||
: getPhotosCached(options);
|
||||
|
||||
// Public actions
|
||||
|
||||
@ -5,23 +5,17 @@ import {
|
||||
unstable_noStore,
|
||||
} from 'next/cache';
|
||||
import {
|
||||
GetPhotosOptions,
|
||||
getPhoto,
|
||||
getPhotos,
|
||||
getPhotosCount,
|
||||
getPhotosCountIncludingHidden,
|
||||
getUniqueCameras,
|
||||
getUniqueTags,
|
||||
getPhotosTagMeta,
|
||||
getPhotosCameraMeta,
|
||||
getUniqueTagsHidden,
|
||||
getUniqueFilmSimulations,
|
||||
getPhotosFilmSimulationMeta,
|
||||
getPhotosDateRange,
|
||||
getPhotosNearId,
|
||||
getPhotosMostRecentUpdate,
|
||||
getPhotosTagHiddenMeta,
|
||||
} from '@/photo/db';
|
||||
getPhotosMeta,
|
||||
} from '@/photo/db/query';
|
||||
import { GetPhotosOptions } from './db';
|
||||
import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo';
|
||||
import { createCameraKey } from '@/camera';
|
||||
import {
|
||||
@ -159,23 +153,12 @@ export const getPhotosNearIdCached = (
|
||||
};
|
||||
});
|
||||
|
||||
export const getPhotosDateRangeCached =
|
||||
unstable_cache(
|
||||
getPhotosDateRange,
|
||||
[KEY_PHOTOS, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotosCountCached =
|
||||
unstable_cache(
|
||||
getPhotosCount,
|
||||
[KEY_PHOTOS, KEY_COUNT],
|
||||
);
|
||||
|
||||
export const getPhotosCountIncludingHiddenCached =
|
||||
unstable_cache(
|
||||
getPhotosCountIncludingHidden,
|
||||
[KEY_PHOTOS, KEY_COUNT, KEY_HIDDEN],
|
||||
);
|
||||
export const getPhotosMetaCached = (
|
||||
...args: Parameters<typeof getPhotosMeta>
|
||||
) => unstable_cache(
|
||||
getPhotosMeta,
|
||||
[KEY_PHOTOS, KEY_COUNT, KEY_DATE_RANGE, ...getPhotosCacheKeys(...args)],
|
||||
)(...args);
|
||||
|
||||
export const getPhotosMostRecentUpdateCached =
|
||||
unstable_cache(
|
||||
@ -183,30 +166,6 @@ export const getPhotosMostRecentUpdateCached =
|
||||
[KEY_PHOTOS, KEY_COUNT, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotosTagMetaCached =
|
||||
unstable_cache(
|
||||
getPhotosTagMeta,
|
||||
[KEY_PHOTOS, KEY_TAGS, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotosTagHiddenMetaCached =
|
||||
unstable_cache(
|
||||
getPhotosTagHiddenMeta,
|
||||
[KEY_PHOTOS, KEY_TAGS, KEY_HIDDEN, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotosCameraMetaCached =
|
||||
unstable_cache(
|
||||
getPhotosCameraMeta,
|
||||
[KEY_PHOTOS, KEY_CAMERAS, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotosFilmSimulationMetaCached =
|
||||
unstable_cache(
|
||||
getPhotosFilmSimulationMeta,
|
||||
[KEY_PHOTOS, KEY_FILM_SIMULATIONS, KEY_DATE_RANGE],
|
||||
);
|
||||
|
||||
export const getPhotoCached = (...args: Parameters<typeof getPhoto>) =>
|
||||
unstable_cache(
|
||||
getPhoto,
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import {
|
||||
getPhotosCountCached,
|
||||
getPhotosMetaCached,
|
||||
getUniqueCamerasCached,
|
||||
getUniqueFilmSimulationsCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/photo/cache';
|
||||
import {
|
||||
getPhotosCount,
|
||||
getPhotosMeta,
|
||||
getUniqueCameras,
|
||||
getUniqueFilmSimulations,
|
||||
getUniqueTags,
|
||||
} from '@/photo/db';
|
||||
} from '@/photo/db/query';
|
||||
import { SHOW_FILM_SIMULATIONS } from '@/site/config';
|
||||
import { sortTagsObject } from '@/tag';
|
||||
|
||||
export const getPhotoSidebarData = () => [
|
||||
getPhotosCount().catch(() => 0),
|
||||
getPhotosMeta()
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
getUniqueTags().then(sortTagsObject).catch(() => []),
|
||||
getUniqueCameras().catch(() => []),
|
||||
SHOW_FILM_SIMULATIONS
|
||||
@ -23,7 +25,9 @@ export const getPhotoSidebarData = () => [
|
||||
] as const;
|
||||
|
||||
export const getPhotoSidebarDataCached = () => [
|
||||
getPhotosCountCached(),
|
||||
getPhotosMetaCached()
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
getUniqueTagsCached().then(sortTagsObject),
|
||||
getUniqueCamerasCached(),
|
||||
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
||||
|
||||
560
src/photo/db.ts
560
src/photo/db.ts
@ -1,560 +0,0 @@
|
||||
import {
|
||||
sql,
|
||||
query,
|
||||
convertArrayToPostgresString,
|
||||
} from '@/services/postgres';
|
||||
import {
|
||||
PhotoDb,
|
||||
PhotoDbInsert,
|
||||
translatePhotoId,
|
||||
parsePhotoFromDb,
|
||||
Photo,
|
||||
PhotoDateRange,
|
||||
} from '@/photo';
|
||||
import { Camera, Cameras, createCameraKey } from '@/camera';
|
||||
import { parameterize } from '@/utility/string';
|
||||
import { TagsWithMeta } from '@/tag';
|
||||
import { FilmSimulation, FilmSimulations } from '@/simulation';
|
||||
import { SHOULD_DEBUG_SQL, PRIORITY_ORDER_ENABLED } from '@/site/config';
|
||||
|
||||
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
||||
|
||||
const PHOTO_DEFAULT_LIMIT = 100;
|
||||
|
||||
const sqlCreatePhotosTable = () =>
|
||||
sql`
|
||||
CREATE TABLE IF NOT EXISTS photos (
|
||||
id VARCHAR(8) PRIMARY KEY,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
extension VARCHAR(255) NOT NULL,
|
||||
aspect_ratio REAL DEFAULT 1.5,
|
||||
blur_data TEXT,
|
||||
title VARCHAR(255),
|
||||
caption TEXT,
|
||||
semantic_description TEXT,
|
||||
tags VARCHAR(255)[],
|
||||
make VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
focal_length SMALLINT,
|
||||
focal_length_in_35mm_format SMALLINT,
|
||||
f_number REAL,
|
||||
iso SMALLINT,
|
||||
exposure_time DOUBLE PRECISION,
|
||||
exposure_compensation REAL,
|
||||
location_name VARCHAR(255),
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude DOUBLE PRECISION,
|
||||
film_simulation VARCHAR(255),
|
||||
priority_order REAL,
|
||||
taken_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
taken_at_naive VARCHAR(255) NOT NULL,
|
||||
hidden BOOLEAN,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
// Migration 01
|
||||
const MIGRATION_FIELDS_01 = ['caption', 'semantic_description'];
|
||||
const sqlRunMigration01 = () =>
|
||||
sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS caption TEXT,
|
||||
ADD COLUMN IF NOT EXISTS semantic_description TEXT
|
||||
`;
|
||||
|
||||
// Must provide id as 8-character nanoid
|
||||
export const sqlInsertPhoto = (photo: PhotoDbInsert) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
INSERT INTO photos (
|
||||
id,
|
||||
url,
|
||||
extension,
|
||||
aspect_ratio,
|
||||
blur_data,
|
||||
title,
|
||||
caption,
|
||||
semantic_description,
|
||||
tags,
|
||||
make,
|
||||
model,
|
||||
focal_length,
|
||||
focal_length_in_35mm_format,
|
||||
f_number,
|
||||
iso,
|
||||
exposure_time,
|
||||
exposure_compensation,
|
||||
location_name,
|
||||
latitude,
|
||||
longitude,
|
||||
film_simulation,
|
||||
priority_order,
|
||||
hidden,
|
||||
taken_at,
|
||||
taken_at_naive
|
||||
)
|
||||
VALUES (
|
||||
${photo.id},
|
||||
${photo.url},
|
||||
${photo.extension},
|
||||
${photo.aspectRatio},
|
||||
${photo.blurData},
|
||||
${photo.title},
|
||||
${photo.caption},
|
||||
${photo.semanticDescription},
|
||||
${convertArrayToPostgresString(photo.tags)},
|
||||
${photo.make},
|
||||
${photo.model},
|
||||
${photo.focalLength},
|
||||
${photo.focalLengthIn35MmFormat},
|
||||
${photo.fNumber},
|
||||
${photo.iso},
|
||||
${photo.exposureTime},
|
||||
${photo.exposureCompensation},
|
||||
${photo.locationName},
|
||||
${photo.latitude},
|
||||
${photo.longitude},
|
||||
${photo.filmSimulation},
|
||||
${photo.priorityOrder},
|
||||
${photo.hidden},
|
||||
${photo.takenAt},
|
||||
${photo.takenAtNaive}
|
||||
)
|
||||
`, 'sqlInsertPhoto');
|
||||
|
||||
export const sqlUpdatePhoto = (photo: PhotoDbInsert) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos SET
|
||||
url=${photo.url},
|
||||
extension=${photo.extension},
|
||||
aspect_ratio=${photo.aspectRatio},
|
||||
blur_data=${photo.blurData},
|
||||
title=${photo.title},
|
||||
caption=${photo.caption},
|
||||
semantic_description=${photo.semanticDescription},
|
||||
tags=${convertArrayToPostgresString(photo.tags)},
|
||||
make=${photo.make},
|
||||
model=${photo.model},
|
||||
focal_length=${photo.focalLength},
|
||||
focal_length_in_35mm_format=${photo.focalLengthIn35MmFormat},
|
||||
f_number=${photo.fNumber},
|
||||
iso=${photo.iso},
|
||||
exposure_time=${photo.exposureTime},
|
||||
exposure_compensation=${photo.exposureCompensation},
|
||||
location_name=${photo.locationName},
|
||||
latitude=${photo.latitude},
|
||||
longitude=${photo.longitude},
|
||||
film_simulation=${photo.filmSimulation},
|
||||
priority_order=${photo.priorityOrder || null},
|
||||
hidden=${photo.hidden},
|
||||
taken_at=${photo.takenAt},
|
||||
taken_at_naive=${photo.takenAtNaive},
|
||||
updated_at=${(new Date()).toISOString()}
|
||||
WHERE id=${photo.id}
|
||||
`, 'sqlUpdatePhoto');
|
||||
|
||||
export const sqlDeletePhotoTagGlobally = (tag: string) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos
|
||||
SET tags=ARRAY_REMOVE(tags, ${tag})
|
||||
WHERE ${tag}=ANY(tags)
|
||||
`, 'sqlDeletePhotoTagGlobally');
|
||||
|
||||
export const sqlRenamePhotoTagGlobally = (tag: string, updatedTag: string) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos
|
||||
SET tags=ARRAY_REPLACE(tags, ${tag}, ${updatedTag})
|
||||
WHERE ${tag}=ANY(tags)
|
||||
`, 'sqlRenamePhotoTagGlobally');
|
||||
|
||||
export const sqlDeletePhoto = (id: string) =>
|
||||
safelyQueryPhotos(
|
||||
() => sql`DELETE FROM photos WHERE id=${id}`,
|
||||
'sqlDeletePhoto',
|
||||
);
|
||||
|
||||
const sqlGetPhoto = (id: string, includeHidden?: boolean) => includeHidden
|
||||
? sql<PhotoDb>`SELECT * FROM photos WHERE id=${id} LIMIT 1`
|
||||
// eslint-disable-next-line max-len
|
||||
: sql<PhotoDb>`SELECT * FROM photos WHERE id=${id} AND hidden IS NOT TRUE LIMIT 1`;
|
||||
|
||||
const sqlGetPhotosCount = async () => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
|
||||
const sqlGetPhotosCountIncludingHidden = async () => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
|
||||
const sqlGetPhotosMostRecentUpdate = async () => sql`
|
||||
SELECT updated_at FROM photos ORDER BY updated_at DESC LIMIT 1
|
||||
`.then(({ rows }) => rows[0] ? rows[0].updated_at as Date : undefined);
|
||||
|
||||
const sqlGetPhotosDateRange = async () => sql`
|
||||
SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
`.then(({ rows }) => rows[0]?.start && rows[0]?.end
|
||||
? rows[0] as PhotoDateRange
|
||||
: undefined);
|
||||
|
||||
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 }) => ({
|
||||
count: parseInt(rows[0].count, 10),
|
||||
...rows[0]?.start && rows[0]?.end
|
||||
? { dateRange: rows[0] as PhotoDateRange }
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
const sqlGetPhotosTagHiddenMeta = async () => sql`
|
||||
SELECT COUNT(*), MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE hidden IS TRUE
|
||||
`.then(({ rows }) => ({
|
||||
count: parseInt(rows[0].count, 10),
|
||||
...rows[0]?.start && rows[0]?.end
|
||||
? { dateRange: rows[0] as PhotoDateRange }
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
const sqlGetPhotosCameraMeta = async (camera: Camera) => sql`
|
||||
SELECT COUNT(*), MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE
|
||||
LOWER(REPLACE(make, ' ', '-'))=${parameterize(camera.make, true)} AND
|
||||
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model, true)} AND
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => ({
|
||||
count: parseInt(rows[0].count, 10),
|
||||
...rows[0]?.start && rows[0]?.end
|
||||
? { dateRange: rows[0] as PhotoDateRange }
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
const sqlGetPhotosFilmSimulationMeta = async (
|
||||
simulation: FilmSimulation,
|
||||
) => sql`
|
||||
SELECT COUNT(*), MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE film_simulation=${simulation} AND
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => ({
|
||||
count: parseInt(rows[0].count, 10),
|
||||
...rows[0]?.start && rows[0]?.end
|
||||
? { dateRange: rows[0] as PhotoDateRange }
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
const sqlGetUniqueTags = async () => sql`
|
||||
SELECT DISTINCT unnest(tags) as tag, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
GROUP BY tag
|
||||
ORDER BY tag ASC
|
||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
||||
tag: tag as string,
|
||||
count: parseInt(count, 10),
|
||||
})));
|
||||
|
||||
const sqlGetUniqueTagsHidden = async () => sql`
|
||||
SELECT DISTINCT unnest(tags) as tag, COUNT(*)
|
||||
FROM photos
|
||||
GROUP BY tag
|
||||
ORDER BY tag ASC
|
||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
||||
tag: tag as string,
|
||||
count: parseInt(count, 10),
|
||||
})));
|
||||
|
||||
const sqlGetUniqueCameras = async () => sql`
|
||||
SELECT DISTINCT make||' '||model as camera, make, model, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
AND trim(make) <> ''
|
||||
AND trim(model) <> ''
|
||||
GROUP BY make, model
|
||||
ORDER BY camera ASC
|
||||
`.then(({ rows }): Cameras => rows.map(({ make, model, count }) => ({
|
||||
cameraKey: createCameraKey({ make, model }),
|
||||
camera: { make, model },
|
||||
count: parseInt(count, 10),
|
||||
})));
|
||||
|
||||
const sqlGetUniqueFilmSimulations = async () => sql`
|
||||
SELECT DISTINCT film_simulation, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE AND film_simulation IS NOT NULL
|
||||
GROUP BY film_simulation
|
||||
ORDER BY film_simulation ASC
|
||||
`.then(({ rows }): FilmSimulations => rows
|
||||
.map(({ film_simulation, count }) => ({
|
||||
simulation: film_simulation as FilmSimulation,
|
||||
count: parseInt(count, 10),
|
||||
})));
|
||||
|
||||
export type GetPhotosOptions = {
|
||||
sortBy?: 'createdAt' | 'takenAt' | 'priority'
|
||||
limit?: number
|
||||
offset?: number
|
||||
query?: string
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
takenBefore?: Date
|
||||
takenAfterInclusive?: Date
|
||||
hidden?: 'exclude' | 'include' | 'only'
|
||||
}
|
||||
|
||||
const safelyQueryPhotos = async <T>(
|
||||
callback: () => Promise<T>,
|
||||
debugMessage: string
|
||||
): Promise<T> => {
|
||||
let result: T;
|
||||
|
||||
const start = new Date();
|
||||
|
||||
try {
|
||||
result = await callback();
|
||||
} catch (e: any) {
|
||||
if (MIGRATION_FIELDS_01.some(field => new RegExp(
|
||||
`column "${field}" of relation "photos" does not exist`,
|
||||
'i',
|
||||
).test(e.message))) {
|
||||
console.log('Running migration 01 ...');
|
||||
await sqlRunMigration01();
|
||||
result = await callback();
|
||||
} else if (/relation "photos" does not exist/i.test(e.message)) {
|
||||
// If the table does not exist, create it
|
||||
console.log('Creating photos table ...');
|
||||
await sqlCreatePhotosTable();
|
||||
result = await callback();
|
||||
} else if (/endpoint is in transition/i.test(e.message)) {
|
||||
console.log('sql get error: endpoint is in transition (setting timeout)');
|
||||
// Wait 5 seconds and try again
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
try {
|
||||
result = await callback();
|
||||
} catch (e: any) {
|
||||
console.log(`sql get error on retry (after 5000ms): ${e.message} `);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
console.log(`sql get error: ${e.message} `);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (SHOULD_DEBUG_SQL && debugMessage) {
|
||||
const time =
|
||||
(((new Date()).getTime() - start.getTime()) / 1000).toFixed(2);
|
||||
console.log(`Executing sql query: ${debugMessage} (${time} seconds)`);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getWheresFromOptions = (options: GetPhotosOptions) => {
|
||||
const {
|
||||
hidden = 'exclude',
|
||||
takenBefore,
|
||||
takenAfterInclusive,
|
||||
query,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
} = options;
|
||||
|
||||
const wheres = [] as string[];
|
||||
const values = [] as (string | number)[];
|
||||
let valuesIndex = 1;
|
||||
|
||||
switch (hidden) {
|
||||
case 'exclude':
|
||||
wheres.push('hidden IS NOT TRUE');
|
||||
break;
|
||||
case 'only':
|
||||
wheres.push('hidden IS TRUE');
|
||||
break;
|
||||
}
|
||||
if (takenBefore) {
|
||||
wheres.push(`taken_at > $${valuesIndex++}`);
|
||||
values.push(takenBefore.toISOString());
|
||||
}
|
||||
if (takenAfterInclusive) {
|
||||
wheres.push(`taken_at <= $${valuesIndex++}`);
|
||||
values.push(takenAfterInclusive.toISOString());
|
||||
}
|
||||
if (query) {
|
||||
// eslint-disable-next-line max-len
|
||||
wheres.push(`CONCAT(title, ' ', caption, ' ', semantic_description) ILIKE $${valuesIndex++}`);
|
||||
values.push(`%${query.toLocaleLowerCase()}%`);
|
||||
}
|
||||
if (tag) {
|
||||
wheres.push(`$${valuesIndex++}=ANY(tags)`);
|
||||
values.push(tag);
|
||||
}
|
||||
if (camera) {
|
||||
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=$${valuesIndex++}`);
|
||||
values.push(simulation);
|
||||
}
|
||||
|
||||
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) {
|
||||
case 'createdAt':
|
||||
sql.push('ORDER BY created_at DESC');
|
||||
break;
|
||||
case 'takenAt':
|
||||
sql.push('ORDER BY taken_at DESC');
|
||||
break;
|
||||
case 'priority':
|
||||
sql.push('ORDER BY priority_order ASC, taken_at DESC');
|
||||
break;
|
||||
}
|
||||
|
||||
// LIMIT + OFFSET
|
||||
sql.push(`LIMIT $${valuesIndex++} OFFSET $${valuesIndex++}`);
|
||||
values.push(limit, offset);
|
||||
|
||||
return safelyQueryPhotos(async () => {
|
||||
return query(sql.join(' '), values);
|
||||
}, sql.join(' '))
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
};
|
||||
|
||||
export const getPhotosNearId = async (
|
||||
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';
|
||||
|
||||
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 photo = rows.find(({ id }) => id === photoId);
|
||||
const indexNumber = photo ? parseInt(photo.row_number) : undefined;
|
||||
return {
|
||||
photos: rows.map(parsePhotoFromDb),
|
||||
indexNumber,
|
||||
};
|
||||
});
|
||||
|
||||
export const getPhotoIds = async ({ limit }: { limit?: number }) => {
|
||||
return safelyQueryPhotos(() => limit
|
||||
? sql`SELECT id FROM photos LIMIT ${limit}`
|
||||
: sql`SELECT id FROM photos`,
|
||||
'getPhotoIds')
|
||||
.then(({ rows }) => rows.map(({ id }) => id as string));
|
||||
};
|
||||
|
||||
export const getPhoto = async (
|
||||
id: string,
|
||||
includeHidden?: boolean,
|
||||
): Promise<Photo | undefined> => {
|
||||
// Check for photo id forwarding
|
||||
// and convert short ids to uuids
|
||||
const photoId = translatePhotoId(id);
|
||||
return safelyQueryPhotos(() =>
|
||||
sqlGetPhoto(photoId, includeHidden), 'sqlGetPhoto')
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
||||
.then(photos => photos.length > 0 ? photos[0] : undefined);
|
||||
};
|
||||
export const getPhotosDateRange = () =>
|
||||
safelyQueryPhotos(sqlGetPhotosDateRange, 'getPhotosDateRange');
|
||||
export const getPhotosCount = () =>
|
||||
safelyQueryPhotos(sqlGetPhotosCount, 'getPhotosCount');
|
||||
export const getPhotosCountIncludingHidden = () =>
|
||||
safelyQueryPhotos(
|
||||
sqlGetPhotosCountIncludingHidden,
|
||||
'getPhotosCountIncludingHidden',
|
||||
);
|
||||
export const getPhotosMostRecentUpdate = () =>
|
||||
safelyQueryPhotos(
|
||||
sqlGetPhotosMostRecentUpdate,
|
||||
'getPhotosMostRecentUpdate',
|
||||
);
|
||||
|
||||
// TAGS
|
||||
export const getUniqueTags = () =>
|
||||
safelyQueryPhotos(sqlGetUniqueTags, 'getUniqueTags');
|
||||
export const getUniqueTagsHidden = () =>
|
||||
safelyQueryPhotos(sqlGetUniqueTagsHidden, 'getUniqueTagsHidden');
|
||||
export const getPhotosTagMeta = (tag: string) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosTagMeta(tag), 'getPhotosTagMeta');
|
||||
export const getPhotosTagHiddenMeta = () =>
|
||||
safelyQueryPhotos(sqlGetPhotosTagHiddenMeta, 'sqlGetPhotosTagHiddenMeta');
|
||||
|
||||
// CAMERAS
|
||||
export const getUniqueCameras = () =>
|
||||
safelyQueryPhotos(sqlGetUniqueCameras, 'getUniqueCameras');
|
||||
export const getPhotosCameraMeta = (camera: Camera) =>
|
||||
safelyQueryPhotos(
|
||||
() => sqlGetPhotosCameraMeta(camera),
|
||||
'getPhotosCameraMeta',
|
||||
);
|
||||
|
||||
// FILM SIMULATIONS
|
||||
export const getUniqueFilmSimulations = () =>
|
||||
safelyQueryPhotos(sqlGetUniqueFilmSimulations, 'getUniqueFilmSimulations');
|
||||
export const getPhotosFilmSimulationMeta =
|
||||
(simulation: FilmSimulation) => safelyQueryPhotos(
|
||||
() => sqlGetPhotosFilmSimulationMeta(simulation),
|
||||
'getPhotosFilmSimulationMeta',
|
||||
);
|
||||
116
src/photo/db/index.ts
Normal file
116
src/photo/db/index.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { Camera } from '@/camera';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import { PRIORITY_ORDER_ENABLED } from '@/site/config';
|
||||
import { parameterize } from '@/utility/string';
|
||||
|
||||
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
||||
export const PHOTO_DEFAULT_LIMIT = 100;
|
||||
|
||||
export type GetPhotosOptions = {
|
||||
sortBy?: 'createdAt' | 'takenAt' | 'priority';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
query?: string;
|
||||
tag?: string;
|
||||
camera?: Camera;
|
||||
simulation?: FilmSimulation;
|
||||
takenBefore?: Date;
|
||||
takenAfterInclusive?: Date;
|
||||
hidden?: 'exclude' | 'include' | 'only';
|
||||
};
|
||||
|
||||
export const getWheresFromOptions = (
|
||||
options: GetPhotosOptions,
|
||||
initialValuesIndex = 1
|
||||
) => {
|
||||
const {
|
||||
hidden = 'exclude',
|
||||
takenBefore,
|
||||
takenAfterInclusive,
|
||||
query,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
} = options;
|
||||
|
||||
const wheres = [] as string[];
|
||||
const wheresValues = [] as (string | number)[];
|
||||
let valuesIndex = initialValuesIndex;
|
||||
|
||||
switch (hidden) {
|
||||
case 'exclude':
|
||||
wheres.push('hidden IS NOT TRUE');
|
||||
break;
|
||||
case 'only':
|
||||
wheres.push('hidden IS TRUE');
|
||||
break;
|
||||
}
|
||||
|
||||
if (takenBefore) {
|
||||
wheres.push(`taken_at > $${valuesIndex++}`);
|
||||
wheresValues.push(takenBefore.toISOString());
|
||||
}
|
||||
if (takenAfterInclusive) {
|
||||
wheres.push(`taken_at <= $${valuesIndex++}`);
|
||||
wheresValues.push(takenAfterInclusive.toISOString());
|
||||
}
|
||||
if (query) {
|
||||
// eslint-disable-next-line max-len
|
||||
wheres.push(`CONCAT(title, ' ', caption, ' ', semantic_description) ILIKE $${valuesIndex++}`);
|
||||
wheresValues.push(`%${query.toLocaleLowerCase()}%`);
|
||||
}
|
||||
if (tag) {
|
||||
wheres.push(`$${valuesIndex++}=ANY(tags)`);
|
||||
wheresValues.push(tag);
|
||||
}
|
||||
if (camera) {
|
||||
wheres.push(`LOWER(REPLACE(make, ' ', '-'))=$${valuesIndex++}`);
|
||||
wheres.push(`LOWER(REPLACE(model, ' ', '-'))=$${valuesIndex++}`);
|
||||
wheresValues.push(parameterize(camera.make, true));
|
||||
wheresValues.push(parameterize(camera.model, true));
|
||||
}
|
||||
if (simulation) {
|
||||
wheres.push(`film_simulation=$${valuesIndex++}`);
|
||||
wheresValues.push(simulation);
|
||||
}
|
||||
|
||||
return {
|
||||
wheres: wheres.length > 0
|
||||
? `WHERE ${wheres.join(' AND ')}`
|
||||
: '',
|
||||
wheresValues,
|
||||
lastValuesIndex: valuesIndex,
|
||||
};
|
||||
};
|
||||
|
||||
export const getOrderByFromOptions = (options: GetPhotosOptions) => {
|
||||
const {
|
||||
sortBy = PRIORITY_ORDER_ENABLED ? 'priority' : 'takenAt',
|
||||
} = options;
|
||||
|
||||
switch (sortBy) {
|
||||
case 'createdAt':
|
||||
return 'ORDER BY created_at DESC';
|
||||
case 'takenAt':
|
||||
return 'ORDER BY taken_at DESC';
|
||||
case 'priority':
|
||||
return 'ORDER BY priority_order ASC, taken_at DESC';
|
||||
}
|
||||
};
|
||||
|
||||
export const getLimitAndOffsetFromOptions = (
|
||||
options: GetPhotosOptions,
|
||||
initialValuesIndex = 1,
|
||||
) => {
|
||||
const {
|
||||
limit = PHOTO_DEFAULT_LIMIT,
|
||||
offset = 0,
|
||||
} = options;
|
||||
|
||||
let valuesIndex = initialValuesIndex;
|
||||
|
||||
return {
|
||||
limitAndOffset: `LIMIT $${valuesIndex++} OFFSET $${valuesIndex++}`,
|
||||
limitAndOffsetValues: [limit, offset],
|
||||
};
|
||||
};
|
||||
395
src/photo/db/query.ts
Normal file
395
src/photo/db/query.ts
Normal file
@ -0,0 +1,395 @@
|
||||
import {
|
||||
sql,
|
||||
query,
|
||||
convertArrayToPostgresString,
|
||||
} from '@/services/postgres';
|
||||
import {
|
||||
PhotoDb,
|
||||
PhotoDbInsert,
|
||||
translatePhotoId,
|
||||
parsePhotoFromDb,
|
||||
Photo,
|
||||
PhotoDateRange,
|
||||
} from '@/photo';
|
||||
import { Cameras, createCameraKey } from '@/camera';
|
||||
import { TagsWithMeta } from '@/tag';
|
||||
import { FilmSimulation, FilmSimulations } from '@/simulation';
|
||||
import { SHOULD_DEBUG_SQL } from '@/site/config';
|
||||
import {
|
||||
GetPhotosOptions,
|
||||
getLimitAndOffsetFromOptions,
|
||||
getOrderByFromOptions,
|
||||
} from '.';
|
||||
import { getWheresFromOptions } from '.';
|
||||
|
||||
const createPhotosTable = () =>
|
||||
sql`
|
||||
CREATE TABLE IF NOT EXISTS photos (
|
||||
id VARCHAR(8) PRIMARY KEY,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
extension VARCHAR(255) NOT NULL,
|
||||
aspect_ratio REAL DEFAULT 1.5,
|
||||
blur_data TEXT,
|
||||
title VARCHAR(255),
|
||||
caption TEXT,
|
||||
semantic_description TEXT,
|
||||
tags VARCHAR(255)[],
|
||||
make VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
focal_length SMALLINT,
|
||||
focal_length_in_35mm_format SMALLINT,
|
||||
f_number REAL,
|
||||
iso SMALLINT,
|
||||
exposure_time DOUBLE PRECISION,
|
||||
exposure_compensation REAL,
|
||||
location_name VARCHAR(255),
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude DOUBLE PRECISION,
|
||||
film_simulation VARCHAR(255),
|
||||
priority_order REAL,
|
||||
taken_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
taken_at_naive VARCHAR(255) NOT NULL,
|
||||
hidden BOOLEAN,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
// Migration 01
|
||||
const MIGRATION_FIELDS_01 = ['caption', 'semantic_description'];
|
||||
const runMigration01 = () =>
|
||||
sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS caption TEXT,
|
||||
ADD COLUMN IF NOT EXISTS semantic_description TEXT
|
||||
`;
|
||||
|
||||
// Wrapper for most queries for JIT table creation/migration running
|
||||
const safelyQueryPhotos = async <T>(
|
||||
callback: () => Promise<T>,
|
||||
debugMessage: string
|
||||
): Promise<T> => {
|
||||
let result: T;
|
||||
|
||||
const start = new Date();
|
||||
|
||||
try {
|
||||
result = await callback();
|
||||
} catch (e: any) {
|
||||
if (MIGRATION_FIELDS_01.some(field => new RegExp(
|
||||
`column "${field}" of relation "photos" does not exist`,
|
||||
'i',
|
||||
).test(e.message))) {
|
||||
console.log('Running migration 01 ...');
|
||||
await runMigration01();
|
||||
result = await callback();
|
||||
} else if (/relation "photos" does not exist/i.test(e.message)) {
|
||||
// If the table does not exist, create it
|
||||
console.log('Creating photos table ...');
|
||||
await createPhotosTable();
|
||||
result = await callback();
|
||||
} else if (/endpoint is in transition/i.test(e.message)) {
|
||||
console.log('sql get error: endpoint is in transition (setting timeout)');
|
||||
// Wait 5 seconds and try again
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
try {
|
||||
result = await callback();
|
||||
} catch (e: any) {
|
||||
console.log(`sql get error on retry (after 5000ms): ${e.message} `);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
console.log(`sql get error: ${e.message} `);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (SHOULD_DEBUG_SQL && debugMessage) {
|
||||
const time =
|
||||
(((new Date()).getTime() - start.getTime()) / 1000).toFixed(2);
|
||||
console.log(`Executing sql query: ${debugMessage} (${time} seconds)`);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Must provide id as 8-character nanoid
|
||||
export const insertPhoto = (photo: PhotoDbInsert) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
INSERT INTO photos (
|
||||
id,
|
||||
url,
|
||||
extension,
|
||||
aspect_ratio,
|
||||
blur_data,
|
||||
title,
|
||||
caption,
|
||||
semantic_description,
|
||||
tags,
|
||||
make,
|
||||
model,
|
||||
focal_length,
|
||||
focal_length_in_35mm_format,
|
||||
f_number,
|
||||
iso,
|
||||
exposure_time,
|
||||
exposure_compensation,
|
||||
location_name,
|
||||
latitude,
|
||||
longitude,
|
||||
film_simulation,
|
||||
priority_order,
|
||||
hidden,
|
||||
taken_at,
|
||||
taken_at_naive
|
||||
)
|
||||
VALUES (
|
||||
${photo.id},
|
||||
${photo.url},
|
||||
${photo.extension},
|
||||
${photo.aspectRatio},
|
||||
${photo.blurData},
|
||||
${photo.title},
|
||||
${photo.caption},
|
||||
${photo.semanticDescription},
|
||||
${convertArrayToPostgresString(photo.tags)},
|
||||
${photo.make},
|
||||
${photo.model},
|
||||
${photo.focalLength},
|
||||
${photo.focalLengthIn35MmFormat},
|
||||
${photo.fNumber},
|
||||
${photo.iso},
|
||||
${photo.exposureTime},
|
||||
${photo.exposureCompensation},
|
||||
${photo.locationName},
|
||||
${photo.latitude},
|
||||
${photo.longitude},
|
||||
${photo.filmSimulation},
|
||||
${photo.priorityOrder},
|
||||
${photo.hidden},
|
||||
${photo.takenAt},
|
||||
${photo.takenAtNaive}
|
||||
)
|
||||
`, 'insertPhoto');
|
||||
|
||||
export const updatePhoto = (photo: PhotoDbInsert) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos SET
|
||||
url=${photo.url},
|
||||
extension=${photo.extension},
|
||||
aspect_ratio=${photo.aspectRatio},
|
||||
blur_data=${photo.blurData},
|
||||
title=${photo.title},
|
||||
caption=${photo.caption},
|
||||
semantic_description=${photo.semanticDescription},
|
||||
tags=${convertArrayToPostgresString(photo.tags)},
|
||||
make=${photo.make},
|
||||
model=${photo.model},
|
||||
focal_length=${photo.focalLength},
|
||||
focal_length_in_35mm_format=${photo.focalLengthIn35MmFormat},
|
||||
f_number=${photo.fNumber},
|
||||
iso=${photo.iso},
|
||||
exposure_time=${photo.exposureTime},
|
||||
exposure_compensation=${photo.exposureCompensation},
|
||||
location_name=${photo.locationName},
|
||||
latitude=${photo.latitude},
|
||||
longitude=${photo.longitude},
|
||||
film_simulation=${photo.filmSimulation},
|
||||
priority_order=${photo.priorityOrder || null},
|
||||
hidden=${photo.hidden},
|
||||
taken_at=${photo.takenAt},
|
||||
taken_at_naive=${photo.takenAtNaive},
|
||||
updated_at=${(new Date()).toISOString()}
|
||||
WHERE id=${photo.id}
|
||||
`, 'updatePhoto');
|
||||
|
||||
export const deletePhotoTagGlobally = (tag: string) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos
|
||||
SET tags=ARRAY_REMOVE(tags, ${tag})
|
||||
WHERE ${tag}=ANY(tags)
|
||||
`, 'deletePhotoTagGlobally');
|
||||
|
||||
export const renamePhotoTagGlobally = (tag: string, updatedTag: string) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
UPDATE photos
|
||||
SET tags=ARRAY_REPLACE(tags, ${tag}, ${updatedTag})
|
||||
WHERE ${tag}=ANY(tags)
|
||||
`, 'renamePhotoTagGlobally');
|
||||
|
||||
export const deletePhoto = (id: string) =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
DELETE FROM photos WHERE id=${id}
|
||||
`, 'deletePhoto');
|
||||
|
||||
export const getPhotosMostRecentUpdate = async () =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
SELECT updated_at FROM photos ORDER BY updated_at DESC LIMIT 1
|
||||
`.then(({ rows }) => rows[0] ? rows[0].updated_at as Date : undefined)
|
||||
, 'getPhotosMostRecentUpdate');
|
||||
|
||||
export const getUniqueTags = async () =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
SELECT DISTINCT unnest(tags) as tag, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
GROUP BY tag
|
||||
ORDER BY tag ASC
|
||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
||||
tag: tag as string,
|
||||
count: parseInt(count, 10),
|
||||
})))
|
||||
, 'getUniqueTags');
|
||||
|
||||
export const getUniqueTagsHidden = async () =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
SELECT DISTINCT unnest(tags) as tag, COUNT(*)
|
||||
FROM photos
|
||||
GROUP BY tag
|
||||
ORDER BY tag ASC
|
||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
||||
tag: tag as string,
|
||||
count: parseInt(count, 10),
|
||||
})))
|
||||
, 'getUniqueTagsHidden');
|
||||
|
||||
export const getUniqueCameras = async () =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
SELECT DISTINCT make||' '||model as camera, make, model, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
AND trim(make) <> ''
|
||||
AND trim(model) <> ''
|
||||
GROUP BY make, model
|
||||
ORDER BY camera ASC
|
||||
`.then(({ rows }): Cameras => rows.map(({ make, model, count }) => ({
|
||||
cameraKey: createCameraKey({ make, model }),
|
||||
camera: { make, model },
|
||||
count: parseInt(count, 10),
|
||||
})))
|
||||
, 'getUniqueCameras');
|
||||
|
||||
export const getUniqueFilmSimulations = async () =>
|
||||
safelyQueryPhotos(() => sql`
|
||||
SELECT DISTINCT film_simulation, COUNT(*)
|
||||
FROM photos
|
||||
WHERE hidden IS NOT TRUE AND film_simulation IS NOT NULL
|
||||
GROUP BY film_simulation
|
||||
ORDER BY film_simulation ASC
|
||||
`.then(({ rows }): FilmSimulations => rows
|
||||
.map(({ film_simulation, count }) => ({
|
||||
simulation: film_simulation as FilmSimulation,
|
||||
count: parseInt(count, 10),
|
||||
})))
|
||||
, 'getUniqueFilmSimulations');
|
||||
|
||||
export const getPhotos = async (options: GetPhotosOptions = {}) =>
|
||||
safelyQueryPhotos(async () => {
|
||||
const sql = ['SELECT * FROM photos'];
|
||||
const values = [] as (string | number)[];
|
||||
|
||||
const {
|
||||
wheres,
|
||||
wheresValues,
|
||||
lastValuesIndex,
|
||||
} = getWheresFromOptions(options);
|
||||
|
||||
let valuesIndex = lastValuesIndex;
|
||||
|
||||
if (wheres) {
|
||||
sql.push(wheres);
|
||||
values.push(...wheresValues);
|
||||
}
|
||||
|
||||
sql.push(getOrderByFromOptions(options));
|
||||
|
||||
const {
|
||||
limitAndOffset,
|
||||
limitAndOffsetValues,
|
||||
} = getLimitAndOffsetFromOptions(options, valuesIndex);
|
||||
|
||||
// LIMIT + OFFSET
|
||||
sql.push(limitAndOffset);
|
||||
values.push(...limitAndOffsetValues);
|
||||
|
||||
return query(sql.join(' '), values)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
}, 'getPhotos');
|
||||
|
||||
export const getPhotosNearId = async (
|
||||
photoId: string,
|
||||
options: GetPhotosOptions,
|
||||
) =>
|
||||
safelyQueryPhotos(async () => {
|
||||
const { limit } = options;
|
||||
|
||||
const {
|
||||
wheres,
|
||||
wheresValues,
|
||||
lastValuesIndex,
|
||||
} = getWheresFromOptions(options);
|
||||
|
||||
let valuesIndex = lastValuesIndex;
|
||||
|
||||
return query(
|
||||
`
|
||||
WITH twi AS (
|
||||
SELECT *, row_number()
|
||||
OVER (${getOrderByFromOptions(options)}) 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++}
|
||||
`,
|
||||
[...wheresValues, photoId, limit]
|
||||
)
|
||||
.then(({ rows }) => {
|
||||
const photo = rows.find(({ id }) => id === photoId);
|
||||
const indexNumber = photo ? parseInt(photo.row_number) : undefined;
|
||||
return {
|
||||
photos: rows.map(parsePhotoFromDb),
|
||||
indexNumber,
|
||||
};
|
||||
});
|
||||
}, `getPhotosNearId: ${photoId}`);
|
||||
|
||||
export const getPhotosMeta = (options: GetPhotosOptions = {}) =>
|
||||
safelyQueryPhotos(async () => {
|
||||
// eslint-disable-next-line max-len
|
||||
let sql = 'SELECT COUNT(*), MIN(taken_at_naive) as start, MAX(taken_at_naive) as end FROM photos';
|
||||
const { wheres, wheresValues } = getWheresFromOptions(options);
|
||||
if (wheres) { sql += ` ${wheres}`; }
|
||||
return query(sql, wheresValues)
|
||||
.then(({ rows }) => ({
|
||||
count: parseInt(rows[0].count, 10),
|
||||
...rows[0]?.start && rows[0]?.end
|
||||
? { dateRange: rows[0] as PhotoDateRange }
|
||||
: undefined,
|
||||
}));
|
||||
}, 'getPhotosMeta');
|
||||
|
||||
export const getPhotoIds = async ({ limit }: { limit?: number }) =>
|
||||
safelyQueryPhotos(() => (limit
|
||||
? sql`SELECT id FROM photos LIMIT ${limit}`
|
||||
: sql`SELECT id FROM photos`)
|
||||
.then(({ rows }) => rows.map(({ id }) => id as string))
|
||||
, 'getPhotoIds');
|
||||
|
||||
export const getPhoto = async (
|
||||
id: string,
|
||||
includeHidden?: boolean,
|
||||
): Promise<Photo | undefined> =>
|
||||
safelyQueryPhotos(async () => {
|
||||
// Check for photo id forwarding and convert short ids to uuids
|
||||
const photoId = translatePhotoId(id);
|
||||
return (includeHidden
|
||||
? sql<PhotoDb>`SELECT * FROM photos WHERE id=${photoId} LIMIT 1`
|
||||
// eslint-disable-next-line max-len
|
||||
: sql<PhotoDb>`SELECT * FROM photos WHERE id=${photoId} AND hidden IS NOT TRUE LIMIT 1`)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
||||
.then(photos => photos.length > 0 ? photos[0] : undefined);
|
||||
}, 'getPhoto');
|
||||
@ -1,6 +1,6 @@
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosFilmSimulationMetaCached,
|
||||
getPhotosMetaCached,
|
||||
} from '@/photo/cache';
|
||||
import { FilmSimulation } from '.';
|
||||
|
||||
@ -13,5 +13,5 @@ export const getPhotosFilmSimulationDataCached = ({
|
||||
}) =>
|
||||
Promise.all([
|
||||
getPhotosCached({ simulation, limit }),
|
||||
getPhotosFilmSimulationMetaCached(simulation),
|
||||
getPhotosMetaCached({ simulation }),
|
||||
]);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import CommandKClient, { CommandKSection } from '@/components/CommandKClient';
|
||||
import {
|
||||
getPhotosCountCached,
|
||||
getPhotosMetaCached,
|
||||
getUniqueCamerasCached,
|
||||
getUniqueFilmSimulationsCached,
|
||||
getUniqueTagsCached,
|
||||
@ -24,7 +24,9 @@ export default async function CommandK() {
|
||||
cameras,
|
||||
filmSimulations,
|
||||
] = await Promise.all([
|
||||
getPhotosCountCached().catch(() => 0),
|
||||
getPhotosMetaCached()
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
getUniqueTagsCached().catch(() => [] as TagsWithMeta),
|
||||
getUniqueCamerasCached().catch(() => []),
|
||||
SHOW_FILM_SIMULATIONS
|
||||
|
||||
@ -7,7 +7,7 @@ import usePathnames from '@/utility/usePathnames';
|
||||
import { getAuthAction, logClientAuthUpdate } from '@/auth/actions';
|
||||
import useSWR from 'swr';
|
||||
import { MATTE_PHOTOS } from '@/site/config';
|
||||
import { getPhotosTagHiddenMetaCachedAction } from '@/photo/actions';
|
||||
import { getPhotosHiddenMetaCachedAction } from '@/photo/actions';
|
||||
|
||||
export default function AppStateProvider({
|
||||
children,
|
||||
@ -53,7 +53,7 @@ export default function AppStateProvider({
|
||||
useEffect(() => {
|
||||
if (isUserSignedIn) {
|
||||
const timeout = setTimeout(() =>
|
||||
getPhotosTagHiddenMetaCachedAction().then(({ count }) =>
|
||||
getPhotosHiddenMetaCachedAction().then(({ count }) =>
|
||||
setHiddenPhotosCount(count))
|
||||
, 100);
|
||||
return () => clearTimeout(timeout);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosTagMetaCached,
|
||||
getPhotosMetaCached,
|
||||
} from '@/photo/cache';
|
||||
|
||||
export const getPhotosTagDataCached = ({
|
||||
@ -12,6 +12,6 @@ export const getPhotosTagDataCached = ({
|
||||
}) =>
|
||||
Promise.all([
|
||||
getPhotosCached({ tag, limit }),
|
||||
getPhotosTagMetaCached(tag),
|
||||
getPhotosMetaCached({ tag }),
|
||||
]);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user