this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::State, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use cid::Cid; 9use jacquard::types::{ 10 did::Did, 11 integer::LimitedU32, 12 string::{Nsid, Tid}, 13}; 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: axum::http::HeaderMap, 35 Json(input): Json<DeleteRecordInput>, 36) -> Response { 37 let auth_header = headers.get("Authorization"); 38 if auth_header.is_none() { 39 return ( 40 StatusCode::UNAUTHORIZED, 41 Json(json!({"error": "AuthenticationRequired"})), 42 ) 43 .into_response(); 44 } 45 let token = auth_header 46 .unwrap() 47 .to_str() 48 .unwrap_or("") 49 .replace("Bearer ", ""); 50 51 let session = sqlx::query!( 52 "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1", 53 token 54 ) 55 .fetch_optional(&state.db) 56 .await 57 .unwrap_or(None); 58 59 let (did, key_bytes) = match session { 60 Some(row) => ( 61 row.did, 62 row.key_bytes, 63 ), 64 None => { 65 return ( 66 StatusCode::UNAUTHORIZED, 67 Json(json!({"error": "AuthenticationFailed"})), 68 ) 69 .into_response(); 70 } 71 }; 72 73 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 74 return ( 75 StatusCode::UNAUTHORIZED, 76 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 77 ) 78 .into_response(); 79 } 80 81 if input.repo != did { 82 return (StatusCode::FORBIDDEN, Json(json!({"error": "InvalidRepo", "message": "Repo does not match authenticated user"}))).into_response(); 83 } 84 85 let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 86 .fetch_optional(&state.db) 87 .await; 88 89 let user_id: uuid::Uuid = match user_query { 90 Ok(Some(row)) => row.id, 91 _ => { 92 return ( 93 StatusCode::INTERNAL_SERVER_ERROR, 94 Json(json!({"error": "InternalError", "message": "User not found"})), 95 ) 96 .into_response(); 97 } 98 }; 99 100 let repo_root_query = sqlx::query!("SELECT repo_root_cid FROM repos WHERE user_id = $1", user_id) 101 .fetch_optional(&state.db) 102 .await; 103 104 let current_root_cid = match repo_root_query { 105 Ok(Some(row)) => { 106 let cid_str: String = row.repo_root_cid; 107 Cid::from_str(&cid_str).ok() 108 } 109 _ => None, 110 }; 111 112 if current_root_cid.is_none() { 113 return ( 114 StatusCode::INTERNAL_SERVER_ERROR, 115 Json(json!({"error": "InternalError", "message": "Repo root not found"})), 116 ) 117 .into_response(); 118 } 119 let current_root_cid = current_root_cid.unwrap(); 120 121 let commit_bytes = match state.block_store.get(&current_root_cid).await { 122 Ok(Some(b)) => b, 123 Ok(None) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Commit block not found"}))).into_response(), 124 Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to load commit block: {:?}", e)}))).into_response(), 125 }; 126 127 let commit = match Commit::from_cbor(&commit_bytes) { 128 Ok(c) => c, 129 Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to parse commit: {:?}", e)}))).into_response(), 130 }; 131 132 let mst_root = commit.data; 133 let store = Arc::new(state.block_store.clone()); 134 let mst = Mst::load(store.clone(), mst_root, None); 135 136 let collection_nsid = match input.collection.parse::<Nsid>() { 137 Ok(n) => n, 138 Err(_) => { 139 return ( 140 StatusCode::BAD_REQUEST, 141 Json(json!({"error": "InvalidCollection"})), 142 ) 143 .into_response(); 144 } 145 }; 146 147 let key = format!("{}/{}", collection_nsid, input.rkey); 148 149 // TODO: Check swapRecord if provided? Skipping for brevity/robustness 150 151 let new_mst = match mst.delete(&key).await { 152 Ok(m) => m, 153 Err(e) => { 154 error!("Failed to delete from MST: {:?}", e); 155 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to delete from MST: {:?}", e)}))).into_response(); 156 } 157 }; 158 159 let new_mst_root = match new_mst.persist().await { 160 Ok(c) => c, 161 Err(e) => { 162 error!("Failed to persist MST: {:?}", e); 163 return ( 164 StatusCode::INTERNAL_SERVER_ERROR, 165 Json(json!({"error": "InternalError", "message": "Failed to persist MST"})), 166 ) 167 .into_response(); 168 } 169 }; 170 171 let did_obj = match Did::new(&did) { 172 Ok(d) => d, 173 Err(_) => { 174 return ( 175 StatusCode::INTERNAL_SERVER_ERROR, 176 Json(json!({"error": "InternalError", "message": "Invalid DID"})), 177 ) 178 .into_response(); 179 } 180 }; 181 182 let rev = Tid::now(LimitedU32::MIN); 183 184 let new_commit = Commit::new_unsigned(did_obj, new_mst_root, rev, Some(current_root_cid)); 185 186 let new_commit_bytes = 187 match new_commit.to_cbor() { 188 Ok(b) => b, 189 Err(_e) => return ( 190 StatusCode::INTERNAL_SERVER_ERROR, 191 Json( 192 json!({"error": "InternalError", "message": "Failed to serialize new commit"}), 193 ), 194 ) 195 .into_response(), 196 }; 197 198 let new_root_cid = match state.block_store.put(&new_commit_bytes).await { 199 Ok(c) => c, 200 Err(_e) => { 201 return ( 202 StatusCode::INTERNAL_SERVER_ERROR, 203 Json(json!({"error": "InternalError", "message": "Failed to save new commit"})), 204 ) 205 .into_response(); 206 } 207 }; 208 209 let update_repo = sqlx::query!("UPDATE repos SET repo_root_cid = $1 WHERE user_id = $2", new_root_cid.to_string(), user_id) 210 .execute(&state.db) 211 .await; 212 213 if let Err(e) = update_repo { 214 error!("Failed to update repo root in DB: {:?}", e); 215 return ( 216 StatusCode::INTERNAL_SERVER_ERROR, 217 Json(json!({"error": "InternalError", "message": "Failed to update repo root in DB"})), 218 ) 219 .into_response(); 220 } 221 222 let record_delete = 223 sqlx::query!("DELETE FROM records WHERE repo_id = $1 AND collection = $2 AND rkey = $3", user_id, input.collection, input.rkey) 224 .execute(&state.db) 225 .await; 226 227 if let Err(e) = record_delete { 228 error!("Error deleting record index: {:?}", e); 229 } 230 231 (StatusCode::OK, Json(json!({}))).into_response() 232}