import { DeleteObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3" import { buildMediaStorageKey } from "@/lib/media/storage-key" type StoreS3UploadParams = { file: File tenantId: string assetId: string fileRole: string variant: string } type StoredUpload = { storageKey: string } type S3Config = { bucket: string region: string endpoint?: string accessKeyId: string secretAccessKey: string forcePathStyle?: boolean } function parseBoolean(value: string | undefined): boolean { return value?.toLowerCase() === "true" } export function resolveS3Config(): S3Config { const bucket = process.env.CMS_MEDIA_S3_BUCKET?.trim() const region = process.env.CMS_MEDIA_S3_REGION?.trim() const accessKeyId = process.env.CMS_MEDIA_S3_ACCESS_KEY_ID?.trim() const secretAccessKey = process.env.CMS_MEDIA_S3_SECRET_ACCESS_KEY?.trim() const endpoint = process.env.CMS_MEDIA_S3_ENDPOINT?.trim() || undefined if (!bucket || !region || !accessKeyId || !secretAccessKey) { throw new Error( "S3 storage selected but required env vars are missing: CMS_MEDIA_S3_BUCKET, CMS_MEDIA_S3_REGION, CMS_MEDIA_S3_ACCESS_KEY_ID, CMS_MEDIA_S3_SECRET_ACCESS_KEY", ) } return { bucket, region, endpoint, accessKeyId, secretAccessKey, forcePathStyle: parseBoolean(process.env.CMS_MEDIA_S3_FORCE_PATH_STYLE), } } export function createS3Client(config: S3Config): S3Client { return new S3Client({ region: config.region, endpoint: config.endpoint, forcePathStyle: config.forcePathStyle, credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey, }, }) } export async function storeUploadToS3(params: StoreS3UploadParams): Promise { const config = resolveS3Config() const client = createS3Client(config) const storageKey = buildMediaStorageKey({ tenantId: params.tenantId, assetId: params.assetId, fileRole: params.fileRole, variant: params.variant, fileName: params.file.name, }) const payload = new Uint8Array(await params.file.arrayBuffer()) await client.send( new PutObjectCommand({ Bucket: config.bucket, Key: storageKey, Body: payload, ContentType: params.file.type || undefined, ContentLength: params.file.size, CacheControl: "public, max-age=31536000, immutable", }), ) return { storageKey } } export async function deleteS3Object(storageKey: string): Promise { const config = resolveS3Config() const client = createS3Client(config) await client.send( new DeleteObjectCommand({ Bucket: config.bucket, Key: storageKey, }), ) return true }