119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import {
|
|
S3Client,
|
|
ListObjectsCommand,
|
|
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';
|
|
|
|
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 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 ? formatBytes(Size) : undefined,
|
|
})) ?? []);
|
|
|
|
export const cloudflareR2Delete = async (Key: string) => {
|
|
cloudflareR2Client().send(new DeleteObjectCommand({
|
|
Bucket: CLOUDFLARE_R2_BUCKET,
|
|
Key,
|
|
}));
|
|
};
|
|
|
|
export const cloudflareR2GetSignedUrl = (
|
|
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 });
|
|
};
|