agentmux_srv\server/
files.rs1use std::path::PathBuf;
5
6use axum::{
7 body::Body,
8 extract::{Path as AxumPath, Query, State},
9 http::StatusCode,
10 response::{IntoResponse, Json, Response},
11};
12use serde_json::json;
13
14use crate::backend::{docsite, schema};
15
16use super::AppState;
17
18#[derive(serde::Deserialize)]
19pub(super) struct FileQueryParams {
20 zoneid: Option<String>,
21 name: Option<String>,
22 #[serde(default)]
23 offset: i64,
24}
25
26pub(super) async fn handle_wave_file(
27 State(state): State<AppState>,
28 Query(params): Query<FileQueryParams>,
29) -> Response {
30 let zone_id = match ¶ms.zoneid {
31 Some(z) if !z.is_empty() => z.as_str(),
32 _ => {
33 return (
34 StatusCode::BAD_REQUEST,
35 Json(json!({"error": "missing zoneid"})),
36 )
37 .into_response()
38 }
39 };
40 let name = match ¶ms.name {
41 Some(n) if !n.is_empty() => n.as_str(),
42 _ => {
43 return (
44 StatusCode::BAD_REQUEST,
45 Json(json!({"error": "missing name"})),
46 )
47 .into_response()
48 }
49 };
50
51 let file_info = match state.filestore.stat(zone_id, name) {
53 Ok(Some(info)) => info,
54 Ok(None) => {
55 return (
56 StatusCode::NOT_FOUND,
57 Json(json!({"error": "file not found"})),
58 )
59 .into_response()
60 }
61 Err(e) => {
62 return (
63 StatusCode::INTERNAL_SERVER_ERROR,
64 Json(json!({"error": e.to_string()})),
65 )
66 .into_response()
67 }
68 };
69
70 let (_, data) = match state.filestore.read_at(zone_id, name, params.offset, 0) {
72 Ok(result) => result,
73 Err(e) => {
74 return (
75 StatusCode::INTERNAL_SERVER_ERROR,
76 Json(json!({"error": e.to_string()})),
77 )
78 .into_response()
79 }
80 };
81
82 let file_info_json = serde_json::to_string(&file_info).unwrap_or_default();
84 let file_info_b64 =
85 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &file_info_json);
86
87 Response::builder()
88 .status(StatusCode::OK)
89 .header("Content-Type", "application/octet-stream")
90 .header("X-ZoneFileInfo", file_info_b64)
91 .body(Body::from(data))
92 .unwrap_or_else(|_| {
93 (
94 StatusCode::INTERNAL_SERVER_ERROR,
95 "failed to build response",
96 )
97 .into_response()
98 })
99}
100
101pub(super) async fn handle_schema(
102 State(state): State<AppState>,
103 AxumPath(path): AxumPath<String>,
104) -> Response {
105 let app_path = if state.app_path.is_empty() {
106 return (
107 StatusCode::NOT_FOUND,
108 Json(json!({"error": "app path not configured"})),
109 )
110 .into_response();
111 } else {
112 PathBuf::from(&state.app_path)
113 };
114
115 let schema_dir = schema::get_schema_dir(&app_path);
116 let name = match schema::normalize_schema_request(&path) {
117 Some(n) => n,
118 None => {
119 return (
120 StatusCode::BAD_REQUEST,
121 Json(json!({"error": "invalid schema path"})),
122 )
123 .into_response()
124 }
125 };
126
127 match schema::resolve_schema_path(&schema_dir, &name) {
128 Some(file_path) => match std::fs::read(&file_path) {
129 Ok(data) => Response::builder()
130 .status(StatusCode::OK)
131 .header("Content-Type", schema::SCHEMA_CONTENT_TYPE)
132 .body(Body::from(data))
133 .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()),
134 Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
135 },
136 None => StatusCode::NOT_FOUND.into_response(),
137 }
138}
139
140pub(super) async fn handle_docsite(AxumPath(path): AxumPath<String>) -> Response {
141 match docsite::resolve_docsite_path(&path) {
142 Some(file_path) => {
143 let content_type = mime_from_path(&file_path);
144 match std::fs::read(&file_path) {
145 Ok(data) => Response::builder()
146 .status(StatusCode::OK)
147 .header("Content-Type", content_type)
148 .body(Body::from(data))
149 .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()),
150 Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
151 }
152 }
153 None => StatusCode::NOT_FOUND.into_response(),
154 }
155}
156
157fn mime_from_path(path: &std::path::Path) -> &'static str {
158 match path.extension().and_then(|e| e.to_str()) {
159 Some("html") => "text/html; charset=utf-8",
160 Some("css") => "text/css; charset=utf-8",
161 Some("js") => "application/javascript; charset=utf-8",
162 Some("json") => "application/json; charset=utf-8",
163 Some("png") => "image/png",
164 Some("jpg") | Some("jpeg") => "image/jpeg",
165 Some("svg") => "image/svg+xml",
166 Some("woff2") => "font/woff2",
167 Some("woff") => "font/woff",
168 _ => "application/octet-stream",
169 }
170}