This commit is contained in:
+4
-11
@@ -4,12 +4,10 @@ use axum::http::Request;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use reqwest::StatusCode;
|
||||
use ring::hmac;
|
||||
use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer};
|
||||
use serde_json::Value;
|
||||
use sha2::Sha256;
|
||||
use subtle::ConstantTimeEq;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{info, instrument};
|
||||
@@ -105,7 +103,7 @@ where
|
||||
});
|
||||
});
|
||||
|
||||
let webhook = parse_webhook(&type_header, &app_state.config.bot_name, &body_bytes)?;
|
||||
let webhook = parse_webhook(&type_header, &app_state.bot.name(), &body_bytes)?;
|
||||
Ok(WebhookExtract(webhook))
|
||||
}
|
||||
}
|
||||
@@ -137,12 +135,7 @@ fn parse_webhook(header: &str, bot_name: &str, body_bytes: &[u8]) -> Result<Webh
|
||||
fn verify_signature(secret_key: &[u8], sig_header: &str, body: &[u8]) -> Result<(), AppError> {
|
||||
let sig_header_decoded =
|
||||
hex::decode(sig_header).map_err(|_| AppError::WebHookSigHeaderInvalidErr)?;
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(secret_key).map_err(anyhow::Error::from)?;
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key);
|
||||
|
||||
mac.update(body);
|
||||
|
||||
let generated_hmac = mac.finalize().into_bytes();
|
||||
bool::from(generated_hmac.ct_eq(&sig_header_decoded))
|
||||
.then_some(())
|
||||
.ok_or(AppError::WebHookSigHeaderInvalidErr)
|
||||
hmac::verify(&key, body, &sig_header_decoded).map_err(|_| AppError::WebHookSigHeaderInvalidErr)
|
||||
}
|
||||
|
||||
+24
-21
@@ -1,10 +1,9 @@
|
||||
use crate::{
|
||||
env::EnvConfig,
|
||||
gitea::{GiteaAPI, WebhookType},
|
||||
open_router::OpenRouterClient,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, instrument};
|
||||
@@ -26,33 +25,37 @@ pub struct ReviewItem {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bot {
|
||||
config: EnvConfig,
|
||||
bot_name: String,
|
||||
gitea_api: GiteaAPI,
|
||||
open_router_client: OpenRouterClient,
|
||||
http_client: reqwest::Client,
|
||||
max_concurrent: usize,
|
||||
open_router_model: String,
|
||||
actions_handled: Arc<Mutex<HashSet<u64>>>,
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
pub fn new(config: EnvConfig) -> anyhow::Result<Self> {
|
||||
let gitea_timeout = config.gitea_timeout;
|
||||
let open_router_timeout = config.open_router_timeout;
|
||||
|
||||
Ok(Self {
|
||||
gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token, gitea_timeout)?,
|
||||
open_router_client: OpenRouterClient::new(
|
||||
&config.open_router_api_key,
|
||||
&config.open_router_model,
|
||||
open_router_timeout,
|
||||
)?,
|
||||
max_concurrent: config.bot_max_concurrent,
|
||||
config,
|
||||
pub fn new(
|
||||
bot_name: String,
|
||||
gitea_api: GiteaAPI,
|
||||
open_router_client: OpenRouterClient,
|
||||
http_client: reqwest::Client,
|
||||
max_concurrent: usize,
|
||||
open_router_model: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
bot_name,
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
http_client,
|
||||
max_concurrent,
|
||||
open_router_model,
|
||||
actions_handled: Arc::new(Mutex::new(HashSet::new())),
|
||||
http_client: reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(gitea_timeout))
|
||||
.build()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.bot_name.clone()
|
||||
}
|
||||
|
||||
pub async fn start(
|
||||
@@ -111,7 +114,7 @@ impl Bot {
|
||||
&self.gitea_api,
|
||||
&self.open_router_client,
|
||||
&self.http_client,
|
||||
&self.config.open_router_model,
|
||||
&self.open_router_model,
|
||||
review_payload,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ pub struct EnvConfig {
|
||||
pub open_router_api_key: String,
|
||||
pub open_router_model: String,
|
||||
pub open_router_timeout: u64,
|
||||
pub bot_name: String,
|
||||
pub bot_max_concurrent: usize,
|
||||
pub gitea_url: String,
|
||||
pub gitea_token: String,
|
||||
@@ -16,7 +15,6 @@ pub struct EnvConfig {
|
||||
|
||||
pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
let http_port = try_get_env("HTTP_PORT")?.parse()?;
|
||||
let bot_name = try_get_env("BOT_NAME")?;
|
||||
let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?;
|
||||
let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?;
|
||||
let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?;
|
||||
@@ -29,7 +27,6 @@ pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
Ok(EnvConfig {
|
||||
http_port,
|
||||
webhook_secret,
|
||||
bot_name,
|
||||
open_router_api_key,
|
||||
open_router_model,
|
||||
open_router_timeout,
|
||||
|
||||
@@ -29,6 +29,21 @@ impl GiteaAPI {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn get_authorized_user(&self) -> anyhow::Result<User> {
|
||||
let url = format!("{}/api/v1/user", self.base_url);
|
||||
let res = self.client.get(url).send().await?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to get authorized user: {}",
|
||||
res.status()
|
||||
));
|
||||
}
|
||||
|
||||
res.json::<User>().await.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn comment(
|
||||
&self,
|
||||
@@ -192,6 +207,7 @@ pub struct Comment {
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct User {
|
||||
pub id: u64,
|
||||
pub login: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
+20
-5
@@ -1,6 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{bot::Bot, gitea::WebhookType, state::AppState};
|
||||
use crate::{bot::Bot, gitea::{GiteaAPI, WebhookType}, open_router::OpenRouterClient, state::AppState};
|
||||
|
||||
use dotenvy::dotenv;
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
@@ -51,17 +49,34 @@ fn main() -> anyhow::Result<()> {
|
||||
async fn run() -> anyhow::Result<()> {
|
||||
let config = env::load_config()?;
|
||||
|
||||
let gitea_api = GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout)?;
|
||||
let gitea_user = gitea_api.get_authorized_user().await?;
|
||||
|
||||
info!(
|
||||
port = config.http_port,
|
||||
model = %config.open_router_model,
|
||||
gitea_url = %config.gitea_url,
|
||||
bot_name = %config.bot_name,
|
||||
bot_name = %gitea_user.login,
|
||||
"Starting Herald"
|
||||
);
|
||||
|
||||
let open_router_client = OpenRouterClient::new(
|
||||
&config.open_router_api_key,
|
||||
&config.open_router_model,
|
||||
config.open_router_timeout,
|
||||
)?;
|
||||
|
||||
let shutdown = CancellationToken::new();
|
||||
|
||||
let bot = Bot::new(config.clone())?;
|
||||
let bot = Bot::new(
|
||||
gitea_user.login,
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
reqwest::Client::new(),
|
||||
config.bot_max_concurrent,
|
||||
config.open_router_model.clone(),
|
||||
);
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<WebhookType>(config.bot_max_concurrent * 2);
|
||||
let app_state = AppState {
|
||||
bot_tx: tx,
|
||||
|
||||
Reference in New Issue
Block a user