use crate::error::AppError;
use async_trait::async_trait;
use chrono::{Duration, Utc};
use google_cloud_storage::client::{Client, ClientConfig};
use google_cloud_storage::http::objects::delete::DeleteObjectRequest;
use google_cloud_storage::http::objects::upload::{Media, UploadObjectRequest, UploadType};
use google_cloud_storage::sign::{SignedURLMethod, SignedURLOptions};
use super::PresignResult;
use crate::storage::StorageProvider;
pub struct GcsProvider {
client: Client,
bucket: String,
}
impl GcsProvider {
pub async fn from_env() -> Option<Self> {
let bucket = std::env::var("GCS_BUCKET").ok()?;
let config = if let Ok(json) = std::env::var("GCS_SERVICE_ACCOUNT_JSON") {
let creds = google_cloud_auth::credentials::CredentialsFile::new_from_str(&json)
.await
.map_err(|e| tracing::error!("GCS_SERVICE_ACCOUNT_JSON parse error: {}", e))
.ok()?;
ClientConfig::default()
.with_credentials(creds)
.await
.map_err(|e| tracing::error!("GCS client config error: {}", e))
.ok()?
} else {
ClientConfig::default()
.with_auth()
.await
.map_err(|e| tracing::error!("GCS auth error: {}", e))
.ok()?
};
Some(GcsProvider {
client: Client::new(config),
bucket,
})
}
}
#[async_trait]
impl StorageProvider for GcsProvider {
async fn upload(&self, path: &str, data: Vec<u8>, content_type: &str) -> Result<(), AppError> {
let mut media = Media::new(path.to_string());
media.content_type = std::borrow::Cow::Owned(content_type.to_string());
let upload_type = UploadType::Simple(media);
self.client
.upload_object(
&UploadObjectRequest {
bucket: self.bucket.clone(),
..Default::default()
},
data,
&upload_type,
)
.await
.map_err(|e| AppError::Storage(format!("GCS upload error: {}", e)))?;
Ok(())
}
async fn presign_url(&self, path: &str, expires_secs: u64) -> Result<PresignResult, AppError> {
let expires_at = Utc::now() + Duration::seconds(expires_secs as i64);
let opts = SignedURLOptions {
method: SignedURLMethod::GET,
expires: std::time::Duration::from_secs(expires_secs),
..Default::default()
};
let url = self
.client
.signed_url(&self.bucket, path, None, None, opts)
.await
.map_err(|e| AppError::Storage(format!("GCS signed URL error: {}", e)))?;
Ok(PresignResult {
url,
expires_at,
expires_in: expires_secs,
})
}
async fn delete(&self, path: &str) -> Result<(), AppError> {
self.client
.delete_object(&DeleteObjectRequest {
bucket: self.bucket.clone(),
object: path.to_string(),
..Default::default()
})
.await
.map_err(|e| AppError::Storage(format!("GCS delete error: {}", e)))?;
Ok(())
}
}