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 if crate::util::is_account_migrated(&state.db, &auth.did) 61 .await 62 .unwrap_or(false) 63 { 64 return ( 65 StatusCode::BAD_REQUEST, 66 Json(json!({ 67 "error": "AccountMigrated", 68 "message": "Account has been migrated. Repo operations are not allowed." 69 })), 70 ) 71 .into_response(); 72 } 73 74 let did = auth.did; 75 let user_id = auth.user_id; 76 let current_root_cid = auth.current_root_cid; 77 let controller_did = auth.controller_did; 78 79 if let Some(swap_commit) = &input.swap_commit 80 && Cid::from_str(swap_commit).ok() != Some(current_root_cid) 81 { 82 return ( 83 StatusCode::CONFLICT, 84 Json(json!({"error": "InvalidSwap", "message": "Repo has been modified"})), 85 ) 86 .into_response(); 87 } 88 let tracking_store = TrackingBlockStore::new(state.block_store.clone()); 89 let commit_bytes = match tracking_store.get(&current_root_cid).await { 90 Ok(Some(b)) => b, 91 _ => { 92 return ( 93 StatusCode::INTERNAL_SERVER_ERROR, 94 Json(json!({"error": "InternalError", "message": "Commit block not found"})), 95 ) 96 .into_response(); 97 } 98 }; 99 let commit = match Commit::from_cbor(&commit_bytes) { 100 Ok(c) => c, 101 _ => { 102 return ( 103 StatusCode::INTERNAL_SERVER_ERROR, 104 Json(json!({"error": "InternalError", "message": "Failed to parse commit"})), 105 ) 106 .into_response(); 107 } 108 }; 109 let mst = Mst::load(Arc::new(tracking_store.clone()), commit.data, None); 110 let collection_nsid = match input.collection.parse::<Nsid>() { 111 Ok(n) => n, 112 Err(_) => { 113 return ( 114 StatusCode::BAD_REQUEST, 115 Json(json!({"error": "InvalidCollection"})), 116 ) 117 .into_response(); 118 } 119 }; 120 let key = format!("{}/{}", collection_nsid, input.rkey); 121 if let Some(swap_record_str) = &input.swap_record { 122 let expected_cid = Cid::from_str(swap_record_str).ok(); 123 let actual_cid = mst.get(&key).await.ok().flatten(); 124 if expected_cid != actual_cid { 125 return (StatusCode::CONFLICT, Json(json!({"error": "InvalidSwap", "message": "Record has been modified or does not exist"}))).into_response(); 126 } 127 } 128 let prev_record_cid = mst.get(&key).await.ok().flatten(); 129 if prev_record_cid.is_none() { 130 return ( 131 StatusCode::OK, 132 Json(DeleteRecordOutput { commit: None }), 133 ) 134 .into_response(); 135 } 136 let new_mst = match mst.delete(&key).await { 137 Ok(m) => m, 138 Err(e) => { 139 error!("Failed to delete from MST: {:?}", e); 140 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to delete from MST: {:?}", e)}))).into_response(); 141 } 142 }; 143 let new_mst_root = match new_mst.persist().await { 144 Ok(c) => c, 145 Err(e) => { 146 error!("Failed to persist MST: {:?}", e); 147 return ( 148 StatusCode::INTERNAL_SERVER_ERROR, 149 Json(json!({"error": "InternalError", "message": "Failed to persist MST"})), 150 ) 151 .into_response(); 152 } 153 }; 154 let collection_for_audit = input.collection.clone(); 155 let rkey_for_audit = input.rkey.clone(); 156 let op = RecordOp::Delete { 157 collection: input.collection, 158 rkey: input.rkey, 159 prev: prev_record_cid, 160 }; 161 let mut relevant_blocks = std::collections::BTreeMap::new(); 162 if new_mst 163 .blocks_for_path(&key, &mut relevant_blocks) 164 .await 165 .is_err() 166 { 167 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to get new MST blocks for path"}))).into_response(); 168 } 169 if mst 170 .blocks_for_path(&key, &mut relevant_blocks) 171 .await 172 .is_err() 173 { 174 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to get old MST blocks for path"}))).into_response(); 175 } 176 let mut written_cids = tracking_store.get_all_relevant_cids(); 177 for cid in relevant_blocks.keys() { 178 if !written_cids.contains(cid) { 179 written_cids.push(*cid); 180 } 181 } 182 let written_cids_str = written_cids 183 .iter() 184 .map(|c| c.to_string()) 185 .collect::<Vec<_>>(); 186 let commit_result = match commit_and_log( 187 &state, 188 CommitParams { 189 did: &did, 190 user_id, 191 current_root_cid: Some(current_root_cid), 192 prev_data_cid: Some(commit.data), 193 new_mst_root, 194 ops: vec![op], 195 blocks_cids: &written_cids_str, 196 blobs: &[], 197 }, 198 ) 199 .await 200 { 201 Ok(res) => res, 202 Err(e) => { 203 return ( 204 StatusCode::INTERNAL_SERVER_ERROR, 205 Json(json!({"error": "InternalError", "message": e})), 206 ) 207 .into_response(); 208 } 209 }; 210 211 if let Some(ref controller) = controller_did { 212 let _ = delegation::log_delegation_action( 213 &state.db, 214 &did, 215 controller, 216 Some(controller), 217 DelegationActionType::RepoWrite, 218 Some(json!({ 219 "action": "delete", 220 "collection": collection_for_audit, 221 "rkey": rkey_for_audit 222 })), 223 None, 224 None, 225 ) 226 .await; 227 } 228 229 ( 230 StatusCode::OK, 231 Json(DeleteRecordOutput { 232 commit: Some(CommitInfo { 233 cid: commit_result.commit_cid.to_string(), 234 rev: commit_result.rev, 235 }), 236 }), 237 ) 238 .into_response() 239}