leankg 0.7.0

Lightweight Knowledge Graph for AI-Assisted Development
Documentation
#![allow(dead_code)]
pub mod handlers;

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{get, post, put},
    Json, Router,
};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::db::schema::{init_db, CozoDb};
use crate::graph::GraphEngine;

#[derive(Clone)]
pub struct AppState {
    pub db_path: std::path::PathBuf,
    pub current_project_path: std::path::PathBuf,
    db: Arc<RwLock<Option<CozoDb>>>,
}

impl AppState {
    pub async fn new(db_path: std::path::PathBuf, current_project_path: std::path::PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
        Ok(Self {
            db_path,
            current_project_path,
            db: Arc::new(RwLock::new(None)),
        })
    }

    pub async fn init_db(&self) -> Result<(), Box<dyn std::error::Error>> {
        let db = init_db(&self.db_path)?;
        let mut lock = self.db.write().await;
        *lock = Some(db);
        Ok(())
    }

    pub fn get_db(&self) -> Result<CozoDb, Box<dyn std::error::Error + Send + Sync>> {
        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            let lock = self.db.read().await;
            lock.clone()
                .ok_or_else(|| "Database not initialized".into())
        })
    }

    pub async fn get_graph_engine(&self) -> Result<GraphEngine, Box<dyn std::error::Error + Send + Sync>> {
        let lock = self.db.read().await;
        let db = lock.clone()
            .ok_or_else(|| -> Box<dyn std::error::Error + Send + Sync> { "Database not initialized".into() })?;
        Ok(GraphEngine::new(db))
    }
}

#[derive(serde::Serialize)]
pub struct ApiResponse<T> {
    pub success: bool,
    pub data: Option<T>,
    pub error: Option<String>,
}

impl<T: serde::Serialize> IntoResponse for ApiResponse<T> {
    fn into_response(self) -> Response {
        let status = if self.success {
            StatusCode::OK
        } else {
            StatusCode::BAD_REQUEST
        };
        (status, Json(self)).into_response()
    }
}

pub async fn start_server(
    port: u16,
    db_path: std::path::PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
    let state = AppState::new(db_path.clone(), db_path).await?;
    state.init_db().await?;

    let app = Router::new()
        .route("/", get(handlers::index))
        .route("/health", get(handlers::index))
        .route("/graph", get(handlers::graph))
        .route("/browse", get(handlers::browse))
        .route("/docs", get(handlers::docs))
        .route("/annotate", get(handlers::annotate))
        .route("/quality", get(handlers::quality))
        .route("/export", get(handlers::export_page))
        .route("/settings", get(handlers::settings))
        .route("/api/elements", get(handlers::api_elements))
        .route("/api/relationships", get(handlers::api_relationships))
        .route("/api/annotations", get(handlers::api_annotations))
        .route("/api/annotations", post(handlers::api_create_annotation))
        .route("/api/annotations/:element", get(handlers::api_get_annotation))
        .route("/api/annotations/:element", put(handlers::api_update_annotation))
        .route("/api/search", get(handlers::api_search))
        .route("/api/graph/data", get(handlers::api_graph_data))
        .route("/api/export/graph", get(handlers::api_export_graph))
        .route("/api/query", post(handlers::api_query))
        .route("/api/path/switch", post(handlers::api_switch_path))
        .route("/api/index/status", get(handlers::api_index_status))
        .route("/api/github/clone", post(handlers::api_github_clone))
        .with_state(state);

    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    println!("LeanKG Web UI listening on http://localhost:{}", port);
    println!("Press Ctrl+C to stop");

    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}