import "server-only"; import { CreateBucketCommand, DeleteObjectCommand, GetObjectCommand, HeadBucketCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; let client: S3Client | undefined; export function getObjectStore() { client ??= new S3Client({ endpoint: requiredEnv("S3_ENDPOINT"), region: process.env.S3_REGION ?? "us-east-1", forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true", credentials: { accessKeyId: requiredEnv("S3_ACCESS_KEY_ID"), secretAccessKey: requiredEnv("S3_SECRET_ACCESS_KEY"), }, }); return client; } export async function ensureAttachmentBucket() { const store = getObjectStore(); const Bucket = requiredEnv("S3_BUCKET"); try { await store.send(new HeadBucketCommand({ Bucket })); } catch { await store.send(new CreateBucketCommand({ Bucket })); } } export async function putAttachmentObject(input: { key: string; body: Uint8Array | ArrayBuffer; contentType: string; }) { await getObjectStore().send( new PutObjectCommand({ Bucket: requiredEnv("S3_BUCKET"), Key: input.key, Body: input.body instanceof ArrayBuffer ? new Uint8Array(input.body) : input.body, ContentType: input.contentType, }), ); } export async function deleteAttachmentObject(key: string) { await getObjectStore().send( new DeleteObjectCommand({ Bucket: requiredEnv("S3_BUCKET"), Key: key }), ); } export function getAttachmentDownloadUrl(key: string) { return getSignedUrl( getObjectStore(), new GetObjectCommand({ Bucket: requiredEnv("S3_BUCKET"), Key: key }), { expiresIn: 600 }, ); } export async function getAttachmentObject(key: string): Promise> { const result = await getObjectStore().send( new GetObjectCommand({ Bucket: requiredEnv("S3_BUCKET"), Key: key }), ); if (!result.Body) { throw new Error("Attachment object has no body"); } return result.Body.transformToWebStream() as ReadableStream; } function requiredEnv(name: string) { const value = process.env[name]; if (!value) throw new Error(`Missing required environment variable: ${name}`); return value; }