Centralize presigned url commands

This commit is contained in:
Sam Becker 2026-02-17 17:18:04 -06:00
parent 053faae9b6
commit 44550824e7
5 changed files with 65 additions and 44 deletions

View File

@ -1,18 +1,5 @@
import { auth } from '@/auth/server'; import { auth } from '@/auth/server';
import { import { getSignedUrl } from '@/platforms/storage';
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';
export async function GET( export async function GET(
_: Request, _: Request,
@ -21,27 +8,9 @@ export async function GET(
const { key } = await params; const { key } = await params;
const session = await auth(); const session = await auth();
if (session?.user && key) { if (session?.user && key) {
let client; const url = await getSignedUrl(key, 'PUT');
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 });
return new Response( return new Response(
url, url,
{ headers: { 'content-type': 'text/plain' } }, { headers: { 'content-type': 'text/plain' } },

View File

@ -4,7 +4,9 @@ import {
DeleteObjectCommand, DeleteObjectCommand,
ListObjectsCommand, ListObjectsCommand,
PutObjectCommand, PutObjectCommand,
GetObjectCommand,
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { StorageListResponse, generateStorageId } from '.'; import { StorageListResponse, generateStorageId } from '.';
import { formatBytes } from '@/utility/number'; import { formatBytes } from '@/utility/number';
@ -30,9 +32,6 @@ const urlForKey = (key?: string) => `${AWS_S3_BASE_URL}/${key}`;
export const isUrlFromAwsS3 = (url?: string) => export const isUrlFromAwsS3 = (url?: string) =>
AWS_S3_BASE_URL && url?.startsWith(AWS_S3_BASE_URL); 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 ( export const awsS3Put = async (
file: Buffer, file: Buffer,
fileName: string, fileName: string,
@ -84,3 +83,15 @@ export const awsS3Delete = async (Key: string) => {
Key, 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 });
};

View File

@ -4,7 +4,9 @@ import {
PutObjectCommand, PutObjectCommand,
DeleteObjectCommand, DeleteObjectCommand,
CopyObjectCommand, CopyObjectCommand,
GetObjectCommand,
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { StorageListResponse, generateStorageId } from '.'; import { StorageListResponse, generateStorageId } from '.';
import { removeUrlProtocol } from '@/utility/url'; import { removeUrlProtocol } from '@/utility/url';
import { formatBytes } from '@/utility/number'; import { formatBytes } from '@/utility/number';
@ -52,9 +54,6 @@ export const isUrlFromCloudflareR2 = (url?: string) => (
url?.startsWith(CLOUDFLARE_R2_BASE_URL_PUBLIC) url?.startsWith(CLOUDFLARE_R2_BASE_URL_PUBLIC)
); );
export const cloudflareR2PutObjectCommandForKey = (Key: string) =>
new PutObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key });
export const cloudflareR2Put = async ( export const cloudflareR2Put = async (
file: Buffer, file: Buffer,
fileName: string, fileName: string,
@ -104,3 +103,16 @@ export const cloudflareR2Delete = async (Key: string) => {
Key, 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 });
};

View File

@ -10,6 +10,7 @@ import {
AWS_S3_BASE_URL, AWS_S3_BASE_URL,
awsS3Copy, awsS3Copy,
awsS3Delete, awsS3Delete,
awsS3GetSignedUrl,
awsS3List, awsS3List,
awsS3Put, awsS3Put,
isUrlFromAwsS3, isUrlFromAwsS3,
@ -26,6 +27,7 @@ import {
CLOUDFLARE_R2_BASE_URL_PUBLIC, CLOUDFLARE_R2_BASE_URL_PUBLIC,
cloudflareR2Copy, cloudflareR2Copy,
cloudflareR2Delete, cloudflareR2Delete,
cloudflareR2GetSignedUrl,
cloudflareR2List, cloudflareR2List,
cloudflareR2Put, cloudflareR2Put,
isUrlFromCloudflareR2, isUrlFromCloudflareR2,
@ -37,6 +39,7 @@ import {
minioList, minioList,
minioPut, minioPut,
isUrlFromMinio, isUrlFromMinio,
minioGetSignedUrl,
} from './minio'; } from './minio';
import { PATH_API_PRESIGNED_URL } from '@/app/path'; 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 = () => export const testStorageConnection = () =>
getStorageUrlsForPrefix(); getStorageUrlsForPrefix();

View File

@ -4,7 +4,9 @@ import {
ListObjectsCommand, ListObjectsCommand,
PutObjectCommand, PutObjectCommand,
DeleteObjectCommand, DeleteObjectCommand,
GetObjectCommand,
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { StorageListResponse, generateStorageId } from '.'; import { StorageListResponse, generateStorageId } from '.';
import { formatBytes } from '@/utility/number'; import { formatBytes } from '@/utility/number';
@ -39,9 +41,6 @@ const urlForKey = (key?: string) => `${MINIO_BASE_URL}/${key}`;
export const isUrlFromMinio = (url?: string) => export const isUrlFromMinio = (url?: string) =>
MINIO_BASE_URL && url?.startsWith(MINIO_BASE_URL); MINIO_BASE_URL && url?.startsWith(MINIO_BASE_URL);
export const minioPutObjectCommandForKey = (Key: string) =>
new PutObjectCommand({ Bucket: MINIO_BUCKET, Key });
export const minioPut = async ( export const minioPut = async (
file: Buffer, file: Buffer,
fileName: string, fileName: string,
@ -93,3 +92,15 @@ export const minioDelete = async (Key: string): Promise<void> => {
}); });
await minioClient().send(deleteObjectCommand); 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 });
};