From 6b262b46de4a1d2e3b157eb4f397eb23cdf72e01 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 16 Jun 2024 13:30:52 -0500 Subject: [PATCH] Add outdated photos page --- src/admin/AdminNav.tsx | 36 ++++++++++----- src/app/admin/outdated/page.tsx | 53 ++++++++++++++++++++++ src/components/Banner.tsx | 4 +- src/components/primitives/LoaderButton.tsx | 4 +- src/photo/db/index.ts | 4 +- src/photo/index.ts | 2 + src/site/paths.ts | 1 + 7 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/app/admin/outdated/page.tsx diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index ce1d1964..b86d91ac 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -5,15 +5,18 @@ import { getUniqueTagsCached, } from '@/photo/cache'; import { + PATH_ADMIN_OUTDATED, PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, } from '@/site/paths'; import AdminNavClient from './AdminNavClient'; +import { OUTDATED_THRESHOLD } from '@/photo'; export default async function AdminNav() { const [ countPhotos, + countPhotosOutdated, countUploads, countTags, mostRecentPhotoUpdateTime, @@ -21,6 +24,12 @@ export default async function AdminNav() { getPhotosMetaCached({ hidden: 'include' }) .then(({ count }) => count) .catch(() => 0), + getPhotosMetaCached({ + hidden: 'include', + takenBefore: OUTDATED_THRESHOLD, + }) + .then(({ count }) => count) + .catch(() => 0), getStorageUploadUrlsNoStore() .then(urls => urls.length) .catch(e => { @@ -31,28 +40,33 @@ export default async function AdminNav() { getPhotosMostRecentUpdateCached().catch(() => undefined), ]); - const navItemPhotos = { + // Photos + const items = [{ label: 'Photos', href: PATH_ADMIN_PHOTOS, count: countPhotos, - }; + }]; - const navItemUploads = { + // Outdated Photos + if (countPhotosOutdated > 0) { items.push({ + label: 'Outdated', + href: PATH_ADMIN_OUTDATED, + count: countPhotosOutdated, + }); } + + // Uploads + if (countUploads > 0) { items.push({ label: 'Uploads', href: PATH_ADMIN_UPLOADS, count: countUploads, - }; + }); } - const navItemTags = { + // Tags + if (countTags > 0) { items.push({ label: 'Tags', href: PATH_ADMIN_TAGS, count: countTags, - }; - - const items = [navItemPhotos]; - - if (countUploads > 0) { items.push(navItemUploads); } - if (countTags > 0) { items.push(navItemTags); } + }); } return ( diff --git a/src/app/admin/outdated/page.tsx b/src/app/admin/outdated/page.tsx new file mode 100644 index 00000000..fe8994b2 --- /dev/null +++ b/src/app/admin/outdated/page.tsx @@ -0,0 +1,53 @@ +import SiteGrid from '@/components/SiteGrid'; +import { AI_TEXT_GENERATION_ENABLED } from '@/site/config'; +import { getPhotos } from '@/photo/db/query'; +import AdminPhotosTable from '@/admin/AdminPhotosTable'; +import { OUTDATED_THRESHOLD } from '@/photo'; +import LoaderButton from '@/components/primitives/LoaderButton'; +import IconGrSync from '@/site/IconGrSync'; +import Banner from '@/components/Banner'; + +const UPDATE_BATCH_SIZE = 5; + +export default async function AdminPhotosPage() { + const photos = await getPhotos({ + hidden: 'include', + sortBy: 'createdAtAsc', + takenBefore: OUTDATED_THRESHOLD, + limit: 1_000, + }).catch(() => []); + + return ( + + +
+ These photos {'('}uploaded before + {' '} + {new Date(OUTDATED_THRESHOLD).toLocaleDateString()}{')'} + {' '} + may have: missing EXIF fields, inaccurate blur data, + {' '} + undesired privacy settings, + {' '} + and missing AI-generated text. +
+
+ } + hideTextOnMobile={false} + className="primary" + > + Sync oldest {UPDATE_BATCH_SIZE} photos + +
+ +
+ } + /> + ); +} diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index db738a84..62e140c7 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -27,8 +27,8 @@ export default function Banner({ >
{icon ?? } {children}
diff --git a/src/components/primitives/LoaderButton.tsx b/src/components/primitives/LoaderButton.tsx index e16fd03c..46aee2fe 100644 --- a/src/components/primitives/LoaderButton.tsx +++ b/src/components/primitives/LoaderButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import Spinner, { SpinnerColor } from '@/components/Spinner'; import { clsx } from 'clsx/lite'; import { ButtonHTMLAttributes, ReactNode } from 'react'; @@ -42,7 +44,7 @@ export default function LoaderButton(props: { : ['h-9']), styleAs === 'link' && 'hover:text-dim', styleAs === 'link-without-hover' && 'hover:text-main', - 'inline-flex items-center gap-2 self-start', + 'inline-flex items-center gap-2 self-start whitespace-nowrap', className, )} disabled={isLoading || disabled} diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index 748cdcff..350cbf4f 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -8,7 +8,7 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000; export const PHOTO_DEFAULT_LIMIT = 100; export type GetPhotosOptions = { - sortBy?: 'createdAt' | 'takenAt' | 'priority' + sortBy?: 'createdAt' | 'createdAtAsc' | 'takenAt' | 'priority' limit?: number offset?: number query?: string @@ -106,6 +106,8 @@ export const getOrderByFromOptions = (options: GetPhotosOptions) => { switch (sortBy) { case 'createdAt': return 'ORDER BY created_at DESC'; + case 'createdAtAsc': + return 'ORDER BY created_at ASC'; case 'takenAt': return 'ORDER BY taken_at DESC'; case 'priority': diff --git a/src/photo/index.ts b/src/photo/index.ts index 59994e22..5f146cc7 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -14,6 +14,8 @@ import camelcaseKeys from 'camelcase-keys'; import { isBefore } from 'date-fns'; import type { Metadata } from 'next'; +export const OUTDATED_THRESHOLD = new Date('2024-06-01'); + // INFINITE SCROLL: LARGE PHOTOS export const INFINITE_SCROLL_LARGE_PHOTO_INITIAL = process.env.NODE_ENV === 'development' ? 2 : 12; diff --git a/src/site/paths.ts b/src/site/paths.ts index c67b0ff3..47d82f27 100644 --- a/src/site/paths.ts +++ b/src/site/paths.ts @@ -30,6 +30,7 @@ const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; // Admin paths export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`; +export const PATH_ADMIN_OUTDATED = `${PATH_ADMIN}/outdated`; export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`; export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`; export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;