feat: init

This commit is contained in:
zhenyi
2026-06-07 11:30:56 +08:00
commit 563381c1ca
361 changed files with 41327 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
pub mod s3;
+101
View File
@@ -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(&region)
.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))
}
}