featherbit/admin/
routes.rs1use std::sync::Arc;
6
7use axum::extract::{Path, State};
8use axum::http::StatusCode;
9use axum::response::IntoResponse;
10use axum::routing::get;
11use axum::{Json, Router};
12
13use crate::config::RouteConfig;
14use crate::state::SharedState;
15
16pub fn router() -> Router<Arc<SharedState>> {
18 Router::new()
19 .route("/api/routes", get(list_routes).post(create_route))
20 .route(
21 "/api/routes/{name}",
22 get(get_route).put(update_route).delete(delete_route),
23 )
24}
25
26async fn list_routes(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
28 let gw = state.gateway.read().await;
29 Json(&gw.routes).into_response()
30}
31
32async fn get_route(
36 State(state): State<Arc<SharedState>>,
37 Path(name): Path<String>,
38) -> impl IntoResponse {
39 let gw = state.gateway.read().await;
40 match gw.routes.iter().find(|r| r.name == name) {
41 Some(route) => Json(route).into_response(),
42 None => (
43 StatusCode::NOT_FOUND,
44 Json(serde_json::json!({"error": "not_found"})),
45 )
46 .into_response(),
47 }
48}
49
50async fn create_route(
58 State(state): State<Arc<SharedState>>,
59 Json(route): Json<RouteConfig>,
60) -> impl IntoResponse {
61 let candidate = {
62 let gw = state.gateway.read().await;
63 if gw.routes.iter().any(|r| r.name == route.name) {
64 return (
65 StatusCode::CONFLICT,
66 Json(serde_json::json!({"error": "route already exists"})),
67 )
68 .into_response();
69 }
70 let mut candidate = gw.clone();
71 candidate.routes.push(route);
72 candidate
73 };
74
75 match state.config_store.clone().commit(&state, candidate).await {
76 Ok(_) => (
77 StatusCode::CREATED,
78 Json(serde_json::json!({"status": "created"})),
79 )
80 .into_response(),
81 Err(e) => (
82 StatusCode::BAD_REQUEST,
83 Json(serde_json::json!({"error": e})),
84 )
85 .into_response(),
86 }
87}
88
89async fn update_route(
96 State(state): State<Arc<SharedState>>,
97 Path(name): Path<String>,
98 Json(mut route): Json<RouteConfig>,
99) -> impl IntoResponse {
100 route.name = name.clone();
101 let candidate = {
102 let gw = state.gateway.read().await;
103 let mut candidate = gw.clone();
104 if let Some(existing) = candidate.routes.iter_mut().find(|r| r.name == name) {
105 *existing = route;
106 } else {
107 return (
108 StatusCode::NOT_FOUND,
109 Json(serde_json::json!({"error": "not_found"})),
110 )
111 .into_response();
112 }
113 candidate
114 };
115
116 match state.config_store.clone().commit(&state, candidate).await {
117 Ok(_) => Json(serde_json::json!({"status": "updated"})).into_response(),
118 Err(e) => (
119 StatusCode::BAD_REQUEST,
120 Json(serde_json::json!({"error": e})),
121 )
122 .into_response(),
123 }
124}
125
126async fn delete_route(
132 State(state): State<Arc<SharedState>>,
133 Path(name): Path<String>,
134) -> impl IntoResponse {
135 let candidate = {
136 let gw = state.gateway.read().await;
137 let mut candidate = gw.clone();
138 let before = candidate.routes.len();
139 candidate.routes.retain(|r| r.name != name);
140 if candidate.routes.len() == before {
141 return (
142 StatusCode::NOT_FOUND,
143 Json(serde_json::json!({"error": "not_found"})),
144 )
145 .into_response();
146 }
147 candidate
148 };
149
150 match state.config_store.clone().commit(&state, candidate).await {
151 Ok(_) => Json(serde_json::json!({"status": "deleted"})).into_response(),
152 Err(e) => (
153 StatusCode::BAD_REQUEST,
154 Json(serde_json::json!({"error": e})),
155 )
156 .into_response(),
157 }
158}