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