mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-14 23:22:51 +00:00
235 lines
8.4 KiB
Rust
235 lines
8.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use axum::Router;
|
|
use axum::http::{HeaderMap, HeaderValue, header};
|
|
use axum::routing::{get, post};
|
|
use serde::Serialize;
|
|
|
|
use super::{ApiError, AppState, IntoResponse, Json, Response, StatusCode, demo};
|
|
|
|
mod artifacts;
|
|
mod automations;
|
|
mod billing;
|
|
mod completions;
|
|
mod environments;
|
|
pub(in crate::server) mod events;
|
|
pub(in crate::server) mod graph;
|
|
pub(in crate::server) mod lifecycle;
|
|
mod llm_sse;
|
|
mod mcp_servers;
|
|
mod models;
|
|
mod pair;
|
|
mod playground;
|
|
pub(in crate::server) mod pull_requests;
|
|
pub(in crate::server) mod runs;
|
|
mod sandbox;
|
|
mod sandboxes;
|
|
mod secrets;
|
|
mod sessions;
|
|
mod steer;
|
|
pub(in crate::server) mod system;
|
|
mod variables;
|
|
mod worker_control;
|
|
mod workflow_versions;
|
|
|
|
pub(super) use system::{health, openapi_spec};
|
|
|
|
async fn not_implemented() -> Response {
|
|
ApiError::new(StatusCode::NOT_IMPLEMENTED, "Not implemented.").into_response()
|
|
}
|
|
|
|
fn parse_required_if_match<R>(
|
|
headers: &HeaderMap,
|
|
resource: &str,
|
|
id: &impl std::fmt::Display,
|
|
) -> Result<R, ApiError>
|
|
where
|
|
R: std::str::FromStr,
|
|
R::Err: std::fmt::Display,
|
|
{
|
|
let Some(value) = headers.get(header::IF_MATCH) else {
|
|
return Err(ApiError::new(
|
|
StatusCode::PRECONDITION_REQUIRED,
|
|
format!("If-Match header is required for {resource}: {id}"),
|
|
));
|
|
};
|
|
let value = value
|
|
.to_str()
|
|
.map_err(|_| ApiError::bad_request("If-Match header must be visible ASCII"))?;
|
|
let value = unquote_etag(value.trim());
|
|
value.parse::<R>().map_err(|err| {
|
|
ApiError::bad_request(format!("invalid If-Match {resource} revision: {err}"))
|
|
})
|
|
}
|
|
|
|
fn unquote_etag(value: &str) -> &str {
|
|
value
|
|
.strip_prefix('"')
|
|
.and_then(|unquoted| unquoted.strip_suffix('"'))
|
|
.unwrap_or(value)
|
|
}
|
|
|
|
fn json_with_etag_response<T>(
|
|
status: StatusCode,
|
|
resource: &str,
|
|
revision: &impl std::fmt::Display,
|
|
body: T,
|
|
) -> Response
|
|
where
|
|
T: Serialize,
|
|
{
|
|
let etag = HeaderValue::from_str(&format!("\"{revision}\""))
|
|
.unwrap_or_else(|_| panic!("{resource} revisions are valid ETag header values"));
|
|
let mut response = (status, Json(body)).into_response();
|
|
response.headers_mut().insert(header::ETAG, etag);
|
|
response
|
|
}
|
|
|
|
pub(super) fn demo_routes() -> Router<Arc<AppState>> {
|
|
Router::new()
|
|
.route("/runs", get(demo::list_runs).post(demo::create_run_stub))
|
|
.route("/runs/resolve", get(demo::resolve_run))
|
|
.route("/attach", get(demo::attach_events_stub))
|
|
.route("/runs/{id}", get(demo::get_run_status))
|
|
.route("/runs/{id}/questions", get(demo::get_questions_stub))
|
|
.route("/runs/{id}/questions/{qid}/answer", post(demo::answer_stub))
|
|
.route("/runs/{id}/state", get(not_implemented))
|
|
.route("/runs/{id}/logs", get(not_implemented))
|
|
.route(
|
|
"/runs/{id}/events",
|
|
get(not_implemented).post(not_implemented),
|
|
)
|
|
.route("/runs/{id}/attach", get(demo::run_events_stub))
|
|
.route("/runs/{id}/blobs", post(not_implemented))
|
|
.route("/runs/{id}/blobs/{blobHash}", get(not_implemented))
|
|
.route(
|
|
"/runs/{id}/stages/{stageId}/logs/output",
|
|
get(not_implemented),
|
|
)
|
|
.route("/runs/{id}/checkpoint", get(demo::checkpoint_stub))
|
|
.route("/runs/{id}/cancel", post(demo::cancel_stub))
|
|
.route("/runs/{id}/start", post(demo::start_run_stub))
|
|
.route("/runs/{id}/approve", post(demo::start_run_stub))
|
|
.route("/runs/{id}/deny", post(demo::deny_run_stub))
|
|
.route("/runs/{id}/pause", post(demo::pause_stub))
|
|
.route("/runs/{id}/unpause", post(demo::unpause_stub))
|
|
.route("/runs/{id}/graph", get(demo::get_run_graph))
|
|
.route("/runs/{id}/graph/source", get(demo::get_run_graph_source))
|
|
.route("/runs/{id}/stages", get(demo::get_run_stages))
|
|
.route("/runs/{id}/artifacts", get(demo::list_run_artifacts_stub))
|
|
.route("/runs/{id}/artifacts/download", get(not_implemented))
|
|
.route("/runs/{id}/files", get(demo::list_run_files_stub))
|
|
.route("/runs/{id}/commits", get(demo::list_run_commits_stub))
|
|
.route(
|
|
"/runs/{id}/stages/{stageId}/events",
|
|
get(demo::get_stage_events),
|
|
)
|
|
.route(
|
|
"/runs/{id}/stages/{stageId}/context-window",
|
|
get(not_implemented),
|
|
)
|
|
.route(
|
|
"/runs/{id}/stages/{stageId}/artifacts",
|
|
get(not_implemented).post(not_implemented),
|
|
)
|
|
.route(
|
|
"/runs/{id}/stages/{stageId}/artifacts/download",
|
|
get(not_implemented),
|
|
)
|
|
.route("/runs/{id}/billing", get(demo::get_run_billing))
|
|
.route("/runs/{id}/settings", get(demo::get_run_settings))
|
|
.route("/runs/{id}/preview", post(demo::generate_preview_url_stub))
|
|
.route("/runs/{id}/ssh", post(demo::create_ssh_access_stub))
|
|
.route(
|
|
"/runs/{id}/sandbox/files",
|
|
get(demo::list_sandbox_files_stub),
|
|
)
|
|
.route(
|
|
"/runs/{id}/sandbox/services",
|
|
get(demo::list_sandbox_services_stub),
|
|
)
|
|
.route(
|
|
"/runs/{id}/sandbox/file",
|
|
get(demo::get_sandbox_file_stub).put(demo::put_sandbox_file_stub),
|
|
)
|
|
.route(
|
|
"/insights/queries",
|
|
get(demo::list_saved_queries).post(demo::save_query_stub),
|
|
)
|
|
.route(
|
|
"/insights/queries/{id}",
|
|
get(demo::get_saved_query)
|
|
.put(demo::update_query_stub)
|
|
.delete(demo::delete_query_stub),
|
|
)
|
|
.route("/insights/execute", post(demo::execute_query_stub))
|
|
.route("/insights/history", get(demo::list_query_history))
|
|
.route(
|
|
"/secrets",
|
|
get(demo::list_secrets)
|
|
.post(demo::create_secret)
|
|
.delete(demo::delete_secret_by_name),
|
|
)
|
|
.route("/repos/github/{owner}/{name}", get(demo::get_github_repo))
|
|
.route("/health/diagnostics", post(demo::run_diagnostics))
|
|
.route("/settings", get(demo::get_server_settings))
|
|
.route("/system/info", get(demo::get_system_info))
|
|
.route("/system/integrations", get(demo::get_system_integrations))
|
|
.route("/system/resources", get(demo::get_system_resources))
|
|
.route("/system/df", get(demo::get_system_disk_usage))
|
|
.route("/system/repair/runs", get(demo::get_system_repair_runs))
|
|
.route("/system/prune/runs", post(demo::prune_runs))
|
|
.route("/billing", get(demo::get_aggregate_billing))
|
|
.route("/workflows", get(demo::list_workflows))
|
|
.route("/workflows/{name}", get(demo::get_workflow))
|
|
.route("/workflows/{name}/runs", get(demo::list_workflow_runs))
|
|
.merge(runs::manifest_routes())
|
|
.merge(graph::manifest_routes())
|
|
.merge(models::routes())
|
|
.merge(completions::routes())
|
|
.merge(playground::routes())
|
|
}
|
|
|
|
pub(super) fn real_routes() -> Router<Arc<AppState>> {
|
|
Router::new()
|
|
.route("/runs/{id}/stages/{stageId}/turns", get(not_implemented))
|
|
.route("/workflows", get(not_implemented))
|
|
.route("/workflows/{name}", get(not_implemented))
|
|
.route("/workflows/{name}/runs", get(not_implemented))
|
|
.route(
|
|
"/insights/queries",
|
|
get(not_implemented).post(not_implemented),
|
|
)
|
|
.route(
|
|
"/insights/queries/{id}",
|
|
get(not_implemented)
|
|
.put(not_implemented)
|
|
.delete(not_implemented),
|
|
)
|
|
.route("/insights/execute", post(not_implemented))
|
|
.route("/insights/history", get(not_implemented))
|
|
.merge(runs::routes())
|
|
.merge(events::routes())
|
|
.merge(billing::routes())
|
|
.merge(pull_requests::routes())
|
|
.merge(artifacts::routes())
|
|
.merge(automations::routes())
|
|
.merge(mcp_servers::routes())
|
|
.merge(environments::routes())
|
|
.merge(sandbox::routes())
|
|
.merge(sandboxes::routes())
|
|
.merge(lifecycle::routes())
|
|
.merge(steer::routes())
|
|
.merge(pair::routes())
|
|
.merge(graph::manifest_routes())
|
|
.merge(graph::run_routes())
|
|
.merge(models::routes())
|
|
.merge(secrets::routes())
|
|
.merge(variables::routes())
|
|
.merge(worker_control::routes())
|
|
.merge(workflow_versions::routes())
|
|
.merge(sessions::routes())
|
|
.merge(system::routes())
|
|
.merge(completions::routes())
|
|
.merge(playground::routes())
|
|
}
|