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<ReadableStream<Uint8Array>> {
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<Uint8Array>;
}
function requiredEnv(name: string) {
const value = process.env[name];
if (!value) throw new Error(`Missing required environment variable: ${name}`);
return value;
}