102 lines
3.1 KiB
Rust
102 lines
3.1 KiB
Rust
use crate::config::AppConfig;
|
|
use crate::error::{AppError, AppResult};
|
|
use object_store::aws::{AmazonS3, AmazonS3Builder};
|
|
use object_store::path::Path;
|
|
use object_store::signer::Signer;
|
|
use object_store::{ObjectStoreExt, PutPayload};
|
|
use reqwest::Method;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppS3Storage {
|
|
client: Arc<AmazonS3>,
|
|
#[allow(dead_code)]
|
|
bucket: String,
|
|
public_url: Option<String>,
|
|
presigned_url_expiry: Duration,
|
|
}
|
|
|
|
impl AppS3Storage {
|
|
pub async fn from_config(config: &AppConfig) -> AppResult<Self> {
|
|
let bucket = config
|
|
.s3_bucket()?
|
|
.ok_or_else(|| AppError::Config("APP_S3_BUCKET is not set".into()))?;
|
|
|
|
let region = config.s3_region()?;
|
|
let access_key = config.s3_access_key()?;
|
|
let secret_key = config.s3_secret_key()?;
|
|
let force_path_style = config.s3_force_path_style()?;
|
|
|
|
let mut builder = AmazonS3Builder::new()
|
|
.with_bucket_name(&bucket)
|
|
.with_region(®ion)
|
|
.with_virtual_hosted_style_request(!force_path_style);
|
|
|
|
if let Some(endpoint) = config.s3_endpoint()? {
|
|
builder = builder.with_endpoint(&endpoint);
|
|
}
|
|
|
|
if let (Some(ak), Some(sk)) = (access_key, secret_key) {
|
|
builder = builder.with_access_key_id(&ak).with_secret_access_key(&sk);
|
|
}
|
|
|
|
let client = builder.build()?;
|
|
|
|
Ok(Self {
|
|
client: Arc::new(client),
|
|
bucket,
|
|
public_url: config.s3_public_url()?,
|
|
presigned_url_expiry: Duration::from_secs(config.s3_presigned_url_expiry()?),
|
|
})
|
|
}
|
|
|
|
pub async fn put(&self, key: &str, data: Vec<u8>) -> AppResult<()> {
|
|
let path = Path::from(key);
|
|
let payload = PutPayload::from_bytes(data.into());
|
|
self.client.put(&path, payload).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get(&self, key: &str) -> AppResult<Vec<u8>> {
|
|
let path = Path::from(key);
|
|
let result = self.client.get(&path).await?;
|
|
let bytes = result.bytes().await?;
|
|
Ok(bytes.to_vec())
|
|
}
|
|
|
|
pub async fn delete(&self, key: &str) -> AppResult<()> {
|
|
let path = Path::from(key);
|
|
self.client.delete(&path).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn presigned_get_url(
|
|
&self,
|
|
key: &str,
|
|
expiry: Option<Duration>,
|
|
) -> AppResult<String> {
|
|
let path = Path::from(key);
|
|
let expires = expiry.unwrap_or(self.presigned_url_expiry);
|
|
let url = self.client.signed_url(Method::GET, &path, expires).await?;
|
|
Ok(url.to_string())
|
|
}
|
|
|
|
pub async fn presigned_put_url(
|
|
&self,
|
|
key: &str,
|
|
expiry: Option<Duration>,
|
|
) -> AppResult<String> {
|
|
let path = Path::from(key);
|
|
let expires = expiry.unwrap_or(self.presigned_url_expiry);
|
|
let url = self.client.signed_url(Method::PUT, &path, expires).await?;
|
|
Ok(url.to_string())
|
|
}
|
|
|
|
pub fn public_url(&self, key: &str) -> Option<String> {
|
|
self.public_url
|
|
.as_ref()
|
|
.map(|base| format!("{}/{}", base.trim_end_matches('/'), key))
|
|
}
|
|
}
|