1use axum::{
2 extract::{Path, State as AxumState, Query},
3 routing::{get, post, delete},
4 Json, Router,
5};
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use std::collections::HashMap;
9use crate::manager::LiteFSManager;
10use minifly_core::Error;
11use crate::Result;
12
13#[derive(Clone)]
14pub struct ServerState {
15 manager: Arc<LiteFSManager>,
16}
17
18#[derive(Debug, Serialize)]
19pub struct LiteFSStatus {
20 pub machine_id: String,
21 pub is_running: bool,
22 pub is_primary: bool,
23 pub mount_path: String,
24 pub proxy_url: String,
25}
26
27#[derive(Debug, Deserialize)]
28pub struct StartRequest {
29 pub is_primary: bool,
30}
31
32#[derive(Debug, Serialize)]
33pub struct ApiResponse<T> {
34 pub success: bool,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub data: Option<T>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub error: Option<String>,
39}
40
41pub fn create_router(manager: Arc<LiteFSManager>) -> Router {
42 let state = ServerState { manager };
43
44 Router::new()
45 .route("/health", get(health_check))
46 .route("/instances", get(list_instances))
47 .route("/instances/:machine_id", get(get_instance))
48 .route("/instances/:machine_id/start", post(start_instance))
49 .route("/instances/:machine_id/stop", post(stop_instance))
50 .route("/instances/:machine_id/status", get(get_status))
51 .with_state(state)
52}
53
54async fn health_check() -> Json<ApiResponse<String>> {
55 Json(ApiResponse {
56 success: true,
57 data: Some("LiteFS server is running".to_string()),
58 error: None,
59 })
60}
61
62async fn list_instances(
63 AxumState(state): AxumState<ServerState>,
64) -> Json<ApiResponse<Vec<String>>> {
65 Json(ApiResponse {
67 success: true,
68 data: Some(vec![]),
69 error: None,
70 })
71}
72
73async fn get_instance(
74 AxumState(state): AxumState<ServerState>,
75 Path(machine_id): Path<String>,
76) -> Json<ApiResponse<LiteFSStatus>> {
77 let is_running = state.manager.is_running(&machine_id).await;
78
79 if !is_running {
80 return Json(ApiResponse {
81 success: false,
82 data: None,
83 error: Some("LiteFS instance not found".to_string()),
84 });
85 }
86
87 let status = LiteFSStatus {
88 machine_id: machine_id.clone(),
89 is_running,
90 is_primary: true, mount_path: state.manager.get_mount_path(&machine_id).to_string_lossy().to_string(),
92 proxy_url: state.manager.get_proxy_url(&machine_id),
93 };
94
95 Json(ApiResponse {
96 success: true,
97 data: Some(status),
98 error: None,
99 })
100}
101
102async fn start_instance(
103 AxumState(state): AxumState<ServerState>,
104 Path(machine_id): Path<String>,
105 Json(req): Json<StartRequest>,
106) -> Json<ApiResponse<LiteFSStatus>> {
107 match state.manager.start_for_machine(&machine_id, req.is_primary).await {
108 Ok(_) => {
109 let status = LiteFSStatus {
110 machine_id: machine_id.clone(),
111 is_running: true,
112 is_primary: req.is_primary,
113 mount_path: state.manager.get_mount_path(&machine_id).to_string_lossy().to_string(),
114 proxy_url: state.manager.get_proxy_url(&machine_id),
115 };
116
117 Json(ApiResponse {
118 success: true,
119 data: Some(status),
120 error: None,
121 })
122 }
123 Err(e) => {
124 Json(ApiResponse {
125 success: false,
126 data: None,
127 error: Some(e.to_string()),
128 })
129 }
130 }
131}
132
133async fn stop_instance(
134 AxumState(state): AxumState<ServerState>,
135 Path(machine_id): Path<String>,
136) -> Json<ApiResponse<String>> {
137 match state.manager.stop_for_machine(&machine_id).await {
138 Ok(_) => {
139 Json(ApiResponse {
140 success: true,
141 data: Some(format!("LiteFS instance {} stopped", machine_id)),
142 error: None,
143 })
144 }
145 Err(e) => {
146 Json(ApiResponse {
147 success: false,
148 data: None,
149 error: Some(e.to_string()),
150 })
151 }
152 }
153}
154
155async fn get_status(
156 AxumState(state): AxumState<ServerState>,
157 Path(machine_id): Path<String>,
158) -> Json<ApiResponse<bool>> {
159 let is_running = state.manager.is_running(&machine_id).await;
160
161 Json(ApiResponse {
162 success: true,
163 data: Some(is_running),
164 error: None,
165 })
166}
167
168pub async fn run_server(manager: Arc<LiteFSManager>, port: u16) -> Result<()> {
169 let app = create_router(manager);
170
171 let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
172
173 let listener = tokio::net::TcpListener::bind(addr).await
174 .map_err(|e| Error::LiteFSError(format!("Failed to bind to port {}: {}", port, e)))?;
175
176 tracing::info!("LiteFS HTTP server listening on {}", addr);
177
178 axum::serve(listener, app).await
179 .map_err(|e| Error::LiteFSError(format!("Server error: {}", e)))?;
180
181 Ok(())
182}