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 entry::ID,
14 store::{StoreError, Table},
15};
16
17pub(super) const BOOTSTRAP_REQUESTS_SUBTREE: &str = "bootstrap_requests";
19
20pub(super) struct BootstrapRequestManager<'a> {
25 txn: &'a Transaction,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30pub struct BootstrapRequest {
31 pub tree_id: ID,
33 pub requesting_pubkey: PublicKey,
35 pub requesting_key_name: String,
37 pub requested_permission: Permission,
39 pub timestamp: String,
41 pub status: RequestStatus,
43 pub peer_address: Address,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49pub enum RequestStatus {
50 Pending,
52 Approved {
54 approved_by: String,
56 approval_time: String,
58 },
59 Rejected {
61 rejected_by: String,
63 rejection_time: String,
65 },
66}
67
68impl<'a> BootstrapRequestManager<'a> {
69 pub(super) fn new(txn: &'a Transaction) -> Self {
71 Self { txn }
72 }
73
74 pub(super) async fn store_request(&self, request: BootstrapRequest) -> Result<String> {
82 let requests = self
83 .txn
84 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
85 .await?;
86
87 debug!(tree_id = %request.tree_id, "Storing bootstrap request");
88
89 let request_id = requests.insert(request.clone()).await?;
91
92 info!(request_id = %request_id, tree_id = %request.tree_id, "Successfully stored bootstrap request");
93 Ok(request_id)
94 }
95
96 pub(super) async fn get_request(&self, request_id: &str) -> Result<Option<BootstrapRequest>> {
104 let requests = self
105 .txn
106 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
107 .await?;
108
109 match requests.get(request_id).await {
110 Ok(request) => Ok(Some(request)),
111 Err(Error::Store(StoreError::KeyNotFound { .. })) => Ok(None),
112 Err(e) => Err(e),
113 }
114 }
115
116 async fn filter_requests(
118 &self,
119 status_filter: &RequestStatus,
120 ) -> Result<Vec<(String, BootstrapRequest)>> {
121 let requests = self
122 .txn
123 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
124 .await?;
125
126 let results = requests
127 .search(|request| {
128 std::mem::discriminant(status_filter) == std::mem::discriminant(&request.status)
129 })
130 .await?;
131
132 Ok(results)
133 }
134
135 pub(super) async fn pending_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
140 self.filter_requests(&RequestStatus::Pending).await
141 }
142
143 pub(super) async fn approved_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
148 self.filter_requests(&RequestStatus::Approved {
149 approved_by: String::new(),
150 approval_time: String::new(),
151 })
152 .await
153 }
154
155 pub(super) async fn rejected_requests(&self) -> Result<Vec<(String, BootstrapRequest)>> {
160 self.filter_requests(&RequestStatus::Rejected {
161 rejected_by: String::new(),
162 rejection_time: String::new(),
163 })
164 .await
165 }
166
167 pub(super) async fn update_status(
176 &self,
177 request_id: &str,
178 new_status: RequestStatus,
179 ) -> Result<()> {
180 let requests = self
181 .txn
182 .get_store::<Table<BootstrapRequest>>(BOOTSTRAP_REQUESTS_SUBTREE)
183 .await?;
184
185 let mut request = requests.get(request_id).await?;
187
188 request.status = new_status;
190
191 requests.set(request_id, request).await?;
193
194 debug!(request_id = %request_id, "Updated bootstrap request status");
195 Ok(())
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::{
203 Clock, Database, Instance, auth::types::Permission, backend::database::InMemory,
204 clock::FixedClock, crdt::Doc,
205 };
206 use std::sync::Arc;
207
208 async fn create_test_sync_tree() -> (Instance, Database, Arc<FixedClock>) {
209 let clock = Arc::new(FixedClock::default());
210 let backend = Box::new(InMemory::new());
211 let instance = Instance::open_with_clock(backend, clock.clone())
212 .await
213 .expect("Failed to create test instance");
214
215 instance.create_user("test", None).await.unwrap();
217 let mut user = instance.login_user("test", None).await.unwrap();
218 let key_id = user.add_private_key(None).await.unwrap();
219
220 let mut sync_settings = Doc::new();
221 sync_settings.set("name", "_sync");
222 sync_settings.set("type", "sync_settings");
223
224 let database = user.create_database(sync_settings, &key_id).await.unwrap();
225
226 (instance, database, clock)
227 }
228
229 fn create_test_request(clock: &FixedClock) -> BootstrapRequest {
230 BootstrapRequest {
231 tree_id: ID::from_bytes("test_tree_id"),
233 requesting_pubkey: PublicKey::random(),
234 requesting_key_name: "laptop_key".to_string(),
235 requested_permission: Permission::Write(5),
236 timestamp: clock.now_rfc3339(),
237 status: RequestStatus::Pending,
238 peer_address: Address {
239 transport_type: "http".to_string(),
240 address: "127.0.0.1:8080".to_string(),
241 },
242 }
243 }
244
245 #[tokio::test]
246 async fn test_store_and_get_request() {
247 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
248 let txn = sync_tree.new_transaction().await.unwrap();
249 let manager = BootstrapRequestManager::new(&txn);
250
251 let request = create_test_request(&clock);
252
253 let request_id = manager.store_request(request.clone()).await.unwrap();
255
256 let retrieved = manager.get_request(&request_id).await.unwrap().unwrap();
258 assert_eq!(retrieved.tree_id, request.tree_id);
259 assert_eq!(retrieved.requesting_pubkey, request.requesting_pubkey);
260 assert_eq!(retrieved.requesting_key_name, request.requesting_key_name);
261 assert_eq!(retrieved.requested_permission, request.requested_permission);
262 assert_eq!(retrieved.status, request.status);
263 assert_eq!(retrieved.peer_address, request.peer_address);
264 }
265
266 #[tokio::test]
267 async fn test_list_requests() {
268 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
269 let txn = sync_tree.new_transaction().await.unwrap();
270 let manager = BootstrapRequestManager::new(&txn);
271
272 let request1 = create_test_request(&clock);
274
275 let mut request2 = create_test_request(&clock);
276 request2.status = RequestStatus::Approved {
277 approved_by: "admin".to_string(),
278 approval_time: clock.now_rfc3339(),
279 };
280
281 manager.store_request(request1).await.unwrap();
282 manager.store_request(request2).await.unwrap();
283
284 let pending_requests = manager.pending_requests().await.unwrap();
286 assert_eq!(pending_requests.len(), 1);
287
288 let approved_requests = manager.approved_requests().await.unwrap();
290 assert_eq!(approved_requests.len(), 1);
291
292 assert!(matches!(
294 pending_requests[0].1.status,
295 RequestStatus::Pending
296 ));
297 assert!(matches!(
298 approved_requests[0].1.status,
299 RequestStatus::Approved { .. }
300 ));
301 }
302
303 #[tokio::test]
304 async fn test_update_status() {
305 let (_instance, sync_tree, clock) = create_test_sync_tree().await;
306 let txn = sync_tree.new_transaction().await.unwrap();
307 let manager = BootstrapRequestManager::new(&txn);
308
309 let request = create_test_request(&clock);
310
311 let request_id = manager.store_request(request).await.unwrap();
313
314 let new_status = RequestStatus::Approved {
316 approved_by: "admin".to_string(),
317 approval_time: clock.now_rfc3339(),
318 };
319 manager
320 .update_status(&request_id, new_status.clone())
321 .await
322 .unwrap();
323
324 let updated_request = manager.get_request(&request_id).await.unwrap().unwrap();
326 assert_eq!(updated_request.status, new_status);
327 }
328
329 #[tokio::test]
330 async fn test_get_nonexistent_request() {
331 let (_instance, sync_tree, _clock) = create_test_sync_tree().await;
332 let txn = sync_tree.new_transaction().await.unwrap();
333 let manager = BootstrapRequestManager::new(&txn);
334
335 let result = manager.get_request("nonexistent").await.unwrap();
336 assert!(result.is_none());
337 }
338}