hyperhive/hive-c0re/src/dashboard/webhook.rs

64 lines
2.3 KiB
Rust

//! Forgejo push-webhook endpoint for the `internal/knowledge` repo.
//!
//! Loopback-only; on a push to `main` of the knowledge repo it triggers a
//! read-only `git pull` on the local clone so agents see up-to-date
//! documents on their next turn.
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use serde::Deserialize;
/// Minimal Forgejo push-webhook payload — only the fields we care about.
#[derive(Deserialize)]
pub(super) struct PushWebhookPayload {
#[serde(rename = "ref")]
git_ref: Option<String>,
repository: Option<PushWebhookRepo>,
}
#[derive(Deserialize)]
pub(super) struct PushWebhookRepo {
full_name: Option<String>,
}
/// POST `/webhook/knowledge` — Forgejo push webhook for
/// `internal/knowledge`. Runs `git pull` on the local clone so
/// agents see up-to-date documents on their next turn.
///
/// Expected Forgejo webhook configuration:
/// - URL: `http://127.0.0.1:<dashboard_port>/webhook/knowledge`
/// - Event: "Push" (fires on merge commits to main as well)
///
/// No signature verification for now; the endpoint is loopback-only
/// and only triggers a read-only `git pull` on an operator-curated repo.
pub(super) async fn post_webhook_knowledge(
axum::extract::Json(payload): axum::extract::Json<PushWebhookPayload>,
) -> Response {
let expected_repo = format!("{}/{}", crate::knowledge::ORG, crate::knowledge::REPO);
let full_name = payload
.repository
.as_ref()
.and_then(|r| r.full_name.as_deref())
.unwrap_or("");
if full_name != expected_repo {
tracing::debug!(
full_name,
"webhook/knowledge: ignoring push from unexpected repo"
);
return (StatusCode::OK, "ignored").into_response();
}
let git_ref = payload.git_ref.as_deref().unwrap_or("");
if git_ref != "refs/heads/main" {
tracing::debug!(git_ref, "webhook/knowledge: ignoring non-main push");
return (StatusCode::OK, "ignored").into_response();
}
tracing::info!("webhook/knowledge: pull triggered by push to {expected_repo}");
tokio::spawn(async {
if let Err(e) = crate::knowledge::pull().await {
tracing::warn!(error = ?e, "webhook/knowledge: pull failed");
}
});
(StatusCode::OK, "ok").into_response()
}