jax_daemon/http_server/api/v0/bucket/
unpublish.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 UnpublishRequest {
14 #[arg(long)]
16 pub bucket_id: Uuid,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct UnpublishResponse {
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<UnpublishRequest>,
29) -> Result<impl IntoResponse, UnpublishError> {
30 tracing::info!("UNPUBLISH API: Unpublishing 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(UnpublishError::NotOwner)?;
43 if *our_share.role() != PrincipalRole::Owner {
44 return Err(UnpublishError::NotOwner);
45 }
46 }
47
48 if !mount.is_published().await {
50 tracing::info!(
51 "UNPUBLISH API: Bucket {} is already unpublished",
52 req.bucket_id
53 );
54 let link = mount.link().await;
55 return Ok((
56 http::StatusCode::OK,
57 Json(UnpublishResponse {
58 bucket_id: req.bucket_id,
59 published: false,
60 new_bucket_link: link.hash().to_string(),
61 }),
62 )
63 .into_response());
64 }
65
66 let new_bucket_link = state.peer().save_mount(&mount, Some(false)).await?;
68
69 tracing::info!(
70 "UNPUBLISH API: Bucket {} unpublished, new link: {}",
71 req.bucket_id,
72 new_bucket_link.hash()
73 );
74
75 Ok((
76 http::StatusCode::OK,
77 Json(UnpublishResponse {
78 bucket_id: req.bucket_id,
79 published: false,
80 new_bucket_link: new_bucket_link.hash().to_string(),
81 }),
82 )
83 .into_response())
84}
85
86#[derive(Debug, thiserror::Error)]
87pub enum UnpublishError {
88 #[error("Mount error: {0}")]
89 Mount(#[from] MountError),
90 #[error("Only the bucket owner can unpublish")]
91 NotOwner,
92}
93
94impl IntoResponse for UnpublishError {
95 fn into_response(self) -> Response {
96 match self {
97 UnpublishError::Mount(_) => (
98 http::StatusCode::INTERNAL_SERVER_ERROR,
99 "Unexpected error".to_string(),
100 )
101 .into_response(),
102 UnpublishError::NotOwner => (
103 http::StatusCode::FORBIDDEN,
104 "Only the bucket owner can unpublish".to_string(),
105 )
106 .into_response(),
107 }
108 }
109}
110
111impl ApiRequest for UnpublishRequest {
112 type Response = UnpublishResponse;
113
114 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
115 let full_url = base_url.join("/api/v0/bucket/unpublish").unwrap();
116 client.post(full_url).json(&self)
117 }
118}