this repo has no description
1use crate::api::error::ApiError; 2use crate::api::repo::record::utils::{CommitParams, RecordOp, commit_and_log}; 3use crate::api::repo::record::write::{CommitInfo, prepare_repo_write}; 4use crate::delegation::{self, DelegationActionType}; 5use crate::repo::tracking::TrackingBlockStore; 6use crate::state::AppState; 7use crate::types::{AtIdentifier, Nsid, Rkey}; 8use axum::{ 9 Json, 10 extract::State, 11 http::{HeaderMap, StatusCode}, 12 response::{IntoResponse, Response}, 13}; 14use cid::Cid; 15use jacquard_repo::{commit::Commit, mst::Mst, storage::BlockStore}; 16use serde::{Deserialize, Serialize}; 17use serde_json::json; 18use std::str::FromStr; 19use std::sync::Arc; 20use tracing::error; 21 22#[derive(Deserialize)] 23pub struct DeleteRecordInput { 24 pub repo: AtIdentifier, 25 pub collection: Nsid, 26 pub rkey: Rkey, 27 #[serde(rename = "swapRecord")] 28 pub swap_record: Option<String>, 29 #[serde(rename = "swapCommit")] 30 pub swap_commit: Option<String>, 31} 32 33#[derive(Serialize)] 34#[serde(rename_all = "camelCase")] 35pub struct DeleteRecordOutput { 36 #[serde(skip_serializing_if = "Option::is_none")] 37 pub commit: Option<CommitInfo>, 38} 39 40pub async fn delete_record( 41 State(state): State<AppState>, 42 headers: HeaderMap, 43 axum::extract::OriginalUri(uri): axum::extract::OriginalUri, 44 Json(input): Json<DeleteRecordInput>, 45) -> Response { 46 let auth = match prepare_repo_write( 47 &state, 48 &headers, 49 &input.repo, 50 "POST", 51 &crate::util::build_full_url(&uri.to_string()), 52 ) 53 .await 54 { 55 Ok(res) => res, 56 Err(err_res) => return err_res, 57 }; 58 59 if let Err(e) = crate::auth::scope_check::check_repo_scope( 60 auth.is_oauth, 61 auth.scope.as_deref(), 62 crate::oauth::RepoAction::Delete, 63 &input.collection, 64 ) { 65 return e; 66 } 67 68 if crate::util::is_account_migrated(&state.db, &auth.did) 69 .await 70 .unwrap_or(false) 71 { 72 return ApiError::AccountMigrated.into_response(); 73 } 74 75 let did = auth.did; 76 let user_id = auth.user_id; 77 let current_root_cid = auth.current_root_cid; 78 let controller_did = auth.controller_did; 79 80 if let Some(swap_commit) = &input.swap_commit 81 && Cid::from_str(swap_commit).ok() != Some(current_root_cid) 82 { 83 return ApiError::InvalidSwap(Some("Repo has been modified".into())).into_response(); 84 } 85 let tracking_store = TrackingBlockStore::new(state.block_store.clone()); 86 let commit_bytes = match tracking_store.get(&current_root_cid).await { 87 Ok(Some(b)) => b, 88 _ => return ApiError::InternalError(Some("Commit block not found".into())).into_response(), 89 }; 90 let commit = match Commit::from_cbor(&commit_bytes) { 91 Ok(c) => c, 92 _ => return ApiError::InternalError(Some("Failed to parse commit".into())).into_response(), 93 }; 94 let mst = Mst::load(Arc::new(tracking_store.clone()), commit.data, None); 95 let key = format!("{}/{}", input.collection, input.rkey); 96 if let Some(swap_record_str) = &input.swap_record { 97 let expected_cid = Cid::from_str(swap_record_str).ok(); 98 let actual_cid = mst.get(&key).await.ok().flatten(); 99 if expected_cid != actual_cid { 100 return ApiError::InvalidSwap(Some( 101 "Record has been modified or does not exist".into(), 102 )) 103 .into_response(); 104 } 105 } 106 let prev_record_cid = mst.get(&key).await.ok().flatten(); 107 if prev_record_cid.is_none() { 108 return (StatusCode::OK, Json(DeleteRecordOutput { commit: None })).into_response(); 109 } 110 let new_mst = match mst.delete(&key).await { 111 Ok(m) => m, 112 Err(e) => { 113 error!("Failed to delete from MST: {:?}", e); 114 return ApiError::InternalError(Some(format!("Failed to delete from MST: {:?}", e))) 115 .into_response(); 116 } 117 }; 118 let new_mst_root = match new_mst.persist().await { 119 Ok(c) => c, 120 Err(e) => { 121 error!("Failed to persist MST: {:?}", e); 122 return ApiError::InternalError(Some("Failed to persist MST".into())).into_response(); 123 } 124 }; 125 let collection_for_audit = input.collection.to_string(); 126 let rkey_for_audit = input.rkey.to_string(); 127 let op = RecordOp::Delete { 128 collection: input.collection.to_string(), 129 rkey: rkey_for_audit.clone(), 130 prev: prev_record_cid, 131 }; 132 let mut new_mst_blocks = std::collections::BTreeMap::new(); 133 let mut old_mst_blocks = std::collections::BTreeMap::new(); 134 if new_mst 135 .blocks_for_path(&key, &mut new_mst_blocks) 136 .await 137 .is_err() 138 { 139 return ApiError::InternalError(Some("Failed to get new MST blocks for path".into())) 140 .into_response(); 141 } 142 if mst 143 .blocks_for_path(&key, &mut old_mst_blocks) 144 .await 145 .is_err() 146 { 147 return ApiError::InternalError(Some("Failed to get old MST blocks for path".into())) 148 .into_response(); 149 } 150 let mut relevant_blocks = new_mst_blocks.clone(); 151 relevant_blocks.extend(old_mst_blocks.iter().map(|(k, v)| (*k, v.clone()))); 152 let written_cids: Vec<Cid> = tracking_store 153 .get_all_relevant_cids() 154 .into_iter() 155 .chain(relevant_blocks.keys().copied()) 156 .collect::<std::collections::HashSet<_>>() 157 .into_iter() 158 .collect(); 159 let written_cids_str: Vec<String> = written_cids.iter().map(|c| c.to_string()).collect(); 160 let obsolete_cids: Vec<Cid> = std::iter::once(current_root_cid) 161 .chain( 162 old_mst_blocks 163 .keys() 164 .filter(|cid| !new_mst_blocks.contains_key(*cid)) 165 .copied(), 166 ) 167 .chain(prev_record_cid) 168 .collect(); 169 let commit_result = match commit_and_log( 170 &state, 171 CommitParams { 172 did: &did, 173 user_id, 174 current_root_cid: Some(current_root_cid), 175 prev_data_cid: Some(commit.data), 176 new_mst_root, 177 ops: vec![op], 178 blocks_cids: &written_cids_str, 179 blobs: &[], 180 obsolete_cids, 181 }, 182 ) 183 .await 184 { 185 Ok(res) => res, 186 Err(e) => return ApiError::InternalError(Some(e)).into_response(), 187 }; 188 189 if let Some(ref controller) = controller_did { 190 let _ = delegation::log_delegation_action( 191 &state.db, 192 &did, 193 controller, 194 Some(controller), 195 DelegationActionType::RepoWrite, 196 Some(json!({ 197 "action": "delete", 198 "collection": collection_for_audit, 199 "rkey": rkey_for_audit 200 })), 201 None, 202 None, 203 ) 204 .await; 205 } 206 207 ( 208 StatusCode::OK, 209 Json(DeleteRecordOutput { 210 commit: Some(CommitInfo { 211 cid: commit_result.commit_cid.to_string(), 212 rev: commit_result.rev, 213 }), 214 }), 215 ) 216 .into_response() 217}