import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand, } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; let cachedClient: S3Client | null = null; let cachedBucket: string | null = null; function getClient() { if (cachedClient && cachedBucket) { return { s3: cachedClient, bucket: cachedBucket }; } const { R2_ENDPOINT, R2_REGION = "auto", R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, } = process.env; if (!R2_ENDPOINT || !R2_ACCESS_KEY_ID || !R2_SECRET_ACCESS_KEY || !R2_BUCKET) { throw new Error("R2 configuration is missing. Check env variables."); } cachedBucket = R2_BUCKET; cachedClient = new S3Client({ endpoint: R2_ENDPOINT, region: R2_REGION, credentials: { accessKeyId: R2_ACCESS_KEY_ID, secretAccessKey: R2_SECRET_ACCESS_KEY, }, }); return { s3: cachedClient, bucket: cachedBucket }; } export async function uploadToR2(params: { key: string; contentType: string; body: Buffer; }) { const { s3, bucket } = getClient(); const command = new PutObjectCommand({ Bucket: bucket, Key: params.key, Body: params.body, ContentType: params.contentType, }); await s3.send(command); } export async function getSignedDownloadUrl(key: string, expiresInSeconds = 300) { const { s3, bucket } = getClient(); const command = new GetObjectCommand({ Bucket: bucket, Key: key, }); return getSignedUrl(s3, command, { expiresIn: expiresInSeconds }); } export async function deleteFromR2(key: string) { const { s3, bucket } = getClient(); const command = new DeleteObjectCommand({ Bucket: bucket, Key: key, }); await s3.send(command); }