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 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}