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