use axum::{
extract::{Path, State},
response::Json,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::handlers::AppState;
use crate::error::DbError;
use crate::triggers::{Trigger, TriggerEvent};
#[derive(Debug, Serialize)]
pub struct ListTriggersResponse {
pub triggers: Vec<Trigger>,
pub total: usize,
}
#[derive(Debug, Deserialize)]
pub struct CreateTriggerRequest {
pub name: String,
pub collection: String,
pub events: Vec<String>, #[serde(default)]
pub script_path: Option<String>,
#[serde(default, alias = "callback_url")]
pub webhook_url: Option<String>,
#[serde(default)]
pub webhook_secret: Option<String>,
#[serde(default)]
pub webhook_headers: Option<HashMap<String, String>>,
#[serde(default)]
pub queue: Option<String>,
#[serde(default)]
pub priority: Option<i32>,
#[serde(default)]
pub max_retries: Option<i32>,
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default)]
pub filter: Option<String>,
}
fn default_enabled() -> bool {
true
}
#[derive(Debug, Deserialize)]
pub struct UpdateTriggerRequest {
pub name: Option<String>,
pub collection: Option<String>,
pub events: Option<Vec<String>>,
pub script_path: Option<String>,
#[serde(default, alias = "callback_url")]
pub webhook_url: Option<String>,
pub webhook_secret: Option<String>,
pub webhook_headers: Option<HashMap<String, String>>,
pub queue: Option<String>,
pub priority: Option<i32>,
pub max_retries: Option<i32>,
pub enabled: Option<bool>,
pub filter: Option<String>,
}
fn parse_events(events: &[String]) -> Result<Vec<TriggerEvent>, DbError> {
let mut result = Vec::new();
for event in events {
match event.to_lowercase().as_str() {
"insert" => result.push(TriggerEvent::Insert),
"update" => result.push(TriggerEvent::Update),
"delete" => result.push(TriggerEvent::Delete),
_ => {
return Err(DbError::BadRequest(format!(
"Invalid event type: '{}'. Must be 'insert', 'update', or 'delete'",
event
)));
}
}
}
if result.is_empty() {
return Err(DbError::BadRequest(
"At least one event type is required".to_string(),
));
}
Ok(result)
}
pub async fn list_triggers_handler(
State(state): State<AppState>,
Path(db_name): Path<String>,
) -> Result<Json<ListTriggersResponse>, DbError> {
let db = state.storage.get_database(&db_name)?;
if db.get_collection("_triggers").is_err() {
return Ok(Json(ListTriggersResponse {
triggers: Vec::new(),
total: 0,
}));
}
let triggers_coll = db.get_collection("_triggers")?;
let mut triggers = Vec::new();
for doc in triggers_coll.scan(None) {
let trigger: Trigger = serde_json::from_value(doc.to_value())
.map_err(|_| DbError::InternalError("Corrupted trigger data".to_string()))?;
triggers.push(trigger);
}
triggers.sort_by_key(|t| std::cmp::Reverse(t.created_at));
let total = triggers.len();
Ok(Json(ListTriggersResponse { triggers, total }))
}
pub async fn get_trigger_handler(
State(state): State<AppState>,
Path((db_name, trigger_id)): Path<(String, String)>,
) -> Result<Json<Trigger>, DbError> {
let db = state.storage.get_database(&db_name)?;
let triggers_coll = db.get_collection("_triggers")?;
let doc = triggers_coll.get(&trigger_id)?;
let trigger: Trigger = serde_json::from_value(doc.to_value())
.map_err(|_| DbError::InternalError("Corrupted trigger data".to_string()))?;
Ok(Json(trigger))
}
pub async fn create_trigger_handler(
State(state): State<AppState>,
Path(db_name): Path<String>,
Json(req): Json<CreateTriggerRequest>,
) -> Result<Json<Trigger>, DbError> {
let db = state.storage.get_database(&db_name)?;
if db.get_collection("_triggers").is_err() {
db.create_collection("_triggers".to_string(), None)?;
}
let triggers_coll = db.get_collection("_triggers")?;
if db.get_collection(&req.collection).is_err() {
return Err(DbError::BadRequest(format!(
"Collection '{}' does not exist",
req.collection
)));
}
let events = parse_events(&req.events)?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let script_path = req.script_path.unwrap_or_default();
crate::queue::validate_job_target(&script_path, req.webhook_url.as_deref())?;
let trigger = Trigger {
id: uuid::Uuid::new_v4().to_string(),
revision: None,
name: req.name,
collection: req.collection,
events,
script_path,
webhook_url: req.webhook_url,
webhook_secret: req.webhook_secret,
webhook_headers: req.webhook_headers,
queue: req.queue.unwrap_or_else(|| "default".to_string()),
priority: req.priority.unwrap_or(0),
max_retries: req.max_retries.unwrap_or(5),
enabled: req.enabled,
filter: req.filter,
created_at: now,
updated_at: now,
};
let doc_val = serde_json::to_value(&trigger)
.map_err(|e| DbError::InternalError(format!("Failed to serialize trigger: {}", e)))?;
triggers_coll.insert(doc_val)?;
Ok(Json(trigger))
}
pub async fn update_trigger_handler(
State(state): State<AppState>,
Path((db_name, trigger_id)): Path<(String, String)>,
Json(req): Json<UpdateTriggerRequest>,
) -> Result<Json<Trigger>, DbError> {
let db = state.storage.get_database(&db_name)?;
let triggers_coll = db.get_collection("_triggers")?;
let doc = triggers_coll.get(&trigger_id)?;
let mut trigger: Trigger = serde_json::from_value(doc.to_value())
.map_err(|_| DbError::InternalError("Corrupted trigger data".to_string()))?;
if let Some(name) = req.name {
trigger.name = name;
}
if let Some(collection) = req.collection {
if db.get_collection(&collection).is_err() {
return Err(DbError::BadRequest(format!(
"Collection '{}' does not exist",
collection
)));
}
trigger.collection = collection;
}
if let Some(events) = req.events {
trigger.events = parse_events(&events)?;
}
if let Some(script_path) = req.script_path {
crate::queue::validate_job_target(&script_path, None)?;
trigger.script_path = script_path;
trigger.webhook_url = None;
trigger.webhook_secret = None;
trigger.webhook_headers = None;
}
if let Some(url) = req.webhook_url {
crate::queue::validate_job_target("", Some(&url))?;
trigger.script_path = String::new();
trigger.webhook_url = Some(url);
if req.webhook_secret.is_some() {
trigger.webhook_secret = req.webhook_secret;
}
if req.webhook_headers.is_some() {
trigger.webhook_headers = req.webhook_headers;
}
}
if let Some(queue) = req.queue {
trigger.queue = queue;
}
if let Some(priority) = req.priority {
trigger.priority = priority;
}
if let Some(max_retries) = req.max_retries {
trigger.max_retries = max_retries;
}
if let Some(enabled) = req.enabled {
trigger.enabled = enabled;
}
if req.filter.is_some() {
trigger.filter = req.filter;
}
trigger.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let rev = trigger.revision.clone().unwrap_or_default();
let doc_val = serde_json::to_value(&trigger)
.map_err(|e| DbError::InternalError(format!("Failed to serialize trigger: {}", e)))?;
triggers_coll.update_with_rev(&trigger_id, &rev, doc_val)?;
Ok(Json(trigger))
}
pub async fn delete_trigger_handler(
State(state): State<AppState>,
Path((db_name, trigger_id)): Path<(String, String)>,
) -> Result<Json<serde_json::Value>, DbError> {
let db = state.storage.get_database(&db_name)?;
let triggers_coll = db.get_collection("_triggers")?;
triggers_coll.delete(&trigger_id)?;
Ok(Json(serde_json::json!({ "success": true })))
}
pub async fn list_collection_triggers_handler(
State(state): State<AppState>,
Path((db_name, collection_name)): Path<(String, String)>,
) -> Result<Json<ListTriggersResponse>, DbError> {
let db = state.storage.get_database(&db_name)?;
if db.get_collection("_triggers").is_err() {
return Ok(Json(ListTriggersResponse {
triggers: Vec::new(),
total: 0,
}));
}
let triggers_coll = db.get_collection("_triggers")?;
let mut triggers = Vec::new();
for doc in triggers_coll.scan(None) {
let trigger: Trigger = serde_json::from_value(doc.to_value())
.map_err(|_| DbError::InternalError("Corrupted trigger data".to_string()))?;
if trigger.collection == collection_name {
triggers.push(trigger);
}
}
triggers.sort_by_key(|t| std::cmp::Reverse(t.created_at));
let total = triggers.len();
Ok(Json(ListTriggersResponse { triggers, total }))
}
pub async fn toggle_trigger_handler(
State(state): State<AppState>,
Path((db_name, trigger_id)): Path<(String, String)>,
) -> Result<Json<Trigger>, DbError> {
let db = state.storage.get_database(&db_name)?;
let triggers_coll = db.get_collection("_triggers")?;
let doc = triggers_coll.get(&trigger_id)?;
let mut trigger: Trigger = serde_json::from_value(doc.to_value())
.map_err(|_| DbError::InternalError("Corrupted trigger data".to_string()))?;
trigger.enabled = !trigger.enabled;
trigger.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let rev = trigger.revision.clone().unwrap_or_default();
let doc_val = serde_json::to_value(&trigger)
.map_err(|e| DbError::InternalError(format!("Failed to serialize trigger: {}", e)))?;
triggers_coll.update_with_rev(&trigger_id, &rev, doc_val)?;
Ok(Json(trigger))
}