Skip to main content

atlas_cli/storage/
database.rs

1use crate::error::{Error, Result};
2use crate::manifest::utils::{determine_manifest_type, manifest_type_to_string};
3use crate::storage::traits::{ManifestMetadata, ManifestType, StorageBackend};
4use atlas_c2pa_lib::manifest::Manifest;
5use reqwest::blocking::Client;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::time::Duration;
9
10#[derive(Debug, Clone)]
11pub struct DatabaseStorage {
12    base_url: String,
13    client: Client,
14}
15
16impl DatabaseStorage {
17    pub fn print_manifest_structure(value: &Value, indent: usize) {
18        let spaces = " ".repeat(indent);
19        match value {
20            Value::Object(map) => {
21                for (key, value) in map {
22                    println!("{spaces}{key}: ");
23                    Self::print_manifest_structure(value, indent + 2);
24                }
25            }
26            Value::Array(arr) => {
27                for value in arr {
28                    Self::print_manifest_structure(value, indent + 2);
29                }
30            }
31            _ => println!("{spaces}{value}"),
32        }
33    }
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37struct StoredManifest {
38    #[serde(skip_serializing_if = "Option::is_none")]
39    _id: Option<serde_json::Value>,
40    manifest_id: String,
41    manifest_type: String,
42    manifest: Value,
43    created_at: String,
44}
45
46impl DatabaseStorage {
47    pub fn new(url: String) -> Result<Self> {
48        let client = Client::builder()
49            .timeout(Duration::from_secs(30))
50            .build()
51            .map_err(|e| Error::Storage(format!("Failed to create HTTP client: {e}")))?;
52
53        Ok(Self {
54            base_url: url.trim_end_matches('/').to_string(),
55            client,
56        })
57    }
58
59    fn manifest_url(&self, id: Option<&str>) -> String {
60        match id {
61            Some(id) => format!("{}/manifests/{}", self.base_url, id),
62            None => format!("{}/manifests", self.base_url),
63        }
64    }
65}
66
67impl StorageBackend for DatabaseStorage {
68    fn get_base_uri(&self) -> String {
69        self.base_url.clone()
70    }
71
72    fn store_manifest(&self, manifest: &Manifest) -> Result<String> {
73        // Check if this ID already exists
74        let existing = self
75            .client
76            .get(format!(
77                "{}/manifests/{}",
78                self.base_url, &manifest.instance_id
79            ))
80            .send()
81            .map_err(|e| Error::Storage(format!("Failed to check existing manifest: {e}")))?;
82
83        if existing.status().is_success() {
84            // Manifest exists - create a new version
85
86            // Parse the existing ID
87            let parts: Vec<&str> = manifest.instance_id.split(':').collect();
88            let uuid_part = if parts.len() >= 3 {
89                parts[2].to_string() // Extract UUID from urn:c2pa:UUID format
90            } else {
91                manifest.instance_id.clone() // Use as-is if not in expected format
92            };
93
94            // Extract claim generator info
95            let claim_generator = manifest.claim_generator.replace('/', "_");
96
97            // Get all manifests to find highest version
98            let all_manifests_response = self
99                .client
100                .get(format!("{}/manifests", self.base_url))
101                .send()
102                .map_err(|e| Error::Storage(format!("Failed to list manifests: {e}")))?;
103
104            let all_manifests: Vec<serde_json::Value> = all_manifests_response
105                .json()
106                .map_err(|e| Error::Storage(format!("Failed to parse manifests list: {e}")))?;
107
108            // Find highest version for this ID
109            let mut max_version = 0;
110            for manifest_entry in all_manifests {
111                if let Some(id) = manifest_entry.get("manifest_id").and_then(|v| v.as_str()) {
112                    if id.starts_with(&format!("urn:c2pa:{uuid_part}:")) {
113                        let id_parts: Vec<&str> = id.split(':').collect();
114                        if id_parts.len() >= 5 {
115                            if let Some(version_reason) = id_parts.get(4) {
116                                if let Some(version_str) = version_reason.split('_').next() {
117                                    if let Ok(version) = version_str.parse::<i32>() {
118                                        max_version = max_version.max(version);
119                                    }
120                                }
121                            }
122                        }
123                    }
124                }
125            }
126
127            // Create new versioned ID
128            // Reason code 1 = Updated manifest
129            let versioned_id = format!(
130                "urn:c2pa:{}:{}:{}_{}",
131                uuid_part,
132                claim_generator,
133                max_version + 1,
134                1
135            );
136
137            // Create a copy of the manifest with the new ID
138            let mut updated_manifest = manifest.clone();
139            updated_manifest.instance_id = versioned_id.clone();
140
141            // Store the manifest with the versioned ID
142            let manifest_type = manifest_type_to_string(&determine_manifest_type(manifest));
143
144            let stored_manifest = StoredManifest {
145                _id: None,
146                manifest_id: versioned_id.clone(),
147                manifest_type,
148                manifest: serde_json::to_value(&updated_manifest)
149                    .map_err(|e| Error::Serialization(e.to_string()))?,
150                created_at: time::OffsetDateTime::now_utc().to_string(),
151            };
152
153            self.client
154                .post(self.manifest_url(Some(&versioned_id)))
155                .json(&stored_manifest)
156                .send()
157                .map_err(|e| Error::Storage(format!("Failed to store manifest: {e}")))?;
158
159            Ok(versioned_id)
160        } else {
161            // No existing manifest - store normally
162            let manifest_type = manifest_type_to_string(&determine_manifest_type(manifest));
163
164            let stored_manifest = StoredManifest {
165                _id: None,
166                manifest_id: manifest.instance_id.clone(),
167                manifest_type,
168                manifest: serde_json::to_value(manifest)
169                    .map_err(|e| Error::Serialization(e.to_string()))?,
170                created_at: time::OffsetDateTime::now_utc().to_string(),
171            };
172
173            self.client
174                .post(self.manifest_url(Some(&manifest.instance_id)))
175                .json(&stored_manifest)
176                .send()
177                .map_err(|e| Error::Storage(format!("Failed to store manifest: {e}")))?;
178
179            Ok(manifest.instance_id.clone())
180        }
181    }
182
183    fn retrieve_manifest(&self, id: &str) -> Result<Manifest> {
184        // Parse the ID to find the base UUID part
185        let parts: Vec<&str> = id.split(':').collect();
186        let uuid_part = if parts.len() >= 3 && parts[0] == "urn" && parts[1] == "c2pa" {
187            parts[2] // Extract UUID from urn:c2pa:UUID format
188        } else {
189            id // Use as-is if not in expected format
190        };
191
192        // First try direct retrieval with the given ID
193        let response = self
194            .client
195            .get(format!("{}/manifests/{}", self.base_url, id))
196            .send()
197            .map_err(|e| Error::Storage(format!("Failed to retrieve manifest: {e}")))?;
198
199        if response.status().is_success() {
200            // Found the manifest, parse it
201            let stored_manifest: StoredManifest = response
202                .json()
203                .map_err(|e| Error::Storage(format!("Failed to parse manifest: {e}")))?;
204
205            // Extract the inner manifest
206            let manifest_value = stored_manifest
207                .manifest
208                .get("manifest")
209                .ok_or_else(|| Error::Storage("Invalid manifest structure".to_string()))?;
210
211            return serde_json::from_value(manifest_value.clone())
212                .map_err(|e| Error::Storage(format!("Failed to parse manifest data: {e}")));
213        }
214
215        // If direct lookup failed, try to find all versions
216        let list_response = self
217            .client
218            .get(format!("{}/manifests", self.base_url))
219            .send()
220            .map_err(|e| Error::Storage(format!("Failed to list manifests: {e}")))?;
221
222        if !list_response.status().is_success() {
223            return Err(Error::Storage(format!(
224                "Failed to list manifests. Status: {}",
225                list_response.status()
226            )));
227        }
228
229        // Parse the manifest list
230        let manifests: Vec<StoredManifest> = list_response
231            .json()
232            .map_err(|e| Error::Storage(format!("Failed to parse manifests list: {e}")))?;
233
234        // Find all versions of this manifest
235        let mut versions: Vec<StoredManifest> = manifests
236            .into_iter()
237            .filter(|m| m.manifest_id.contains(&format!("urn:c2pa:{uuid_part}:")))
238            .collect();
239
240        if versions.is_empty() {
241            return Err(Error::Storage(format!("Manifest not found for ID: {id}")));
242        }
243
244        // Sort by created_at timestamp (newest first)
245        versions.sort_by(|a, b| b.created_at.cmp(&a.created_at));
246
247        // Get the latest version
248        let latest = &versions[0];
249
250        // Extract the inner manifest
251        let manifest_value = latest
252            .manifest
253            .get("manifest")
254            .ok_or_else(|| Error::Storage("Invalid manifest structure".to_string()))?;
255
256        serde_json::from_value(manifest_value.clone())
257            .map_err(|e| Error::Storage(format!("Failed to parse manifest data: {e}")))
258    }
259
260    fn list_manifests(&self) -> Result<Vec<ManifestMetadata>> {
261        let response = self
262            .client
263            .get(self.manifest_url(None))
264            .send()
265            .map_err(|e| Error::Storage(format!("Failed to list manifests: {e}")))?;
266
267        if !response.status().is_success() {
268            return Err(Error::Storage(format!(
269                "Failed to list manifests. Status: {}",
270                response.status()
271            )));
272        }
273
274        let stored_manifests: Vec<StoredManifest> = response
275            .json()
276            .map_err(|e| Error::Storage(format!("Failed to parse manifests list: {e}")))?;
277
278        Ok(stored_manifests
279            .into_iter()
280            .map(|m| {
281                let title = m
282                    .manifest
283                    .get("manifest")
284                    .and_then(|inner| inner.get("manifest"))
285                    .and_then(|manifest| manifest.get("title"))
286                    .and_then(|t| t.as_str())
287                    .unwrap_or("Unknown")
288                    .to_string();
289
290                ManifestMetadata {
291                    id: m.manifest_id,
292                    name: title,
293                    manifest_type: match m.manifest_type.as_str() {
294                        "dataset" => ManifestType::Dataset,
295                        _ => ManifestType::Model,
296                    },
297                    created_at: m.created_at,
298                }
299            })
300            .collect())
301    }
302
303    fn delete_manifest(&self, id: &str) -> Result<()> {
304        let response = self
305            .client
306            .delete(self.manifest_url(Some(id)))
307            .send()
308            .map_err(|e| Error::Storage(format!("Failed to delete manifest: {e}")))?;
309
310        if !response.status().is_success() {
311            return Err(Error::Storage(format!(
312                "Failed to delete manifest. Status: {}",
313                response.status()
314            )));
315        }
316
317        Ok(())
318    }
319
320    // Added for test suite
321    fn as_any(&self) -> &dyn std::any::Any {
322        self
323    }
324}