1use super::*;
2
3use coven_protocol::circle_journal::{
4 CircleOperationIntent, CircleOperationJournal, CircleOperationProgress, PreparedCircleOperation,
5};
6
7#[derive(serde::Serialize, serde::Deserialize)]
15#[serde(deny_unknown_fields)]
16struct PreparedCircleOperationPayload {
17 operation_id: coven_protocol::circle::CircleOperationId,
18 circle_id: coven_protocol::circle::CircleId,
19 intent: CircleOperationIntent,
20 operation: PreparedCircleOperation,
21}
22
23pub struct PreparedCircleOperationRow {
24 pub operation_id: String,
25 pub circle_id: String,
26 pub prepared: Vec<u8>,
27 pub phase: String,
28}
29
30impl PreparedCircleOperationRow {
31 pub fn from_journal(journal: &CircleOperationJournal) -> Result<Self, DbError> {
32 journal.validate_identity().map_err(DbError::from)?;
33 Ok(Self {
34 operation_id: journal.operation_id.as_str().to_string(),
35 circle_id: journal.circle_id.to_string(),
36 prepared: prepared_circle_operation_payload(journal)?,
37 phase: circle_operation_phase_json(&journal.progress)?,
38 })
39 }
40}
41
42pub(crate) fn prepared_circle_operation_payload(
44 journal: &CircleOperationJournal,
45) -> Result<Vec<u8>, DbError> {
46 serde_json::to_vec(&PreparedCircleOperationPayload {
47 operation_id: journal.operation_id.clone(),
48 circle_id: journal.circle_id,
49 intent: journal.intent.clone(),
50 operation: journal.operation.clone(),
51 })
52 .map_err(|error| DbError::context("serialize prepared circle operation", error))
53}
54
55pub(crate) fn circle_operation_phase_json(
56 progress: &CircleOperationProgress,
57) -> Result<String, DbError> {
58 serde_json::to_string(progress)
59 .map_err(|error| DbError::context("serialize circle operation phase", error))
60}
61
62pub fn parse_circle_operation_row(
69 stored_operation_id: &str,
70 stored_circle_id: &str,
71 prepared: &[u8],
72 phase: &str,
73 uploaded: BTreeSet<String>,
74) -> Result<CircleOperationJournal, DbError> {
75 let payload: PreparedCircleOperationPayload = serde_json::from_slice(prepared)
76 .map_err(|error| DbError::context("parse prepared circle operation", error))?;
77 let progress: CircleOperationProgress = serde_json::from_str(phase)
78 .map_err(|error| DbError::context("parse circle operation phase", error))?;
79 if payload.operation_id.as_str() != stored_operation_id {
80 return Err(DbError::Message(format!(
81 "circle operation id row names {stored_operation_id} but its payload operation id is {}",
82 payload.operation_id
83 )));
84 }
85 if payload.circle_id.to_string() != stored_circle_id {
86 return Err(DbError::Message(format!(
87 "circle operation {stored_operation_id} row names circle {stored_circle_id} but its payload circle id is {}",
88 payload.circle_id
89 )));
90 }
91 let journal = CircleOperationJournal {
92 operation_id: payload.operation_id,
93 circle_id: payload.circle_id,
94 intent: payload.intent,
95 operation: payload.operation,
96 progress,
97 uploaded,
98 };
99 journal.validate_identity().map_err(DbError::from)?;
100 journal.validate_uploaded().map_err(DbError::from)?;
101 Ok(journal)
102}
103
104pub(crate) fn circle_operation_uploaded_steps_on(
105 conn: &Connection,
106 operation_id: &str,
107) -> Result<BTreeSet<String>, DbError> {
108 crate::query_mapped_rows(
109 conn,
110 "SELECT step FROM circle_operation_uploads WHERE operation_id = ?1 ORDER BY step",
111 [operation_id],
112 |row| row.get::<_, String>(0),
113 )
114 .map_err(DbError::from)
115 .map(BTreeSet::from_iter)
116}
117
118pub(crate) fn load_circle_operation_on(
119 conn: &Connection,
120 operation_id: &str,
121) -> Result<Option<CircleOperationJournal>, DbError> {
122 let Some((stored_operation_id, circle_id, prepared, phase)) = conn
123 .query_row(
124 "SELECT operation_id, circle_id, prepared, phase
125 FROM circle_operations
126 WHERE operation_id = ?1",
127 [operation_id],
128 |row| {
129 Ok((
130 row.get::<_, String>(0)?,
131 row.get::<_, String>(1)?,
132 row.get::<_, Vec<u8>>(2)?,
133 row.get::<_, String>(3)?,
134 ))
135 },
136 )
137 .optional()
138 .map_err(DbError::from)?
139 else {
140 return Ok(None);
141 };
142 let uploaded = circle_operation_uploaded_steps_on(conn, &stored_operation_id)?;
143 parse_circle_operation_row(
144 &stored_operation_id,
145 &circle_id,
146 &prepared,
147 &phase,
148 uploaded,
149 )
150 .map(Some)
151}
152
153pub(crate) fn circle_operation_ids_in_phase_on(
159 conn: &Connection,
160 accept: impl Fn(&CircleOperationProgress) -> bool,
161) -> Result<Vec<String>, DbError> {
162 let rows = crate::query_mapped_rows(
163 conn,
164 "SELECT operation_id, phase FROM circle_operations ORDER BY rowid",
165 [],
166 |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)),
167 )
168 .map_err(DbError::from)?;
169 let mut matching = Vec::new();
170 for (operation_id, phase) in rows {
171 let progress: CircleOperationProgress = serde_json::from_str(&phase)
172 .map_err(|error| DbError::context("parse circle operation phase", error))?;
173 if accept(&progress) {
174 matching.push(operation_id);
175 }
176 }
177 Ok(matching)
178}