const { S3Client, PutObjectCommand, DeleteObjectCommand, } = require("@aws-sdk/client-s3"); const config = { bucket: process.env.BUCKET_NAME, region: process.env.BUCKET_REGION || "us-west-2", endpoint: process.env.PARSPACK_S3_ENDPOINT, accessKeyId: process.env.BUCKET_ACCESS_KEY, secretAccessKey: process.env.BUCKET_SECRET_KEY, publicUrl: process.env.BUCKET_PUBLIC_URL || process.env.PARSPACK_S3_ENDPOINT, }; let s3Client = null; const isConfigured = () => Boolean( config.bucket && config.endpoint && config.accessKeyId && config.secretAccessKey ); const getClient = () => { if (!isConfigured()) { throw new Error("S3 storage is not configured"); } if (!s3Client) { s3Client = new S3Client({ region: config.region, endpoint: config.endpoint, credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey, }, forcePathStyle: true, }); } return s3Client; }; const normalizeKey = (key) => key.replace(/^\/+/, ""); const getPublicUrl = (key) => { const normalizedKey = normalizeKey(key); const baseUrl = (config.publicUrl || config.endpoint).replace(/\/+$/, ""); return `${baseUrl}/${normalizedKey}`; }; const uploadBuffer = async ( key, buffer, contentType = "application/octet-stream" ) => { const normalizedKey = normalizeKey(key); await getClient().send( new PutObjectCommand({ Bucket: config.bucket, Key: normalizedKey, Body: buffer, ContentType: contentType, ACL: "public-read", }) ); return { key: normalizedKey, url: getPublicUrl(normalizedKey), }; }; const deleteObject = async (key) => { const normalizedKey = normalizeKey(key); await getClient().send( new DeleteObjectCommand({ Bucket: config.bucket, Key: normalizedKey, }) ); }; module.exports = { isConfigured, getPublicUrl, uploadBuffer, deleteObject, };