parse gitea pr webhook
This commit is contained in:
@@ -12,8 +12,18 @@
|
|||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"SHELL": "/bin/bash"
|
"SHELL": "/bin/bash"
|
||||||
},
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": ["rust-lang.rust-analyzer"],
|
||||||
|
"settings": {
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind",
|
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind",
|
||||||
"workspaceFolder": "/workspaces/herald",
|
"workspaceFolder": "/workspaces/herald",
|
||||||
"runArgs": ["--userns=keep-id", "--security-opt", "label=disable"],
|
|
||||||
"appPort": [3000]
|
"appPort": [3000]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
{
|
||||||
|
"action": "created",
|
||||||
|
"issue": {
|
||||||
|
"id": 1,
|
||||||
|
"url": "https://gitea.example.com/api/v1/repos/username/repo-name/issues/1",
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name/pulls/1",
|
||||||
|
"number": 1,
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"login_name": "",
|
||||||
|
"source_id": 0,
|
||||||
|
"full_name": "",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"language": "en-US",
|
||||||
|
"is_admin": true,
|
||||||
|
"last_login": "2026-01-01T00:00:00+02:00",
|
||||||
|
"created": "2025-01-01T00:00:00+02:00",
|
||||||
|
"restricted": false,
|
||||||
|
"active": true,
|
||||||
|
"prohibit_login": false,
|
||||||
|
"location": "",
|
||||||
|
"website": "",
|
||||||
|
"description": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 0,
|
||||||
|
"starred_repos_count": 0,
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"original_author": "",
|
||||||
|
"original_author_id": 0,
|
||||||
|
"title": "impl webhook route",
|
||||||
|
"body": "",
|
||||||
|
"ref": "",
|
||||||
|
"assets": [],
|
||||||
|
"labels": [],
|
||||||
|
"milestone": null,
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": null,
|
||||||
|
"state": "open",
|
||||||
|
"is_locked": false,
|
||||||
|
"comments": 1,
|
||||||
|
"created_at": "2026-01-01T00:00:00+02:00",
|
||||||
|
"updated_at": "2026-01-01T00:00:00+02:00",
|
||||||
|
"closed_at": null,
|
||||||
|
"due_date": null,
|
||||||
|
"time_estimate": 0,
|
||||||
|
"pull_request": {
|
||||||
|
"merged": false,
|
||||||
|
"merged_at": null,
|
||||||
|
"draft": false,
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name/pulls/1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 8,
|
||||||
|
"name": "repo-name",
|
||||||
|
"owner": "username",
|
||||||
|
"full_name": "username/repo-name"
|
||||||
|
},
|
||||||
|
"pin_order": 0,
|
||||||
|
"content_version": 0
|
||||||
|
},
|
||||||
|
"pull_request": {
|
||||||
|
"id": 1,
|
||||||
|
"url": "https://gitea.example.com/username/repo-name/pulls/1",
|
||||||
|
"number": 1,
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"login_name": "",
|
||||||
|
"source_id": 0,
|
||||||
|
"full_name": "",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"language": "en-US",
|
||||||
|
"is_admin": true,
|
||||||
|
"last_login": "2026-01-01T00:00:00+02:00",
|
||||||
|
"created": "2025-01-01T00:00:00+02:00",
|
||||||
|
"restricted": false,
|
||||||
|
"active": true,
|
||||||
|
"prohibit_login": false,
|
||||||
|
"location": "",
|
||||||
|
"website": "",
|
||||||
|
"description": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 0,
|
||||||
|
"starred_repos_count": 0,
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"title": "impl webhook route",
|
||||||
|
"body": "",
|
||||||
|
"labels": [],
|
||||||
|
"milestone": null,
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": [],
|
||||||
|
"requested_reviewers": [],
|
||||||
|
"requested_reviewers_teams": [],
|
||||||
|
"state": "open",
|
||||||
|
"draft": false,
|
||||||
|
"is_locked": false,
|
||||||
|
"comments": 1,
|
||||||
|
"review_comments": 0,
|
||||||
|
"additions": 3,
|
||||||
|
"deletions": 3,
|
||||||
|
"changed_files": 2,
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name/pulls/1",
|
||||||
|
"diff_url": "https://gitea.example.com/username/repo-name/pulls/1.diff",
|
||||||
|
"patch_url": "https://gitea.example.com/username/repo-name/pulls/1.patch",
|
||||||
|
"mergeable": true,
|
||||||
|
"merged": false,
|
||||||
|
"merged_at": null,
|
||||||
|
"merge_commit_sha": null,
|
||||||
|
"merged_by": null,
|
||||||
|
"allow_maintainer_edit": false,
|
||||||
|
"base": {
|
||||||
|
"label": "main",
|
||||||
|
"ref": "main",
|
||||||
|
"sha": "aabbccdd00112233445566778899aabbccdd0011",
|
||||||
|
"repo_id": 8,
|
||||||
|
"repo": {
|
||||||
|
"id": 8,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"name": "repo-name",
|
||||||
|
"full_name": "username/repo-name",
|
||||||
|
"description": "A self-hosted Gitea bot.",
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name",
|
||||||
|
"url": "https://gitea.example.com/api/v1/repos/username/repo-name",
|
||||||
|
"ssh_url": "git@gitea.example.com:username/repo-name.git",
|
||||||
|
"clone_url": "https://gitea.example.com/username/repo-name.git",
|
||||||
|
"default_branch": "main"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"label": "webhook",
|
||||||
|
"ref": "webhook",
|
||||||
|
"sha": "eeff00112233445566778899aabbccddeeff0011",
|
||||||
|
"repo_id": 8,
|
||||||
|
"repo": {
|
||||||
|
"id": 8,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"name": "repo-name",
|
||||||
|
"full_name": "username/repo-name",
|
||||||
|
"description": "A self-hosted Gitea bot.",
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name",
|
||||||
|
"url": "https://gitea.example.com/api/v1/repos/username/repo-name",
|
||||||
|
"ssh_url": "git@gitea.example.com:username/repo-name.git",
|
||||||
|
"clone_url": "https://gitea.example.com/username/repo-name.git",
|
||||||
|
"default_branch": "main"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"merge_base": "aabbccdd00112233445566778899aabbccdd0011",
|
||||||
|
"due_date": null,
|
||||||
|
"created_at": "2026-01-01T00:00:00+02:00",
|
||||||
|
"updated_at": "2026-01-01T00:00:00+02:00",
|
||||||
|
"closed_at": null
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"id": 3,
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name/pulls/1#issuecomment-3",
|
||||||
|
"pull_request_url": "https://gitea.example.com/username/repo-name/pulls/1",
|
||||||
|
"issue_url": "",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"original_author": "",
|
||||||
|
"original_author_id": 0,
|
||||||
|
"body": "Test comment",
|
||||||
|
"assets": [],
|
||||||
|
"created_at": "2026-01-01T00:00:00+02:00",
|
||||||
|
"updated_at": "2026-01-01T00:00:00+02:00"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 8,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"name": "repo-name",
|
||||||
|
"full_name": "username/repo-name",
|
||||||
|
"description": "A self-hosted Gitea bot.",
|
||||||
|
"html_url": "https://gitea.example.com/username/repo-name",
|
||||||
|
"url": "https://gitea.example.com/api/v1/repos/username/repo-name",
|
||||||
|
"ssh_url": "git@gitea.example.com:username/repo-name.git",
|
||||||
|
"clone_url": "https://gitea.example.com/username/repo-name.git",
|
||||||
|
"default_branch": "main"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"id": 1,
|
||||||
|
"login": "username",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"avatar_url": "https://gitea.example.com/avatars/aabbccdd",
|
||||||
|
"html_url": "https://gitea.example.com/username",
|
||||||
|
"username": "username"
|
||||||
|
},
|
||||||
|
"is_pull": true
|
||||||
|
}
|
||||||
+44
-7
@@ -1,10 +1,15 @@
|
|||||||
use axum::Router;
|
use axum::response::{Response, IntoResponse};
|
||||||
use axum::routing::get;
|
use axum::{Json, Router};
|
||||||
|
use axum::extract::FromRequest;
|
||||||
|
use axum::routing::{post, get};
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::env;
|
use crate::env::{EnvConfig};
|
||||||
|
use crate::gitea::WebhookType;
|
||||||
|
|
||||||
pub async fn start_api(config: env::EnvConfig) -> anyhow::Result<()> {
|
pub async fn start_api(config: EnvConfig) -> anyhow::Result<()> {
|
||||||
let app = Router::new().route("/webhook", get(webhook));
|
let app = Router::new().route("/", get(root)).route("/webhook", post(webhook));
|
||||||
let listerner = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.http_port)).await?;
|
let listerner = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.http_port)).await?;
|
||||||
|
|
||||||
axum::serve(listerner, app)
|
axum::serve(listerner, app)
|
||||||
@@ -12,6 +17,38 @@ pub async fn start_api(config: env::EnvConfig) -> anyhow::Result<()> {
|
|||||||
.map_err(|e| anyhow::anyhow!(e))
|
.map_err(|e| anyhow::anyhow!(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn webhook() -> &'static str {
|
async fn root() -> &'static str {
|
||||||
"Hello, World!"
|
"Hi, i'm Herald :)"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn webhook(WebhookExtract(wb): WebhookExtract) -> Response {
|
||||||
|
match wb {
|
||||||
|
WebhookType::Review(id, _) => format!("Received {} pr id", id),
|
||||||
|
_ => String::from("Nothing to see :/")
|
||||||
|
}.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WebhookExtract(pub WebhookType);
|
||||||
|
|
||||||
|
impl<S> FromRequest<S> for WebhookExtract
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = Response;
|
||||||
|
|
||||||
|
async fn from_request(
|
||||||
|
req: axum::extract::Request,
|
||||||
|
state: &S,
|
||||||
|
) -> Result<Self, Self::Rejection> {
|
||||||
|
let Json(value) = Json::<Value>::from_request(req, state)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.into_response())?;
|
||||||
|
|
||||||
|
let webhook = WebhookType::try_from(value)
|
||||||
|
.map_err(|e| {
|
||||||
|
(StatusCode::UNPROCESSABLE_ENTITY, e.to_string()).into_response()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(WebhookExtract(webhook))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use crate::gitea::WebhookType;
|
||||||
|
|
||||||
|
pub async fn exec(webhook: WebhookType) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
||||||
|
pub enum WebhookType {
|
||||||
|
Review(u64, String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Value> for WebhookType {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(json: Value) -> Result<Self, Self::Error> {
|
||||||
|
let pull_request = json.get("pull_request");
|
||||||
|
let comment = json.get("comment");
|
||||||
|
|
||||||
|
if let (Some(pull_request), Some(comment)) = (pull_request, comment) {
|
||||||
|
let comment_body = comment
|
||||||
|
.get("body")
|
||||||
|
.ok_or(anyhow!("comment body not found"))?
|
||||||
|
.as_str()
|
||||||
|
.ok_or(anyhow!("error while get pr comment"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let pr_id = pull_request
|
||||||
|
.get("id")
|
||||||
|
.ok_or(anyhow!("pr id not found"))?
|
||||||
|
.as_u64()
|
||||||
|
.ok_or(anyhow!("error while get pr id"))?;
|
||||||
|
|
||||||
|
return Ok(WebhookType::Review(pr_id, comment_body));
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("unknow webhook type")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
mod api;
|
mod api;
|
||||||
mod env;
|
mod env;
|
||||||
|
mod gitea;
|
||||||
|
mod bot;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user