From 4f826d7da587ea37d99a74152994ed7c72f72b1db4a81b2729ffee70e4ac7617 Mon Sep 17 00:00:00 2001 From: RavenX8 <7156279+RavenX8@users.noreply.github.com> Date: Sat, 14 Dec 2024 01:51:00 -0500 Subject: [PATCH] - add: api service gateway to allow website logins --- api-service/Cargo.toml | 23 ++++++++++ api-service/build.rs | 6 +++ api-service/src/axum_gateway.rs | 75 +++++++++++++++++++++++++++++++ api-service/src/main.rs | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 api-service/Cargo.toml create mode 100644 api-service/build.rs create mode 100644 api-service/src/axum_gateway.rs create mode 100644 api-service/src/main.rs diff --git a/api-service/Cargo.toml b/api-service/Cargo.toml new file mode 100644 index 0000000..22d26f7 --- /dev/null +++ b/api-service/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "api-service" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7.9" +hyper = { version = "1.5.1", features = ["server"] } +prost = "0.13.4" +serde = { version = "1.0.216", features = ["derive"] } +serde_json = "1.0.133" +tokio = { version = "1.42.0", features = ["full"] } +tonic = { version = "0.12.3", features = ["transport", "prost"] } +tower = "0.5.2" +log = "0.4.22" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" +utils = { path = "../utils" } +dotenv = "0.15" +tower-http = { version = "0.6.2", features = ["cors"] } + +[build-dependencies] +tonic-build = "0.12.3" diff --git a/api-service/build.rs b/api-service/build.rs new file mode 100644 index 0000000..1b15765 --- /dev/null +++ b/api-service/build.rs @@ -0,0 +1,6 @@ +fn main() -> Result<(), Box> { + tonic_build::configure() + .compile_well_known_types(true) + .compile_protos(&["../proto/auth.proto"], &["../proto"])?; + Ok(()) +} diff --git a/api-service/src/axum_gateway.rs b/api-service/src/axum_gateway.rs new file mode 100644 index 0000000..aeb3371 --- /dev/null +++ b/api-service/src/axum_gateway.rs @@ -0,0 +1,75 @@ +use axum::extract::State; +use axum::{routing::post, Json, Router}; +use serde::{Deserialize, Serialize}; +use std::env; +use std::sync::Arc; +use axum::http::Method; +use tonic::transport::Channel; +use tower_http::cors::{Any, CorsLayer}; + +use auth::auth_service_client::AuthServiceClient; +use auth::LoginRequest; +use log::error; +use tokio::sync::Mutex; + +pub mod auth { + tonic::include_proto!("auth"); +} + +#[derive(Serialize, Deserialize)] +struct RestLoginRequest { + username: String, + password: String, +} + +#[derive(Serialize, Deserialize)] +struct RestLoginResponse { + token: String, +} + +async fn login_handler( + State(grpc_client): State>>>, + Json(payload): Json, +) -> Result, axum::http::StatusCode> { + let request = tonic::Request::new(LoginRequest { + username: payload.username.clone(), + password: payload.password.clone(), + }); + + let mut client = grpc_client.lock().await; // Lock the mutex to get mutable access + match client.login(request).await { + Ok(response) => { + let token = response.into_inner().token; + Ok(Json(RestLoginResponse { token })) + } + Err(e) => { + error!("gRPC Login call failed: {}", e); + Err(axum::http::StatusCode::UNAUTHORIZED) + } + } +} + +pub async fn serve_rest_api( + grpc_client: Arc>>, +) -> Result<(), Box> { + let cors = CorsLayer::new() + .allow_origin(Any) // Allow requests from any origin + .allow_methods([Method::GET, Method::POST]) // Allow specific methods + .allow_headers(Any); // Allow any headers + + let app = Router::new() + .route("/api/login", post(login_handler)) + .with_state(grpc_client) + .layer(cors); + + let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("API_SERVICE_PORT").unwrap_or_else(|_| "50050".to_string()); + let listener = tokio::net::TcpListener::bind(format!("{}:{}", addr, port)) + .await + .unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + + Ok(()) +} diff --git a/api-service/src/main.rs b/api-service/src/main.rs new file mode 100644 index 0000000..958be0d --- /dev/null +++ b/api-service/src/main.rs @@ -0,0 +1,79 @@ +use crate::axum_gateway::auth::auth_service_client::AuthServiceClient; +use dotenv::dotenv; +use std::collections::HashMap; +use std::env; +use std::str::FromStr; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio::{select, signal}; +use tracing::{info, Level}; +use utils::consul_registration; +use utils::service_discovery::get_service_address; + +mod axum_gateway; + +#[tokio::main] +async fn main() -> Result<(), Box> { + dotenv().ok(); + tracing_subscriber::fmt() + .with_max_level( + Level::from_str(&env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string())) + .unwrap_or_else(|_| Level::INFO), + ) + .init(); + + // Set the gRPC server address + let addr = env::var("API_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("API_SERVICE_PORT").unwrap_or_else(|_| "50050".to_string()); + let health_port = env::var("HEALTH_CHECK_PORT").unwrap_or_else(|_| "8079".to_string()); + + let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string()); + let service_name = env::var("SERVICE_NAME").unwrap_or_else(|_| "api-service".to_string()); + let service_address = addr.as_str(); + let service_port = port.clone(); + let health_check_url = format!("http://{}:{}/health", service_address, health_port); + + // Register service with Consul + let service_id = consul_registration::generate_service_id(); + let tags = vec!["version-1.0".to_string()]; + let meta = HashMap::new(); + consul_registration::register_service( + &consul_url, + service_id.as_str(), + service_name.as_str(), + service_address, + service_port.parse().unwrap_or(50050), + tags, + meta, + &health_check_url, + ) + .await?; + + // Start health-check endpoint + consul_registration::start_health_check(addr.as_str()).await?; + + let auth_node = get_service_address(&consul_url, "auth-service").await?; + let auth_address = auth_node.get(0).unwrap(); + let auth_service_address = format!( + "http://{}:{}", + auth_address.ServiceAddress, auth_address.ServicePort + ); + + // Connect to the gRPC auth-service + let grpc_client = AuthServiceClient::connect(auth_service_address.to_string()).await?; + let grpc_client = Arc::new(Mutex::new(grpc_client)); + + // Start the Axum REST API + info!("Starting REST API on 0.0.0.0:8079"); + axum_gateway::serve_rest_api(grpc_client).await?; + + select! { + _ = signal::ctrl_c() => {}, + } + + consul_registration::deregister_service(&consul_url, service_id.as_str()) + .await + .expect(""); + info!("service {} deregistered", service_name); + Ok(()) +}