Skip to main content

jax_daemon/http_server/api/v0/mounts/
create.rs

1//! Create mount API endpoint
2
3use axum::extract::{Json, State};
4use axum::response::{IntoResponse, Response};
5use reqwest::{Client, RequestBuilder, Url};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::database::models::FuseMount;
10use crate::http_server::api::client::ApiRequest;
11use crate::ServiceState;
12
13/// Request to create a new mount configuration
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CreateMountRequest {
16    pub bucket_id: Uuid,
17    pub mount_point: String,
18    #[serde(default)]
19    pub auto_mount: bool,
20    #[serde(default)]
21    pub read_only: bool,
22    pub cache_size_mb: Option<u32>,
23    pub cache_ttl_secs: Option<u32>,
24}
25
26/// Response containing the created mount
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct CreateMountResponse {
29    pub mount: MountInfo,
30}
31
32/// Information about a mount configuration
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct MountInfo {
35    pub mount_id: Uuid,
36    pub bucket_id: Uuid,
37    pub mount_point: String,
38    pub enabled: bool,
39    pub auto_mount: bool,
40    pub read_only: bool,
41    pub cache_size_mb: u32,
42    pub cache_ttl_secs: u32,
43    pub status: String,
44    pub error_message: Option<String>,
45    pub created_at: String,
46    pub updated_at: String,
47}
48
49impl From<FuseMount> for MountInfo {
50    fn from(m: FuseMount) -> Self {
51        Self {
52            mount_id: *m.mount_id,
53            bucket_id: *m.bucket_id,
54            mount_point: m.mount_point,
55            enabled: *m.enabled,
56            auto_mount: *m.auto_mount,
57            read_only: *m.read_only,
58            cache_size_mb: m.cache_size_mb as u32,
59            cache_ttl_secs: m.cache_ttl_secs as u32,
60            status: m.status.as_str().to_string(),
61            error_message: m.error_message,
62            created_at: m.created_at.to_string(),
63            updated_at: m.updated_at.to_string(),
64        }
65    }
66}
67
68pub async fn handler(
69    State(state): State<ServiceState>,
70    Json(req): Json<CreateMountRequest>,
71) -> Result<impl IntoResponse, CreateMountError> {
72    let mount_manager = state.mount_manager().read().await;
73    let mount_manager = mount_manager
74        .as_ref()
75        .ok_or(CreateMountError::MountManagerUnavailable)?;
76
77    let mount = mount_manager
78        .create_mount(
79            req.bucket_id,
80            &req.mount_point,
81            req.auto_mount,
82            req.read_only,
83            req.cache_size_mb,
84            req.cache_ttl_secs,
85        )
86        .await?;
87
88    Ok((
89        http::StatusCode::CREATED,
90        Json(CreateMountResponse {
91            mount: mount.into(),
92        }),
93    )
94        .into_response())
95}
96
97#[derive(Debug, thiserror::Error)]
98pub enum CreateMountError {
99    #[error("Mount manager unavailable")]
100    MountManagerUnavailable,
101    #[error("Mount error: {0}")]
102    Mount(#[from] crate::fuse::MountError),
103}
104
105impl IntoResponse for CreateMountError {
106    fn into_response(self) -> Response {
107        match self {
108            CreateMountError::MountManagerUnavailable => (
109                http::StatusCode::SERVICE_UNAVAILABLE,
110                "Mount manager not available",
111            )
112                .into_response(),
113            CreateMountError::Mount(e) => {
114                (http::StatusCode::BAD_REQUEST, format!("Mount error: {}", e)).into_response()
115            }
116        }
117    }
118}
119
120impl ApiRequest for CreateMountRequest {
121    type Response = CreateMountResponse;
122
123    fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
124        let full_url = base_url.join("/api/v0/mounts/").unwrap();
125        client.post(full_url).json(&self)
126    }
127}