mod agent; mod lease; use std::net::SocketAddr; use agent::AgentService; use anyhow::Error; use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::{Json, Router}; use churn_domain::{LeaseResp, ServerEnrollReq, ServerMonitorResp}; use clap::{Parser, Subcommand}; use lease::LeaseService; use serde::{Deserialize, Serialize}; use serde_json::json; #[derive(Parser)] #[command(author, version, about, long_about = None, subcommand_required = true)] struct Command { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { Serve { #[arg(env = "SERVICE_HOST", long, default_value = "127.0.0.1:3000")] host: SocketAddr, }, } #[derive(Clone, Debug, Deserialize, Serialize)] struct Agent { pub name: String, } #[derive(Clone)] struct AppState { agent: AgentService, leases: LeaseService, } #[tokio::main] async fn main() -> anyhow::Result<()> { dotenv::dotenv().ok(); tracing_subscriber::fmt::init(); let cli = Command::parse(); match cli.command { Some(Commands::Serve { host }) => { tracing::info!("Starting churn server"); let app = Router::new() .route("/ping", get(ping)) .route("/logs", get(logs)) .nest( "/agent", Router::new() .route("/enroll", post(enroll)) .route("/ping", post(agent_ping)) .route("/events", post(get_tasks)) .route("/lease", post(agent_lease)), ) .with_state(AppState { agent: AgentService::default(), leases: LeaseService::default(), }); tracing::info!("churn server listening on {}", host); axum::Server::bind(&host) .serve(app.into_make_service()) .await .unwrap(); } None => {} } Ok(()) } enum AppError { Internal(Error), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_message) = match self { AppError::Internal(e) => { tracing::error!("failed with error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "failed with internal error", ) } }; let body = Json(json!({ "error": error_message, })); (status, body).into_response() } } async fn enroll( State(state): State, Json(req): Json, ) -> Result, AppError> { let name = state.agent.enroll(req).await.map_err(AppError::Internal)?; Ok(Json(Agent { name })) } async fn agent_lease(State(state): State) -> Result, AppError> { let lease = state .leases .create_lease() .await .map_err(AppError::Internal)?; Ok(Json(LeaseResp { token: lease })) } async fn agent_ping() -> impl IntoResponse { todo!() } async fn get_tasks() -> impl IntoResponse { todo!() } async fn ping() -> impl IntoResponse { "pong!" } #[derive(Clone, Deserialize)] struct LogsQuery { cursor: Option, } async fn logs(Query(cursor): Query) -> Result, AppError> { match cursor.cursor { Some(cursor) => { tracing::trace!("finding logs from cursor: {}", cursor); } None => { tracing::trace!("finding logs from beginning"); } } Ok(Json(ServerMonitorResp { cursor: uuid::Uuid::new_v4().to_string(), logs: vec!["something".to_string()], })) }