+ {hasImage === undefined
+ ? 'Checking ...'
+ : hasImage === false
+ ? '❌'
+ : '✅'}
+
;
+}
diff --git a/src/admin/storage/AdminStorageTable.tsx b/src/admin/storage/AdminStorageTable.tsx
new file mode 100644
index 00000000..d48bc3e8
--- /dev/null
+++ b/src/admin/storage/AdminStorageTable.tsx
@@ -0,0 +1,58 @@
+import { pathForPhoto } from '@/app/path';
+import LinkWithStatus from '@/components/LinkWithStatus';
+import { Photo } from '@/photo';
+import { getPhotoUrls } from '@/photo/query';
+import { getStorageUrlsForPhoto } from '@/photo/storage';
+
+export default async function AdminStoragePage() {
+ const _urls = await getPhotoUrls({ limit: 1000, hidden: 'include' });
+
+ const urls = await Promise.all(_urls.map(async ({ url, ...partialPhoto }) => {
+ const urlSet = await getStorageUrlsForPhoto({ url } as Photo);
+ const status = urlSet.length === 4
+ ? 'complete'
+ : urlSet.length === 0
+ ? 'missing'
+ : 'partial';
+ return { ...partialPhoto, status };
+ }));
+
+ const countComplete = urls
+ .filter(({ status }) => status === 'complete').length;
+ const countPartial = urls
+ .filter(({ status }) => status === 'partial').length;
+ const countMissing = urls
+ .filter(({ status }) => status === 'missing').length;
+
+ return (
+
+
+ Storage ({countComplete + countPartial}/{urls.length})
+
+
+
✅ {countComplete.toString().padStart(3, '0')} Complete
+
⚠️ {countPartial.toString().padStart(3, '0')} Partial
+
❌ {countMissing.toString().padStart(3, '0')} Missing
+
+
+ {urls.map(({ id, title, hidden, status }) => (
+
+
+ {title}
+ {status === 'complete'
+ ? '✅'
+ : status === 'partial'
+ ? '⚠️'
+ : '❌'}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/album/actions.ts b/src/album/actions.ts
index 607b15d0..d3b16e40 100644
--- a/src/album/actions.ts
+++ b/src/album/actions.ts
@@ -1,13 +1,12 @@
'use server';
import { runAuthenticatedAdminServerAction } from '@/auth/server';
-import { addPhotoAlbumIds, deleteAlbum, updateAlbum } from './query';
+import { deleteAlbum, updateAlbum } from './query';
import { revalidateAllKeysAndPaths } from '@/cache';
import { redirect } from 'next/navigation';
import { PATH_ADMIN_ALBUMS, PATH_ROOT, pathForAlbum } from '@/app/path';
import { convertFormDataToAlbum } from './form';
import { Album } from '.';
-import { createAlbumsAndGetIds } from './server';
export const updateAlbumAction = async (formData: FormData) =>
runAuthenticatedAdminServerAction(async () => {
@@ -35,13 +34,3 @@ export const deleteAlbumAction = async (
redirect(PATH_ROOT);
}
});
-
-export const addPhotosToAlbumsAction = async (
- photoIds: string[],
- albumTitles: string[],
-) =>
- runAuthenticatedAdminServerAction(async () => {
- const albumIds = await createAlbumsAndGetIds(albumTitles);
- await addPhotoAlbumIds(photoIds, albumIds);
- revalidateAllKeysAndPaths();
- });
diff --git a/src/app/config.ts b/src/app/config.ts
index a48860da..d690006d 100644
--- a/src/app/config.ts
+++ b/src/app/config.ts
@@ -399,6 +399,8 @@ export const ADMIN_DEBUG_TOOLS_ENABLED = process.env.ADMIN_DEBUG_TOOLS === '1';
export const ADMIN_SQL_DEBUG_ENABLED =
process.env.ADMIN_SQL_DEBUG === '1' &&
!IS_BUILDING;
+export const ADMIN_STORAGE_DEBUG_ENABLED =
+ process.env.ADMIN_STORAGE_DEBUG === '1';
export const APP_CONFIGURATION = {
// Storage
@@ -527,6 +529,7 @@ export const APP_CONFIGURATION = {
),
areAdminDebugToolsEnabled: ADMIN_DEBUG_TOOLS_ENABLED,
isAdminSqlDebugEnabled: ADMIN_SQL_DEBUG_ENABLED,
+ isAdminStorageDebugEnabled: ADMIN_STORAGE_DEBUG_ENABLED,
// Misc
nextVersion: dependencies.next,
reactVersion: dependencies.react,
diff --git a/src/app/static.ts b/src/app/static.ts
index 85bf2f0f..be50d1a1 100644
--- a/src/app/static.ts
+++ b/src/app/static.ts
@@ -8,7 +8,7 @@ import {
STATICALLY_OPTIMIZED_PHOTOS,
} from '@/app/config';
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/db';
-import { getPublicPhotoIds } from '@/photo/query';
+import { getAllPublicPhotoIds } from '@/photo/query';
import { depluralize, pluralize } from '@/utility/string';
type StaticOutput = 'page' | 'image';
@@ -25,7 +25,7 @@ export const staticallyGeneratePhotosIfConfigured = (type: StaticOutput) => (
(type === 'image' && STATICALLY_OPTIMIZED_PHOTO_OG_IMAGES)
)
? async () => {
- const photoIds = await getPublicPhotoIds({
+ const photoIds = await getAllPublicPhotoIds({
limit: GENERATE_STATIC_PARAMS_LIMIT,
})
.catch(e => {
diff --git a/src/components/EnvVar.tsx b/src/components/EnvVar.tsx
index 1f360b6b..2bd429f5 100644
--- a/src/components/EnvVar.tsx
+++ b/src/components/EnvVar.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import clsx from 'clsx/lite';
import { ReactNode } from 'react';
import CopyButton from './CopyButton';
diff --git a/src/db/index.ts b/src/db/index.ts
index abf5e00b..c69edbb6 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -37,6 +37,7 @@ export type PhotoQueryOptions = {
camera?: Partial