feat: add add_upstream_target handler and response struct for upstream target management
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
pub mod create_upstream;
|
||||
pub mod create_upstream_target;
|
||||
pub mod get_upstream;
|
||||
pub mod get_upstream_target;
|
||||
pub mod info;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Router, routing::get};
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
use crate::routes::AppState;
|
||||
|
||||
@@ -16,6 +20,10 @@ pub fn get_upstream_router(state: Arc<AppState>) -> Router {
|
||||
get(get_upstream::get_upstream_list).post(create_upstream::create_upstream),
|
||||
)
|
||||
.route("/upstreams/{upstream_id}", get(get_upstream::get_upstream))
|
||||
.route(
|
||||
"/upstreams/{upstream_id}/targets",
|
||||
post(create_upstream_target::add_upstream_target),
|
||||
)
|
||||
.route(
|
||||
"/upstream_targets/{upstream_target_id}",
|
||||
get(get_upstream_target::get_upstream_target),
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Json, extract::State, response::Result as AxumResult};
|
||||
|
||||
use crate::{
|
||||
errors::api_error::ApiError,
|
||||
middlewares::request_info::AuthenticatedRequestInfo,
|
||||
routes::{
|
||||
AppState, api::restricted::nginx::upstream::info::response::UpstreamTargetInfoResponse,
|
||||
},
|
||||
services::nginx::info::upstream_target::UpstreamTargetCreateInfo,
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize, utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct CreateUpstreamTargetInfo {
|
||||
pub upstream_id: uuid::Uuid,
|
||||
pub host: String,
|
||||
pub port: i64,
|
||||
pub weight: Option<i64>,
|
||||
pub is_backup: Option<bool>,
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct ConcreteCreateUpstreamTargetInfo {
|
||||
pub upstream_id: uuid::Uuid,
|
||||
pub host: String,
|
||||
pub port: i64,
|
||||
pub weight: i64,
|
||||
pub is_backup: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl From<CreateUpstreamTargetInfo> for ConcreteCreateUpstreamTargetInfo {
|
||||
fn from(info: CreateUpstreamTargetInfo) -> Self {
|
||||
Self {
|
||||
upstream_id: info.upstream_id,
|
||||
host: info.host,
|
||||
port: info.port,
|
||||
weight: info.weight.unwrap_or(1),
|
||||
is_backup: info.is_backup.unwrap_or(false),
|
||||
enabled: info.enabled.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn add_upstream_target(
|
||||
_request_info: AuthenticatedRequestInfo,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<CreateUpstreamTargetInfo>,
|
||||
) -> AxumResult<Json<UpstreamTargetInfoResponse>, ApiError> {
|
||||
let upstream_service = &state.service.nginx.get_upstream_service();
|
||||
let concrete_payload: ConcreteCreateUpstreamTargetInfo = payload.into();
|
||||
|
||||
let create_info = UpstreamTargetCreateInfo {
|
||||
weight: concrete_payload.weight,
|
||||
is_backup: concrete_payload.is_backup,
|
||||
enabled: concrete_payload.enabled,
|
||||
target_host: concrete_payload.host,
|
||||
target_port: concrete_payload.port,
|
||||
upstream_id: concrete_payload.upstream_id,
|
||||
};
|
||||
|
||||
let upstream_info = upstream_service
|
||||
.create_upstream_target(create_info, None)
|
||||
.await?;
|
||||
|
||||
Ok(Json(upstream_info.into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use axum_test::TestServer;
|
||||
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase};
|
||||
|
||||
use database::generated::entities::upstream_target;
|
||||
|
||||
use crate::{
|
||||
configs::{FromConfig, ProgramSettings},
|
||||
middlewares::require_auth::mock::REQUEST_AUTH_USER_INVALID_HEADER,
|
||||
routes::api::restricted::nginx::upstream::{
|
||||
create_upstream_target::CreateUpstreamTargetInfo, get_upstream_router,
|
||||
},
|
||||
services::get_app_service,
|
||||
};
|
||||
|
||||
fn get_router_with_state(db: DatabaseConnection) -> axum::Router {
|
||||
let program_settings = ProgramSettings::mock();
|
||||
let app_service = get_app_service(&Arc::new(db.clone()), &program_settings);
|
||||
let state = Arc::new(crate::routes::AppState {
|
||||
database_connection: Arc::new(db),
|
||||
service: Arc::new(app_service),
|
||||
config: Arc::new(program_settings),
|
||||
});
|
||||
get_upstream_router(state).layer(axum::middleware::from_fn(
|
||||
crate::middlewares::require_auth::mock::mock_require_auth,
|
||||
))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_add_upstream_target_succeeds_returns_created() {
|
||||
let up_id = uuid::Uuid::new_v4();
|
||||
|
||||
let target_id = uuid::Uuid::new_v4();
|
||||
let target_model = upstream_target::Model {
|
||||
id: target_id,
|
||||
upstream_id: up_id,
|
||||
target_host: "127.0.0.1".to_string(),
|
||||
target_port: 8080,
|
||||
weight: 1,
|
||||
is_backup: false,
|
||||
enabled: true,
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![target_model.clone()]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let payload = CreateUpstreamTargetInfo {
|
||||
upstream_id: up_id,
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
weight: None,
|
||||
is_backup: None,
|
||||
enabled: None,
|
||||
};
|
||||
|
||||
let res = server
|
||||
.post(&format!("/upstreams/{}/targets", up_id))
|
||||
.json(&payload)
|
||||
.await;
|
||||
|
||||
res.assert_status_ok();
|
||||
let text = res.text();
|
||||
let body: crate::routes::api::restricted::nginx::upstream::info::response::UpstreamTargetInfoResponse =
|
||||
serde_json::from_str(&text).expect("failed to parse json");
|
||||
|
||||
assert_eq!(body.id, target_id);
|
||||
assert_eq!(body.host, "127.0.0.1");
|
||||
assert_eq!(body.port, 8080);
|
||||
assert_eq!(body.upstream_id, up_id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_add_upstream_target_invalid_payload_returns_bad_request() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite).into_connection();
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let res = server
|
||||
.post(&format!("/upstreams/{}/targets", uuid::Uuid::new_v4()))
|
||||
.json(&serde_json::json!({}))
|
||||
.await;
|
||||
|
||||
res.assert_status(StatusCode::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_add_upstream_target_unauthenticated_returns_unauthorized() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite).into_connection();
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let payload = CreateUpstreamTargetInfo {
|
||||
upstream_id: uuid::Uuid::new_v4(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
weight: None,
|
||||
is_backup: None,
|
||||
enabled: None,
|
||||
};
|
||||
|
||||
let res = server
|
||||
.post(&format!("/upstreams/{}/targets", payload.upstream_id))
|
||||
.add_header(REQUEST_AUTH_USER_INVALID_HEADER, "true")
|
||||
.json(&payload)
|
||||
.await;
|
||||
|
||||
res.assert_status(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{api_error::ApiError, service_error::ServiceError},
|
||||
errors::api_error::ApiError,
|
||||
routes::{AppState, api::restricted::nginx::upstream::info::response::UpstreamTargetInfo},
|
||||
};
|
||||
|
||||
|
||||
@@ -127,3 +127,38 @@ pub struct UpstreamListResponse {
|
||||
pub items: Vec<UpstreamInfoResponse>,
|
||||
pub pagination: PaginationInfo,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpstreamTargetInfoResponse {
|
||||
pub id: uuid::Uuid,
|
||||
pub host: String,
|
||||
pub port: i64,
|
||||
pub enabled: bool,
|
||||
pub is_backup: bool,
|
||||
pub weight: i32,
|
||||
//
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
//
|
||||
pub upstream_id: Uuid,
|
||||
}
|
||||
|
||||
impl From<crate::services::nginx::info::upstream_target::UpstreamTargetInfo>
|
||||
for UpstreamTargetInfoResponse
|
||||
{
|
||||
fn from(info: crate::services::nginx::info::upstream_target::UpstreamTargetInfo) -> Self {
|
||||
Self {
|
||||
id: info.id,
|
||||
host: info.target_host,
|
||||
port: info.target_port,
|
||||
enabled: info.enabled,
|
||||
is_backup: info.is_backup,
|
||||
weight: info.weight as i32,
|
||||
//
|
||||
created_at: info.created_at,
|
||||
updated_at: info.updated_at,
|
||||
//
|
||||
upstream_id: info.upstream_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user