Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub client #318

Merged
merged 16 commits into from
Jan 14, 2025
Merged
2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository = "https://github.com/r-Techsupport/hyde"
readme = "../README.md"
keywords = ["cms", "wiki"]
categories = ["web-programming"]
rust-version = "1.75.0"
rust-version = "1.80.0"

[dependencies]
axum = { version = "0.8.1", features = ["http2", "macros"] }
Expand Down
576 changes: 271 additions & 305 deletions backend/src/gh.rs

Large diffs are not rendered by default.

118 changes: 16 additions & 102 deletions backend/src/handlers_prelude/github_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use axum::routing::{get, post, put};
use tracing::{error, info};
use serde::{Serialize, Deserialize};
use serde_json::Value;
use crate::gh::GitHubClient;
use crate::handlers_prelude::eyre_to_axum_err;
use crate::AppState;
use color_eyre::Result;
Expand Down Expand Up @@ -70,35 +69,14 @@ pub struct UpdatePRRequest {
pub issue_numbers: Option<Vec<u64>>,
}

/// Retrieves the GitHub access token from the application state.
async fn get_github_token(state: &AppState) -> Result<String, (StatusCode, String)> {
state.gh_credentials.get(&state.reqwest_client, &state.config.oauth.github.client_id).await.map_err(|err| {
eyre_to_axum_err(err)
})
}

/// Fetches the list of branches from a GitHub repository.
pub async fn list_branches_handler(
State(state): State<AppState>,
) -> Result<(StatusCode, Json<ApiResponse<BranchesData>>), (StatusCode, String)> {
info!("Received request to fetch branches");

// Get the GitHub access token
let token = get_github_token(&state).await.map_err(|err| {
// Format the error message as a string
let error_message = format!("Error: {:?}", err); // Use {:?} to format the tuple
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?;

// Retrieve the repository URL from state (assuming it is stored in state.config.files.repo_url)
let repo_url = state.config.files.repo_url.clone();

// Create an instance of GitHubClient with the repository URL, reqwest client, and token
let github_client = GitHubClient::new(repo_url, state.reqwest_client.clone(), token);

// Fetch the branch details from GitHub using the GitHubClient instance
let branch_details = github_client
.get_all_branch_details() // Call the method on the GitHubClient instance
let branch_details = state.gh_client
.list_branches() // Call the method on the GitHubClient instance
TheKrol marked this conversation as resolved.
Show resolved Hide resolved
.await
.map_err(|err| {
// Handle errors in fetching branch details (e.g., connection issues)
Expand Down Expand Up @@ -135,24 +113,10 @@ pub async fn create_pull_request_handler(
State(state): State<AppState>,
Json(payload): Json<CreatePRRequest>,
) -> Result<(StatusCode, Json<ApiResponse<CreatePRData>>), (StatusCode, String)> {
info!("Received create pull request request: {:?}", payload);

// Get the GitHub access token
let token = get_github_token(&state).await.map_err(|err| {
// Handle token retrieval error
let error_message = format!("Failed to get GitHub token: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?;

// Create an instance of the GitHubClient
let github_client = GitHubClient::new(
state.config.files.repo_url.clone(),
state.reqwest_client.clone(),
token,
);

// Create the pull request using the new method from GitHubClient
match github_client
match state
.gh_client
.create_pull_request(
&payload.head_branch,
&payload.base_branch,
Expand Down Expand Up @@ -188,21 +152,9 @@ pub async fn update_pull_request_handler(
Json(payload): Json<UpdatePRRequest>,
) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {

// Get the GitHub access token
let token = get_github_token(&state).await.map_err(|err| {
let error_message = format!("Failed to get GitHub token: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?;

// Create an instance of the GitHubClient
let github_client = GitHubClient::new(
state.config.files.repo_url.clone(),
state.reqwest_client.clone(),
token,
);

// Update the pull request
match github_client
match state
.gh_client
.update_pull_request(
payload.pr_number,
payload.title.as_deref(),
Expand Down Expand Up @@ -235,23 +187,11 @@ pub async fn close_pull_request_handler(
State(state): State<AppState>,
Path(pr_number): Path<u64>,
) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {
info!("Received request to close pull request #{}", pr_number);

// Get the GitHub access token
let token = get_github_token(&state).await.map_err(|err| {
let error_message = format!("Failed to get GitHub token: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?;

// Create an instance of the GitHubClient
let github_client = GitHubClient::new(
state.config.files.repo_url.clone(),
state.reqwest_client.clone(),
token,
);

// Attempt to close the pull request
match github_client.close_pull_request(pr_number).await {
match state
.gh_client
.close_pull_request(pr_number).await {
Ok(_) => {
info!("Pull request #{} closed successfully", pr_number);
Ok((
Expand All @@ -276,7 +216,6 @@ pub async fn checkout_or_create_branch_handler(
State(state): State<AppState>,
Path(branch_name): Path<String>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
info!("Checking out or creating branch: {}", branch_name);

// Use the git interface to perform operations
match state.git.checkout_or_create_branch(&branch_name) {
Expand All @@ -296,7 +235,6 @@ pub async fn pull_handler(
State(state): State<AppState>,
Path(branch): Path<String>,
) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {
info!("Received request to pull latest changes for branch '{}'", branch);

// Attempt to pull the latest changes for the specified branch
match state.git.git_pull_branch(&branch) {
Expand All @@ -323,7 +261,6 @@ pub async fn pull_handler(

/// Handler for fetching the current branch of the repository.
pub async fn get_current_branch_handler(State(state): State<AppState>) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {
info!("Received request to fetch current branch");

// Use the git::Interface from AppState to get the current branch
match state.git.get_current_branch().await {
Expand Down Expand Up @@ -351,25 +288,13 @@ pub async fn get_current_branch_handler(State(state): State<AppState>) -> Result
}

/// Handler for fetching the default branch of the repository.
pub async fn get_default_branch_handler(State(state): State<AppState>) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {
info!("Received request to fetch default branch");

// Get the GitHub access token
let token = get_github_token(&state).await.map_err(|err| {
let error_message = format!("Failed to get GitHub token: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?;

// Create an instance of the GitHubClient
let github_client = GitHubClient::new(
state.config.files.repo_url.clone(),
state.reqwest_client.clone(),
token,
);

pub async fn get_default_branch_handler(State(state): State<AppState>) -> Result<(StatusCode, Json<ApiResponse<String>>), (StatusCode, String)> {

// Use the `get_default_branch` method from the `Gh` struct in AppState
match github_client.get_default_branch().await {
match state
.gh_client
.get_default_branch()
.await {
Ok(default_branch) => {
info!("Default branch is: {}", default_branch);

Expand Down Expand Up @@ -398,23 +323,12 @@ pub async fn get_issues_handler(
State(state): State<AppState>,
Path(state_param): Path<String>,
) -> Result<(StatusCode, Json<ApiResponse<IssuesData>>), (StatusCode, String)> {
info!("Received request to fetch issues");

let state_param = state_param.as_str();

// Get the GitHubClient instance
let github_client = GitHubClient::new(
state.config.files.repo_url.clone(),
state.reqwest_client.clone(),
get_github_token(&state).await.map_err(|err| {
let error_message = format!("Failed to get GitHub token: {:?}", err);
error!("{}", error_message); // Log the error here
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
})?,
);

// Fetch issues using the GitHub client
match github_client
match state
.gh_client
.get_issues(Some(state_param), None)
.await
{
Expand Down
60 changes: 27 additions & 33 deletions backend/src/handlers_prelude/repo_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ pub struct GetDocResponse {
pub contents: String,
}

async fn get_gh_token(state: &AppState) -> Result<String, (StatusCode, String)> {
state
.gh_client
.get_token()
.await
.map_err(|e| {
error!("Failed to retrieve GitHub token: {e}");
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
})
}

/// This handler accepts a `GET` request to `/api/doc?path=`.
/// TODO: refactor to pass it in directly as a url path instead of doing the whole url arguments thing
pub async fn get_doc_handler(
Expand Down Expand Up @@ -75,21 +86,6 @@ pub async fn put_doc_handler(
)
.await?;

let gh_token = match &state
.gh_credentials
.get(&state.reqwest_client, &state.config.oauth.github.client_id)
.await
{
Ok(t) => t.clone(),
Err(e) => {
error!("Failed to authenticate with github for a put_doc request with error: {e:?}");
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to authenticate with github to push changes".to_string(),
));
}
};

// Generate commit message combining author and default update message
let default_commit_message = format!("{} updated {}", author.username, body.path);
let final_commit_message = format!("{}\n\n{}", default_commit_message, body.commit_message);
Expand All @@ -99,7 +95,13 @@ pub async fn put_doc_handler(

match state
.git
.put_doc(&body.path, &body.contents, &final_commit_message, &gh_token, branch_name)
.put_doc(
&body.path,
&body.contents,
&final_commit_message,
&get_gh_token(&state).await?,
branch_name,
)
{
Ok(_) => Ok(StatusCode::CREATED),
Err(e) => {
Expand All @@ -125,17 +127,12 @@ pub async fn delete_doc_handler(
)
.await?;

let gh_token = state
.gh_credentials
.get(&state.reqwest_client, &state.config.oauth.github.client_id)
.await
.unwrap();
state
.git
.delete_doc(
&query.path,
&format!("{} deleted {}", author.username, query.path),
&gh_token,
&get_gh_token(&state).await?,
)
.map_err(eyre_to_axum_err)?;

Expand Down Expand Up @@ -221,19 +218,20 @@ pub async fn put_asset_handler(
.await?;
// Generate commit message combining author and default update message
let message = format!("{} updated {}", author.username, path);

// Call put_asset to update the asset, passing the required parameters
state
.git
.put_asset(
&path,
&body,
&message,
&state
.gh_credentials
.get(&state.reqwest_client, &state.config.oauth.github.client_id)
.await
.map_err(eyre_to_axum_err)?,
&get_gh_token(&state).await?,
)
.map_err(eyre_to_axum_err)?;
.map_err(|e| {
error!("Failed to update asset: {e}");
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
})?;

Ok(StatusCode::CREATED)
}
Expand All @@ -254,11 +252,7 @@ pub async fn delete_asset_handler(
.delete_asset(
&path,
&message,
&state
.gh_credentials
.get(&state.reqwest_client, &state.config.oauth.github.client_id)
.await
.map_err(eyre_to_axum_err)?,
&get_gh_token(&state).await?,
)
.map_err(eyre_to_axum_err)?;

Expand Down
21 changes: 15 additions & 6 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use clap::{
use color_eyre::eyre::Context;
use color_eyre::Result;
use db::Database;
use gh::GithubAccessToken;
use gh::GitHubClient;
use handlers_prelude::*;
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, TokenUrl};
use reqwest::{
Expand All @@ -32,6 +32,7 @@ use reqwest::{
};
use std::env::current_exe;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::Duration;
use tracing::{debug, info, info_span, warn};
use tracing::{Level, Span};
Expand All @@ -49,7 +50,7 @@ pub struct AppState {
git: git::Interface,
oauth: BasicClient,
reqwest_client: Client,
gh_credentials: GithubAccessToken,
gh_client: GitHubClient,
db: Database,
}

Expand Down Expand Up @@ -128,10 +129,15 @@ async fn main() -> Result<()> {
Ok(())
}

static CONFIG: LazyLock<Arc<AppConf>> = LazyLock::new(|| {
TheKrol marked this conversation as resolved.
Show resolved Hide resolved
let args = Args::parse();
AppConf::load(&args.cfg).expect("Failed to load configuration")
});

/// Initialize an instance of [`AppState`]
#[tracing::instrument]
async fn init_state(cli_args: &Args) -> Result<AppState> {
let config: Arc<AppConf> = AppConf::load(&cli_args.cfg)?;
let config = CONFIG.clone();
TheKrol marked this conversation as resolved.
Show resolved Hide resolved

let repo_url = config.files.repo_url.clone();
let repo_path = config.files.repo_path.clone();
Expand All @@ -151,11 +157,14 @@ async fn init_state(cli_args: &Args) -> Result<AppState> {
);

Ok(AppState {
config,
config: config.clone(),
TheKrol marked this conversation as resolved.
Show resolved Hide resolved
git,
oauth,
reqwest_client,
gh_credentials: GithubAccessToken::new(),
reqwest_client: reqwest_client.clone(),
gh_client: GitHubClient::new(
config.files.repo_url.clone(),
reqwest_client.clone(),
config.oauth.github.client_id.clone()),
db: Database::new().await?,
TheKrol marked this conversation as resolved.
Show resolved Hide resolved
TheKrol marked this conversation as resolved.
Show resolved Hide resolved
})
}
Expand Down
Loading
Loading