use std::sync::Mutex;
use std::thread::{self, JoinHandle};
use std::time::Duration;
use chrono::{DateTime, Utc};
use reqwest::blocking::Client;
use serde::Serialize;
#[non_exhaustive]
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Action {
Create,
Read,
Update,
Delete,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize)]
pub enum Mechanism {
#[serde(rename = "API")]
Api,
#[serde(rename = "BTS")]
Bts,
#[serde(rename = "DESKTOP")]
Desktop,
#[serde(rename = "EMAIL")]
Email,
#[serde(rename = "FIXES")]
Fixes,
#[serde(rename = "FORCES")]
Forces,
#[serde(rename = "JIRA")]
Jira,
#[serde(rename = "MCP")]
Mcp,
#[serde(rename = "MELTS")]
Melts,
#[serde(rename = "MESSAGING")]
Messaging,
#[serde(rename = "MIGRATION")]
Migration,
#[serde(rename = "RETRIEVES")]
Retrieves,
#[serde(rename = "SCHEDULER")]
Scheduler,
#[serde(rename = "SMELLS")]
Smells,
#[serde(rename = "TASK")]
Task,
#[serde(rename = "WEB")]
Web,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize)]
pub struct Event {
pub action: Action,
pub author: String,
pub date: DateTime<Utc>,
pub mechanism: Mechanism,
pub metadata: serde_json::Value,
pub object: String,
pub object_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub author_anonymous: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author_ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author_role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author_user_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
impl Event {
pub fn new(
action: Action,
author: String,
date: DateTime<Utc>,
mechanism: Mechanism,
metadata: serde_json::Value,
object: String,
object_id: String,
) -> Self {
Event {
action,
author,
date,
mechanism,
metadata,
object,
object_id,
author_anonymous: None,
author_ip: None,
author_role: None,
author_user_agent: None,
session_id: None,
}
}
}
pub struct EventResource {
base_url: String,
http: Client,
retry_attempts: u32,
pending: Mutex<Vec<JoinHandle<()>>>,
}
impl EventResource {
pub(crate) fn new(base_url: String, http: Client, retry_attempts: u32) -> Self {
EventResource {
base_url,
http,
retry_attempts,
pending: Mutex::new(Vec::new()),
}
}
pub fn create(&self, event: Event) {
if !event.metadata.is_object() {
return;
}
let url = format!("{}/event", self.base_url.trim_end_matches('/'));
let http = self.http.clone();
let retry_attempts = self.retry_attempts;
let handle = thread::spawn(move || {
for attempt in 0..retry_attempts {
match http.post(&url).json(&event).send() {
Ok(resp) if resp.status().is_success() => return,
_ => {
if attempt + 1 < retry_attempts {
thread::sleep(Duration::from_millis(100 * 2u64.pow(attempt)));
}
}
}
}
});
if let Ok(mut pending) = self.pending.lock() {
pending.retain(|h| !h.is_finished());
pending.push(handle);
}
}
pub fn flush(&self) {
let handles = self
.pending
.lock()
.map(|mut p| std::mem::take(&mut *p))
.unwrap_or_default();
for handle in handles {
let _ = handle.join();
}
}
}