-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into extract-prod-feature
- Loading branch information
Showing
18 changed files
with
468 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DROP TABLE github_repository_provider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CREATE TABLE github_repository_provider( | ||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||
display_name TEXT NOT NULL, | ||
application_id TEXT NOT NULL, | ||
secret TEXT NOT NULL, | ||
access_token TEXT, | ||
CONSTRAINT `idx_application_id` UNIQUE (`application_id`) | ||
); |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use anyhow::{anyhow, Result}; | ||
use sqlx::{prelude::FromRow, query, query_as}; | ||
use tabby_db_macros::query_paged_as; | ||
|
||
use crate::{DbConn, SQLXResultExt}; | ||
|
||
#[derive(FromRow)] | ||
pub struct GithubRepositoryProviderDAO { | ||
pub id: i64, | ||
pub display_name: String, | ||
pub application_id: String, | ||
pub secret: String, | ||
pub access_token: Option<String>, | ||
} | ||
|
||
impl DbConn { | ||
pub async fn create_github_provider( | ||
&self, | ||
name: String, | ||
application_id: String, | ||
secret: String, | ||
) -> Result<i64> { | ||
let res = query!("INSERT INTO github_repository_provider (display_name, application_id, secret) VALUES ($1, $2, $3);", | ||
name, | ||
application_id, | ||
secret | ||
).execute(&self.pool).await.unique_error("GitHub Application ID already exists")?; | ||
Ok(res.last_insert_rowid()) | ||
} | ||
|
||
pub async fn get_github_provider(&self, id: i64) -> Result<GithubRepositoryProviderDAO> { | ||
let provider = query_as!( | ||
GithubRepositoryProviderDAO, | ||
"SELECT id, display_name, application_id, secret, access_token FROM github_repository_provider WHERE id = ?;", | ||
id | ||
) | ||
.fetch_one(&self.pool) | ||
.await?; | ||
Ok(provider) | ||
} | ||
|
||
pub async fn delete_github_provider(&self, id: i64) -> Result<()> { | ||
let res = query!("DELETE FROM github_repository_provider WHERE id = ?;", id) | ||
.execute(&self.pool) | ||
.await?; | ||
if res.rows_affected() != 1 { | ||
return Err(anyhow!("No github provider details to delete")); | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub async fn update_github_provider_access_token( | ||
&self, | ||
id: i64, | ||
access_token: String, | ||
) -> Result<()> { | ||
let res = query!( | ||
"UPDATE github_repository_provider SET access_token = ? WHERE id = ?", | ||
access_token, | ||
id | ||
) | ||
.execute(&self.pool) | ||
.await?; | ||
|
||
if res.rows_affected() != 1 { | ||
return Err(anyhow!( | ||
"The specified Github repository provider does not exist" | ||
)); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub async fn list_github_repository_providers( | ||
&self, | ||
limit: Option<usize>, | ||
skip_id: Option<i32>, | ||
backwards: bool, | ||
) -> Result<Vec<GithubRepositoryProviderDAO>> { | ||
let providers = query_paged_as!( | ||
GithubRepositoryProviderDAO, | ||
"github_repository_provider", | ||
[ | ||
"id", | ||
"display_name", | ||
"application_id", | ||
"secret", | ||
"access_token" | ||
], | ||
limit, | ||
skip_id, | ||
backwards | ||
) | ||
.fetch_all(&self.pool) | ||
.await?; | ||
Ok(providers) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod github; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
use std::sync::Arc; | ||
|
||
use anyhow::Result; | ||
use axum::{ | ||
extract::{Path, Query, State}, | ||
response::Redirect, | ||
routing, Router, | ||
}; | ||
use hyper::StatusCode; | ||
use juniper::ID; | ||
use serde::Deserialize; | ||
use tracing::error; | ||
use url::Url; | ||
|
||
use crate::{ | ||
oauth::github::GithubOAuthResponse, | ||
schema::{ | ||
github_repository_provider::GithubRepositoryProviderService, setting::SettingService, | ||
}, | ||
}; | ||
|
||
#[derive(Deserialize)] | ||
struct CallbackParams { | ||
state: ID, | ||
code: String, | ||
} | ||
|
||
#[derive(Clone)] | ||
struct IntegrationState { | ||
pub settings: Arc<dyn SettingService>, | ||
pub github_repository_provider: Arc<dyn GithubRepositoryProviderService>, | ||
} | ||
|
||
pub fn routes( | ||
settings: Arc<dyn SettingService>, | ||
github_repository_provider: Arc<dyn GithubRepositoryProviderService>, | ||
) -> Router { | ||
let state = IntegrationState { | ||
settings, | ||
github_repository_provider, | ||
}; | ||
Router::new() | ||
.route("/connect/:id", routing::get(connect)) | ||
.route("/callback", routing::get(callback)) | ||
.with_state(state) | ||
} | ||
|
||
fn get_authorize_url(client_id: &str, redirect_uri: &str, id: &ID) -> Result<Url> { | ||
Ok(Url::parse_with_params( | ||
"https://github.com/login/oauth/authorize", | ||
&[ | ||
("client_id", client_id), | ||
("response_type", "code"), | ||
("scope", "repo"), | ||
( | ||
"redirect_uri", | ||
&format!("{redirect_uri}/integrations/github/callback"), | ||
), | ||
("state", &id.to_string()), | ||
], | ||
)?) | ||
} | ||
|
||
async fn exchange_access_token( | ||
state: &IntegrationState, | ||
params: &CallbackParams, | ||
) -> Result<GithubOAuthResponse> { | ||
let client = reqwest::Client::new(); | ||
|
||
let client_id = state | ||
.github_repository_provider | ||
.get_github_repository_provider(params.state.clone()) | ||
.await? | ||
.application_id; | ||
|
||
let secret = state | ||
.github_repository_provider | ||
.read_github_repository_provider_secret(params.state.clone()) | ||
.await?; | ||
|
||
Ok(client | ||
.post("https://github.com/login/oauth/access_token") | ||
.header(reqwest::header::ACCEPT, "application/json") | ||
.form(&[ | ||
("client_id", &client_id), | ||
("client_secret", &secret), | ||
("code", ¶ms.code), | ||
]) | ||
.send() | ||
.await? | ||
.json() | ||
.await?) | ||
} | ||
|
||
async fn callback( | ||
State(state): State<IntegrationState>, | ||
Query(params): Query<CallbackParams>, | ||
) -> Result<Redirect, StatusCode> { | ||
let response = match exchange_access_token(&state, ¶ms).await { | ||
Ok(response) => response, | ||
Err(e) => { | ||
error!("Failed to exchange access token: {e}"); | ||
return Err(StatusCode::INTERNAL_SERVER_ERROR); | ||
} | ||
}; | ||
|
||
if let Err(e) = state | ||
.github_repository_provider | ||
.update_github_repository_provider_access_token(params.state, response.access_token) | ||
.await | ||
{ | ||
error!("Failed to update github access token: {e}"); | ||
return Err(StatusCode::INTERNAL_SERVER_ERROR); | ||
} | ||
|
||
Ok(Redirect::temporary("/")) | ||
} | ||
|
||
async fn connect( | ||
State(state): State<IntegrationState>, | ||
Path(id): Path<ID>, | ||
) -> Result<Redirect, StatusCode> { | ||
let network_setting = match state.settings.read_network_setting().await { | ||
Ok(setting) => setting, | ||
Err(e) => { | ||
error!("Failed to read network setting: {e}"); | ||
return Err(StatusCode::INTERNAL_SERVER_ERROR); | ||
} | ||
}; | ||
let external_url = network_setting.external_url; | ||
let provider = match state | ||
.github_repository_provider | ||
.get_github_repository_provider(id.clone()) | ||
.await | ||
{ | ||
Ok(provider) => provider, | ||
Err(e) => { | ||
error!("Github repository provider not found: {e}"); | ||
return Err(StatusCode::NOT_FOUND); | ||
} | ||
}; | ||
|
||
let redirect = match get_authorize_url(&provider.application_id, &external_url, &id) { | ||
Ok(redirect) => redirect, | ||
Err(e) => { | ||
error!("Failed to generate callback URL: {e}"); | ||
return Err(StatusCode::INTERNAL_SERVER_ERROR); | ||
} | ||
}; | ||
|
||
Ok(Redirect::temporary(redirect.as_str())) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
mod cron; | ||
mod handler; | ||
mod hub; | ||
mod integrations; | ||
mod oauth; | ||
mod path; | ||
mod repositories; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.