headless_lms_chatbot/
azure_blob_storage.rs1use std::collections::HashMap;
2
3use secrecy::ExposeSecret;
4
5use crate::prelude::*;
6use anyhow::Context;
7use azure_core::prelude::Metadata;
8use azure_storage::StorageCredentials;
9use azure_storage_blobs::prelude::*;
10use bytes::Bytes;
11use futures::StreamExt;
12use headless_lms_base::config::{ApplicationConfiguration, AzureBlobStorageConfiguration};
13
14pub struct AzureBlobClient {
16 container_client: ContainerClient,
17 pub container_name: String,
18}
19
20impl AzureBlobClient {
21 pub async fn new(
22 app_config: &ApplicationConfiguration,
23 container_name: &str,
24 ) -> anyhow::Result<Self> {
25 let azure_configuration = app_config
26 .azure_configuration
27 .as_ref()
28 .context("Azure configuration is missing")?;
29 let AzureBlobStorageConfiguration {
30 storage_account,
31 access_key,
32 } = azure_configuration
33 .blob_storage_config
34 .clone()
35 .context("Azure Blob Storage configuration is missing")?;
36
37 let container_name = container_name.to_string();
38
39 let storage_credentials = StorageCredentials::access_key(
41 &storage_account,
42 access_key.expose_secret().to_string(),
43 );
44 let blob_service_client = BlobServiceClient::new(storage_account, storage_credentials);
45 let container_client = blob_service_client.container_client(container_name.clone());
46
47 Ok(AzureBlobClient {
48 container_client,
49 container_name,
50 })
51 }
52
53 pub async fn ensure_container_exists(&self) -> anyhow::Result<()> {
55 if self.container_client.exists().await? {
56 return Ok(());
57 }
58
59 info!(
60 "Azure blob storage container '{}' does not exist. Creating...",
61 self.container_client.container_name()
62 );
63 self.container_client
64 .create()
65 .public_access(PublicAccess::None)
66 .await?;
67 Ok(())
68 }
69
70 pub async fn upload_file(
72 &self,
73 blob_path: &str,
74 file_bytes: &[u8],
75 metadata: Option<HashMap<String, Bytes>>,
76 ) -> anyhow::Result<()> {
77 let blob_client = self.container_client.blob_client(blob_path);
78
79 let mut put_blob = blob_client.put_block_blob(file_bytes.to_vec());
80
81 if let Some(meta) = metadata {
82 let mut m = Metadata::new();
83 for (key, value) in meta {
84 m.insert(key, value);
85 }
86 put_blob = put_blob.metadata(m);
87 }
88
89 put_blob.await?;
90
91 info!("Blob '{}' uploaded successfully.", blob_path);
92 Ok(())
93 }
94
95 pub async fn delete_file(&self, path: &str) -> anyhow::Result<()> {
97 let blob_client = self.container_client.blob_client(path);
98
99 blob_client.delete().await?;
100
101 info!("Blob '{}' deleted successfully.", path);
102 Ok(())
103 }
104
105 pub async fn list_files_with_prefix(&self, prefix: &str) -> anyhow::Result<Vec<String>> {
106 let mut result = Vec::new();
107 let prefix_owned = prefix.to_string();
108 let response = self.container_client.list_blobs().prefix(prefix_owned);
109 let mut stream = response.into_stream();
110
111 while let Some(list) = stream.next().await {
112 let list = list?;
113 let blobs: Vec<_> = list.blobs.blobs().collect();
114 for blob in blobs {
115 result.push(blob.name.clone());
116 }
117 }
118 Ok(result)
119 }
120}