gitdataai/libs/service/git/webhook.rs
2026-04-14 19:02:01 +08:00

299 lines
9.3 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::repos::repo_webhook;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
#[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)]
pub struct WebhookEvent {
#[serde(default)]
pub push: bool,
#[serde(default)]
pub tag_push: bool,
#[serde(default)]
pub pull_request: bool,
#[serde(default)]
pub issue_comment: bool,
#[serde(default)]
pub release: bool,
}
impl Default for WebhookEvent {
fn default() -> Self {
Self {
push: true,
tag_push: false,
pull_request: false,
issue_comment: false,
release: false,
}
}
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct CreateWebhookParams {
pub url: String,
#[serde(default)]
pub content_type: Option<String>,
#[serde(default)]
pub secret: Option<String>,
#[serde(default)]
pub insecure_ssl: Option<bool>,
#[serde(default)]
pub events: WebhookEvent,
#[serde(default = "default_active")]
pub active: bool,
}
fn default_active() -> bool {
true
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateWebhookParams {
pub url: Option<String>,
pub content_type: Option<String>,
pub secret: Option<String>,
pub insecure_ssl: Option<bool>,
pub events: Option<WebhookEvent>,
pub active: Option<bool>,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct WebhookResponse {
pub id: i64,
pub repo_uuid: String,
pub url: String,
pub content_type: String,
pub secret: Option<String>,
pub events: WebhookEvent,
pub active: bool,
pub created_at: chrono::DateTime<Utc>,
pub last_delivered_at: Option<chrono::DateTime<Utc>>,
pub touch_count: i64,
}
impl From<repo_webhook::Model> for WebhookResponse {
fn from(m: repo_webhook::Model) -> Self {
let events: WebhookEvent = serde_json::from_value(m.event.clone()).unwrap_or_default();
let url = m.url.unwrap_or_default();
let (content_type, secret, active) =
serde_json::from_value::<serde_json::Value>(m.event.clone())
.ok()
.map(|v| {
(
v.get("content_type")
.and_then(|v| v.as_str())
.unwrap_or("json")
.to_string(),
v.get("secret").and_then(|v| v.as_str()).map(String::from),
v.get("active").and_then(|v| v.as_bool()).unwrap_or(true),
)
})
.unwrap_or(("json".to_string(), None, true));
WebhookResponse {
id: m.id,
repo_uuid: m.repo.to_string(),
url,
content_type,
secret,
events,
active,
created_at: m.created_at,
last_delivered_at: m.last_delivered_at,
touch_count: m.touch_count,
}
}
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct WebhookListResponse {
pub webhooks: Vec<WebhookResponse>,
pub total: usize,
}
impl AppService {
pub async fn git_webhook_list(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<WebhookListResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let webhooks = repo_webhook::Entity::find()
.filter(repo_webhook::Column::Repo.eq(repo.id))
.order_by_asc(repo_webhook::Column::Id)
.all(&self.db)
.await?;
let total = webhooks.len();
let webhooks = webhooks.into_iter().map(WebhookResponse::from).collect();
Ok(WebhookListResponse { webhooks, total })
}
pub async fn git_webhook_create(
&self,
namespace: String,
repo_name: String,
params: CreateWebhookParams,
ctx: &Session,
) -> Result<WebhookResponse, AppError> {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let _ = self
.utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
.await?;
let event_json = serde_json::json!({
"push": params.events.push,
"tag_push": params.events.tag_push,
"pull_request": params.events.pull_request,
"issue_comment": params.events.issue_comment,
"release": params.events.release,
"content_type": params.content_type.unwrap_or_else(|| "json".to_string()),
"secret": params.secret,
"active": params.active,
});
let model = repo_webhook::ActiveModel {
repo: Set(repo.id),
event: Set(event_json),
url: Set(Some(params.url)),
access_key: Set(None),
secret_key: Set(params.secret),
created_at: Set(Utc::now()),
last_delivered_at: Set(None),
touch_count: Set(0),
..Default::default()
}
.insert(&self.db)
.await?;
Ok(WebhookResponse::from(model))
}
pub async fn git_webhook_get(
&self,
namespace: String,
repo_name: String,
webhook_id: i64,
ctx: &Session,
) -> Result<WebhookResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let webhook = repo_webhook::Entity::find_by_id(webhook_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Webhook not found".to_string()))?;
if webhook.repo != repo.id {
return Err(AppError::NotFound("Webhook not found".to_string()));
}
Ok(WebhookResponse::from(webhook))
}
pub async fn git_webhook_update(
&self,
namespace: String,
repo_name: String,
webhook_id: i64,
params: UpdateWebhookParams,
ctx: &Session,
) -> Result<WebhookResponse, AppError> {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let _ = self
.utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
.await?;
let webhook = repo_webhook::Entity::find_by_id(webhook_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Webhook not found".to_string()))?;
if webhook.repo != repo.id {
return Err(AppError::NotFound("Webhook not found".to_string()));
}
let mut active: repo_webhook::ActiveModel = webhook.clone().into();
if let Some(url) = params.url {
active.url = Set(Some(url));
}
let secret_val = params.secret.clone();
if let Some(secret) = params.secret {
active.secret_key = Set(Some(secret));
}
if params.events.is_some() || params.content_type.is_some() || params.active.is_some() {
let existing: serde_json::Value = webhook.event.clone();
let events = params
.events
.unwrap_or_else(|| serde_json::from_value(existing.clone()).unwrap_or_default());
let content_type = params
.content_type
.or_else(|| {
existing
.get("content_type")
.and_then(|v| v.as_str())
.map(String::from)
})
.unwrap_or_else(|| "json".to_string());
let active_val = params
.active
.or_else(|| existing.get("active").and_then(|v| v.as_bool()))
.unwrap_or(true);
let secret_in_event = existing
.get("secret")
.and_then(|v| v.as_str())
.map(String::from);
let final_secret = secret_val.clone().or(secret_in_event);
active.event = Set(serde_json::json!({
"push": events.push,
"tag_push": events.tag_push,
"pull_request": events.pull_request,
"issue_comment": events.issue_comment,
"release": events.release,
"content_type": content_type,
"secret": final_secret,
"active": active_val,
}));
}
let updated = active.update(&self.db).await?;
Ok(WebhookResponse::from(updated))
}
pub async fn git_webhook_delete(
&self,
namespace: String,
repo_name: String,
webhook_id: i64,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let _ = self
.utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
.await?;
let webhook = repo_webhook::Entity::find_by_id(webhook_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Webhook not found".to_string()))?;
if webhook.repo != repo.id {
return Err(AppError::NotFound("Webhook not found".to_string()));
}
repo_webhook::Entity::delete_by_id(webhook_id)
.exec(&self.db)
.await?;
Ok(())
}
}