import { S3Client, PutObjectCommand, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const RUSTFS_ENDPOINT = process.env.RUSTFS_ENDPOINT || "http://localhost:9000"; const RUSTFS_ACCESS_KEY = process.env.RUSTFS_ACCESS_KEY || "minioadmin"; const RUSTFS_SECRET_KEY = process.env.RUSTFS_SECRET_KEY || "minioadmin"; const RUSTFS_BUCKET = process.env.RUSTFS_BUCKET || "surf"; const RUSTFS_PUBLIC = (process.env.RUSTFS_PUBLIC || "true") === "true"; let client: S3Client | null = null; function getRustFsClient(): S3Client { if (!client) { client = new S3Client({ endpoint: RUSTFS_ENDPOINT, region: "us-east-1", credentials: { accessKeyId: RUSTFS_ACCESS_KEY, secretAccessKey: RUSTFS_SECRET_KEY, }, forcePathStyle: true, }); } return client; } function getPublicUrl(key: string): string { if (RUSTFS_PUBLIC) { return `${RUSTFS_ENDPOINT}/${RUSTFS_BUCKET}/${key}`; } return ""; } export function getObjectUrl(key: string): string { const publicUrl = getPublicUrl(key); if (publicUrl) return publicUrl; return `/api/videos/file/${encodeURIComponent(key)}`; } export async function uploadObject( key: string, body: Buffer, contentType: string, ): Promise { const s3 = getRustFsClient(); await s3.send( new PutObjectCommand({ Bucket: RUSTFS_BUCKET, Key: key, Body: body, ContentType: contentType, }), ); } export async function deleteObject(key: string): Promise { const s3 = getRustFsClient(); await s3.send( new DeleteObjectCommand({ Bucket: RUSTFS_BUCKET, Key: key, }), ); } export async function getPresignedUploadUrl( key: string, contentType: string, expiresInSeconds = 3600, ): Promise { const s3 = getRustFsClient(); const command = new PutObjectCommand({ Bucket: RUSTFS_BUCKET, Key: key, ContentType: contentType, }); return getSignedUrl(s3, command, { expiresIn: expiresInSeconds }); } export async function createMultipartUpload( key: string, contentType: string, ): Promise<{ uploadId: string }> { const s3 = getRustFsClient(); const result = await s3.send( new CreateMultipartUploadCommand({ Bucket: RUSTFS_BUCKET, Key: key, ContentType: contentType, }), ); if (!result.UploadId) { throw new Error("Failed to initiate multipart upload"); } return { uploadId: result.UploadId }; } export async function getPresignedUploadPartUrl( key: string, uploadId: string, partNumber: number, expiresInSeconds = 3600, ): Promise { const s3 = getRustFsClient(); const command = new UploadPartCommand({ Bucket: RUSTFS_BUCKET, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return getSignedUrl(s3, command, { expiresIn: expiresInSeconds }); } export async function completeMultipartUpload( key: string, uploadId: string, parts: { PartNumber: number; ETag: string }[], ): Promise { const s3 = getRustFsClient(); await s3.send( new CompleteMultipartUploadCommand({ Bucket: RUSTFS_BUCKET, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts.sort((a, b) => a.PartNumber - b.PartNumber), }, }), ); } export async function abortMultipartUpload( key: string, uploadId: string, ): Promise { const s3 = getRustFsClient(); await s3.send( new AbortMultipartUploadCommand({ Bucket: RUSTFS_BUCKET, Key: key, UploadId: uploadId, }), ); }