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(¤t_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 let new_mst = match mst.delete(&key).await {
155 Ok(m) => m,
156 Err(e) => {
157 error!("Failed to delete from MST: {:?}", e);
158 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": format!("Failed to delete from MST: {:?}", e)}))).into_response();
159 }
160 };
161
162 let new_mst_root = match new_mst.persist().await {
163 Ok(c) => c,
164 Err(e) => {
165 error!("Failed to persist MST: {:?}", e);
166 return (
167 StatusCode::INTERNAL_SERVER_ERROR,
168 Json(json!({"error": "InternalError", "message": "Failed to persist MST"})),
169 )
170 .into_response();
171 }
172 };
173
174 let did_obj = match Did::new(&did) {
175 Ok(d) => d,
176 Err(_) => {
177 return (
178 StatusCode::INTERNAL_SERVER_ERROR,
179 Json(json!({"error": "InternalError", "message": "Invalid DID"})),
180 )
181 .into_response();
182 }
183 };
184
185 let rev = Tid::now(LimitedU32::MIN);
186
187 let new_commit = Commit::new_unsigned(did_obj, new_mst_root, rev, Some(current_root_cid));
188
189 let new_commit_bytes =
190 match new_commit.to_cbor() {
191 Ok(b) => b,
192 Err(_e) => return (
193 StatusCode::INTERNAL_SERVER_ERROR,
194 Json(
195 json!({"error": "InternalError", "message": "Failed to serialize new commit"}),
196 ),
197 )
198 .into_response(),
199 };
200
201 let new_root_cid = match state.block_store.put(&new_commit_bytes).await {
202 Ok(c) => c,
203 Err(_e) => {
204 return (
205 StatusCode::INTERNAL_SERVER_ERROR,
206 Json(json!({"error": "InternalError", "message": "Failed to save new commit"})),
207 )
208 .into_response();
209 }
210 };
211
212 let update_repo = sqlx::query("UPDATE repos SET repo_root_cid = $1 WHERE user_id = $2")
213 .bind(new_root_cid.to_string())
214 .bind(user_id)
215 .execute(&state.db)
216 .await;
217
218 if let Err(e) = update_repo {
219 error!("Failed to update repo root in DB: {:?}", e);
220 return (
221 StatusCode::INTERNAL_SERVER_ERROR,
222 Json(json!({"error": "InternalError", "message": "Failed to update repo root in DB"})),
223 )
224 .into_response();
225 }
226
227 let record_delete =
228 sqlx::query("DELETE FROM records WHERE repo_id = $1 AND collection = $2 AND rkey = $3")
229 .bind(user_id)
230 .bind(&input.collection)
231 .bind(&input.rkey)
232 .execute(&state.db)
233 .await;
234
235 if let Err(e) = record_delete {
236 error!("Error deleting record index: {:?}", e);
237 }
238
239 (StatusCode::OK, Json(json!({}))).into_response()
240}