Flag photos without recipes as 'outdated'

This commit is contained in:
Sam Becker 2025-02-23 23:41:05 -06:00
parent 34667efedf
commit ee6aed896c
11 changed files with 89 additions and 39 deletions

View File

@ -1,17 +1,12 @@
import { getPhotos } from '@/photo/db/query';
import { OUTDATED_THRESHOLD } from '@/photo';
import AdminOutdatedClient from '@/admin/AdminOutdatedClient';
import { AI_TEXT_GENERATION_ENABLED } from '@/app/config';
import { getOutdatedPhotos } from '@/photo/db/query';
export const maxDuration = 60;
export default async function AdminOutdatedPage() {
const photos = await getPhotos({
hidden: 'include',
sortBy: 'createdAtAsc',
updatedBefore: OUTDATED_THRESHOLD,
limit: 1_000,
}).catch(() => []);
const photos = await getOutdatedPhotos()
.catch(() => []);
return (
<AdminOutdatedClient {...{

View File

@ -1,11 +1,11 @@
import { getStoragePhotoUrlsNoStore } from '@/platforms/storage/cache';
import { getPhotos } from '@/photo/db/query';
import { getPhotosMetaCached } from '@/photo/cache';
import { OUTDATED_THRESHOLD } from '@/photo';
import AdminPhotosClient from '@/admin/AdminPhotosClient';
import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';
import { TIMEZONE_COOKIE_NAME } from '@/utility/timezone';
import { getOutdatedPhotosCount } from '@/photo/db/query';
export const maxDuration = 60;
@ -31,11 +31,7 @@ export default async function AdminPhotosPage() {
getPhotosMetaCached({ hidden: 'include'})
.then(({ count }) => count)
.catch(() => 0),
getPhotosMetaCached({
hidden: 'include',
updatedBefore: OUTDATED_THRESHOLD,
})
.then(({ count }) => count)
getOutdatedPhotosCount()
.catch(() => 0),
DEBUG_PHOTO_BLOBS
? getStoragePhotoUrlsNoStore()

View File

@ -1,6 +1,6 @@
'use client';
import { OUTDATED_THRESHOLD, Photo } from '@/photo';
import { Photo } from '@/photo';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import LoaderButton from '@/components/primitives/LoaderButton';
import IconGrSync from '@/app/IconGrSync';
@ -80,18 +80,13 @@ export default function AdminOutdatedClient({
<Note>
<div className="space-y-1.5">
<div className="font-bold">
Outdated photos found
{photos.length} outdated
{' '}
{photos.length === 1 ? 'photo' : 'photos'} found
</div>
{photos.length}
They may have missing EXIF fields, inaccurate blur data,
{' '}
{photos.length === 1 ? 'photo' : 'photos'}
{' ('}last updated before
{' '}
{new Date(OUTDATED_THRESHOLD).toLocaleDateString()}{')'}
{' '}
may have: missing EXIF fields, inaccurate blur data,
{' '}
undesired privacy settings, or missing AI-generated text
undesired privacy settings, or text that can be AI-generated
</div>
</Note>
<div className="space-y-4">

View File

@ -22,6 +22,8 @@ import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll';
import { MdOutlineFileDownload } from 'react-icons/md';
import MoreMenuItem from '@/components/more/MoreMenuItem';
import IconGrSync from '@/app/IconGrSync';
import { isPhotoOutdated } from '@/photo/outdated';
import { FaCircle } from 'react-icons/fa6';
export default function AdminPhotoMenuClient({
photo,
@ -76,7 +78,14 @@ export default function AdminPhotoMenuClient({
hrefDownloadName: downloadFileNameForPhoto(photo),
});
items.push({
label: 'Sync',
label: <span className="inline-flex items-center gap-2">
<span>Sync</span>
{isPhotoOutdated(photo) &&
<FaCircle
size={8}
className="text-amber-500 translate-y-[1.5px]"
/>}
</span>,
icon: <IconGrSync className="translate-x-[-1px]" />,
action: () => syncPhotoAction(photo.id)
.then(() => revalidatePhoto?.(photo.id)),

View File

@ -12,8 +12,8 @@ import {
HAS_STATIC_OPTIMIZATION,
MATTE_PHOTOS,
} from '@/app/config';
import { OUTDATED_THRESHOLD } from '@/photo';
import { getGitHubMetaForCurrentApp, getSignificantInsights } from '.';
import { getOutdatedPhotosCount } from '@/photo/db/query';
const BASIC_PHOTO_INSTALLATION_COUNT = 32;
@ -21,7 +21,7 @@ export default async function AdminAppInsights() {
const [
{ count: photosCount, dateRange },
{ count: photosCountHidden },
{ count: photosCountOutdated },
photosCountOutdated,
{ count: photosCountPortrait },
tags,
cameras,
@ -31,7 +31,7 @@ export default async function AdminAppInsights() {
] = await Promise.all([
getPhotosMeta({ hidden: 'include' }),
getPhotosMeta({ hidden: 'only' }),
getPhotosMeta({ hidden: 'include', updatedBefore: OUTDATED_THRESHOLD }),
getOutdatedPhotosCount(),
getPhotosMeta({ maximumAspectRatio: 0.9 }),
getUniqueTags(),
getUniqueCameras(),

View File

@ -342,8 +342,10 @@ export default function AdminAppInsightsClient({
)}
/>}
content={renderHighlightText(
// eslint-disable-next-line max-len
pluralize(photosCountOutdated || DEBUG_PHOTOS_COUNT_OUTDATED, 'outdated photo'),
pluralize(
photosCountOutdated || DEBUG_PHOTOS_COUNT_OUTDATED,
'outdated photo',
),
'yellow',
)}
expandPath={PATH_ADMIN_OUTDATED}

View File

@ -6,18 +6,17 @@ import {
getSignificantInsights,
InsightIndicatorStatus,
} from '.';
import { getPhotosMeta } from '@/photo/db/query';
import { OUTDATED_THRESHOLD } from '@/photo';
import { getOutdatedPhotosCount } from '@/photo/db/query';
export const getShouldShowInsightsIndicatorAction =
async (): Promise<InsightIndicatorStatus> =>
runAuthenticatedAdminServerAction(async () => {
const [
codeMeta,
{ count: photosCountOutdated },
photosCountOutdated,
] = await Promise.all([
getGitHubMetaForCurrentApp(),
getPhotosMeta({ hidden: 'include', updatedBefore: OUTDATED_THRESHOLD }),
getOutdatedPhotosCount(),
]);
const {

View File

@ -1,6 +1,7 @@
import { PRIORITY_ORDER_ENABLED } from '@/app/config';
import { parameterize } from '@/utility/string';
import { PhotoSetCategory } from '..';
import { Camera } from '@/camera';
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
export const PHOTO_DEFAULT_LIMIT = 100;
@ -22,7 +23,9 @@ export type GetPhotosOptions = {
takenAfterInclusive?: Date
updatedBefore?: Date
hidden?: 'exclude' | 'include' | 'only'
} & PhotoSetCategory;
} & Omit<PhotoSetCategory, 'camera'> & {
camera?: Partial<Camera>
};
export const areOptionsSensitive = (options: GetPhotosOptions) =>
options.hidden === 'include' || options.hidden === 'only';
@ -83,9 +86,11 @@ export const getWheresFromOptions = (
wheres.push(`$${valuesIndex++}=ANY(tags)`);
wheresValues.push(tag);
}
if (camera) {
if (camera?.make) {
wheres.push(`${parameterizeForDb('make')}=$${valuesIndex++}`);
wheresValues.push(parameterize(camera.make, true));
}
if (camera?.model) {
wheres.push(`${parameterizeForDb('model')}=$${valuesIndex++}`);
wheresValues.push(parameterize(camera.model, true));
}

View File

@ -24,6 +24,8 @@ import { getWheresFromOptions } from '.';
import { FocalLengths } from '@/focal';
import { Lenses, createLensKey } from '@/lens';
import { migrationForError } from './migration';
import { UPDATED_BEFORE_01, UPDATED_BEFORE_02 } from '../outdated';
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
const createPhotosTable = () =>
sql`
@ -445,3 +447,39 @@ export const getPhoto = async (
.then(({ rows }) => rows.map(parsePhotoFromDb))
.then(photos => photos.length > 0 ? photos[0] : undefined);
}, 'getPhoto');
// Outdated queries
const outdatedWhereClause =
// eslint-disable-next-line quotes
`WHERE updated_at < $1 OR (updated_at < $2 AND make = $3)`;
const outdatedValues = [
UPDATED_BEFORE_01.toISOString(),
UPDATED_BEFORE_02.toISOString(),
MAKE_FUJIFILM,
];
export const getOutdatedPhotos = () => safelyQueryPhotos(
() => query(`
SELECT * FROM photos
${outdatedWhereClause}
ORDER BY created_at ASC
LIMIT 1000
`,
outdatedValues,
)
.then(({ rows }) => rows.map(parsePhotoFromDb)),
'getOutdatedPhotos',
);
export const getOutdatedPhotosCount = () => safelyQueryPhotos(
() => query(`
SELECT COUNT(*) FROM photos
${outdatedWhereClause}
`,
outdatedValues,
)
.then(({ rows }) => parseInt(rows[0].count, 10)),
'getOutdatedPhotosCount',
);

View File

@ -22,8 +22,6 @@ import { isBefore } from 'date-fns';
import type { Metadata } from 'next';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
export const OUTDATED_THRESHOLD = new Date('2024-06-16');
// INFINITE SCROLL: FEED
export const INFINITE_SCROLL_FEED_INITIAL =
process.env.NODE_ENV === 'development' ? 2 : 12;

13
src/photo/outdated.ts Normal file
View File

@ -0,0 +1,13 @@
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
import { Photo } from '.';
export const UPDATED_BEFORE_01 = new Date('2024-06-16');
// UTC 2025-02-24 05:30:00
export const UPDATED_BEFORE_02 = new Date(Date.UTC(2025, 1, 24, 5, 30, 0));
export const isPhotoOutdated = (photo: Photo) => {
return photo.updatedAt < UPDATED_BEFORE_01 || (
photo.updatedAt < UPDATED_BEFORE_02 &&
photo.make === MAKE_FUJIFILM
);
};