added storage library

This commit is contained in:
2025-06-30 22:58:25 -04:00
parent 585728de9d
commit a6b4f6917b
8 changed files with 3814 additions and 0 deletions

2
backend/storage/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target/
.env

3589
backend/storage/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
[package]
name = "storage"
description = "Internal object storage library"
version = "0.1.0"
edition = "2024"
[dependencies]
aws-sdk-s3 = "1.94.0"
aws-config = "1.8"
azure_core = "0.25.0"
azure_storage = "0.21.0"
azure_storage_blobs = "0.21.0"
async-trait = "0.1"
tokio = { version = "1.0", features = ["full"] }
thiserror = "2.0.12"

View File

@ -0,0 +1,11 @@
use azure_core::error::HttpError;
#[derive(Debug, thiserror::Error)]
pub enum AdapterError {
#[error("Azure error: {0}")]
Azure(#[from] azure_core::Error),
#[error("HTTP error: {0}")]
Http(#[from] HttpError),
#[error("Serialization error: {0}")]
Serialization(String),
}

View File

@ -0,0 +1,2 @@
pub mod error;
pub mod services;

View File

@ -0,0 +1,101 @@
use crate::{error::AdapterError, services::ObjectStorageClient};
use async_trait::async_trait;
use aws_config::{BehaviorVersion, Region};
use aws_sdk_s3::{Client, Config, config::Credentials};
use std::env;
#[derive(Debug)]
pub struct S3ClientConfig {
pub access_key: String,
secret_key: String,
endpoint: String,
pub bucket: String,
region: String,
}
pub struct S3Client {
client: Client,
}
impl S3ClientConfig {
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
Ok(S3ClientConfig {
access_key: env::var("S3_ACCESS_KEY")
.map_err(|_| "S3_ACCESS_KEY environment variable not set")?,
secret_key: env::var("S3_SECRET_KEY")
.map_err(|_| "S3_SECRET_KEY environment variable not set")?,
endpoint: env::var("S3_ENDPOINT")
.unwrap_or_else(|_| "us-ord-1.linodeobjects.com".to_string()),
bucket: env::var("S3_BUCKET").map_err(|_| "S3_BUCKET environment variable not set")?,
region: env::var("S3_REGION").unwrap_or_else(|_| "us-ord".to_string()),
})
}
}
impl S3Client {
pub fn new(config: &S3ClientConfig) -> Self {
let credentials = Credentials::new(
&config.access_key,
&config.secret_key,
None,
None,
"linode-object-storage",
);
let s3_config = Config::builder()
.behavior_version(BehaviorVersion::latest())
.region(Region::new(config.region.clone()))
.endpoint_url(format!("https://{}", config.endpoint))
.credentials_provider(credentials)
.force_path_style(false)
.build();
Self {
client: Client::from_conf(s3_config),
}
}
}
#[async_trait]
impl ObjectStorageClient for S3Client {
type Error = AdapterError;
async fn put_object(&self, bucket: &str, key: &str, data: Vec<u8>) -> Result<(), Self::Error> {
println!("Uploading to S3 (or S3 like) Object Storage...");
println!("Bucket: {}", bucket);
let _ = self
.client
.put_object()
.bucket(bucket)
.key(key)
.body(data.into())
.acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
.content_type("application/xml")
.send()
.await
.unwrap();
Ok(())
}
async fn get_object(&self, _bucket: &str, _key: &str) -> Result<Vec<u8>, Self::Error> {
todo!("not impl")
}
async fn delete_object(&self, _bucket: &str, _key: &str) -> Result<(), Self::Error> {
todo!("not impl")
}
async fn list_objects(
&self,
_bucket: &str,
_prefix: Option<&str>,
) -> Result<Vec<String>, Self::Error> {
todo!("not impl")
}
async fn object_exists(&self, _bucket: &str, _key: &str) -> Result<bool, Self::Error> {
todo!("not impl")
}
}

View File

@ -0,0 +1,71 @@
use crate::error::AdapterError;
use async_trait::async_trait;
use azure_storage::prelude::*;
use azure_storage_blobs::prelude::*;
use super::ObjectStorageClient;
pub struct AzureBlobClient {
client: BlobServiceClient,
}
impl AzureBlobClient {
pub fn new(account_name: &str, account_key: String) -> Self {
let storage_credentials = StorageCredentials::access_key(account_name, account_key);
let client = BlobServiceClient::new(account_name, storage_credentials);
Self { client }
}
// Helper method to get container client
fn get_container_client(&self, container_name: &str) -> ContainerClient {
self.client.container_client(container_name)
}
// Helper method to get blob client
fn get_blob_client(&self, container_name: &str, blob_name: &str) -> BlobClient {
self.get_container_client(container_name)
.blob_client(blob_name)
}
}
#[async_trait]
impl ObjectStorageClient for AzureBlobClient {
type Error = AdapterError;
async fn put_object(
&self,
bucket: &str, // container name
key: &str, // blob name
data: Vec<u8>,
) -> Result<(), Self::Error> {
let blob_client = self.get_blob_client(bucket, key);
let _request = blob_client.put_block_blob(data).await.unwrap();
Ok(())
}
async fn get_object(&self, bucket: &str, key: &str) -> Result<Vec<u8>, Self::Error> {
let blob_client = self.get_blob_client(bucket, key);
let response = blob_client.get_content().await.unwrap();
Ok(response)
}
async fn delete_object(&self, bucket: &str, key: &str) -> Result<(), Self::Error> {
let blob_client = self.get_blob_client(bucket, key);
blob_client.delete().await.unwrap();
Ok(())
}
async fn list_objects(
&self,
_bucket: &str,
_prefix: Option<&str>,
) -> Result<Vec<String>, Self::Error> {
todo!("not impl")
}
async fn object_exists(&self, _bucket: &str, _key: &str) -> Result<bool, Self::Error> {
todo!("not impl")
}
}

View File

@ -0,0 +1,23 @@
pub mod aws;
pub mod azure;
use async_trait::async_trait;
#[async_trait]
pub trait ObjectStorageClient {
type Error;
async fn put_object(&self, bucket: &str, key: &str, data: Vec<u8>) -> Result<(), Self::Error>;
async fn get_object(&self, bucket: &str, key: &str) -> Result<Vec<u8>, Self::Error>;
async fn delete_object(&self, bucket: &str, key: &str) -> Result<(), Self::Error>;
async fn list_objects(
&self,
bucket: &str,
prefix: Option<&str>,
) -> Result<Vec<String>, Self::Error>;
async fn object_exists(&self, bucket: &str, key: &str) -> Result<bool, Self::Error>;
}