1
0
forked from boos/BoosAPI

feat: add workflow studio and postgres support

This commit is contained in:
boos
2026-05-25 16:03:07 +08:00
parent 7cdb02ef1d
commit cb35d5fc6e
96 changed files with 12414 additions and 15548 deletions
+40 -24
View File
@@ -1,7 +1,7 @@
use anyhow::Context;
use axum::serve;
use boosapi::{config::AppConfig, db, services::builtin_keys, AppState};
use clap::Parser;
use clap::{Parser, Subcommand};
use std::ffi::OsString;
use std::net::SocketAddr;
use tokio::signal;
@@ -15,6 +15,9 @@ use tracing_subscriber::EnvFilter;
about = "AI API Gateway — Rust rewrite of BoosAPI"
)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
/// Config file path
#[arg(short, long, default_value = "config.toml")]
config: String,
@@ -40,10 +43,42 @@ struct Cli {
check_config: bool,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Copy core data from SQLite into PostgreSQL and exit
MigrateSqliteToPostgres {
/// Source SQLite URL, e.g. sqlite:boosapi.db?mode=rwc
#[arg(long)]
sqlite_url: String,
/// Target PostgreSQL URL, e.g. postgres://user:pass@host:5432/boosapi
#[arg(long)]
postgres_url: String,
/// Truncate target business tables before importing
#[arg(long)]
force_truncate: bool,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Cli::parse_from(normalize_legacy_args());
if let Some(Commands::MigrateSqliteToPostgres {
sqlite_url,
postgres_url,
force_truncate,
}) = &args.command
{
let sqlite = db::create_pool(sqlite_url, 1).await?;
let pg = db::create_pg_pool(postgres_url, 2).await?;
db::run_migrations(db::DbKind::Postgres, None, Some(&pg)).await?;
let summary = db::sqlite_to_postgres::migrate(&sqlite, &pg, *force_truncate).await?;
println!("{}", serde_json::to_string_pretty(&summary)?);
return Ok(());
}
// Load config
let mut config = AppConfig::load_from_file(&args.config)?;
@@ -73,12 +108,14 @@ async fn main() -> anyhow::Result<()> {
tracing::info!("Loaded configuration from {}", args.config);
let db_kind = db::DbKind::from_url(&config.db.url)?;
// Create database pool
let pool = db::create_pool(&config.db.url, config.db.max_connections).await?;
tracing::info!("Database connected");
// Run migrations
sqlx::migrate!("./migrations").run(&pool).await?;
db::run_migrations(db_kind, Some(&pool), None).await?;
tracing::info!("Database migrations applied");
if args.migrate_only {
@@ -105,20 +142,6 @@ async fn main() -> anyhow::Result<()> {
}
let comfyui_process = state.comfyui.clone();
// Multi-tenant ComfyUI pool: adopt any containers that survived a
// previous API restart, then start the idle-reaper task. Only
// wired in when the `multi_tenant_comfy` feature is on; under the
// legacy singleton path the pool is constructed but unused, and
// `comfyui_pool` is `None` so we skip shutdown_all on exit.
#[cfg(feature = "multi_tenant_comfy")]
let comfyui_pool = {
state.comfyui_pool.reconcile_on_startup().await;
boosapi::services::comfyui_pool::spawn_reaper(state.comfyui_pool.clone());
Some(state.comfyui_pool.clone())
};
#[cfg(not(feature = "multi_tenant_comfy"))]
let comfyui_pool: Option<std::sync::Arc<boosapi::services::comfyui_pool::ComfyUIPool>> = None;
// Build router
let app = boosapi::routes::build_router(state);
@@ -128,7 +151,7 @@ async fn main() -> anyhow::Result<()> {
serve(
tokio::net::TcpListener::bind(addr).await?,
app.into_make_service(),
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(shutdown_signal())
.await?;
@@ -137,13 +160,6 @@ async fn main() -> anyhow::Result<()> {
tracing::warn!(%err, "ComfyUI embedded process did not stop cleanly");
}
// Tear down any per-user ComfyUI containers we spawned. This is
// best-effort — a hard kill of the API leaves them running and the
// next startup will adopt them via reconcile_on_startup().
if let Some(pool) = comfyui_pool {
pool.shutdown_all().await;
}
Ok(())
}