diff --git a/app/api/storage/presigned-url/[key]/route.ts b/app/api/storage/presigned-url/[key]/route.ts
index ea5016be..314c8764 100644
--- a/app/api/storage/presigned-url/[key]/route.ts
+++ b/app/api/storage/presigned-url/[key]/route.ts
@@ -1,5 +1,5 @@
import { auth } from '@/auth/server';
-import { getSignedUrl } from '@/platforms/storage';
+import { getSignedUrlForKey } from '@/platforms/storage';
export async function GET(
_: Request,
@@ -10,7 +10,7 @@ export async function GET(
const session = await auth();
if (session?.user && key) {
- const url = await getSignedUrl(key, 'PUT');
+ const url = await getSignedUrlForKey(key, 'PUT');
return new Response(
url,
{ headers: { 'content-type': 'text/plain' } },
diff --git a/src/image-response/components/ImagePhotoGrid.tsx b/src/image-response/components/ImagePhotoGrid.tsx
index f1b893a7..3756cda5 100644
--- a/src/image-response/components/ImagePhotoGrid.tsx
+++ b/src/image-response/components/ImagePhotoGrid.tsx
@@ -7,6 +7,8 @@ import {
doAllPhotosHaveOptimizedFiles,
getOptimizedPhotoUrl,
} from '@/photo/storage';
+import { fetchBase64ImageFromUrl } from '@/utility/image';
+import { getSignedUrlForUrl } from '@/platforms/storage';
export default async function ImagePhotoGrid({
photos,
@@ -53,7 +55,23 @@ export default async function ImagePhotoGrid({
const doOptimizedFilesExist = await doAllPhotosHaveOptimizedFiles(photos);
- const renderPhoto = ({ id, url }: Photo, width: number, height: number) =>
+ const photoDataUrls = await Promise.all(photos.map(async({ id, url }) => {
+ const optimizedUrl = getOptimizedPhotoUrl({
+ imageUrl: url,
+ size: nextImageWidth,
+ addBypassSecret: IS_PREVIEW,
+ compatibilityMode: !doOptimizedFilesExist,
+ });
+ const presignedUrl = await getSignedUrlForUrl(optimizedUrl, 'GET');
+ const data = await fetchBase64ImageFromUrl(presignedUrl);
+ return { id, data };
+ }));
+
+ const renderPhoto = (
+ { id, data }: typeof photoDataUrls[number],
+ width: number,
+ height: number,
+ ) =>
![]()
- {renderPhoto(photos[0], cellWidth, cellHeight * 2)}
+ {renderPhoto(photoDataUrls[0], cellWidth, cellHeight * 2)}
{/* Small images (R) */}
- {photos.slice(1).map(photo =>
+ {photoDataUrls.slice(1).map(photo =>
renderPhoto(photo, cellWidth, cellHeight),
)}
>
- : photos.slice(0, count).map(photo =>
+ : photoDataUrls.slice(0, count).map(photo =>
renderPhoto(photo, cellWidth, cellHeight),
)}
diff --git a/src/platforms/storage/aws-s3.ts b/src/platforms/storage/aws-s3.ts
index 411a82a5..b64e8d59 100644
--- a/src/platforms/storage/aws-s3.ts
+++ b/src/platforms/storage/aws-s3.ts
@@ -84,7 +84,7 @@ export const awsS3Delete = async (Key: string) => {
}));
};
-export const awsS3GetSignedUrl = async (
+export const awsS3GetSignedUrl = (
Key: string,
method: 'GET' | 'PUT',
expiresIn: number,
diff --git a/src/platforms/storage/cloudflare-r2.ts b/src/platforms/storage/cloudflare-r2.ts
index ebb38650..50aa7458 100644
--- a/src/platforms/storage/cloudflare-r2.ts
+++ b/src/platforms/storage/cloudflare-r2.ts
@@ -104,7 +104,7 @@ export const cloudflareR2Delete = async (Key: string) => {
}));
};
-export const cloudflareR2GetSignedUrl = async (
+export const cloudflareR2GetSignedUrl = (
Key: string,
method: 'GET' | 'PUT',
expiresIn: number,
diff --git a/src/platforms/storage/index.ts b/src/platforms/storage/index.ts
index 3a05bda8..6a773a51 100644
--- a/src/platforms/storage/index.ts
+++ b/src/platforms/storage/index.ts
@@ -251,7 +251,8 @@ export const getStorageUrlsForPrefix = async (prefix = '') => {
});
};
-export const getSignedUrl = async (
+// Used primarily for uploading files
+export const getSignedUrlForKey = async (
key: string,
method: 'GET' | 'PUT',
expiresIn = 3600,
@@ -266,5 +267,24 @@ export const getSignedUrl = async (
}
};
+// Used for safely fetching files via presigned URLs
+export const getSignedUrlForUrl = (
+ url: string,
+ method: 'GET' | 'PUT',
+ expiresIn = 3600,
+) => {
+ const { fileName } = getFileNamePartsFromStorageUrl(url);
+ switch (storageTypeFromUrl(url)) {
+ case 'cloudflare-r2':
+ return cloudflareR2GetSignedUrl(fileName, method, expiresIn);
+ case 'minio':
+ return minioGetSignedUrl(fileName, method, expiresIn);
+ case 'aws-s3':
+ return awsS3GetSignedUrl(fileName, method, expiresIn);
+ default:
+ return url;
+ }
+};
+
export const testStorageConnection = () =>
getStorageUrlsForPrefix();
diff --git a/src/platforms/storage/minio.ts b/src/platforms/storage/minio.ts
index 552386ef..e2f52e73 100644
--- a/src/platforms/storage/minio.ts
+++ b/src/platforms/storage/minio.ts
@@ -93,7 +93,7 @@ export const minioDelete = async (Key: string): Promise => {
await minioClient().send(deleteObjectCommand);
};
-export const minioGetSignedUrl = async (
+export const minioGetSignedUrl = (
Key: string,
method: 'GET' | 'PUT',
expiresIn: number,