prospero_api/
automations.rs1use axum::Json;
14use axum::body::Bytes;
15use axum::extract::{Path, Query, State};
16use axum::http::{HeaderMap, StatusCode};
17use prospero_core::automation::{
18 Automation, AutomationRun, CreateAutomationBody, CreatedAutomationResponse, FiredResponse,
19 RunSource, SetEnabledBody,
20};
21use serde::Deserialize;
22
23use crate::AppState;
24use crate::error::ApiError;
25
26pub const SIGNATURE_HEADER: &str = "x-prospero-signature";
28
29const RUNS_DEFAULT: usize = 50;
31
32#[derive(Debug, Default, Deserialize)]
34pub struct RunsQuery {
35 pub limit: Option<usize>,
37}
38
39fn engine(
42 st: &AppState,
43) -> Result<&std::sync::Arc<prospero_core::automation::AutomationEngine>, ApiError> {
44 st.automations.as_ref().ok_or_else(|| {
45 ApiError::MethodNotAllowed(
46 "automations need a shared config store; run with --database-url \
47 (or the local backend) to enable them"
48 .to_string(),
49 )
50 })
51}
52
53pub async fn list_automations(
55 State(st): State<AppState>,
56) -> Result<Json<Vec<Automation>>, ApiError> {
57 let stored = engine(&st)?.list_automations().await?;
58 Ok(Json(stored.into_iter().map(|s| s.automation).collect()))
60}
61
62pub async fn create_automation(
65 State(st): State<AppState>,
66 Json(body): Json<CreateAutomationBody>,
67) -> Result<(StatusCode, Json<CreatedAutomationResponse>), ApiError> {
68 let created = engine(&st)?.create_automation(body).await?;
69 Ok((StatusCode::CREATED, Json(created)))
70}
71
72pub async fn delete_automation(
74 State(st): State<AppState>,
75 Path(id): Path<String>,
76) -> Result<StatusCode, ApiError> {
77 if engine(&st)?.delete_automation(&id).await? {
78 Ok(StatusCode::NO_CONTENT)
79 } else {
80 Err(ApiError::NotFound(format!("automation not found: {id}")))
81 }
82}
83
84pub async fn set_enabled(
86 State(st): State<AppState>,
87 Path(id): Path<String>,
88 Json(body): Json<SetEnabledBody>,
89) -> Result<StatusCode, ApiError> {
90 engine(&st)?
91 .set_automation_enabled(&id, body.enabled)
92 .await?;
93 Ok(StatusCode::NO_CONTENT)
94}
95
96pub async fn list_runs(
98 State(st): State<AppState>,
99 Path(id): Path<String>,
100 Query(q): Query<RunsQuery>,
101) -> Result<Json<Vec<AutomationRun>>, ApiError> {
102 let runs = engine(&st)?
103 .automation_runs(&id, q.limit.unwrap_or(RUNS_DEFAULT))
104 .await?;
105 Ok(Json(runs))
106}
107
108pub async fn run_now(
110 State(st): State<AppState>,
111 Path(id): Path<String>,
112) -> Result<Json<FiredResponse>, ApiError> {
113 let run = engine(&st)?
114 .fire_automation(&id, RunSource::Manual, None)
115 .await?;
116 Ok(Json(FiredResponse { run }))
117}
118
119pub async fn trigger(
126 State(st): State<AppState>,
127 Path(id): Path<String>,
128 headers: HeaderMap,
129 body: Bytes,
130) -> Result<Json<FiredResponse>, ApiError> {
131 let signature = headers
132 .get(SIGNATURE_HEADER)
133 .and_then(|v| v.to_str().ok())
134 .map(str::to_string);
135 let run = engine(&st)?
136 .trigger_webhook(&id, &body, signature.as_deref())
137 .await?;
138 Ok(Json(FiredResponse { run }))
139}