jax_daemon/http_server/api/v0/bucket/
publish.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use common::mount::PrincipalRole;
4use common::prelude::MountError;
5use reqwest::{Client, RequestBuilder, Url};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::http_server::api::client::ApiRequest;
10use crate::ServiceState;
11
12#[derive(Debug, Clone, Serialize, Deserialize, clap::Args)]
13pub struct PublishRequest {
14 #[arg(long)]
16 pub bucket_id: Uuid,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct PublishResponse {
21 pub bucket_id: Uuid,
22 pub published: bool,
23 pub new_bucket_link: String,
24}
25
26pub async fn handler(
27 State(state): State<ServiceState>,
28 Json(req): Json<PublishRequest>,
29) -> Result<impl IntoResponse, PublishError> {
30 tracing::info!("PUBLISH API: Publishing bucket {}", req.bucket_id);
31
32 let mount = state.peer().mount(req.bucket_id).await?;
34
35 let our_key = state.peer().secret().public();
37 {
38 let manifest = mount.inner().await;
39 let our_share = manifest
40 .manifest()
41 .get_share(&our_key)
42 .ok_or(PublishError::NotOwner)?;
43 if *our_share.role() != PrincipalRole::Owner {
44 return Err(PublishError::NotOwner);
45 }
46 }
47
48 if mount.is_published().await {
50 tracing::info!("PUBLISH API: Bucket {} is already published", req.bucket_id);
51 }
53
54 let new_bucket_link = state.peer().save_mount(&mount, Some(true)).await?;
56
57 tracing::info!(
58 "PUBLISH API: Bucket {} published, new link: {}",
59 req.bucket_id,
60 new_bucket_link.hash()
61 );
62
63 Ok((
64 http::StatusCode::OK,
65 Json(PublishResponse {
66 bucket_id: req.bucket_id,
67 published: true,
68 new_bucket_link: new_bucket_link.hash().to_string(),
69 }),
70 )
71 .into_response())
72}
73
74#[derive(Debug, thiserror::Error)]
75pub enum PublishError {
76 #[error("Mount error: {0}")]
77 Mount(#[from] MountError),
78 #[error("Only the bucket owner can publish")]
79 NotOwner,
80}
81
82impl IntoResponse for PublishError {
83 fn into_response(self) -> Response {
84 match self {
85 PublishError::Mount(_) => (
86 http::StatusCode::INTERNAL_SERVER_ERROR,
87 "Unexpected error".to_string(),
88 )
89 .into_response(),
90 PublishError::NotOwner => (
91 http::StatusCode::FORBIDDEN,
92 "Only the bucket owner can publish".to_string(),
93 )
94 .into_response(),
95 }
96 }
97}
98
99impl ApiRequest for PublishRequest {
101 type Response = PublishResponse;
102
103 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
104 let full_url = base_url.join("/api/v0/bucket/publish").unwrap();
105 client.post(full_url).json(&self)
106 }
107}