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