service/http_server/api/v0/bucket/
share.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use common::prelude::Link;
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::mount_ops::MountOpsError;
12use crate::ServiceState;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[cfg_attr(feature = "clap", derive(clap::Args))]
16pub struct ShareRequest {
17 #[cfg_attr(feature = "clap", arg(long))]
19 pub bucket_id: Uuid,
20
21 #[cfg_attr(feature = "clap", arg(long))]
23 pub peer_public_key: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ShareResponse {
28 pub bucket_id: Uuid,
29 pub peer_public_key: String,
30 pub new_bucket_link: String,
31}
32
33#[axum::debug_handler]
34pub async fn handler(
35 State(state): State<ServiceState>,
36 Json(req): Json<ShareRequest>,
37) -> Result<impl IntoResponse, ShareError> {
38 let peer_public_key = PublicKey::from_hex(&req.peer_public_key)
40 .map_err(|e| ShareError::InvalidPublicKey(e.to_string()))?;
41
42 let new_bucket_link = tokio::task::spawn_blocking(move || -> Result<Link, MountOpsError> {
44 tokio::runtime::Handle::current().block_on(async {
45 tracing::info!("Adding file to mount");
46 let bucket_link =
47 crate::mount_ops::share_bucket(req.bucket_id, peer_public_key, &state).await?;
48 Ok(bucket_link)
49 })
50 })
51 .await
52 .map_err(|e| ShareError::Mount(format!("Task join error: {}", e)))??;
53
54 tracing::info!(
55 "Bucket {} shared with peer {}",
56 req.bucket_id,
57 req.peer_public_key
58 );
59
60 Ok((
61 http::StatusCode::OK,
62 Json(ShareResponse {
63 bucket_id: req.bucket_id,
64 peer_public_key: req.peer_public_key,
65 new_bucket_link: new_bucket_link.hash().to_string(),
66 }),
67 )
68 .into_response())
69}
70
71#[derive(Debug, thiserror::Error)]
72pub enum ShareError {
73 #[error("Bucket not found: {0}")]
74 BucketNotFound(Uuid),
75 #[error("Invalid public key: {0}")]
76 InvalidPublicKey(String),
77 #[error("Share not found")]
78 ShareNotFound,
79 #[error("Database error: {0}")]
80 Database(String),
81 #[error("Mount error: {0}")]
82 Mount(String),
83 #[error("Crypto error: {0}")]
84 Crypto(String),
85}
86
87impl From<MountOpsError> for ShareError {
88 fn from(err: MountOpsError) -> Self {
89 match err {
90 MountOpsError::BucketNotFound(id) => ShareError::BucketNotFound(id),
91 MountOpsError::ShareNotFound => ShareError::ShareNotFound,
92 MountOpsError::Database(msg) => ShareError::Database(msg),
93 MountOpsError::Mount(e) => ShareError::Mount(e.to_string()),
94 MountOpsError::CryptoError(msg) => ShareError::Crypto(msg),
95 MountOpsError::ShareError(msg) => ShareError::Crypto(msg),
96 MountOpsError::InvalidPath(msg) => ShareError::Mount(msg),
97 }
98 }
99}
100
101impl IntoResponse for ShareError {
102 fn into_response(self) -> Response {
103 match self {
104 ShareError::BucketNotFound(id) => (
105 http::StatusCode::NOT_FOUND,
106 format!("Bucket not found: {}", id),
107 )
108 .into_response(),
109 ShareError::InvalidPublicKey(msg) => (
110 http::StatusCode::BAD_REQUEST,
111 format!("Invalid public key: {}", msg),
112 )
113 .into_response(),
114 ShareError::ShareNotFound => (
115 http::StatusCode::NOT_FOUND,
116 "Share not found for this bucket".to_string(),
117 )
118 .into_response(),
119 ShareError::Database(_) | ShareError::Mount(_) | ShareError::Crypto(_) => (
120 http::StatusCode::INTERNAL_SERVER_ERROR,
121 "Unexpected error".to_string(),
122 )
123 .into_response(),
124 }
125 }
126}
127
128impl ApiRequest for ShareRequest {
130 type Response = ShareResponse;
131
132 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
133 let full_url = base_url.join("/api/v0/bucket/share").unwrap();
134 client.post(full_url).json(&self)
135 }
136}