greentic-gui 0.4.9

Greentic GUI runtime (Axum-based) that serves tenant packs, enforces auth, and exposes worker/session APIs plus a browser SDK.
use crate::integration::{SessionInfo, SessionManager};
use crate::tenant::{AuthPack, ResolvedRoute, TenantGuiConfig};
use anyhow::Context;
use tokio::fs;

#[derive(Debug)]
pub enum RouteDecision {
    Serve(Box<RouteContent>),
    Redirect(String),
    NotFound,
}

#[derive(Debug)]
pub struct RouteContent {
    pub html: String,
    pub fragments: Vec<crate::tenant::FragmentTarget>,
    pub session: Option<SessionInfo>,
}

pub async fn resolve_route(
    tenant_cfg: &TenantGuiConfig,
    path: &str,
    session_token: Option<String>,
    session_manager: &dyn SessionManager,
) -> anyhow::Result<RouteDecision> {
    let resolved = tenant_cfg.resolve_route(path);
    let Some(resolved) = resolved else {
        return Ok(RouteDecision::NotFound);
    };

    let requires_auth = resolved.authenticated;
    let session = session_manager.validate(session_token).await?;
    if requires_auth && session.is_none() {
        let login_target = login_path(tenant_cfg.auth.as_ref());
        return Ok(RouteDecision::Redirect(login_target));
    }

    let html = load_html(&resolved).await?;
    Ok(RouteDecision::Serve(Box::new(RouteContent {
        html,
        fragments: resolved.fragments,
        session,
    })))
}

async fn load_html(resolved: &ResolvedRoute) -> anyhow::Result<String> {
    let contents = fs::read_to_string(&resolved.html_path)
        .await
        .with_context(|| format!("reading html {:?}", resolved.html_path))?;
    Ok(contents)
}

fn login_path(auth: Option<&AuthPack>) -> String {
    if let Some(auth) = auth
        && let Some(route) = auth.manifest.routes.first()
    {
        return route.path.clone();
    }
    "/login".to_string()
}