157 lines
5.9 KiB
Rust
157 lines
5.9 KiB
Rust
|
|
// vapora-backend: REST API server for VAPORA v1.0
|
||
|
|
// Phase 1: Complete backend with SurrealDB integration
|
||
|
|
|
||
|
|
mod api;
|
||
|
|
mod config;
|
||
|
|
mod services;
|
||
|
|
|
||
|
|
use anyhow::Result;
|
||
|
|
use axum::{
|
||
|
|
routing::{delete, get, post, put},
|
||
|
|
Extension, Router,
|
||
|
|
};
|
||
|
|
use std::sync::Arc;
|
||
|
|
use std::net::SocketAddr;
|
||
|
|
use tower_http::cors::{Any, CorsLayer};
|
||
|
|
use tracing::{info, Level};
|
||
|
|
use vapora_swarm::{SwarmCoordinator, SwarmMetrics};
|
||
|
|
|
||
|
|
use crate::api::AppState;
|
||
|
|
use crate::config::Config;
|
||
|
|
use crate::services::{AgentService, ProjectService, TaskService};
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<()> {
|
||
|
|
// Load environment variables from .env file if present
|
||
|
|
dotenv::dotenv().ok();
|
||
|
|
|
||
|
|
// Initialize logging
|
||
|
|
tracing_subscriber::fmt()
|
||
|
|
.with_max_level(Level::INFO)
|
||
|
|
.with_target(false)
|
||
|
|
.compact()
|
||
|
|
.init();
|
||
|
|
|
||
|
|
info!("VAPORA Backend v{}", env!("CARGO_PKG_VERSION"));
|
||
|
|
info!("Phase 1: Backend Core + SurrealDB");
|
||
|
|
|
||
|
|
// Load configuration
|
||
|
|
let config = Config::load("config/vapora.toml")?;
|
||
|
|
info!("Configuration loaded successfully");
|
||
|
|
|
||
|
|
// Connect to SurrealDB
|
||
|
|
info!("Connecting to SurrealDB at {}", config.database.url);
|
||
|
|
let db = surrealdb::Surreal::new::<surrealdb::engine::remote::ws::Ws>(&config.database.url)
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
// Sign in to database
|
||
|
|
db.signin(surrealdb::opt::auth::Root {
|
||
|
|
username: "root",
|
||
|
|
password: "root",
|
||
|
|
})
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
// Use namespace and database
|
||
|
|
db.use_ns("vapora").use_db("main").await?;
|
||
|
|
info!("Connected to SurrealDB");
|
||
|
|
|
||
|
|
// Initialize services
|
||
|
|
let project_service = ProjectService::new(db.clone());
|
||
|
|
let task_service = TaskService::new(db.clone());
|
||
|
|
let agent_service = AgentService::new(db.clone());
|
||
|
|
|
||
|
|
// Create application state
|
||
|
|
let app_state = AppState::new(project_service, task_service, agent_service);
|
||
|
|
|
||
|
|
// Create SwarmMetrics for Prometheus monitoring
|
||
|
|
let metrics = match SwarmMetrics::new() {
|
||
|
|
Ok(m) => {
|
||
|
|
info!("SwarmMetrics initialized for Prometheus monitoring");
|
||
|
|
m
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
tracing::warn!("Failed to initialize SwarmMetrics: {:?}, continuing without metrics", e);
|
||
|
|
// Create new registry and metrics as fallback
|
||
|
|
SwarmMetrics::new().unwrap()
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Create SwarmCoordinator for multi-agent coordination
|
||
|
|
let mut swarm_coordinator = SwarmCoordinator::new();
|
||
|
|
swarm_coordinator.set_metrics(Arc::clone(&metrics));
|
||
|
|
let swarm_coordinator = Arc::new(swarm_coordinator);
|
||
|
|
info!("SwarmCoordinator initialized for Phase 5.2");
|
||
|
|
|
||
|
|
// Configure CORS
|
||
|
|
let cors = CorsLayer::new()
|
||
|
|
.allow_origin(Any)
|
||
|
|
.allow_methods(Any)
|
||
|
|
.allow_headers(Any);
|
||
|
|
|
||
|
|
// Build router
|
||
|
|
let app = Router::new()
|
||
|
|
// Health endpoint
|
||
|
|
.route("/health", get(api::health::health))
|
||
|
|
// Metrics endpoint (Prometheus)
|
||
|
|
.route("/metrics", get(api::metrics::metrics_handler))
|
||
|
|
// Project endpoints
|
||
|
|
.route("/api/v1/projects", get(api::projects::list_projects).post(api::projects::create_project))
|
||
|
|
.route(
|
||
|
|
"/api/v1/projects/:id",
|
||
|
|
get(api::projects::get_project)
|
||
|
|
.put(api::projects::update_project)
|
||
|
|
.delete(api::projects::delete_project),
|
||
|
|
)
|
||
|
|
.route("/api/v1/projects/:id/features", post(api::projects::add_feature))
|
||
|
|
.route("/api/v1/projects/:id/features/:feature", delete(api::projects::remove_feature))
|
||
|
|
.route("/api/v1/projects/:id/archive", post(api::projects::archive_project))
|
||
|
|
// Task endpoints
|
||
|
|
.route("/api/v1/tasks", get(api::tasks::list_tasks).post(api::tasks::create_task))
|
||
|
|
.route(
|
||
|
|
"/api/v1/tasks/:id",
|
||
|
|
get(api::tasks::get_task)
|
||
|
|
.put(api::tasks::update_task)
|
||
|
|
.delete(api::tasks::delete_task),
|
||
|
|
)
|
||
|
|
.route("/api/v1/tasks/:id/reorder", put(api::tasks::reorder_task))
|
||
|
|
.route("/api/v1/tasks/:id/status", put(api::tasks::update_task_status))
|
||
|
|
.route("/api/v1/tasks/:id/assign", put(api::tasks::assign_task))
|
||
|
|
.route("/api/v1/tasks/:id/priority", put(api::tasks::update_priority))
|
||
|
|
// Agent endpoints (specific routes before parameterized routes)
|
||
|
|
.route("/api/v1/agents", get(api::agents::list_agents).post(api::agents::register_agent))
|
||
|
|
.route("/api/v1/agents/available", get(api::agents::get_available_agents))
|
||
|
|
.route(
|
||
|
|
"/api/v1/agents/:id",
|
||
|
|
get(api::agents::get_agent)
|
||
|
|
.put(api::agents::update_agent)
|
||
|
|
.delete(api::agents::deregister_agent),
|
||
|
|
)
|
||
|
|
.route("/api/v1/agents/:id/health", get(api::agents::check_agent_health))
|
||
|
|
.route("/api/v1/agents/:id/status", put(api::agents::update_agent_status))
|
||
|
|
.route("/api/v1/agents/:id/capabilities", post(api::agents::add_capability))
|
||
|
|
.route("/api/v1/agents/:id/capabilities/:capability", delete(api::agents::remove_capability))
|
||
|
|
.route("/api/v1/agents/:id/skills", post(api::agents::add_skill))
|
||
|
|
// Tracking endpoints
|
||
|
|
.route("/api/v1/tracking/entries", get(api::tracking::list_tracking_entries))
|
||
|
|
.route("/api/v1/tracking/summary", get(api::tracking::get_tracking_summary))
|
||
|
|
.route("/api/v1/tracking/health", get(api::tracking::tracking_health))
|
||
|
|
// Swarm endpoints (Phase 5.2)
|
||
|
|
.route("/api/v1/swarm/stats", get(api::swarm::swarm_statistics))
|
||
|
|
.route("/api/v1/swarm/health", get(api::swarm::swarm_health))
|
||
|
|
// Apply CORS, state, and extensions
|
||
|
|
.layer(Extension(swarm_coordinator))
|
||
|
|
.layer(cors)
|
||
|
|
.with_state(app_state);
|
||
|
|
|
||
|
|
// Start server
|
||
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], config.server.port));
|
||
|
|
info!("Server listening on {}", addr);
|
||
|
|
info!("Health check: http://{}/health", addr);
|
||
|
|
info!("API documentation: http://{}/api/v1", addr);
|
||
|
|
|
||
|
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||
|
|
axum::serve(listener, app).await?;
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|