AkurAI Build
Menu

Public repository / 54570403655120661aa5aeb6120646216896cc76

AkurAI-CRM / crates/crm-cli/src/cmd_serve.rs

Latest change ac0d3d868ec83e4002ee32531d1532c1f990c589 - style: cargo fmt by Ólafur Búi Ólafsson

use akurai_http::{Method, Request, Response, Server};
use crm_api::{build_router_with_mcp, CrmState, Route};
use crm_mcp::{McpConfig, McpServer};
use std::io;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

pub struct Config {
    pub host: String,
    pub port: u16,
    pub dir: PathBuf,
    pub db: String,
}

pub fn run(cfg: Config) -> io::Result<()> {
    crm_api::IdpConfig::from_env().map_err(io::Error::other)?;
    let state = CrmState::new(&cfg.db, cfg.dir.clone()).map_err(io::Error::other)?;
    let state = Arc::new(Mutex::new(state));

    let mcp = Arc::new(
        McpServer::new(McpConfig {
            db_path: cfg.db.clone(),
        })
        .map_err(io::Error::other)?,
    );
    let routes = build_router_with_mcp(state, mcp.http_handler());

    let addr = format!("{}:{}", cfg.host, cfg.port);
    let server = Server::bind(&addr)?;
    let local = server.local_addr()?;

    println!("AkurAI-CRM");
    println!("  database: {}", cfg.db);
    println!("  frontend: {}", cfg.dir.display());
    println!("  → http://{local}");

    server.run(move |req: &Request| -> Response { dispatch(&routes, req) })
}

fn dispatch(routes: &[Route], req: &Request) -> Response {
    let mut best: Option<(Response, (usize, usize, usize))> = None;

    for route in routes {
        if !method_matches(route.method, &req.method) {
            continue;
        }
        let pattern = akurai_router::Pattern::parse(route.pattern);
        if pattern.match_path(&req.path).is_some() {
            let score = pattern.specificity();
            if best.as_ref().is_none_or(|(_, b)| score > *b) {
                let resp = (route.handler)(req);
                best = Some((resp, score));
            }
        }
    }

    match best {
        Some((resp, _)) => resp,
        None => Response::not_found(),
    }
}

fn method_matches(pattern: &str, method: &Method) -> bool {
    match method {
        Method::Get => pattern.eq_ignore_ascii_case("GET"),
        Method::Post => pattern.eq_ignore_ascii_case("POST"),
        Method::Put => pattern.eq_ignore_ascii_case("PUT"),
        Method::Patch => pattern.eq_ignore_ascii_case("PATCH"),
        Method::Delete => pattern.eq_ignore_ascii_case("DELETE"),
        Method::Head => pattern.eq_ignore_ascii_case("HEAD"),
        Method::Options => pattern.eq_ignore_ascii_case("OPTIONS"),
        Method::Other(s) => pattern.eq_ignore_ascii_case(s),
    }
}