diff --git a/Cargo.lock b/Cargo.lock index d5cfea7..5259b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -542,7 +542,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1709,9 +1709,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2468,7 +2470,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -3073,8 +3075,10 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -3084,6 +3088,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "mime_guess", "native-tls", "percent-encoding", @@ -4193,6 +4198,27 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -5008,9 +5034,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -5035,19 +5061,54 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -5056,7 +5117,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5101,7 +5162,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5141,7 +5202,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5367,6 +5428,7 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" name = "yet-another-nginx-proxy-manager" version = "0.1.0" dependencies = [ + "agent_client", "argon2", "async-trait", "axum", @@ -5380,9 +5442,11 @@ dependencies = [ "migration", "mime_guess", "once_cell", + "reqwest", "sea-orm", "serde", "serde_json", + "tempfile", "tokio", "tower", "tower-http", diff --git a/apps/api/Cargo.toml b/apps/api/Cargo.toml index 593f5ec..f12859c 100644 --- a/apps/api/Cargo.toml +++ b/apps/api/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] database = { path = "../../public/database" } migration = { path = "../../public/migration" } +agent_client = { path = "../../public/agent-client" } axum = { version = "0.8.7", features = ["form", "http1", "http2", "json", "matched-path", "original-uri", "query", "tokio", "tower-log", "tracing", "macros"] } axum-extra = { version = "0.12.2", features = ["cookie"] } @@ -28,4 +29,7 @@ argon2 = { version = "0.5.3", features = ["std"] } jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] } uuid = { version = "1.19.0", features = ["v4", "serde", "fast-rng"] } tower-http = { version = "0.6.8", features = ["cors"] } +reqwest = { version = "^0.12", features = ["json", "multipart", "stream"] } +[dev-dependencies] +tempfile = "3" diff --git a/apps/api/src/services.rs b/apps/api/src/services.rs index 173b067..6f68779 100644 --- a/apps/api/src/services.rs +++ b/apps/api/src/services.rs @@ -1,3 +1,4 @@ +pub mod agent_client; pub mod auth; pub mod server_state; pub mod settings; diff --git a/apps/api/src/services/agent_client.rs b/apps/api/src/services/agent_client.rs new file mode 100644 index 0000000..0dc7449 --- /dev/null +++ b/apps/api/src/services/agent_client.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; + +use agent_client::apis::{ApiClient, configuration::Configuration}; + +pub struct AgentService { + client: Arc, +} + +impl AgentService { + pub fn new(config: impl Into>) -> Self { + let client = ApiClient::new(config.into()); + AgentService { + client: Arc::new(client), + } + } + + pub fn get_client(&self) -> Arc { + Arc::clone(&self.client) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use agent_client::{ + apis::{Api, nginx_agent_api::StatusSuccess}, + models::StatusResp, + }; + use axum::{http::StatusCode, response::Json}; + use std::time::Duration; + use tempfile::tempdir; + use tokio::time::sleep; + + #[test] + fn test_agent_service_creation() { + let config = Configuration::default(); + let service = AgentService::new(config); + let client = service.get_client(); + assert!(Arc::ptr_eq(&client, &service.client)); + } + + #[tokio::test] + async fn test_agent_socket_support() { + // create temporary socket path + let dir = tempdir().expect("Failed to create temp dir"); + let socket_path = dir.path().join("agent.sock"); + + // create axum app with a simple /status route + let app = axum::Router::new().route( + "/status", + axum::routing::get(|| async { + let result: (StatusCode, StatusResp) = (StatusCode::OK, StatusResp { ok: true }); + + (result.0, Json(result.1)) + }), + ); + + // bind tokio unix listener and serve in background + let listener = + tokio::net::UnixListener::bind(&socket_path).expect("Failed to bind to unix socket"); + let server_fut = axum::serve::serve(listener, app); + let _srv = tokio::spawn(async move { + let _ = server_fut.await; + }); + + // give server a moment to start + sleep(Duration::from_millis(50)).await; + + let client: ApiClient = ApiClient::new(Arc::new(Configuration { + base_path: "http://localhost".to_string(), + client: reqwest::Client::builder() + .unix_socket(socket_path.clone()) + .build() + .expect("Failed to build reqwest client"), + ..Default::default() + })); + + let res = client + .nginx_agent_api() + .status() + .await + .expect("Failed to get status"); + let body = res.entity.expect("Response entity is missing"); + + assert!(res.status.is_success()); + if let StatusSuccess::Status200(body) = body { + assert!(body.ok); + } else { + panic!("Unexpected response body"); + } + } +}