This commit is contained in:
2025-11-08 02:03:52 +01:00
parent 9bf1fc1983
commit d24d2c9249
6 changed files with 524 additions and 1027 deletions
+36 -58
View File
@@ -1,12 +1,10 @@
use std::time::Duration;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use aws_sdk_s3::presigning::PresigningConfig;
use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::Client as S3Client;
use s3::bucket::Bucket;
use crate::models::Tenant;
@@ -33,16 +31,16 @@ pub trait ObjectStorage: Send + Sync + 'static {
}
pub struct S3Storage {
client: S3Client,
bucket: String,
bucket: Bucket,
}
impl S3Storage {
pub fn new(client: S3Client, bucket: impl Into<String>) -> Self {
Self {
client,
bucket: bucket.into(),
}
pub fn new(bucket: Bucket) -> Self {
Self { bucket }
}
fn default_content_type(content_type: Option<String>) -> String {
content_type.unwrap_or_else(|| "application/octet-stream".to_string())
}
}
@@ -55,23 +53,19 @@ impl ObjectStorage for S3Storage {
content_type: Option<String>,
content_disposition: Option<String>,
) -> Result<()> {
let mut request = self
.client
.put_object()
.bucket(&self.bucket)
.key(key)
.body(ByteStream::from(bytes));
let mut builder = self
.bucket
.put_object_builder(key, &bytes)
.with_content_type(Self::default_content_type(content_type));
if let Some(content_type) = content_type {
request = request.content_type(content_type);
if let Some(disposition) = content_disposition {
builder = builder
.with_content_disposition(disposition)
.context("invalid content disposition header")?;
}
if let Some(content_disposition) = content_disposition {
request = request.content_disposition(content_disposition);
}
request
.send()
builder
.execute()
.await
.context("failed to upload object to S3")?;
@@ -84,51 +78,35 @@ impl ObjectStorage for S3Storage {
expires_in: Duration,
response_content_disposition: Option<&str>,
) -> Result<String> {
let presign_config = PresigningConfig::builder()
.expires_in(expires_in)
.build()
.context("failed to build S3 presigning config")?;
let expiry_secs = u32::try_from(expires_in.as_secs())
.context("presign expiry exceeds u32 range")?;
let mut request = self.client.get_object().bucket(&self.bucket).key(key);
let mut queries = HashMap::new();
if let Some(value) = response_content_disposition {
request = request.response_content_disposition(value);
queries.insert(
"response-content-disposition".to_string(),
value.to_string(),
);
}
let presigned = request
.presigned(presign_config)
self.bucket
.presign_get(key, expiry_secs, (!queries.is_empty()).then_some(queries))
.await
.context("failed to generate presigned download URL")?;
Ok(presigned.uri().to_string())
.context("failed to generate presigned download URL")
}
async fn get_object(&self, key: &str) -> Result<Vec<u8>> {
let response = self
.client
.get_object()
.bucket(&self.bucket)
.key(key)
.send()
let data = self
.bucket
.get_object(key)
.await
.context("failed to download object from S3")?;
let bytes = response
.body
.collect()
.await
.context("failed to read object stream")?
.into_bytes()
.to_vec();
Ok(bytes)
Ok(data.into_bytes().to_vec())
}
async fn delete_object(&self, key: &str) -> Result<()> {
self.client
.delete_object()
.bucket(&self.bucket)
.key(key)
.send()
self.bucket
.delete_object(key)
.await
.context("failed to delete object from S3")?;
Ok(())