1use serde::{Deserialize, Serialize};
7use tracing::{debug, info};
8
9use super::peer_types::Address;
10use crate::{
11 Error, Result, Transaction,
12 auth::{Permission, crypto::PublicKey},
13 crdt::Doc,
14 entry::ID,
15 store::{StoreError, Table},
16};
17
18pub(super) const BOOTSTRAP_REQUESTS_SUBTREE: &str = "bootstrap_requests";
20
21pub(super) struct BootstrapRequestManager<'a> {
26 txn: &'a Transaction,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct BootstrapRequest {
32 pub tree_id: ID,
34 pub requesting_pubkey: PublicKey,
36 pub requesting_key_name: String,
38 pub requested_permission: Permission,
40 pub timestamp: String,
42 pub status: RequestStatus,
44 pub peer_address: Address,
46 #[serde(default)]
49 pub metadata: Option<Doc>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub enum RequestStatus {
55 Pending,
57 Approved {
59 approved_by: String,
61 approval_time: String,
63 },
64 Rejected {
66 rejected_by: String,
68 rejection_time: String,
70 },
71}
72
73impl<'a> BootstrapRequestManager<'a> {
74 pub(super) fn new(txn: &'a Transaction) -> Self {
76 Self { txn }
77 }
78
79 pub(super) async fn store_request(&self, request: BootstrapRequest) -> Result<String> {
87 let requests = self
88 .txn
89 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
90 .await?;
91
92 debug!(tree_id = %request.tree_id, "Storing bootstrap request");
93
94 let request_id = requests.insert(request.clone()).await?;
96
97 info!(request_id = %request_id, tree_id = %request.tree_id, "Successfully stored bootstrap request");
98 Ok(request_id)
99 }
100
101 pub(super) async fn get_request(&self, request_id: &str) -> Result<Option<BootstrapRequest>> {
109 let requests = self
110 .txn
111 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
112 .await?;
113
114 match requests.get(request_id).await {
115 Ok(request) => Ok(Some(request)),
116 Err(Error::Store(ref e)) if matches!(**e, StoreError::KeyNotFound { .. }) => Ok(None),
117 Err(e) => Err(e),
118 }
119 }
120
121 async fn filter_requests(
123 &self,
124 status_filter: &RequestStatus,
125 ) -> Result<Vec<(String, BootstrapRequest)>> {
126 let requests = self
127 .txn
128 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
129 .await?;
130
131 let results = requests
132 .search(|request| {
133 std::mem::discriminant(status_filter) == std::mem::discriminant(&request.status)
134 })
135 .await?;
136
137 Ok(results)
138 }
139
140 pub(super) async fn pending_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
145 self.filter_requests(&RequestStatus::Pending).await
146 }
147
148 pub(super) async fn approved_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
153 self.filter_requests(&RequestStatus::Approved {
154 approved_by: String::new(),
155 approval_time: String::new(),
156 })
157 .await
158 }
159
160 pub(super) async fn rejected_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
165 self.filter_requests(&RequestStatus::Rejected {
166 rejected_by: String::new(),
167 rejection_time: String::new(),
168 })
169 .await
170 }
171
172 pub(super) async fn update_status(
181 &self,
182 request_id: &str,
183 new_status: RequestStatus,
184 ) -> Result<()> {
185 let requests = self
186 .txn
187 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
188 .await?;
189
190 let mut request = requests.get(request_id).await?;
192
193 request.status = new_status;
195
196 requests.set(request_id, request).await?;
198
199 debug!(request_id = %request_id, "Updated bootstrap request status");
200 Ok(())
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::{
208 Clock, Database, Instance, auth::types::Permission, backend::database::InMemory,
209 clock::FixedClock, crdt::Doc,
210 };
211 use std::sync::Arc;
212
213 async fn create_test_sync_tree() -> (Instance, Database, Arc<FixedClock>) {
214 let clock = Arc::new(FixedClock::default());
215 let (instance, mut user) = Instance::create_backend_with_clock(
216 Box::new(InMemory::new()),
217 clock.clone(),
218 crate::NewUser::passwordless("test"),
219 )
220 .await
221 .expect("Failed to create test instance");
222
223 let mut sync_settings = Doc::new();
224 sync_settings.set("name", "_sync");
225 sync_settings.set("type", "sync_settings");
226
227 let (database, _) = user
228 .new_database()
229 .settings(sync_settings)
230 .build()
231 .await
232 .unwrap();
233
234 (instance, database, clock)
235 }
236
237 fn create_test_request(clock: &FixedClock) -> BootstrapRequest {
238 BootstrapRequest {
239 tree_id: ID::from_bytes("test_tree_id"),
241 requesting_pubkey: PublicKey::random(),
242 requesting_key_name: "laptop_key".to_string(),
243 requested_permission: Permission::Write(5),
244 timestamp: clock.now_rfc3339(),
245 status: RequestStatus::Pending,
246 peer_address: Address {
247 transport_type: "http".to_string(),
248 address: "127.0.0.1:8080".to_string(),
249 },
250 metadata: None,
251 }
252 }
253
254 #[tokio::test]
255 async fn test_store_and_get_request() {
256 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
257 let txn = sync_tree.new_transaction().await.unwrap();
258 let manager = BootstrapRequestManager::new(&txn);
259
260 let request = create_test_request(&clock);
261
262 let request_id = manager.store_request(request.clone()).await.unwrap();
264
265 let retrieved = manager.get_request(&request_id).await.unwrap().unwrap();
267 assert_eq!(retrieved.tree_id, request.tree_id);
268 assert_eq!(retrieved.requesting_pubkey, request.requesting_pubkey);
269 assert_eq!(retrieved.requesting_key_name, request.requesting_key_name);
270 assert_eq!(retrieved.requested_permission, request.requested_permission);
271 assert_eq!(retrieved.status, request.status);
272 assert_eq!(retrieved.peer_address, request.peer_address);
273 }
274
275 #[tokio::test]
276 async fn test_list_requests() {
277 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
278 let txn = sync_tree.new_transaction().await.unwrap();
279 let manager = BootstrapRequestManager::new(&txn);
280
281 let request1 = create_test_request(&clock);
283
284 let mut request2 = create_test_request(&clock);
285 request2.status = RequestStatus::Approved {
286 approved_by: "admin".to_string(),
287 approval_time: clock.now_rfc3339(),
288 };
289
290 manager.store_request(request1).await.unwrap();
291 manager.store_request(request2).await.unwrap();
292
293 let pending_requests = manager.pending_requests().await.unwrap();
295 assert_eq!(pending_requests.len(), 1);
296
297 let approved_requests = manager.approved_requests().await.unwrap();
299 assert_eq!(approved_requests.len(), 1);
300
301 assert!(matches!(
303 pending_requests[0].1.status,
304 RequestStatus::Pending
305 ));
306 assert!(matches!(
307 approved_requests[0].1.status,
308 RequestStatus::Approved { .. }
309 ));
310 }
311
312 #[tokio::test]
313 async fn test_update_status() {
314 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
315 let txn = sync_tree.new_transaction().await.unwrap();
316 let manager = BootstrapRequestManager::new(&txn);
317
318 let request = create_test_request(&clock);
319
320 let request_id = manager.store_request(request).await.unwrap();
322
323 let new_status = RequestStatus::Approved {
325 approved_by: "admin".to_string(),
326 approval_time: clock.now_rfc3339(),
327 };
328 manager
329 .update_status(&request_id, new_status.clone())
330 .await
331 .unwrap();
332
333 let updated_request = manager.get_request(&request_id).await.unwrap().unwrap();
335 assert_eq!(updated_request.status, new_status);
336 }
337
338 #[tokio::test]
339 async fn test_get_nonexistent_request() {
340 let (_instance, sync_tree, _clock) = create_test_sync_tree().await;
341 let txn = sync_tree.new_transaction().await.unwrap();
342 let manager = BootstrapRequestManager::new(&txn);
343
344 let result = manager.get_request("nonexistent").await.unwrap();
345 assert!(result.is_none());
346 }
347}