stigmergy 0.1.0

stigmergy provides emergent agent behavior
Documentation
use arrrg::CommandLine;
use arrrg_derive::CommandLine;
use axum::Router;
use tokio::net::TcpListener;
use tokio::signal;

use stigmergy::{
    create_component_definition_router, create_component_instance_router, create_entity_router,
    create_invariant_router, create_system_router,
};

#[derive(CommandLine, Default, PartialEq, Eq)]
struct Args {
    #[arrrg(optional, "PostgreSQL database URL")]
    database_url: Option<String>,
    #[arrrg(optional, "Host to bind the HTTP server")]
    host: Option<String>,
    #[arrrg(optional, "Port to bind the HTTP server")]
    port: Option<u16>,
    #[arrrg(flag, "Enable verbose logging")]
    verbose: bool,
}

const HELP_TEXT: &str = r#"stigmergyd - Stigmergy daemon

USAGE:
    stigmergyd [OPTIONS]

OPTIONS:
    --database-url <URL> PostgreSQL database URL [env: DATABASE_URL]
    --host <HOST>        Host to bind the HTTP server [default: 127.0.0.1]
    --port <PORT>        Port to bind the HTTP server [default: 8080]
    --verbose            Enable verbose logging

DESCRIPTION:
    Runs the Stigmergy daemon with entity and component management
    endpoints mounted under /api/v1/

    The server supports graceful shutdown via SIGTERM or Ctrl+C.

API ENDPOINTS:
    Entity Management:
      POST   /api/v1/entity              Create a new entity
      DELETE /api/v1/entity/{id}         Delete an entity

    System Management:
      GET    /api/v1/system              List all systems
      POST   /api/v1/system              Create a system
      POST   /api/v1/system/from-markdown Create system from markdown
      GET    /api/v1/system/{id}         Get a specific system
      PUT    /api/v1/system/{id}         Update a system
      PATCH  /api/v1/system/{id}         Patch a system
      DELETE /api/v1/system/{id}         Delete a system
      DELETE /api/v1/system              Delete all systems

    Component Definitions:
      GET    /api/v1/componentdefinition       List all definitions
      POST   /api/v1/componentdefinition       Create a definition
      GET    /api/v1/componentdefinition/{id}  Get a specific definition
      PUT    /api/v1/componentdefinition/{id}  Update a definition
      PATCH  /api/v1/componentdefinition/{id}  Patch a definition
      DELETE /api/v1/componentdefinition/{id}  Delete a definition
      DELETE /api/v1/componentdefinition       Delete all definitions

    Component Instances:
      GET    /api/v1/component       List all components
      POST   /api/v1/component       Create a component
      GET    /api/v1/component/{id}  Get a specific component
      PUT    /api/v1/component/{id}  Update a component
      PATCH  /api/v1/component/{id}  Patch a component
      DELETE /api/v1/component/{id}  Delete a component
      DELETE /api/v1/component       Delete all components

    Invariants:
      GET    /api/v1/invariant       List all invariants
      POST   /api/v1/invariant       Create an invariant
      GET    /api/v1/invariant/{id}  Get a specific invariant
      PUT    /api/v1/invariant/{id}  Update an invariant
      DELETE /api/v1/invariant/{id}  Delete an invariant"#;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (args, free) = Args::from_command_line("USAGE: stigmergyd [OPTIONS]");

    if !free.is_empty() && free[0] == "help" {
        println!("{}", HELP_TEXT);
        return Ok(());
    }

    let config = ServerConfig::from_args(args);

    if config.verbose {
        println!("Stigmergy daemon starting with configuration:");
        println!("  Database URL: {}", config.database_url);
        println!("  Bind address: {}:{}", config.host, config.port);
    }

    // Connect to PostgreSQL
    let pool = sqlx::PgPool::connect(&config.database_url)
        .await
        .map_err(|e| format!("Failed to connect to database: {}", e))?;

    if config.verbose {
        println!("Connected to PostgreSQL database");
    }

    // Run migrations
    sqlx::migrate!("./migrations")
        .run(&pool)
        .await
        .map_err(|e| format!("Failed to run migrations: {}", e))?;

    if config.verbose {
        println!("Database migrations completed");
    }

    // Create routers
    let entity_router = create_entity_router(pool.clone());
    let component_definition_router = create_component_definition_router(pool.clone());
    let component_router = create_component_instance_router(pool.clone());
    let system_router = create_system_router(pool.clone());
    let invariant_router = create_invariant_router(pool.clone());

    let app = Router::new()
        .nest("/api/v1", entity_router)
        .nest("/api/v1", component_definition_router)
        .nest("/api/v1", component_router)
        .nest("/api/v1", system_router)
        .nest("/api/v1", invariant_router);

    // Bind to address
    let addr = format!("{}:{}", config.host, config.port);
    let listener = TcpListener::bind(&addr)
        .await
        .map_err(|e| format!("Failed to bind to {}: {}", addr, e))?;

    println!("🚀 Stigmergy daemon started successfully!");
    println!("📡 Server listening on: http://{}", addr);
    println!("💾 Database: {}", config.database_url);
    println!("🔄 Ready to accept API requests");

    if config.verbose {
        print_api_endpoints();
    }

    println!("💡 Use Ctrl+C or send SIGTERM for graceful shutdown");
    println!();

    // Set up graceful shutdown
    let shutdown_signal = async {
        signal::ctrl_c()
            .await
            .expect("Failed to install Ctrl+C handler");
    };

    // Run server with graceful shutdown
    let server = axum::serve(listener, app);

    tokio::select! {
        result = server => {
            if let Err(e) = result {
                eprintln!("❌ Server error: {}", e);
                std::process::exit(1);
            }
        }
        () = shutdown_signal => {
            println!();
            println!("🛑 Shutdown signal received, stopping server gracefully...");

            if config.verbose {
                println!("📊 Final statistics:");
                println!("   Database: {}", config.database_url);
                println!("   Shutdown completed successfully");
            }

            println!("👋 Stigmergy daemon stopped");
        }
    }

    Ok(())
}

struct ServerConfig {
    database_url: String,
    host: String,
    port: u16,
    verbose: bool,
}

impl ServerConfig {
    fn from_args(args: Args) -> Self {
        let database_url = args
            .database_url
            .or_else(|| std::env::var("DATABASE_URL").ok())
            .unwrap_or_else(|| "postgres://localhost/stigmergy".to_string());

        Self {
            database_url,
            host: args.host.unwrap_or_else(|| "127.0.0.1".to_string()),
            port: args.port.unwrap_or(8080),
            verbose: args.verbose,
        }
    }
}

fn print_api_endpoints() {
    println!();
    println!("📋 Available API endpoints:");
    println!();
    println!("  Entity Management:");
    println!("    POST   /api/v1/entity              Create a new entity");
    println!("    DELETE /api/v1/entity/{{id}}         Delete an entity");
    println!();
    println!("  System Management:");
    println!("    GET    /api/v1/system              List all systems");
    println!("    POST   /api/v1/system              Create a system");
    println!("    POST   /api/v1/system/from-markdown Create system from markdown");
    println!("    GET    /api/v1/system/{{id}}         Get a specific system");
    println!("    PUT    /api/v1/system/{{id}}         Update a system");
    println!("    PATCH  /api/v1/system/{{id}}         Patch a system");
    println!("    DELETE /api/v1/system/{{id}}         Delete a system");
    println!("    DELETE /api/v1/system              Delete all systems");
    println!();
    println!("  Component Definitions:");
    println!("    GET    /api/v1/componentdefinition       List all definitions");
    println!("    POST   /api/v1/componentdefinition       Create a definition");
    println!("    GET    /api/v1/componentdefinition/{{id}}  Get a specific definition");
    println!("    PUT    /api/v1/componentdefinition/{{id}}  Update a definition");
    println!("    PATCH  /api/v1/componentdefinition/{{id}}  Patch a definition");
    println!("    DELETE /api/v1/componentdefinition/{{id}}  Delete a definition");
    println!("    DELETE /api/v1/componentdefinition       Delete all definitions");
    println!();
    println!("  Component Instances:");
    println!("    GET    /api/v1/component       List all components");
    println!("    POST   /api/v1/component       Create a component");
    println!("    GET    /api/v1/component/{{id}}  Get a specific component");
    println!("    PUT    /api/v1/component/{{id}}  Update a component");
    println!("    PATCH  /api/v1/component/{{id}}  Patch a component");
    println!("    DELETE /api/v1/component/{{id}}  Delete a component");
    println!("    DELETE /api/v1/component       Delete all components");
    println!();
    println!("  Invariants:");
    println!("    GET    /api/v1/invariant       List all invariants");
    println!("    POST   /api/v1/invariant       Create an invariant");
    println!("    GET    /api/v1/invariant/{{id}}  Get a specific invariant");
    println!("    PUT    /api/v1/invariant/{{id}}  Update an invariant");
    println!("    DELETE /api/v1/invariant/{{id}}  Delete an invariant");
    println!();
}