jax_daemon/http_server/api/v0/bucket/
latest_published.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use reqwest::{Client, RequestBuilder, Url};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use common::bucket_log::BucketLogProvider;
8
9use crate::http_server::api::client::ApiRequest;
10use crate::ServiceState;
11
12#[derive(Debug, Clone, Serialize, Deserialize, clap::Args)]
13pub struct LatestPublishedRequest {
14 #[arg(long)]
16 pub bucket_id: Uuid,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct LatestPublishedResponse {
21 pub bucket_id: Uuid,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub link: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub height: Option<u64>,
28}
29
30pub async fn handler(
31 State(state): State<ServiceState>,
32 Json(req): Json<LatestPublishedRequest>,
33) -> Result<impl IntoResponse, LatestPublishedError> {
34 tracing::info!(
35 "LATEST_PUBLISHED: Querying latest published version for bucket {}",
36 req.bucket_id
37 );
38
39 let exists = state
41 .peer()
42 .logs()
43 .exists(req.bucket_id)
44 .await
45 .map_err(|e| LatestPublishedError::Internal(e.to_string()))?;
46
47 if !exists {
48 return Err(LatestPublishedError::BucketNotFound(req.bucket_id));
49 }
50
51 let result = state
53 .peer()
54 .logs()
55 .latest_published(req.bucket_id)
56 .await
57 .map_err(|e| LatestPublishedError::Internal(e.to_string()))?;
58
59 let (link, height) = match result {
60 Some((link, height)) => (Some(link.to_string()), Some(height)),
61 None => (None, None),
62 };
63
64 tracing::info!(
65 "LATEST_PUBLISHED: Bucket {} latest published: link={:?}, height={:?}",
66 req.bucket_id,
67 link,
68 height
69 );
70
71 Ok((
72 http::StatusCode::OK,
73 Json(LatestPublishedResponse {
74 bucket_id: req.bucket_id,
75 link,
76 height,
77 }),
78 )
79 .into_response())
80}
81
82#[derive(Debug, thiserror::Error)]
83pub enum LatestPublishedError {
84 #[error("Bucket not found: {0}")]
85 BucketNotFound(Uuid),
86 #[error("Internal error: {0}")]
87 Internal(String),
88}
89
90impl IntoResponse for LatestPublishedError {
91 fn into_response(self) -> Response {
92 tracing::error!("LATEST_PUBLISHED ERROR: {:?}", self);
93 match self {
94 LatestPublishedError::BucketNotFound(id) => (
95 http::StatusCode::NOT_FOUND,
96 format!("Bucket not found: {}", id),
97 )
98 .into_response(),
99 LatestPublishedError::Internal(msg) => (
100 http::StatusCode::INTERNAL_SERVER_ERROR,
101 format!("Internal error: {}", msg),
102 )
103 .into_response(),
104 }
105 }
106}
107
108impl ApiRequest for LatestPublishedRequest {
110 type Response = LatestPublishedResponse;
111
112 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
113 let full_url = base_url.join("/api/v0/bucket/latest-published").unwrap();
114 client.post(full_url).json(&self)
115 }
116}