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