From 44550824e74ddcb8dc8ea126366d9776c0e54fee Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 17 Feb 2026 17:18:04 -0600 Subject: [PATCH] Centralize presigned url commands --- app/api/storage/presigned-url/[key]/route.ts | 39 ++------------------ src/platforms/storage/aws-s3.ts | 17 +++++++-- src/platforms/storage/cloudflare-r2.ts | 18 +++++++-- src/platforms/storage/index.ts | 18 +++++++++ src/platforms/storage/minio.ts | 17 +++++++-- 5 files changed, 65 insertions(+), 44 deletions(-) diff --git a/app/api/storage/presigned-url/[key]/route.ts b/app/api/storage/presigned-url/[key]/route.ts index 7cd5cc0d..ea5016be 100644 --- a/app/api/storage/presigned-url/[key]/route.ts +++ b/app/api/storage/presigned-url/[key]/route.ts @@ -1,18 +1,5 @@ import { auth } from '@/auth/server'; -import { - awsS3Client, - awsS3PutObjectCommandForKey, -} from '@/platforms/storage/aws-s3'; -import { - cloudflareR2Client, - cloudflareR2PutObjectCommandForKey, -} from '@/platforms/storage/cloudflare-r2'; -import { - minioClient, - minioPutObjectCommandForKey, -} from '@/platforms/storage/minio'; -import { CURRENT_STORAGE } from '@/app/config'; -import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { getSignedUrl } from '@/platforms/storage'; export async function GET( _: Request, @@ -21,27 +8,9 @@ export async function GET( const { key } = await params; const session = await auth(); - if (session?.user && key) { - let client; - let command; - - switch (CURRENT_STORAGE) { - case 'cloudflare-r2': - client = cloudflareR2Client(); - command = cloudflareR2PutObjectCommandForKey(key); - break; - case 'minio': - client = minioClient(); - command = minioPutObjectCommandForKey(key); - break; - default: - client = awsS3Client(); - command = awsS3PutObjectCommandForKey(key); - break; - } - - const url = await getSignedUrl(client, command, { expiresIn: 3600 }); - + + if (session?.user && key) { + const url = await getSignedUrl(key, 'PUT'); return new Response( url, { headers: { 'content-type': 'text/plain' } }, diff --git a/src/platforms/storage/aws-s3.ts b/src/platforms/storage/aws-s3.ts index d7d5a740..411a82a5 100644 --- a/src/platforms/storage/aws-s3.ts +++ b/src/platforms/storage/aws-s3.ts @@ -4,7 +4,9 @@ import { DeleteObjectCommand, ListObjectsCommand, PutObjectCommand, + GetObjectCommand, } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { StorageListResponse, generateStorageId } from '.'; import { formatBytes } from '@/utility/number'; @@ -30,9 +32,6 @@ const urlForKey = (key?: string) => `${AWS_S3_BASE_URL}/${key}`; export const isUrlFromAwsS3 = (url?: string) => AWS_S3_BASE_URL && url?.startsWith(AWS_S3_BASE_URL); -export const awsS3PutObjectCommandForKey = (Key: string) => - new PutObjectCommand({ Bucket: AWS_S3_BUCKET, Key, ACL: 'public-read' }); - export const awsS3Put = async ( file: Buffer, fileName: string, @@ -84,3 +83,15 @@ export const awsS3Delete = async (Key: string) => { Key, })); }; + +export const awsS3GetSignedUrl = async ( + Key: string, + method: 'GET' | 'PUT', + expiresIn: number, +) => { + const client = awsS3Client(); + const command = method === 'GET' + ? new GetObjectCommand({ Bucket: AWS_S3_BUCKET, Key }) + : new PutObjectCommand({ Bucket: AWS_S3_BUCKET, Key, ACL: 'public-read' }); + return getSignedUrl(client, command, { expiresIn }); +}; diff --git a/src/platforms/storage/cloudflare-r2.ts b/src/platforms/storage/cloudflare-r2.ts index 96fd228a..ebb38650 100644 --- a/src/platforms/storage/cloudflare-r2.ts +++ b/src/platforms/storage/cloudflare-r2.ts @@ -4,7 +4,9 @@ import { PutObjectCommand, DeleteObjectCommand, CopyObjectCommand, + GetObjectCommand, } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { StorageListResponse, generateStorageId } from '.'; import { removeUrlProtocol } from '@/utility/url'; import { formatBytes } from '@/utility/number'; @@ -52,9 +54,6 @@ export const isUrlFromCloudflareR2 = (url?: string) => ( url?.startsWith(CLOUDFLARE_R2_BASE_URL_PUBLIC) ); -export const cloudflareR2PutObjectCommandForKey = (Key: string) => - new PutObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key }); - export const cloudflareR2Put = async ( file: Buffer, fileName: string, @@ -104,3 +103,16 @@ export const cloudflareR2Delete = async (Key: string) => { Key, })); }; + +export const cloudflareR2GetSignedUrl = async ( + Key: string, + method: 'GET' | 'PUT', + expiresIn: number, +) => { + const client = cloudflareR2Client(); + const command = method === 'GET' + ? new GetObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key }) + // eslint-disable-next-line max-len + : new PutObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key, ACL: 'public-read' }); + return getSignedUrl(client, command, { expiresIn }); +}; diff --git a/src/platforms/storage/index.ts b/src/platforms/storage/index.ts index 5b3944de..3a05bda8 100644 --- a/src/platforms/storage/index.ts +++ b/src/platforms/storage/index.ts @@ -10,6 +10,7 @@ import { AWS_S3_BASE_URL, awsS3Copy, awsS3Delete, + awsS3GetSignedUrl, awsS3List, awsS3Put, isUrlFromAwsS3, @@ -26,6 +27,7 @@ import { CLOUDFLARE_R2_BASE_URL_PUBLIC, cloudflareR2Copy, cloudflareR2Delete, + cloudflareR2GetSignedUrl, cloudflareR2List, cloudflareR2Put, isUrlFromCloudflareR2, @@ -37,6 +39,7 @@ import { minioList, minioPut, isUrlFromMinio, + minioGetSignedUrl, } from './minio'; import { PATH_API_PRESIGNED_URL } from '@/app/path'; @@ -248,5 +251,20 @@ export const getStorageUrlsForPrefix = async (prefix = '') => { }); }; +export const getSignedUrl = async ( + key: string, + method: 'GET' | 'PUT', + expiresIn = 3600, +) => { + switch (CURRENT_STORAGE) { + case 'cloudflare-r2': + return cloudflareR2GetSignedUrl(key, method, expiresIn); + case 'minio': + return minioGetSignedUrl(key, method, expiresIn); + default: + return awsS3GetSignedUrl(key, method, expiresIn); + } +}; + export const testStorageConnection = () => getStorageUrlsForPrefix(); diff --git a/src/platforms/storage/minio.ts b/src/platforms/storage/minio.ts index cf8cbf80..552386ef 100644 --- a/src/platforms/storage/minio.ts +++ b/src/platforms/storage/minio.ts @@ -4,7 +4,9 @@ import { ListObjectsCommand, PutObjectCommand, DeleteObjectCommand, + GetObjectCommand, } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { StorageListResponse, generateStorageId } from '.'; import { formatBytes } from '@/utility/number'; @@ -39,9 +41,6 @@ const urlForKey = (key?: string) => `${MINIO_BASE_URL}/${key}`; export const isUrlFromMinio = (url?: string) => MINIO_BASE_URL && url?.startsWith(MINIO_BASE_URL); -export const minioPutObjectCommandForKey = (Key: string) => - new PutObjectCommand({ Bucket: MINIO_BUCKET, Key }); - export const minioPut = async ( file: Buffer, fileName: string, @@ -93,3 +92,15 @@ export const minioDelete = async (Key: string): Promise => { }); await minioClient().send(deleteObjectCommand); }; + +export const minioGetSignedUrl = async ( + Key: string, + method: 'GET' | 'PUT', + expiresIn: number, +) => { + const client = minioClient(); + const command = method === 'GET' + ? new GetObjectCommand({ Bucket: MINIO_BUCKET, Key }) + : new PutObjectCommand({ Bucket: MINIO_BUCKET, Key, ACL: 'public-read' }); + return getSignedUrl(client, command, { expiresIn }); +};