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(¤t_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}