use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReiWebhook {
pub id: Uuid,
pub rei_id: Uuid,
pub name: String,
pub url: String,
pub secret: Option<String>,
pub enabled: bool,
pub events: Vec<WebhookEventType>,
#[serde(default)]
pub headers: serde_json::Value,
#[serde(default)]
pub payload_format: Option<String>,
pub max_retries: i32,
pub timeout_ms: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEventType {
ResponseCompleted,
StateChanged,
MemoryAdded,
SearchCompleted,
LearningCompleted,
DigestCompleted,
Custom(String),
All,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookPayload {
pub delivery_id: Uuid,
pub event: WebhookEventType,
pub rei_id: Uuid,
pub timestamp: DateTime<Utc>,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookDelivery {
pub id: Uuid,
pub webhook_id: Uuid,
pub payload: WebhookPayload,
pub status: DeliveryStatus,
pub status_code: Option<i32>,
pub response_body: Option<String>,
pub attempts: i32,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DeliveryStatus {
Pending,
Success,
Failed,
Retrying,
}
impl ReiWebhook {
pub fn new(rei_id: Uuid, name: String, url: String) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
rei_id,
name,
url,
secret: None,
enabled: true,
events: vec![WebhookEventType::DigestCompleted],
headers: serde_json::json!({}),
payload_format: None,
max_retries: 3,
timeout_ms: 30000,
created_at: now,
updated_at: now,
}
}
pub fn with_payload_format(mut self, format: String) -> Self {
self.payload_format = Some(format);
self
}
pub fn with_secret(mut self, secret: String) -> Self {
self.secret = Some(secret);
self
}
pub fn with_events(mut self, events: Vec<WebhookEventType>) -> Self {
self.events = events;
self
}
pub fn with_headers(mut self, headers: serde_json::Value) -> Self {
self.headers = headers;
self
}
pub fn should_receive(&self, event: &WebhookEventType) -> bool {
if !self.enabled {
return false;
}
self.events.contains(&WebhookEventType::All) || self.events.contains(event)
}
}
impl WebhookPayload {
pub fn new(event: WebhookEventType, rei_id: Uuid, data: serde_json::Value) -> Self {
Self {
delivery_id: Uuid::new_v4(),
event,
rei_id,
timestamp: Utc::now(),
data,
}
}
}
impl WebhookDelivery {
pub fn new(webhook_id: Uuid, payload: WebhookPayload) -> Self {
Self {
id: Uuid::new_v4(),
webhook_id,
payload,
status: DeliveryStatus::Pending,
status_code: None,
response_body: None,
attempts: 0,
created_at: Utc::now(),
completed_at: None,
}
}
pub fn success(mut self, status_code: i32, response_body: Option<String>) -> Self {
self.status = DeliveryStatus::Success;
self.status_code = Some(status_code);
self.response_body = response_body;
self.completed_at = Some(Utc::now());
self.attempts += 1;
self
}
pub fn failed(mut self, status_code: Option<i32>, error: String) -> Self {
self.status = DeliveryStatus::Failed;
self.status_code = status_code;
self.response_body = Some(error);
self.completed_at = Some(Utc::now());
self.attempts += 1;
self
}
pub fn retry(mut self) -> Self {
self.status = DeliveryStatus::Retrying;
self.attempts += 1;
self
}
}
impl std::fmt::Display for WebhookEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ResponseCompleted => write!(f, "response_completed"),
Self::StateChanged => write!(f, "state_changed"),
Self::MemoryAdded => write!(f, "memory_added"),
Self::SearchCompleted => write!(f, "search_completed"),
Self::LearningCompleted => write!(f, "learning_completed"),
Self::DigestCompleted => write!(f, "digest_completed"),
Self::Custom(name) => write!(f, "custom:{}", name),
Self::All => write!(f, "all"),
}
}
}