this repo has no description
1use crate::api::repo::record::utils::{CommitParams, RecordOp, commit_and_log};
2use crate::api::repo::record::write::prepare_repo_write;
3use crate::repo::tracking::TrackingBlockStore;
4use crate::state::AppState;
5use axum::{
6 Json,
7 extract::State,
8 http::{HeaderMap, StatusCode},
9 response::{IntoResponse, Response},
10};
11use cid::Cid;
12use jacquard::types::string::Nsid;
13use jacquard_repo::{commit::Commit, mst::Mst, storage::BlockStore};
14use serde::Deserialize;
15use serde_json::json;
16use std::str::FromStr;
17use std::sync::Arc;
18use tracing::error;
19
20#[derive(Deserialize)]
21pub struct DeleteRecordInput {
22 pub repo: String,
23 pub collection: String,
24 pub rkey: String,
25 #[serde(rename = "swapRecord")]
26 pub swap_record: Option<String>,
27 #[serde(rename = "swapCommit")]
28 pub swap_commit: Option<String>,
29}
30
31pub async fn delete_record(
32 State(state): State<AppState>,
33 headers: HeaderMap,
34 axum::extract::OriginalUri(uri): axum::extract::OriginalUri,
35 Json(input): Json<DeleteRecordInput>,
36) -> Response {
37 let auth =
38 match prepare_repo_write(&state, &headers, &input.repo, "POST", &uri.to_string()).await {
39 Ok(res) => res,
40 Err(err_res) => return err_res,
41 };
42
43 if let Err(e) = crate::auth::scope_check::check_repo_scope(
44 auth.is_oauth,
45 auth.scope.as_deref(),
46 crate::oauth::RepoAction::Delete,
47 &input.collection,
48 ) {
49 return e;
50 }
51
52 let did = auth.did;
53 let user_id = auth.user_id;
54 let current_root_cid = auth.current_root_cid;
55
56 if let Some(swap_commit) = &input.swap_commit
57 && Cid::from_str(swap_commit).ok() != Some(current_root_cid)
58 {
59 return (
60 StatusCode::CONFLICT,
61 Json(json!({"error": "InvalidSwap", "message": "Repo has been modified"})),
62 )
63 .into_response();
64 }
65 let tracking_store = TrackingBlockStore::new(state.block_store.clone());
66 let commit_bytes = match tracking_store.get(¤t_root_cid).await {
67 Ok(Some(b)) => b,
68 _ => {
69 return (
70 StatusCode::INTERNAL_SERVER_ERROR,
71 Json(json!({"error": "InternalError", "message": "Commit block not found"})),
72 )
73 .into_response();
74 }
75 };
76 let commit = match Commit::from_cbor(&commit_bytes) {
77 Ok(c) => c,
78 _ => {
79 return (
80 StatusCode::INTERNAL_SERVER_ERROR,
81 Json(json!({"error": "InternalError", "message": "Failed to parse commit"})),
82 )
83 .into_response();
84 }
85 };
86 let mst = Mst::load(Arc::new(tracking_store.clone()), commit.data, None);
87 let collection_nsid = match input.collection.parse::<Nsid>() {
88 Ok(n) => n,
89 Err(_) => {
90 return (
91 StatusCode::BAD_REQUEST,
92 Json(json!({"error": "InvalidCollection"})),
93 )
94 .into_response();
95 }
96 };
97 let key = format!("{}/{}", collection_nsid, input.rkey);
98 if let Some(swap_record_str) = &input.swap_record {
99 let expected_cid = Cid::from_str(swap_record_str).ok();
100 let actual_cid = mst.get(&key).await.ok().flatten();
101 if expected_cid != actual_cid {
102 return (StatusCode::CONFLICT, Json(json!({"error": "InvalidSwap", "message": "Record has been modified or does not exist"}))).into_response();
103 }
104 }
105 let prev_record_cid = mst.get(&key).await.ok().flatten();
106 if prev_record_cid.is_none() {
107 return (StatusCode::OK, Json(json!({}))).into_response();
108 }
109 let new_mst = match mst.delete(&key).await {
110 Ok(m) => m,
111 Err(e) => {
112 error!("Failed to delete from MST: {:?}", e);
113 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to delete from MST: {:?}", e)}))).into_response();
114 }
115 };
116 let new_mst_root = match new_mst.persist().await {
117 Ok(c) => c,
118 Err(e) => {
119 error!("Failed to persist MST: {:?}", e);
120 return (
121 StatusCode::INTERNAL_SERVER_ERROR,
122 Json(json!({"error": "InternalError", "message": "Failed to persist MST"})),
123 )
124 .into_response();
125 }
126 };
127 let op = RecordOp::Delete {
128 collection: input.collection,
129 rkey: input.rkey,
130 prev: prev_record_cid,
131 };
132 let mut relevant_blocks = std::collections::BTreeMap::new();
133 if new_mst
134 .blocks_for_path(&key, &mut relevant_blocks)
135 .await
136 .is_err()
137 {
138 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to get new MST blocks for path"}))).into_response();
139 }
140 if mst
141 .blocks_for_path(&key, &mut relevant_blocks)
142 .await
143 .is_err()
144 {
145 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to get old MST blocks for path"}))).into_response();
146 }
147 let mut written_cids = tracking_store.get_all_relevant_cids();
148 for cid in relevant_blocks.keys() {
149 if !written_cids.contains(cid) {
150 written_cids.push(*cid);
151 }
152 }
153 let written_cids_str = written_cids
154 .iter()
155 .map(|c| c.to_string())
156 .collect::<Vec<_>>();
157 if let Err(e) = commit_and_log(
158 &state,
159 CommitParams {
160 did: &did,
161 user_id,
162 current_root_cid: Some(current_root_cid),
163 prev_data_cid: Some(commit.data),
164 new_mst_root,
165 ops: vec![op],
166 blocks_cids: &written_cids_str,
167 },
168 )
169 .await
170 {
171 return (
172 StatusCode::INTERNAL_SERVER_ERROR,
173 Json(json!({"error": "InternalError", "message": e})),
174 )
175 .into_response();
176 };
177 (StatusCode::OK, Json(json!({}))).into_response()
178}