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