use s3::bucket::Bucket; use s3::creds::Credentials; use s3::region::Region; #[derive(Clone)] pub struct Storage { bucket: Box, } impl Storage { pub fn new( endpoint: &str, bucket_name: &str, access_key: &str, secret_key: &str, ) -> Result> { let region = Region::Custom { region: "us-east-1".to_string(), endpoint: endpoint.to_string(), }; let credentials = Credentials::new(Some(access_key), Some(secret_key), None, None, None)?; let bucket = Bucket::new(bucket_name, region, credentials)?.with_path_style(); Ok(Self { bucket }) } pub async fn put(&self, key: &str, data: &[u8], content_type: &str) -> Result<(), String> { self.bucket .put_object_with_content_type(key, data, content_type) .await .map_err(|e| format!("S3 put failed: {e}"))?; Ok(()) } pub async fn get(&self, key: &str) -> Result<(Vec, String), String> { let response = self .bucket .get_object(key) .await .map_err(|e| format!("S3 get failed: {e}"))?; let content_type = response .headers() .get("content-type") .cloned() .unwrap_or_else(|| "application/octet-stream".to_string()); Ok((response.to_vec(), content_type)) } pub async fn delete(&self, key: &str) -> Result<(), String> { self.bucket .delete_object(key) .await .map_err(|e| format!("S3 delete failed: {e}"))?; Ok(()) } }