this repo has no description
1use crate::state::AppState;
2use axum::{
3 Json,
4 extract::{Query, State},
5 http::StatusCode,
6 response::{IntoResponse, Response},
7};
8use cid::Cid;
9use jacquard_repo::commit::Commit;
10use jacquard_repo::storage::BlockStore;
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13use std::str::FromStr;
14use tracing::error;
15async fn get_rev_from_commit(state: &AppState, cid_str: &str) -> Option<String> {
16 let cid = Cid::from_str(cid_str).ok()?;
17 let block = state.block_store.get(&cid).await.ok()??;
18 let commit = Commit::from_cbor(&block).ok()?;
19 Some(commit.rev().to_string())
20}
21#[derive(Deserialize)]
22pub struct GetLatestCommitParams {
23 pub did: String,
24}
25#[derive(Serialize)]
26pub struct GetLatestCommitOutput {
27 pub cid: String,
28 pub rev: String,
29}
30pub async fn get_latest_commit(
31 State(state): State<AppState>,
32 Query(params): Query<GetLatestCommitParams>,
33) -> Response {
34 let did = params.did.trim();
35 if did.is_empty() {
36 return (
37 StatusCode::BAD_REQUEST,
38 Json(json!({"error": "InvalidRequest", "message": "did is required"})),
39 )
40 .into_response();
41 }
42 let result = sqlx::query!(
43 r#"
44 SELECT r.repo_root_cid
45 FROM repos r
46 JOIN users u ON r.user_id = u.id
47 WHERE u.did = $1
48 "#,
49 did
50 )
51 .fetch_optional(&state.db)
52 .await;
53 match result {
54 Ok(Some(row)) => {
55 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await
56 .unwrap_or_else(|| chrono::Utc::now().timestamp_millis().to_string());
57 (
58 StatusCode::OK,
59 Json(GetLatestCommitOutput {
60 cid: row.repo_root_cid,
61 rev,
62 }),
63 )
64 .into_response()
65 }
66 Ok(None) => (
67 StatusCode::NOT_FOUND,
68 Json(json!({"error": "RepoNotFound", "message": "Could not find repo for DID"})),
69 )
70 .into_response(),
71 Err(e) => {
72 error!("DB error in get_latest_commit: {:?}", e);
73 (
74 StatusCode::INTERNAL_SERVER_ERROR,
75 Json(json!({"error": "InternalError"})),
76 )
77 .into_response()
78 }
79 }
80}
81#[derive(Deserialize)]
82pub struct ListReposParams {
83 pub limit: Option<i64>,
84 pub cursor: Option<String>,
85}
86#[derive(Serialize)]
87#[serde(rename_all = "camelCase")]
88pub struct RepoInfo {
89 pub did: String,
90 pub head: String,
91 pub rev: String,
92 pub active: bool,
93}
94#[derive(Serialize)]
95pub struct ListReposOutput {
96 pub cursor: Option<String>,
97 pub repos: Vec<RepoInfo>,
98}
99pub async fn list_repos(
100 State(state): State<AppState>,
101 Query(params): Query<ListReposParams>,
102) -> Response {
103 let limit = params.limit.unwrap_or(50).clamp(1, 1000);
104 let cursor_did = params.cursor.as_deref().unwrap_or("");
105 let result = sqlx::query!(
106 r#"
107 SELECT u.did, r.repo_root_cid
108 FROM repos r
109 JOIN users u ON r.user_id = u.id
110 WHERE u.did > $1
111 ORDER BY u.did ASC
112 LIMIT $2
113 "#,
114 cursor_did,
115 limit + 1
116 )
117 .fetch_all(&state.db)
118 .await;
119 match result {
120 Ok(rows) => {
121 let has_more = rows.len() as i64 > limit;
122 let mut repos: Vec<RepoInfo> = Vec::new();
123 for row in rows.iter().take(limit as usize) {
124 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await
125 .unwrap_or_else(|| chrono::Utc::now().timestamp_millis().to_string());
126 repos.push(RepoInfo {
127 did: row.did.clone(),
128 head: row.repo_root_cid.clone(),
129 rev,
130 active: true,
131 });
132 }
133 let next_cursor = if has_more {
134 repos.last().map(|r| r.did.clone())
135 } else {
136 None
137 };
138 (
139 StatusCode::OK,
140 Json(ListReposOutput {
141 cursor: next_cursor,
142 repos,
143 }),
144 )
145 .into_response()
146 }
147 Err(e) => {
148 error!("DB error in list_repos: {:?}", e);
149 (
150 StatusCode::INTERNAL_SERVER_ERROR,
151 Json(json!({"error": "InternalError"})),
152 )
153 .into_response()
154 }
155 }
156}
157#[derive(Deserialize)]
158pub struct GetRepoStatusParams {
159 pub did: String,
160}
161#[derive(Serialize)]
162pub struct GetRepoStatusOutput {
163 pub did: String,
164 pub active: bool,
165 pub rev: Option<String>,
166}
167pub async fn get_repo_status(
168 State(state): State<AppState>,
169 Query(params): Query<GetRepoStatusParams>,
170) -> Response {
171 let did = params.did.trim();
172 if did.is_empty() {
173 return (
174 StatusCode::BAD_REQUEST,
175 Json(json!({"error": "InvalidRequest", "message": "did is required"})),
176 )
177 .into_response();
178 }
179 let result = sqlx::query!(
180 r#"
181 SELECT u.did, r.repo_root_cid
182 FROM users u
183 LEFT JOIN repos r ON u.id = r.user_id
184 WHERE u.did = $1
185 "#,
186 did
187 )
188 .fetch_optional(&state.db)
189 .await;
190 match result {
191 Ok(Some(row)) => {
192 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await;
193 (
194 StatusCode::OK,
195 Json(GetRepoStatusOutput {
196 did: row.did,
197 active: true,
198 rev,
199 }),
200 )
201 .into_response()
202 }
203 Ok(None) => (
204 StatusCode::NOT_FOUND,
205 Json(json!({"error": "RepoNotFound", "message": "Could not find repo for DID"})),
206 )
207 .into_response(),
208 Err(e) => {
209 error!("DB error in get_repo_status: {:?}", e);
210 (
211 StatusCode::INTERNAL_SERVER_ERROR,
212 Json(json!({"error": "InternalError"})),
213 )
214 .into_response()
215 }
216 }
217}