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