#![allow(dead_code)]
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use super::AuthClient;
use crate::config::Config;
pub const WEBHOOK_EVENTS: &[(&str, &str)] = &[
("dm.version.added", "New file version added"),
("dm.version.modified", "File version modified"),
("dm.version.deleted", "File version deleted"),
("dm.version.moved", "File version moved"),
("dm.version.copied", "File version copied"),
("dm.folder.added", "Folder created"),
("dm.folder.modified", "Folder modified"),
("dm.folder.deleted", "Folder deleted"),
("dm.folder.moved", "Folder moved"),
("dm.folder.copied", "Folder copied"),
(
"extraction.finished",
"Model derivative extraction finished",
),
("extraction.updated", "Model derivative extraction updated"),
];
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Webhook {
pub hook_id: String,
pub tenant: Option<String>,
pub callback_url: String,
pub created_by: Option<String>,
pub event: String,
pub created_date: Option<String>,
pub last_updated_date: Option<String>,
pub system: String,
pub creator_type: Option<String>,
pub status: String,
pub scope: Option<WebhookScope>,
pub hook_attribute: Option<serde_json::Value>,
pub urn: Option<String>,
pub auto_reactivate_hook: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WebhookScope {
pub folder: Option<String>,
pub workflow: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateWebhookRequest {
pub callback_url: String,
pub scope: CreateWebhookScope,
#[serde(skip_serializing_if = "Option::is_none")]
pub hook_attribute: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hub_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_reactivate_hook: Option<bool>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateWebhookScope {
#[serde(skip_serializing_if = "Option::is_none")]
pub folder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workflow: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct WebhooksResponse {
pub data: Vec<Webhook>,
pub links: Option<WebhooksLinks>,
}
#[derive(Debug, Deserialize)]
pub struct WebhooksLinks {
pub next: Option<String>,
}
pub struct WebhooksClient {
config: Config,
auth: AuthClient,
http_client: reqwest::Client,
}
impl WebhooksClient {
pub fn new(config: Config, auth: AuthClient) -> Self {
Self::new_with_http_config(config, auth, crate::http::HttpClientConfig::default())
}
pub fn new_with_http_config(
config: Config,
auth: AuthClient,
http_config: crate::http::HttpClientConfig,
) -> Self {
let http_client = http_config
.create_client()
.unwrap_or_else(|_| reqwest::Client::new());
Self {
config,
auth,
http_client,
}
}
pub async fn list_webhooks(&self, system: &str, event: &str) -> Result<Vec<Webhook>> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/systems/{}/events/{}/hooks",
self.config.webhooks_url(),
system,
event
);
let response = self
.http_client
.get(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to list webhooks")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to list webhooks ({}): {}", status, error_text);
}
let webhooks_response: WebhooksResponse = response
.json()
.await
.context("Failed to parse webhooks response")?;
Ok(webhooks_response.data)
}
pub async fn list_all_webhooks(&self) -> Result<Vec<Webhook>> {
let token = self.auth.get_token().await?;
let url = format!("{}/hooks", self.config.webhooks_url());
let response = self
.http_client
.get(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to list all webhooks")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to list webhooks ({}): {}", status, error_text);
}
let webhooks_response: WebhooksResponse = response
.json()
.await
.context("Failed to parse webhooks response")?;
Ok(webhooks_response.data)
}
pub async fn create_webhook(
&self,
system: &str,
event: &str,
callback_url: &str,
folder_urn: Option<&str>,
) -> Result<Webhook> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/systems/{}/events/{}/hooks",
self.config.webhooks_url(),
system,
event
);
let request = CreateWebhookRequest {
callback_url: callback_url.to_string(),
scope: CreateWebhookScope {
folder: folder_urn.map(|s| s.to_string()),
workflow: None,
},
hook_attribute: None,
filter: None,
hub_id: None,
project_id: None,
auto_reactivate_hook: Some(true),
};
let response = self
.http_client
.post(&url)
.bearer_auth(&token)
.header("Content-Type", "application/json")
.json(&request)
.send()
.await
.context("Failed to create webhook")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to create webhook ({}): {}", status, error_text);
}
let webhook: Webhook = response
.json()
.await
.context("Failed to parse webhook response")?;
Ok(webhook)
}
pub async fn delete_webhook(&self, system: &str, event: &str, hook_id: &str) -> Result<()> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/systems/{}/events/{}/hooks/{}",
self.config.webhooks_url(),
system,
event,
hook_id
);
let response = self
.http_client
.delete(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to delete webhook")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to delete webhook ({}): {}", status, error_text);
}
Ok(())
}
pub fn available_events(&self) -> &[(&str, &str)] {
WEBHOOK_EVENTS
}
}