pub mod config;
pub mod handlers;
pub mod repository;
pub mod routes;
pub mod runtime;
use chrono::{DateTime, Utc};
pub use config::ObserverManagementConfig;
pub use handlers::{ObserverState, RuntimeHealthState};
pub use repository::ObserverRepository;
pub use routes::{observer_routes, observer_runtime_routes};
pub use runtime::{ObserverRuntime, ObserverRuntimeConfig, RuntimeHealth};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Observer {
pub pk_observer: i64,
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub entity_type: Option<String>,
pub event_type: Option<String>,
pub condition_expression: Option<String>,
pub actions: serde_json::Value,
pub enabled: bool,
pub priority: i32,
pub retry_config: serde_json::Value,
pub timeout_ms: i32,
pub fk_customer_org: Option<i64>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: Option<String>,
pub updated_by: Option<String>,
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateObserverRequest {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub entity_type: Option<String>,
#[serde(default)]
pub event_type: Option<String>,
#[serde(default)]
pub condition_expression: Option<String>,
pub actions: Vec<ActionConfig>,
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_priority")]
pub priority: i32,
#[serde(default)]
pub retry_config: Option<RetryConfig>,
#[serde(default = "default_timeout")]
pub timeout_ms: i32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateObserverRequest {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub entity_type: Option<String>,
#[serde(default)]
pub event_type: Option<String>,
#[serde(default)]
pub condition_expression: Option<String>,
#[serde(default)]
pub actions: Option<Vec<ActionConfig>>,
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub priority: Option<i32>,
#[serde(default)]
pub retry_config: Option<RetryConfig>,
#[serde(default)]
pub timeout_ms: Option<i32>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ActionConfig {
Webhook {
url: String,
#[serde(default = "default_method")]
method: String,
#[serde(default)]
headers: Option<std::collections::HashMap<String, String>>,
#[serde(default)]
body_template: Option<String>,
},
Email {
to: String,
#[serde(default)]
cc: Option<String>,
subject_template: String,
body_template: String,
},
Slack {
webhook_url: String,
#[serde(default)]
channel: Option<String>,
message_template: String,
},
Database {
function_name: String,
#[serde(default)]
params: Option<serde_json::Value>,
},
Log {
#[serde(default = "default_log_level")]
level: String,
message_template: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
#[serde(default = "default_max_attempts")]
pub max_attempts: i32,
#[serde(default = "default_backoff")]
pub backoff: String,
#[serde(default = "default_initial_delay")]
pub initial_delay_ms: i64,
#[serde(default = "default_max_delay")]
pub max_delay_ms: i64,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: 3,
backoff: "exponential".to_string(),
initial_delay_ms: 1000,
max_delay_ms: 60000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct ObserverLog {
pub pk_observer_log: i64,
pub id: Uuid,
pub fk_observer: i64,
pub event_id: Uuid,
pub entity_type: String,
pub entity_id: Uuid,
pub event_type: String,
pub status: String,
pub action_index: Option<i32>,
pub action_type: Option<String>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub duration_ms: Option<i32>,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub attempt_number: i32,
pub trace_id: Option<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct ObserverStats {
pub pk_observer: i64,
pub observer_id: Uuid,
pub observer_name: String,
pub entity_type: Option<String>,
pub event_type: Option<String>,
pub enabled: bool,
pub total_executions: i64,
pub successful_executions: i64,
pub failed_executions: i64,
pub timeout_executions: i64,
pub skipped_executions: i64,
pub success_rate_pct: Option<f64>,
pub avg_duration_ms: Option<f64>,
pub max_duration_ms: Option<i32>,
pub min_duration_ms: Option<i32>,
pub last_execution_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListObserversQuery {
#[serde(default)]
pub entity_type: Option<String>,
#[serde(default)]
pub event_type: Option<String>,
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub include_deleted: bool,
#[serde(default = "default_page")]
pub page: i64,
#[serde(default = "default_page_size")]
pub page_size: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListObserverLogsQuery {
#[serde(default)]
pub observer_id: Option<Uuid>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub event_id: Option<Uuid>,
#[serde(default)]
pub trace_id: Option<String>,
#[serde(default = "default_page")]
pub page: i64,
#[serde(default = "default_page_size")]
pub page_size: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginatedResponse<T> {
pub data: Vec<T>,
pub page: i64,
pub page_size: i64,
pub total_count: i64,
pub total_pages: i64,
}
impl<T> PaginatedResponse<T> {
#[must_use]
pub const fn new(data: Vec<T>, page: i64, page_size: i64, total_count: i64) -> Self {
let total_pages = (total_count + page_size - 1) / page_size;
Self {
data,
page,
page_size,
total_count,
total_pages,
}
}
}
const fn default_true() -> bool {
true
}
const fn default_priority() -> i32 {
100
}
const fn default_timeout() -> i32 {
30000
}
fn default_method() -> String {
"POST".to_string()
}
fn default_log_level() -> String {
"info".to_string()
}
const fn default_max_attempts() -> i32 {
3
}
fn default_backoff() -> String {
"exponential".to_string()
}
const fn default_initial_delay() -> i64 {
1000
}
const fn default_max_delay() -> i64 {
60000
}
const fn default_page() -> i64 {
1
}
const fn default_page_size() -> i64 {
20
}