jax_daemon/http_server/api/v0/bucket/
share.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use common::prelude::MountError;
4use reqwest::{Client, RequestBuilder, Url};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8use common::crypto::PublicKey;
9
10use crate::http_server::api::client::ApiRequest;
11use crate::ServiceState;
12
13#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, clap::ValueEnum)]
15#[serde(rename_all = "lowercase")]
16pub enum ShareRole {
17 #[default]
19 Owner,
20 Mirror,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, clap::Args)]
25pub struct ShareRequest {
26 #[arg(long)]
28 pub bucket_id: Uuid,
29
30 #[arg(long)]
32 pub peer_public_key: String,
33
34 #[arg(long, default_value = "owner")]
36 #[serde(default)]
37 pub role: ShareRole,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ShareResponse {
42 pub bucket_id: Uuid,
43 pub peer_public_key: String,
44 pub new_bucket_link: String,
45}
46
47pub async fn handler(
48 State(state): State<ServiceState>,
49 Json(req): Json<ShareRequest>,
50) -> Result<impl IntoResponse, ShareError> {
51 tracing::info!(
52 "SHARE API: Received share request for bucket {} with peer {} as {:?}",
53 req.bucket_id,
54 req.peer_public_key,
55 req.role
56 );
57
58 let peer_public_key = PublicKey::from_hex(&req.peer_public_key)
60 .map_err(|e| ShareError::InvalidPublicKey(e.to_string()))?;
61
62 tracing::info!("SHARE API: Parsed peer public key successfully");
63
64 let mut mount = state.peer().mount(req.bucket_id).await?;
66 tracing::info!("SHARE API: Loaded mount for bucket {}", req.bucket_id);
67
68 match req.role {
70 ShareRole::Owner => {
71 mount.add_owner(peer_public_key).await?;
72 tracing::info!(
73 "SHARE API: Mount.add_owner() completed for peer {}",
74 req.peer_public_key
75 );
76 }
77 ShareRole::Mirror => {
78 mount.add_mirror(peer_public_key).await;
79 tracing::info!(
80 "SHARE API: Mount.add_mirror() completed for peer {}",
81 req.peer_public_key
82 );
83 }
84 }
85
86 tracing::info!("SHARE API: Calling save_mount for bucket {}", req.bucket_id);
87 let new_bucket_link = state.peer().save_mount(&mount, false).await?;
89
90 tracing::info!(
91 "SHARE API: Bucket {} shared with peer {} as {:?}, new link: {}",
92 req.bucket_id,
93 req.peer_public_key,
94 req.role,
95 new_bucket_link.hash()
96 );
97
98 Ok((
99 http::StatusCode::OK,
100 Json(ShareResponse {
101 bucket_id: req.bucket_id,
102 peer_public_key: req.peer_public_key,
103 new_bucket_link: new_bucket_link.hash().to_string(),
104 }),
105 )
106 .into_response())
107}
108
109#[derive(Debug, thiserror::Error)]
110pub enum ShareError {
111 #[error("Invalid public key: {0}")]
112 InvalidPublicKey(String),
113 #[error("Mount error: {0}")]
114 Mount(#[from] MountError),
115}
116
117impl IntoResponse for ShareError {
118 fn into_response(self) -> Response {
119 match self {
120 ShareError::InvalidPublicKey(msg) => (
121 http::StatusCode::BAD_REQUEST,
122 format!("Invalid public key: {}", msg),
123 )
124 .into_response(),
125 ShareError::Mount(_) => (
126 http::StatusCode::INTERNAL_SERVER_ERROR,
127 "Unexpected error".to_string(),
128 )
129 .into_response(),
130 }
131 }
132}
133
134impl ApiRequest for ShareRequest {
136 type Response = ShareResponse;
137
138 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
139 let full_url = base_url.join("/api/v0/bucket/share").unwrap();
140 client.post(full_url).json(&self)
141 }
142}