AkurAI Build
Menu

Public repository / d7881502b38897a157bd49340f35ea33e81cbaa7

AkurAI-CRM / crates/crm-api/src/router_setup.rs

Latest change d7881502b38897a157bd49340f35ea33e81cbaa7 - feat: initial AkurAI-CRM implementation by Ólafur B. J.

use crate::handlers;
use akurai_http::{Request, Response};
use akurai_storage::BTree;
use crm_core::metadata::ObjectMetadata;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

/// A registered route: HTTP method, URL pattern, and handler closure.
pub struct Route {
    pub method: &'static str,
    pub pattern: &'static str,
    pub handler: Box<dyn Fn(&Request) -> Response + Send + Sync>,
}

/// Shared CRM application state
pub struct CrmState {
    pub db: Arc<Mutex<BTree>>,
    pub objects: Vec<ObjectMetadata>,
    pub frontend_dir: PathBuf,
}

impl CrmState {
    pub fn new(db_path: &str, frontend_dir: PathBuf) -> Result<Self, String> {
        let btree = BTree::open(db_path).map_err(|e| format!("failed to open storage: {e}"))?;
        let objects = ObjectMetadata::standard_objects();
        Ok(Self {
            db: Arc::new(Mutex::new(btree)),
            objects,
            frontend_dir,
        })
    }

    pub fn now() -> i64 {
        use std::time::{SystemTime, UNIX_EPOCH};
        SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64
    }
}

pub fn r(state: &'static str, p: &'static str, h: Box<dyn Fn(&Request) -> Response + Send + Sync>) -> Route {
    Route { method: state, pattern: p, handler: h }
}

/// Build the route table for the CRM API
pub fn build_router(state: Arc<Mutex<CrmState>>) -> Vec<Route> {
    vec![
        r("GET", "/", handlers::static_file_route(Arc::clone(&state))),
        r("GET", "/*", handlers::static_file_route(Arc::clone(&state))),
        r("GET", "/api/_meta", handlers::meta_route(Arc::clone(&state))),
        r("GET", "/api/people", handlers::list_route(Arc::clone(&state), "people")),
        r("GET", "/api/people/:id", handlers::get_route(Arc::clone(&state), "people")),
        r("POST", "/api/people", handlers::create_route(Arc::clone(&state), "people")),
        r("PUT", "/api/people/:id", handlers::update_route(Arc::clone(&state), "people")),
        r("DELETE", "/api/people/:id", handlers::delete_route(Arc::clone(&state), "people")),
        r("GET", "/api/companies", handlers::list_route(Arc::clone(&state), "companies")),
        r("GET", "/api/companies/:id", handlers::get_route(Arc::clone(&state), "companies")),
        r("POST", "/api/companies", handlers::create_route(Arc::clone(&state), "companies")),
        r("PUT", "/api/companies/:id", handlers::update_route(Arc::clone(&state), "companies")),
        r("DELETE", "/api/companies/:id", handlers::delete_route(Arc::clone(&state), "companies")),
        r("GET", "/api/opportunities", handlers::list_route(Arc::clone(&state), "opportunities")),
        r("GET", "/api/opportunities/:id", handlers::get_route(Arc::clone(&state), "opportunities")),
        r("POST", "/api/opportunities", handlers::create_route(Arc::clone(&state), "opportunities")),
        r("PUT", "/api/opportunities/:id", handlers::update_route(Arc::clone(&state), "opportunities")),
        r("DELETE", "/api/opportunities/:id", handlers::delete_route(Arc::clone(&state), "opportunities")),
        r("GET", "/api/tasks", handlers::list_route(Arc::clone(&state), "tasks")),
        r("GET", "/api/tasks/:id", handlers::get_route(Arc::clone(&state), "tasks")),
        r("POST", "/api/tasks", handlers::create_route(Arc::clone(&state), "tasks")),
        r("PUT", "/api/tasks/:id", handlers::update_route(Arc::clone(&state), "tasks")),
        r("DELETE", "/api/tasks/:id", handlers::delete_route(Arc::clone(&state), "tasks")),
        r("GET", "/api/notes", handlers::list_route(Arc::clone(&state), "notes")),
        r("GET", "/api/notes/:id", handlers::get_route(Arc::clone(&state), "notes")),
        r("POST", "/api/notes", handlers::create_route(Arc::clone(&state), "notes")),
        r("PUT", "/api/notes/:id", handlers::update_route(Arc::clone(&state), "notes")),
        r("DELETE", "/api/notes/:id", handlers::delete_route(Arc::clone(&state), "notes")),
        r("GET", "/api/search", handlers::search_route(Arc::clone(&state))),
        r("GET", "/api/timeline", handlers::timeline_route(Arc::clone(&state))),
    ]
}

/// Upper bound helper for BTree range queries: appends a 0xFF byte so the bound
/// is strictly greater than every key with the given prefix.
pub fn upper_bound(prefix: &[u8]) -> Vec<u8> {
    let mut bound = prefix.to_vec();
    bound.push(0xff);
    bound
}

pub fn json_response(data: akurai_json::Value) -> Response {
    let body = data.to_json();
    Response::ok()
        .with_header("Content-Type", "application/json")
        .with_header("Access-Control-Allow-Origin", "*")
        .with_body("application/json", body.into_bytes())
}

pub fn error_response(status: u16, msg: &str) -> Response {
    let data = akurai_json::Value::Object(vec![
        ("error".into(), akurai_json::Value::Str(msg.into())),
    ]);
    let mut resp = json_response(data);
    resp.status = status;
    resp
}

pub fn not_found(msg: &str) -> Response {
    error_response(404, msg)
}

pub fn bad_request(msg: &str) -> Response {
    error_response(400, msg)
}

pub fn internal_error(msg: &str) -> Response {
    error_response(500, msg)
}