Flag photos without recipes as 'outdated'
This commit is contained in:
parent
34667efedf
commit
ee6aed896c
@ -1,17 +1,12 @@
|
|||||||
import { getPhotos } from '@/photo/db/query';
|
|
||||||
import { OUTDATED_THRESHOLD } from '@/photo';
|
|
||||||
import AdminOutdatedClient from '@/admin/AdminOutdatedClient';
|
import AdminOutdatedClient from '@/admin/AdminOutdatedClient';
|
||||||
import { AI_TEXT_GENERATION_ENABLED } from '@/app/config';
|
import { AI_TEXT_GENERATION_ENABLED } from '@/app/config';
|
||||||
|
import { getOutdatedPhotos } from '@/photo/db/query';
|
||||||
|
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
export default async function AdminOutdatedPage() {
|
export default async function AdminOutdatedPage() {
|
||||||
const photos = await getPhotos({
|
const photos = await getOutdatedPhotos()
|
||||||
hidden: 'include',
|
.catch(() => []);
|
||||||
sortBy: 'createdAtAsc',
|
|
||||||
updatedBefore: OUTDATED_THRESHOLD,
|
|
||||||
limit: 1_000,
|
|
||||||
}).catch(() => []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminOutdatedClient {...{
|
<AdminOutdatedClient {...{
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { getStoragePhotoUrlsNoStore } from '@/platforms/storage/cache';
|
import { getStoragePhotoUrlsNoStore } from '@/platforms/storage/cache';
|
||||||
import { getPhotos } from '@/photo/db/query';
|
import { getPhotos } from '@/photo/db/query';
|
||||||
import { getPhotosMetaCached } from '@/photo/cache';
|
import { getPhotosMetaCached } from '@/photo/cache';
|
||||||
import { OUTDATED_THRESHOLD } from '@/photo';
|
|
||||||
import AdminPhotosClient from '@/admin/AdminPhotosClient';
|
import AdminPhotosClient from '@/admin/AdminPhotosClient';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { TIMEZONE_COOKIE_NAME } from '@/utility/timezone';
|
import { TIMEZONE_COOKIE_NAME } from '@/utility/timezone';
|
||||||
|
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||||
|
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
@ -31,11 +31,7 @@ export default async function AdminPhotosPage() {
|
|||||||
getPhotosMetaCached({ hidden: 'include'})
|
getPhotosMetaCached({ hidden: 'include'})
|
||||||
.then(({ count }) => count)
|
.then(({ count }) => count)
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
getPhotosMetaCached({
|
getOutdatedPhotosCount()
|
||||||
hidden: 'include',
|
|
||||||
updatedBefore: OUTDATED_THRESHOLD,
|
|
||||||
})
|
|
||||||
.then(({ count }) => count)
|
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
DEBUG_PHOTO_BLOBS
|
DEBUG_PHOTO_BLOBS
|
||||||
? getStoragePhotoUrlsNoStore()
|
? getStoragePhotoUrlsNoStore()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { OUTDATED_THRESHOLD, Photo } from '@/photo';
|
import { Photo } from '@/photo';
|
||||||
import AdminPhotosTable from '@/admin/AdminPhotosTable';
|
import AdminPhotosTable from '@/admin/AdminPhotosTable';
|
||||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||||
import IconGrSync from '@/app/IconGrSync';
|
import IconGrSync from '@/app/IconGrSync';
|
||||||
@ -80,18 +80,13 @@ export default function AdminOutdatedClient({
|
|||||||
<Note>
|
<Note>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="font-bold">
|
<div className="font-bold">
|
||||||
Outdated photos found
|
{photos.length} outdated
|
||||||
|
{' '}
|
||||||
|
{photos.length === 1 ? 'photo' : 'photos'} found
|
||||||
</div>
|
</div>
|
||||||
{photos.length}
|
They may have missing EXIF fields, inaccurate blur data,
|
||||||
{' '}
|
{' '}
|
||||||
{photos.length === 1 ? 'photo' : 'photos'}
|
undesired privacy settings, or text that can be AI-generated
|
||||||
{' ('}last updated before
|
|
||||||
{' '}
|
|
||||||
{new Date(OUTDATED_THRESHOLD).toLocaleDateString()}{')'}
|
|
||||||
{' '}
|
|
||||||
may have: missing EXIF fields, inaccurate blur data,
|
|
||||||
{' '}
|
|
||||||
undesired privacy settings, or missing AI-generated text
|
|
||||||
</div>
|
</div>
|
||||||
</Note>
|
</Note>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@ -22,6 +22,8 @@ import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll';
|
|||||||
import { MdOutlineFileDownload } from 'react-icons/md';
|
import { MdOutlineFileDownload } from 'react-icons/md';
|
||||||
import MoreMenuItem from '@/components/more/MoreMenuItem';
|
import MoreMenuItem from '@/components/more/MoreMenuItem';
|
||||||
import IconGrSync from '@/app/IconGrSync';
|
import IconGrSync from '@/app/IconGrSync';
|
||||||
|
import { isPhotoOutdated } from '@/photo/outdated';
|
||||||
|
import { FaCircle } from 'react-icons/fa6';
|
||||||
|
|
||||||
export default function AdminPhotoMenuClient({
|
export default function AdminPhotoMenuClient({
|
||||||
photo,
|
photo,
|
||||||
@ -76,7 +78,14 @@ export default function AdminPhotoMenuClient({
|
|||||||
hrefDownloadName: downloadFileNameForPhoto(photo),
|
hrefDownloadName: downloadFileNameForPhoto(photo),
|
||||||
});
|
});
|
||||||
items.push({
|
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]" />,
|
icon: <IconGrSync className="translate-x-[-1px]" />,
|
||||||
action: () => syncPhotoAction(photo.id)
|
action: () => syncPhotoAction(photo.id)
|
||||||
.then(() => revalidatePhoto?.(photo.id)),
|
.then(() => revalidatePhoto?.(photo.id)),
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import {
|
|||||||
HAS_STATIC_OPTIMIZATION,
|
HAS_STATIC_OPTIMIZATION,
|
||||||
MATTE_PHOTOS,
|
MATTE_PHOTOS,
|
||||||
} from '@/app/config';
|
} from '@/app/config';
|
||||||
import { OUTDATED_THRESHOLD } from '@/photo';
|
|
||||||
import { getGitHubMetaForCurrentApp, getSignificantInsights } from '.';
|
import { getGitHubMetaForCurrentApp, getSignificantInsights } from '.';
|
||||||
|
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||||
|
|
||||||
const BASIC_PHOTO_INSTALLATION_COUNT = 32;
|
const BASIC_PHOTO_INSTALLATION_COUNT = 32;
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export default async function AdminAppInsights() {
|
|||||||
const [
|
const [
|
||||||
{ count: photosCount, dateRange },
|
{ count: photosCount, dateRange },
|
||||||
{ count: photosCountHidden },
|
{ count: photosCountHidden },
|
||||||
{ count: photosCountOutdated },
|
photosCountOutdated,
|
||||||
{ count: photosCountPortrait },
|
{ count: photosCountPortrait },
|
||||||
tags,
|
tags,
|
||||||
cameras,
|
cameras,
|
||||||
@ -31,7 +31,7 @@ export default async function AdminAppInsights() {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosMeta({ hidden: 'include' }),
|
getPhotosMeta({ hidden: 'include' }),
|
||||||
getPhotosMeta({ hidden: 'only' }),
|
getPhotosMeta({ hidden: 'only' }),
|
||||||
getPhotosMeta({ hidden: 'include', updatedBefore: OUTDATED_THRESHOLD }),
|
getOutdatedPhotosCount(),
|
||||||
getPhotosMeta({ maximumAspectRatio: 0.9 }),
|
getPhotosMeta({ maximumAspectRatio: 0.9 }),
|
||||||
getUniqueTags(),
|
getUniqueTags(),
|
||||||
getUniqueCameras(),
|
getUniqueCameras(),
|
||||||
|
|||||||
@ -342,8 +342,10 @@ export default function AdminAppInsightsClient({
|
|||||||
)}
|
)}
|
||||||
/>}
|
/>}
|
||||||
content={renderHighlightText(
|
content={renderHighlightText(
|
||||||
// eslint-disable-next-line max-len
|
pluralize(
|
||||||
pluralize(photosCountOutdated || DEBUG_PHOTOS_COUNT_OUTDATED, 'outdated photo'),
|
photosCountOutdated || DEBUG_PHOTOS_COUNT_OUTDATED,
|
||||||
|
'outdated photo',
|
||||||
|
),
|
||||||
'yellow',
|
'yellow',
|
||||||
)}
|
)}
|
||||||
expandPath={PATH_ADMIN_OUTDATED}
|
expandPath={PATH_ADMIN_OUTDATED}
|
||||||
|
|||||||
@ -6,18 +6,17 @@ import {
|
|||||||
getSignificantInsights,
|
getSignificantInsights,
|
||||||
InsightIndicatorStatus,
|
InsightIndicatorStatus,
|
||||||
} from '.';
|
} from '.';
|
||||||
import { getPhotosMeta } from '@/photo/db/query';
|
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||||
import { OUTDATED_THRESHOLD } from '@/photo';
|
|
||||||
|
|
||||||
export const getShouldShowInsightsIndicatorAction =
|
export const getShouldShowInsightsIndicatorAction =
|
||||||
async (): Promise<InsightIndicatorStatus> =>
|
async (): Promise<InsightIndicatorStatus> =>
|
||||||
runAuthenticatedAdminServerAction(async () => {
|
runAuthenticatedAdminServerAction(async () => {
|
||||||
const [
|
const [
|
||||||
codeMeta,
|
codeMeta,
|
||||||
{ count: photosCountOutdated },
|
photosCountOutdated,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getGitHubMetaForCurrentApp(),
|
getGitHubMetaForCurrentApp(),
|
||||||
getPhotosMeta({ hidden: 'include', updatedBefore: OUTDATED_THRESHOLD }),
|
getOutdatedPhotosCount(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { PRIORITY_ORDER_ENABLED } from '@/app/config';
|
import { PRIORITY_ORDER_ENABLED } from '@/app/config';
|
||||||
import { parameterize } from '@/utility/string';
|
import { parameterize } from '@/utility/string';
|
||||||
import { PhotoSetCategory } from '..';
|
import { PhotoSetCategory } from '..';
|
||||||
|
import { Camera } from '@/camera';
|
||||||
|
|
||||||
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
||||||
export const PHOTO_DEFAULT_LIMIT = 100;
|
export const PHOTO_DEFAULT_LIMIT = 100;
|
||||||
@ -22,7 +23,9 @@ export type GetPhotosOptions = {
|
|||||||
takenAfterInclusive?: Date
|
takenAfterInclusive?: Date
|
||||||
updatedBefore?: Date
|
updatedBefore?: Date
|
||||||
hidden?: 'exclude' | 'include' | 'only'
|
hidden?: 'exclude' | 'include' | 'only'
|
||||||
} & PhotoSetCategory;
|
} & Omit<PhotoSetCategory, 'camera'> & {
|
||||||
|
camera?: Partial<Camera>
|
||||||
|
};
|
||||||
|
|
||||||
export const areOptionsSensitive = (options: GetPhotosOptions) =>
|
export const areOptionsSensitive = (options: GetPhotosOptions) =>
|
||||||
options.hidden === 'include' || options.hidden === 'only';
|
options.hidden === 'include' || options.hidden === 'only';
|
||||||
@ -83,9 +86,11 @@ export const getWheresFromOptions = (
|
|||||||
wheres.push(`$${valuesIndex++}=ANY(tags)`);
|
wheres.push(`$${valuesIndex++}=ANY(tags)`);
|
||||||
wheresValues.push(tag);
|
wheresValues.push(tag);
|
||||||
}
|
}
|
||||||
if (camera) {
|
if (camera?.make) {
|
||||||
wheres.push(`${parameterizeForDb('make')}=$${valuesIndex++}`);
|
wheres.push(`${parameterizeForDb('make')}=$${valuesIndex++}`);
|
||||||
wheresValues.push(parameterize(camera.make, true));
|
wheresValues.push(parameterize(camera.make, true));
|
||||||
|
}
|
||||||
|
if (camera?.model) {
|
||||||
wheres.push(`${parameterizeForDb('model')}=$${valuesIndex++}`);
|
wheres.push(`${parameterizeForDb('model')}=$${valuesIndex++}`);
|
||||||
wheresValues.push(parameterize(camera.model, true));
|
wheresValues.push(parameterize(camera.model, true));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import { getWheresFromOptions } from '.';
|
|||||||
import { FocalLengths } from '@/focal';
|
import { FocalLengths } from '@/focal';
|
||||||
import { Lenses, createLensKey } from '@/lens';
|
import { Lenses, createLensKey } from '@/lens';
|
||||||
import { migrationForError } from './migration';
|
import { migrationForError } from './migration';
|
||||||
|
import { UPDATED_BEFORE_01, UPDATED_BEFORE_02 } from '../outdated';
|
||||||
|
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
|
||||||
|
|
||||||
const createPhotosTable = () =>
|
const createPhotosTable = () =>
|
||||||
sql`
|
sql`
|
||||||
@ -445,3 +447,39 @@ export const getPhoto = async (
|
|||||||
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
||||||
.then(photos => photos.length > 0 ? photos[0] : undefined);
|
.then(photos => photos.length > 0 ? photos[0] : undefined);
|
||||||
}, 'getPhoto');
|
}, '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',
|
||||||
|
);
|
||||||
|
|||||||
@ -22,8 +22,6 @@ import { isBefore } from 'date-fns';
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||||
|
|
||||||
export const OUTDATED_THRESHOLD = new Date('2024-06-16');
|
|
||||||
|
|
||||||
// INFINITE SCROLL: FEED
|
// INFINITE SCROLL: FEED
|
||||||
export const INFINITE_SCROLL_FEED_INITIAL =
|
export const INFINITE_SCROLL_FEED_INITIAL =
|
||||||
process.env.NODE_ENV === 'development' ? 2 : 12;
|
process.env.NODE_ENV === 'development' ? 2 : 12;
|
||||||
|
|||||||
13
src/photo/outdated.ts
Normal file
13
src/photo/outdated.ts
Normal 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
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user