qualia_client_core/wellfair/api/
backup_clinical.rs1use super::super::backup::{self, BackupReport};
4use super::super::blob_store::BlobStore;
5use super::super::journal::JournalEntry;
6use super::super::sync_outbox::SyncOutbox;
7use wellfare_core::clinical::{
8 build_clinical_attachment_envelope, build_clinical_report_envelope,
9 clinical_attachment_summary, clinical_report_summary, AttachmentMeta, ClinicalReport,
10 ClinicalReportType,
11};
12
13use super::*;
14
15impl WebizenHostApi {
16 pub fn export_backup_bytes(&self) -> Result<Vec<u8>, String> {
21 backup::create_backup(&self.storage_root, Self::now_unix() as u32)
22 }
23
24 pub fn import_backup_bytes(&self, bytes: &[u8]) -> Result<BackupReport, String> {
26 backup::restore_backup(&self.storage_root, bytes)
27 }
28
29 #[cfg(not(target_arch = "wasm32"))]
31 pub fn export_backup_to_path(&self, path: &str) -> Result<BackupReport, String> {
32 let archive = backup::build_archive(&self.storage_root, Self::now_unix() as u32)?;
33 let files = archive.files.len();
34 let bytes = backup::encode_archive(&archive)?;
35 let size = bytes.len() as u64;
36 std::fs::write(path, &bytes).map_err(|e| e.to_string())?;
37 Ok(BackupReport { files, bytes: size })
38 }
39
40 #[cfg(not(target_arch = "wasm32"))]
42 pub fn import_backup_from_path(&self, path: &str) -> Result<BackupReport, String> {
43 let bytes = std::fs::read(path).map_err(|e| e.to_string())?;
44 self.import_backup_bytes(&bytes)
45 }
46
47 #[cfg(not(target_arch = "wasm32"))]
50 pub fn diagnostics_report(&self) -> Result<DiagnosticsReport, String> {
51 let journal_records = self.list_health_records(4096)?.len();
52 let outbox_queued = SyncOutbox::open(&self.storage_root)
53 .map_err(|e| e.to_string())?
54 .count_queued()
55 .map_err(|e| e.to_string())?;
56 let inbox_validated = self.validated_sync_operations()?.len();
57 let (data_files, data_bytes) = backup::wellfair_data_stats(&self.storage_root)?;
58 Ok(DiagnosticsReport {
59 crate_version: env!("CARGO_PKG_VERSION").to_string(),
60 sanctuary_configured: super::super::sanctuary_vault::is_configured(&self.storage_root),
61 sanctuary_keychain_wrapped: super::super::sanctuary_vault::is_keychain_wrapped(
62 &self.storage_root,
63 ),
64 journal_records,
65 outbox_queued,
66 inbox_validated,
67 data_files,
68 data_bytes,
69 })
70 }
71
72 pub fn add_clinical_report(
75 &mut self,
76 title: &str,
77 report_type: ClinicalReportType,
78 observed_at_unix: u32,
79 body: &str,
80 author_label: Option<String>,
81 ) -> Result<JournalEntry, String> {
82 let mut report = ClinicalReport::new(title, report_type, observed_at_unix, body);
83 report.author_label = author_label.filter(|s| !s.is_empty());
84 let hash =
85 Self::payload_hash_hex(&serde_json::to_string(&report).map_err(|e| e.to_string())?);
86 let asserted = Self::now_unix() as u32;
87 let envelope = build_clinical_report_envelope(
88 &report,
89 &self.owner_did,
90 &self.author_did,
91 asserted,
92 Some(hash),
93 );
94 let summary = clinical_report_summary(&report);
95 self.submit_record_with_summary(QAPP_CLINICAL, envelope, SOURCE_CLINICAL, Some(summary))?;
96 self.finalize_batch().ok();
97 self.latest_journal_entry()
98 }
99
100 pub fn list_clinical_reports(&self, limit: usize) -> Result<Vec<JournalEntry>, String> {
101 self.list_journal_by_kind("clinical_report", limit)
102 }
103
104 pub fn add_clinical_attachment(
107 &mut self,
108 filename: &str,
109 media_type: &str,
110 bytes: &[u8],
111 ) -> Result<JournalEntry, String> {
112 let store = BlobStore::open(&self.storage_root).map_err(|e| e.to_string())?;
113 let content_hash = store.put(bytes).map_err(|e| e.to_string())?;
114 let meta = AttachmentMeta::new(filename, media_type, bytes.len() as u64, content_hash);
115 let asserted = Self::now_unix() as u32;
116 let envelope =
117 build_clinical_attachment_envelope(&meta, &self.owner_did, &self.author_did, asserted);
118 let summary = clinical_attachment_summary(&meta);
119 self.submit_record_with_summary(QAPP_CLINICAL, envelope, SOURCE_CLINICAL, Some(summary))?;
120 self.finalize_batch().ok();
121 self.latest_journal_entry()
122 }
123
124 pub fn list_clinical_attachments(&self, limit: usize) -> Result<Vec<JournalEntry>, String> {
125 self.list_journal_by_kind("clinical_attachment", limit)
126 }
127
128 pub fn attachment_bytes(&self, record_id: &str) -> Result<Option<Vec<u8>>, String> {
131 let Some(entry) = self
132 .list_health_records(256)?
133 .into_iter()
134 .find(|e| e.id == record_id)
135 else {
136 return Ok(None);
137 };
138 let Some(hash) = entry.blob_hash else {
139 return Ok(None);
140 };
141 BlobStore::open(&self.storage_root)
142 .map_err(|e| e.to_string())?
143 .get(&hash)
144 .map_err(|e| e.to_string())
145 }
146}