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