Vercel/src/platforms/storage/cloudflare-r2.ts
2025-03-14 22:10:26 -05:00

107 lines
3.3 KiB
TypeScript

import {
S3Client,
ListObjectsCommand,
PutObjectCommand,
DeleteObjectCommand,
CopyObjectCommand,
} from '@aws-sdk/client-s3';
import { StorageListResponse, generateStorageId } from '.';
import { removeUrlProtocol } from '@/utility/url';
import { formatBytesToMB } from '@/utility/number';
const CLOUDFLARE_R2_BUCKET =
process.env.NEXT_PUBLIC_CLOUDFLARE_R2_BUCKET ?? '';
const CLOUDFLARE_R2_ACCOUNT_ID =
process.env.NEXT_PUBLIC_CLOUDFLARE_R2_ACCOUNT_ID ?? '';
const CLOUDFLARE_R2_PUBLIC_DOMAIN =
removeUrlProtocol(process.env.NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_DOMAIN) ?? '';
const CLOUDFLARE_R2_ACCESS_KEY =
process.env.CLOUDFLARE_R2_ACCESS_KEY ?? '';
const CLOUDFLARE_R2_SECRET_ACCESS_KEY =
process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY ?? '';
const CLOUDFLARE_R2_ENDPOINT = CLOUDFLARE_R2_ACCOUNT_ID
? `https://${CLOUDFLARE_R2_ACCOUNT_ID}.r2.cloudflarestorage.com`
: undefined;
export const CLOUDFLARE_R2_BASE_URL_PUBLIC = CLOUDFLARE_R2_PUBLIC_DOMAIN
? `https://${CLOUDFLARE_R2_PUBLIC_DOMAIN}`
: undefined;
export const CLOUDFLARE_R2_BASE_URL_PRIVATE =
CLOUDFLARE_R2_ENDPOINT && CLOUDFLARE_R2_BUCKET
? `${CLOUDFLARE_R2_ENDPOINT}/${CLOUDFLARE_R2_BUCKET}`
: undefined;
export const cloudflareR2Client = () => new S3Client({
region: 'auto',
endpoint: CLOUDFLARE_R2_ENDPOINT,
credentials: {
accessKeyId: CLOUDFLARE_R2_ACCESS_KEY,
secretAccessKey: CLOUDFLARE_R2_SECRET_ACCESS_KEY,
},
});
const urlForKey = (key?: string, isPublic = true) => isPublic
? `${CLOUDFLARE_R2_BASE_URL_PUBLIC}/${key}`
: `${CLOUDFLARE_R2_BASE_URL_PRIVATE}/${key}`;
export const isUrlFromCloudflareR2 = (url?: string) => (
CLOUDFLARE_R2_BASE_URL_PRIVATE &&
url?.startsWith(CLOUDFLARE_R2_BASE_URL_PRIVATE)
) || (
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 (
file: Buffer,
fileName: string,
): Promise<string> =>
cloudflareR2Client().send(new PutObjectCommand({
Bucket: CLOUDFLARE_R2_BUCKET,
Key: fileName,
Body: file,
}))
.then(() => urlForKey(fileName));
export const cloudflareR2Copy = async (
fileNameSource: string,
fileNameDestination: string,
addRandomSuffix?: boolean,
) => {
const name = fileNameSource.split('.')[0];
const extension = fileNameSource.split('.')[1];
const Key = addRandomSuffix
? `${name}-${generateStorageId()}.${extension}`
: fileNameDestination;
return cloudflareR2Client().send(new CopyObjectCommand({
Bucket: CLOUDFLARE_R2_BUCKET,
CopySource: `${CLOUDFLARE_R2_BUCKET}/${fileNameSource}`,
Key,
}))
.then(() => urlForKey(fileNameDestination));
};
export const cloudflareR2List = async (
Prefix: string,
): Promise<StorageListResponse> =>
cloudflareR2Client().send(new ListObjectsCommand({
Bucket: CLOUDFLARE_R2_BUCKET,
Prefix,
}))
.then((data) => data.Contents?.map(({ Key, LastModified, Size }) => ({
url: urlForKey(Key),
fileName: Key ?? '',
uploadedAt: LastModified,
size: Size ? formatBytesToMB(Size) : undefined,
})) ?? []);
export const cloudflareR2Delete = async (Key: string) => {
cloudflareR2Client().send(new DeleteObjectCommand({
Bucket: CLOUDFLARE_R2_BUCKET,
Key,
}));
};