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);
}
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");
}
sqlx::migrate!("./migrations")
.run(&pool)
.await
.map_err(|e| format!("Failed to run migrations: {}", e))?;
if config.verbose {
println!("Database migrations completed");
}
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);
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!();
let shutdown_signal = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
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!();
}