Skip to main content

jax_daemon/http_server/api/v0/bucket/
latest_published.rs

1use 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    /// The bucket ID to query
15    #[arg(long)]
16    pub bucket_id: Uuid,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct LatestPublishedResponse {
21    pub bucket_id: Uuid,
22    /// The link of the latest published version, if any
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub link: Option<String>,
25    /// The height of the latest published version, if any
26    #[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    // Check if the bucket exists
40    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    // Query latest published version
52    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
108// Client implementation - builds request for this operation
109impl 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}