qualia_client_core/wellfair/api/
coop.rs1use super::super::blob_store::BlobStore;
4use super::super::journal::JournalEntry;
5use wellfare_core::credentials::{
6 build_credential_envelope, build_presentation, credential_summary, CredentialRecord,
7 FieldSelectedPresentation,
8};
9use wellfare_core::projects::{
10 build_contribution_envelope, build_membership_envelope, build_project_envelope,
11 contribution_summary, derive_obligations, membership_summary, project_summary, Contribution,
12 Obligation, Project, ProjectMembership,
13};
14
15use super::*;
16
17impl WebizenHostApi {
18 pub fn add_project(&mut self, project: &Project) -> Result<JournalEntry, String> {
21 let asserted = Self::now_unix() as u32;
22 let hash =
23 Self::payload_hash_hex(&serde_json::to_string(project).map_err(|e| e.to_string())?);
24 let envelope = build_project_envelope(
25 project,
26 &self.owner_did,
27 &self.author_did,
28 asserted,
29 Some(hash),
30 );
31 let summary = project_summary(project);
32 self.submit_record_with_summary(QAPP_PROJECTS, envelope, SOURCE_PROJECTS, Some(summary))?;
33 self.finalize_batch().ok();
34 self.latest_journal_entry()
35 }
36
37 pub fn add_project_membership(
38 &mut self,
39 membership: &ProjectMembership,
40 ) -> Result<JournalEntry, String> {
41 let asserted = Self::now_unix() as u32;
42 let hash =
43 Self::payload_hash_hex(&serde_json::to_string(membership).map_err(|e| e.to_string())?);
44 let envelope = build_membership_envelope(
45 membership,
46 &self.owner_did,
47 &self.author_did,
48 asserted,
49 Some(hash),
50 );
51 let summary = membership_summary(membership);
52 self.submit_record_with_summary(QAPP_PROJECTS, envelope, SOURCE_PROJECTS, Some(summary))?;
53 self.finalize_batch().ok();
54 self.latest_journal_entry()
55 }
56
57 pub fn add_contribution(
58 &mut self,
59 contribution: &Contribution,
60 ) -> Result<JournalEntry, String> {
61 let asserted = Self::now_unix() as u32;
62 let hash = Self::payload_hash_hex(
63 &serde_json::to_string(contribution).map_err(|e| e.to_string())?,
64 );
65 let envelope = build_contribution_envelope(
66 contribution,
67 &self.owner_did,
68 &self.author_did,
69 asserted,
70 Some(hash),
71 );
72 let summary = contribution_summary(contribution);
73 self.submit_record_with_summary(QAPP_PROJECTS, envelope, SOURCE_PROJECTS, Some(summary))?;
74 self.finalize_batch().ok();
75 self.latest_journal_entry()
76 }
77
78 pub fn list_contributions(&self, limit: usize) -> Result<Vec<JournalEntry>, String> {
79 self.list_journal_by_kind("contribution", limit)
80 }
81
82 fn local_contributions(&self, limit: usize) -> Result<Vec<Contribution>, String> {
84 let mut out = Vec::new();
85 for row in self.list_contributions(limit)? {
86 if let Some(ref summary) = row.summary {
87 if let Some(c) =
88 contribution_from_summary(row.id.clone(), summary, row.asserted_time_unix)
89 {
90 out.push(c);
91 }
92 }
93 }
94 Ok(out)
95 }
96
97 pub fn project_obligations(&self, limit: usize) -> Result<Vec<Obligation>, String> {
101 Ok(derive_obligations(&self.local_contributions(limit)?))
102 }
103
104 pub fn synced_project_obligations(&self, limit: usize) -> Result<Vec<Obligation>, String> {
110 let mut contributions = self.local_contributions(limit)?;
111 for op in self.validated_sync_operations()? {
112 if op.kind == "contribution" {
113 if let Some(c) = contribution_from_summary(
114 op.record_id.clone(),
115 &op.payload_summary,
116 op.committed_unix,
117 ) {
118 contributions.push(c);
119 }
120 }
121 }
122 Ok(derive_obligations(&contributions))
123 }
124
125 pub fn add_credential(
128 &mut self,
129 credential: &CredentialRecord,
130 ) -> Result<JournalEntry, String> {
131 let asserted = Self::now_unix() as u32;
132 let json = serde_json::to_string(credential).map_err(|e| e.to_string())?;
133 let hash = BlobStore::open(&self.storage_root)
136 .and_then(|store| store.put(json.as_bytes()))
137 .map_err(|e| e.to_string())?;
138 let envelope = build_credential_envelope(
139 credential,
140 &self.owner_did,
141 &self.author_did,
142 asserted,
143 Some(hash),
144 );
145 let summary = credential_summary(credential);
146 self.submit_record_with_summary(
147 QAPP_CREDENTIALS,
148 envelope,
149 SOURCE_CREDENTIALS,
150 Some(summary),
151 )?;
152 self.finalize_batch().ok();
153 self.latest_journal_entry()
154 }
155
156 pub fn list_credentials(&self, limit: usize) -> Result<Vec<JournalEntry>, String> {
157 self.list_journal_by_kind("credential", limit)
158 }
159
160 pub fn get_credential(&self, record_id: &str) -> Result<Option<CredentialRecord>, String> {
163 let Some(entry) = self
164 .list_credentials(256)?
165 .into_iter()
166 .find(|e| e.id == record_id)
167 else {
168 return Ok(None);
169 };
170 let Some(hash) = entry.blob_hash else {
171 return Ok(None);
172 };
173 let store = BlobStore::open(&self.storage_root).map_err(|e| e.to_string())?;
174 let Some(bytes) = store.get(&hash).map_err(|e| e.to_string())? else {
175 return Ok(None);
176 };
177 let cred: CredentialRecord = serde_json::from_slice(&bytes).map_err(|e| e.to_string())?;
178 Ok(Some(cred))
179 }
180
181 pub fn present_credential(
184 &self,
185 record_id: &str,
186 selected_claim_keys: &[String],
187 ) -> Result<FieldSelectedPresentation, String> {
188 let cred = self
189 .get_credential(record_id)?
190 .ok_or_else(|| format!("credential '{record_id}' not found or blob missing"))?;
191 Ok(build_presentation(&cred, selected_claim_keys))
192 }
193}