this repo has no description
1use crate::api::error::ApiError;
2use crate::auth::{extract_bearer_token_from_header, validate_bearer_token};
3use crate::state::AppState;
4use axum::{
5 Json,
6 extract::State,
7 http::HeaderMap,
8 response::{IntoResponse, Response},
9};
10use cid::Cid;
11use jacquard_repo::storage::BlockStore;
12use serde::{Deserialize, Serialize};
13use std::str::FromStr;
14
15#[derive(Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct CheckSignupQueueOutput {
18 pub activated: bool,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub place_in_queue: Option<i64>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub estimated_time_ms: Option<i64>,
23}
24
25pub async fn check_signup_queue(State(state): State<AppState>, headers: HeaderMap) -> Response {
26 if let Some(token) =
27 extract_bearer_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok()))
28 && let Ok(user) = validate_bearer_token(&state.db, &token).await
29 && user.is_oauth
30 {
31 return ApiError::Forbidden.into_response();
32 }
33 Json(CheckSignupQueueOutput {
34 activated: true,
35 place_in_queue: None,
36 estimated_time_ms: None,
37 })
38 .into_response()
39}
40
41#[derive(Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct DereferenceScopeInput {
44 pub scope: String,
45}
46
47#[derive(Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct DereferenceScopeOutput {
50 pub scope: String,
51}
52
53pub async fn dereference_scope(
54 State(state): State<AppState>,
55 headers: HeaderMap,
56 Json(input): Json<DereferenceScopeInput>,
57) -> Response {
58 let Some(token) = extract_bearer_token_from_header(
59 headers.get("Authorization").and_then(|h| h.to_str().ok()),
60 ) else {
61 return ApiError::AuthenticationRequired.into_response();
62 };
63
64 if validate_bearer_token(&state.db, &token).await.is_err() {
65 return ApiError::AuthenticationFailed(None).into_response();
66 }
67
68 let scope_parts: Vec<&str> = input.scope.split_whitespace().collect();
69 let mut resolved_scopes: Vec<String> = Vec::new();
70
71 for part in scope_parts {
72 if let Some(cid_str) = part.strip_prefix("ref:") {
73 let cache_key = format!("scope_ref:{}", cid_str);
74 if let Some(cached) = state.cache.get(&cache_key).await {
75 for s in cached.split_whitespace() {
76 if !resolved_scopes.contains(&s.to_string()) {
77 resolved_scopes.push(s.to_string());
78 }
79 }
80 continue;
81 }
82
83 let cid = match Cid::from_str(cid_str) {
84 Ok(c) => c,
85 Err(_) => {
86 tracing::warn!("Invalid CID in scope ref: {}", cid_str);
87 continue;
88 }
89 };
90
91 let block_bytes = match state.block_store.get(&cid).await {
92 Ok(Some(b)) => b,
93 Ok(None) => {
94 tracing::warn!("Scope ref block not found: {}", cid_str);
95 continue;
96 }
97 Err(e) => {
98 tracing::warn!("Error fetching scope ref block {}: {:?}", cid_str, e);
99 continue;
100 }
101 };
102
103 let scope_record: serde_json::Value = match serde_ipld_dagcbor::from_slice(&block_bytes)
104 {
105 Ok(v) => v,
106 Err(e) => {
107 tracing::warn!("Failed to decode scope ref block {}: {:?}", cid_str, e);
108 continue;
109 }
110 };
111
112 if let Some(scope_value) = scope_record.get("scope").and_then(|v| v.as_str()) {
113 let _ = state
114 .cache
115 .set(
116 &cache_key,
117 scope_value,
118 std::time::Duration::from_secs(3600),
119 )
120 .await;
121 for s in scope_value.split_whitespace() {
122 if !resolved_scopes.contains(&s.to_string()) {
123 resolved_scopes.push(s.to_string());
124 }
125 }
126 }
127 } else if !resolved_scopes.contains(&part.to_string()) {
128 resolved_scopes.push(part.to_string());
129 }
130 }
131
132 Json(DereferenceScopeOutput {
133 scope: resolved_scopes.join(" "),
134 })
135 .into_response()
136}