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