alien_commands/server/
storage.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use alien_bindings::traits::Storage;
5use alien_error::Context;
6use chrono::{DateTime, Utc};
7
8#[cfg(feature = "server")]
9use object_store::path::Path as StoragePath;
10
11use crate::error::{ErrorData, Result};
12
13pub struct ArcStorageHelper {
15 storage: Arc<dyn Storage>,
16}
17
18impl ArcStorageHelper {
19 pub fn new(storage: Arc<dyn Storage>) -> Self {
20 Self { storage }
21 }
22
23 pub async fn generate_params_put_url(
25 &self,
26 command_id: &str,
27 expires_in: Duration,
28 ) -> Result<String> {
29 let path = StoragePath::from(format!("arc/commands/{}/params", command_id));
30 let presigned = self
31 .storage
32 .presigned_put(&path, expires_in)
33 .await
34 .context(ErrorData::StorageOperationFailed {
35 message: "Failed to create presigned PUT URL".to_string(),
36 operation: Some("presigned_put".to_string()),
37 path: Some(path.to_string()),
38 })?;
39
40 Ok(presigned.url())
41 }
42
43 pub async fn generate_params_get_url(
45 &self,
46 command_id: &str,
47 expires_in: Duration,
48 ) -> Result<String> {
49 let path = StoragePath::from(format!("arc/commands/{}/params", command_id));
50 let presigned = self
51 .storage
52 .presigned_get(&path, expires_in)
53 .await
54 .context(ErrorData::StorageOperationFailed {
55 message: "Failed to create presigned GET URL".to_string(),
56 operation: Some("presigned_get".to_string()),
57 path: Some(path.to_string()),
58 })?;
59
60 Ok(presigned.url())
61 }
62
63 pub async fn generate_response_put_url(
65 &self,
66 command_id: &str,
67 expires_in: Duration,
68 ) -> Result<String> {
69 let path = StoragePath::from(format!("arc/commands/{}/response", command_id));
70 let presigned = self
71 .storage
72 .presigned_put(&path, expires_in)
73 .await
74 .context(ErrorData::StorageOperationFailed {
75 message: "Failed to create response PUT URL".to_string(),
76 operation: Some("presigned_put".to_string()),
77 path: Some(path.to_string()),
78 })?;
79
80 Ok(presigned.url())
81 }
82
83 pub async fn generate_response_get_url(
85 &self,
86 command_id: &str,
87 expires_in: Duration,
88 ) -> Result<String> {
89 let path = StoragePath::from(format!("arc/commands/{}/response", command_id));
90 let presigned = self
91 .storage
92 .presigned_get(&path, expires_in)
93 .await
94 .context(ErrorData::StorageOperationFailed {
95 message: "Failed to create response GET URL".to_string(),
96 operation: Some("presigned_get".to_string()),
97 path: Some(path.to_string()),
98 })?;
99
100 Ok(presigned.url())
101 }
102
103 pub async fn cleanup_command_storage(&self, command_id: &str) -> Result<()> {
105 let params_path = StoragePath::from(format!("arc/commands/{}/params", command_id));
106 let response_path = StoragePath::from(format!("arc/commands/{}/response", command_id));
107
108 if let Err(e) = self.storage.delete(¶ms_path).await {
110 tracing::warn!("Failed to cleanup params for command {}: {}", command_id, e);
111 }
112
113 if let Err(e) = self.storage.delete(&response_path).await {
114 tracing::warn!(
115 "Failed to cleanup response for command {}: {}",
116 command_id,
117 e
118 );
119 }
120
121 Ok(())
122 }
123
124 pub fn get_base_url(&self) -> String {
126 self.storage.get_url().to_string()
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct StorageUrlConfig {
133 pub default_expires_in: Duration,
135 pub max_expires_in: Duration,
137}
138
139impl Default for StorageUrlConfig {
140 fn default() -> Self {
141 Self {
142 default_expires_in: Duration::from_secs(3600), max_expires_in: Duration::from_secs(24 * 3600), }
145 }
146}
147
148impl StorageUrlConfig {
149 pub fn validate_expires_in(&self, requested: Duration) -> Duration {
151 if requested > self.max_expires_in {
152 self.max_expires_in
153 } else if requested.is_zero() {
154 self.default_expires_in
155 } else {
156 requested
157 }
158 }
159
160 pub fn expires_in_until(&self, target: DateTime<Utc>) -> Duration {
162 let now = Utc::now();
163 if target <= now {
164 Duration::from_secs(60) } else {
166 let diff = target.signed_duration_since(now);
167 let seconds = diff.num_seconds().max(60) as u64; self.validate_expires_in(Duration::from_secs(seconds))
169 }
170 }
171}