feat: init
This commit is contained in:
+101
@@ -0,0 +1,101 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user