From f628f5cacd7b4bb5e35c9dd5a8a1cd1c9defed42 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Tue, 9 Apr 2024 07:16:33 -0700 Subject: [PATCH 1/2] fix(webserver): /resolve/all should read from RepositoryService (#1796) --- ee/tabby-webserver/src/repositories/mod.rs | 2 +- ee/tabby-webserver/src/repositories/resolve.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ee/tabby-webserver/src/repositories/mod.rs b/ee/tabby-webserver/src/repositories/mod.rs index 95f8e21d5784..3fd02745e521 100644 --- a/ee/tabby-webserver/src/repositories/mod.rs +++ b/ee/tabby-webserver/src/repositories/mod.rs @@ -87,5 +87,5 @@ async fn meta( } async fn resolve(State(rs): State>) -> Result { - rs.resolve_all().map_err(|_| StatusCode::NOT_FOUND) + rs.resolve_all().await.map_err(|_| StatusCode::NOT_FOUND) } diff --git a/ee/tabby-webserver/src/repositories/resolve.rs b/ee/tabby-webserver/src/repositories/resolve.rs index 883e39f25d71..6e2a6d53f143 100644 --- a/ee/tabby-webserver/src/repositories/resolve.rs +++ b/ee/tabby-webserver/src/repositories/resolve.rs @@ -264,15 +264,17 @@ impl RepositoryCache { None } - pub fn resolve_all(&self) -> Result { - let entries: Vec<_> = self - .repository_lookup - .read() - .unwrap() - .keys() + pub async fn resolve_all(&self) -> Result { + let repositories = self + .service + .list_repositories(None, None, None, None) + .await?; + + let entries = repositories + .into_iter() .map(|repo| DirEntry { kind: DirEntryKind::Dir, - basename: repo.repo_name.clone(), + basename: repo.name.clone(), }) .collect(); From ed25f4a17a31f2cd035241695a1df12c07dadc2d Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 13:40:09 -0400 Subject: [PATCH 2/2] feat(db, webserver): Implement GitHubRepositoryProvider connect flow (#1749) * feat(db): Create github provider table and DAO * [autofix.ci] apply automated fixes * Apply suggestions * Apply suggestions, add DAO for github repositories * Add list query * Apply suggestion * Apply suggestions * Update db spec * Use schema::result * Implement github provider OAuth flow * Resolve conflicts * Fixup * Apply suggestions, add GraphQL endpoints * Restructure routes * [autofix.ci] apply automated fixes * Use Url::parse_with_params to generate redirect url * Apply suggestions * Make redirect temporary --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../migrations/0022_github-provider.down.sql | 1 + .../migrations/0022_github-provider.up.sql | 8 + ee/tabby-db/schema.sqlite | Bin 131072 -> 139264 bytes ee/tabby-db/src/github_repository_provider.rs | 98 +++++++++++ ee/tabby-db/src/lib.rs | 2 + ee/tabby-webserver/graphql/schema.graphql | 17 ++ ee/tabby-webserver/src/handler.rs | 6 +- ee/tabby-webserver/src/integrations.rs | 1 + ee/tabby-webserver/src/integrations/github.rs | 152 ++++++++++++++++++ ee/tabby-webserver/src/lib.rs | 1 + ee/tabby-webserver/src/oauth/github.rs | 14 +- .../src/schema/github_repository_provider.rs | 49 ++++++ ee/tabby-webserver/src/schema/mod.rs | 30 +++- ee/tabby-webserver/src/service/dao.rs | 15 +- .../src/service/github_repository_provider.rs | 62 +++++++ ee/tabby-webserver/src/service/mod.rs | 13 ++ 16 files changed, 458 insertions(+), 11 deletions(-) create mode 100644 ee/tabby-db/migrations/0022_github-provider.down.sql create mode 100644 ee/tabby-db/migrations/0022_github-provider.up.sql create mode 100644 ee/tabby-db/src/github_repository_provider.rs create mode 100644 ee/tabby-webserver/src/integrations.rs create mode 100644 ee/tabby-webserver/src/integrations/github.rs create mode 100644 ee/tabby-webserver/src/schema/github_repository_provider.rs create mode 100644 ee/tabby-webserver/src/service/github_repository_provider.rs diff --git a/ee/tabby-db/migrations/0022_github-provider.down.sql b/ee/tabby-db/migrations/0022_github-provider.down.sql new file mode 100644 index 000000000000..2af049ced3da --- /dev/null +++ b/ee/tabby-db/migrations/0022_github-provider.down.sql @@ -0,0 +1 @@ +DROP TABLE github_repository_provider; diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql new file mode 100644 index 000000000000..0634b42cb7ac --- /dev/null +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -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`) +); diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index b11dc9144c9d766a36cf6b06ecfb83501bc25064..e7846cb398f045f19946a01ebdd383f102b0d513 100644 GIT binary patch delta 1164 zcmZ|OZ%7ki90%}wym>dzZFiMz?vLK7g|2Cy)3Su3K}LyWk;2sKHr?fx%Z<7Ih-6|? zgdx^X8d0I`MS(9Oy=qYr)r;yy(SL&ez6c^MdQ}t{*`pEeARq2H_&mSo`#g8|Jl);e z?pxXcUY|?MHrr_7GgdoKkHtt21I;;7=wZdh1!-xUkw%Wvv><702}J$7n~K7b(4K%# zjyOeUS&@jZl|o5b#j?_hva<83c`fYVlhatm*9-Fl{!8`vbAN-e9;QqM%xnUbWQye0*j4al3bR}a{g~bMLAafnoy_By8}s~N zGuxB7#w?QI76bH{^OIS@T>st%|L+@{L*PIH?D25n1{L+)dCPjtehO%<{v z3xBFJL$EDNwUWb_-K|w{ElU;h6?6Tj49Lk=Of?Uy8`#PIqyMdK zZkF5IrD&*CZky>Z1W{*ns1iIv-Np@@>neo;k2>08XY6_fbx#$?2e|lv7+=GUtBdkL#|A|vC{ib@hdDu<|1PrP zDl!D=_A69H5Q|=V&`HFAh)!haWiU|jpp$_h?&4{lGFD%PVDfmp!-pT=_l5UL^K7Za zRM|_Hx4WAJ zG*WPA3HE88XE@{Vlnr+^&y|as!AT1Xob%-2GGM*aY%}uVsdCCHr_eQh3$`iVSD-?- z!d6fS9pqp83Nu%K%*>6NClHVP*!B$`NdjVJn5P5gEIo(R@_9cNMG;N!zc_Mwym)D% zcwu7m!2S6HR^ceR&-gfv>am)~&{mqJ57)LGN3UiB8#Q4VB@hP&AAxhN*i`rxCd)ei z)A^EZRF(?A3+4DF?icrzyWFbcH-4!l$piBkRRe2Ole$Mn{w*tW3ccVqOV-xTqHorp dJQ_o0SIaa48~31~iAv2>y&}{rM!i=c`!5N^&9eXi diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs new file mode 100644 index 000000000000..b2741d238650 --- /dev/null +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -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, +} + +impl DbConn { + pub async fn create_github_provider( + &self, + name: String, + application_id: String, + secret: String, + ) -> Result { + 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 { + 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, + skip_id: Option, + backwards: bool, + ) -> Result> { + 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) + } +} diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 2fc01f669e48..5c4c4422b47c 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -4,6 +4,7 @@ use anyhow::anyhow; use cache::Cache; use chrono::{DateTime, NaiveDateTime, Utc}; pub use email_setting::EmailSettingDAO; +pub use github_repository_provider::GithubRepositoryProviderDAO; pub use invitations::InvitationDAO; pub use job_runs::JobRunDAO; pub use oauth_credential::OAuthCredentialDAO; @@ -17,6 +18,7 @@ pub use users::UserDAO; pub mod cache; mod email_setting; +mod github_repository_provider; mod invitations; mod job_runs; mod oauth_credential; diff --git a/ee/tabby-webserver/graphql/schema.graphql b/ee/tabby-webserver/graphql/schema.graphql index 5aa4cf4d2bd8..23ef1a79eb1e 100644 --- a/ee/tabby-webserver/graphql/schema.graphql +++ b/ee/tabby-webserver/graphql/schema.graphql @@ -3,6 +3,11 @@ enum Language { PYTHON } +type GithubRepositoryProviderEdge { + node: GithubRepositoryProvider! + cursor: String! +} + type JobRun { id: ID! job: String! @@ -44,6 +49,11 @@ type LicenseInfo { expiresAt: DateTimeUtc } +type GithubRepositoryProviderConnection { + edges: [GithubRepositoryProviderEdge!]! + pageInfo: PageInfo! +} + input SecuritySettingInput { allowedRegisterDomainList: [String!]! disableClientSideTelemetry: Boolean! @@ -72,6 +82,12 @@ type ServerInfo { allowSelfSignup: Boolean! } +type GithubRepositoryProvider { + id: ID! + displayName: String! + applicationId: String! +} + input PasswordChangeInput { oldPassword: String newPassword1: String! @@ -176,6 +192,7 @@ type Query { me: User! users(after: String, before: String, first: Int, last: Int): UserConnection! invitations(after: String, before: String, first: Int, last: Int): InvitationConnection! + githubRepositoryProviders(after: String, before: String, first: Int, last: Int): GithubRepositoryProviderConnection! jobRuns(ids: [ID!], jobs: [String!], after: String, before: String, first: Int, last: Int): JobRunConnection! jobRunStats(jobs: [String!]): JobStats! emailSetting: EmailSetting diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 31dbe01d9ded..8ac832f6edb2 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -19,7 +19,7 @@ use tabby_db::DbConn; use tracing::{error, warn}; use crate::{ - cron, hub, oauth, + cron, hub, integrations, oauth, repositories::{self, RepositoryCache}, schema::{auth::AuthenticationService, create_schema, Schema, ServiceLocator}, service::{create_service_locator, event_logger::create_event_logger}, @@ -84,6 +84,10 @@ impl WebserverHandle { "/repositories", repositories::routes(rs.clone(), ctx.auth()), ) + .nest( + "/integrations/github", + integrations::github::routes(ctx.setting(), ctx.github_repository_provider()), + ) .route( "/avatar/:id", routing::get(avatar) diff --git a/ee/tabby-webserver/src/integrations.rs b/ee/tabby-webserver/src/integrations.rs new file mode 100644 index 000000000000..72246d32d261 --- /dev/null +++ b/ee/tabby-webserver/src/integrations.rs @@ -0,0 +1 @@ +pub mod github; diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs new file mode 100644 index 000000000000..a0dd22e42d70 --- /dev/null +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -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, + pub github_repository_provider: Arc, +} + +pub fn routes( + settings: Arc, + github_repository_provider: Arc, +) -> 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 { + 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 { + 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, + Query(params): Query, +) -> Result { + 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, + Path(id): Path, +) -> Result { + 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())) +} diff --git a/ee/tabby-webserver/src/lib.rs b/ee/tabby-webserver/src/lib.rs index d20b7d053cdc..f2c0ee9f51dc 100644 --- a/ee/tabby-webserver/src/lib.rs +++ b/ee/tabby-webserver/src/lib.rs @@ -3,6 +3,7 @@ mod cron; mod handler; mod hub; +mod integrations; mod oauth; mod repositories; mod schema; diff --git a/ee/tabby-webserver/src/oauth/github.rs b/ee/tabby-webserver/src/oauth/github.rs index c7faefe57d8c..32d9c7e598ed 100644 --- a/ee/tabby-webserver/src/oauth/github.rs +++ b/ee/tabby-webserver/src/oauth/github.rs @@ -9,20 +9,20 @@ use crate::schema::auth::{AuthenticationService, OAuthCredential, OAuthProvider} #[derive(Debug, Deserialize)] #[allow(dead_code)] -struct GithubOAuthResponse { +pub struct GithubOAuthResponse { #[serde(default)] - access_token: String, + pub access_token: String, #[serde(default)] - scope: String, + pub scope: String, #[serde(default)] - token_type: String, + pub token_type: String, #[serde(default)] - error: String, + pub error: String, #[serde(default)] - error_description: String, + pub error_description: String, #[serde(default)] - error_uri: String, + pub error_uri: String, } #[derive(Debug, Deserialize)] diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs new file mode 100644 index 000000000000..334c62e23d5c --- /dev/null +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; +use juniper::{GraphQLObject, ID}; +use juniper_axum::relay::NodeType; + +use super::Context; +use crate::schema::Result; + +#[derive(GraphQLObject, Debug)] +#[graphql(context = Context)] +pub struct GithubRepositoryProvider { + pub id: ID, + pub display_name: String, + pub application_id: String, +} + +impl NodeType for GithubRepositoryProvider { + type Cursor = String; + + fn cursor(&self) -> Self::Cursor { + self.id.to_string() + } + + fn connection_type_name() -> &'static str { + "GithubRepositoryProviderConnection" + } + + fn edge_type_name() -> &'static str { + "GithubRepositoryProviderEdge" + } +} + +#[async_trait] +pub trait GithubRepositoryProviderService: Send + Sync { + async fn get_github_repository_provider(&self, id: ID) -> Result; + async fn read_github_repository_provider_secret(&self, id: ID) -> Result; + async fn update_github_repository_provider_access_token( + &self, + id: ID, + access_token: String, + ) -> Result<()>; + + async fn list_github_repository_providers( + &self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> Result>; +} diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index 3168b09bd8a4..a6f658f39398 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -1,6 +1,7 @@ pub mod analytic; pub mod auth; pub mod email; +pub mod github_repository_provider; pub mod job; pub mod license; pub mod repository; @@ -36,13 +37,15 @@ use self::{ RequestInvitationInput, RequestPasswordResetEmailInput, UpdateOAuthCredentialInput, }, email::{EmailService, EmailSetting, EmailSettingInput}, + github_repository_provider::{GithubRepositoryProvider, GithubRepositoryProviderService}, job::JobStats, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, - repository::{FileEntrySearchResult, Repository, RepositoryService}, + repository::{Repository, RepositoryService}, setting::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, }; +use crate::schema::repository::FileEntrySearchResult; pub trait ServiceLocator: Send + Sync { fn auth(&self) -> Arc; @@ -55,6 +58,7 @@ pub trait ServiceLocator: Send + Sync { fn setting(&self) -> Arc; fn license(&self) -> Arc; fn analytic(&self) -> Arc; + fn github_repository_provider(&self) -> Arc; } pub struct Context { @@ -226,6 +230,30 @@ impl Query { .await } + async fn github_repository_providers( + ctx: &Context, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> FieldResult> { + check_admin(ctx).await?; + relay::query_async( + after, + before, + first, + last, + |after, before, first, last| async move { + Ok(ctx + .locator + .github_repository_provider() + .list_github_repository_providers(after, before, first, last) + .await?) + }, + ) + .await + } + async fn job_runs( ctx: &Context, ids: Option>, diff --git a/ee/tabby-webserver/src/service/dao.rs b/ee/tabby-webserver/src/service/dao.rs index 4260f4dc6cbe..402e973540d3 100644 --- a/ee/tabby-webserver/src/service/dao.rs +++ b/ee/tabby-webserver/src/service/dao.rs @@ -2,13 +2,14 @@ use anyhow::anyhow; use hash_ids::HashIds; use lazy_static::lazy_static; use tabby_db::{ - EmailSettingDAO, InvitationDAO, JobRunDAO, OAuthCredentialDAO, RepositoryDAO, ServerSettingDAO, - UserDAO, + EmailSettingDAO, GithubRepositoryProviderDAO, InvitationDAO, JobRunDAO, OAuthCredentialDAO, + RepositoryDAO, ServerSettingDAO, UserDAO, }; use crate::schema::{ auth::{self, OAuthCredential, OAuthProvider}, email::{AuthMethod, EmailSetting, Encryption}, + github_repository_provider::GithubRepositoryProvider, job, repository::Repository, setting::{NetworkSetting, SecuritySetting}, @@ -119,6 +120,16 @@ impl From for NetworkSetting { } } +impl From for GithubRepositoryProvider { + fn from(value: GithubRepositoryProviderDAO) -> Self { + Self { + display_name: value.display_name, + application_id: value.application_id, + id: value.id.as_id(), + } + } +} + lazy_static! { static ref HASHER: HashIds = HashIds::builder() .with_salt("tabby-id-serializer") diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs new file mode 100644 index 000000000000..5f4970b19443 --- /dev/null +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -0,0 +1,62 @@ +use async_trait::async_trait; +use juniper::ID; +use tabby_db::DbConn; + +use super::AsRowid; +use crate::{ + schema::{ + github_repository_provider::{GithubRepositoryProvider, GithubRepositoryProviderService}, + Result, + }, + service::graphql_pagination_to_filter, +}; + +struct GithubRepositoryProviderServiceImpl { + db: DbConn, +} + +pub fn new_github_repository_provider_service(db: DbConn) -> impl GithubRepositoryProviderService { + GithubRepositoryProviderServiceImpl { db } +} + +#[async_trait] +impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { + async fn get_github_repository_provider(&self, id: ID) -> Result { + let provider = self.db.get_github_provider(id.as_rowid()?).await?; + Ok(provider.into()) + } + + async fn read_github_repository_provider_secret(&self, id: ID) -> Result { + let provider = self.db.get_github_provider(id.as_rowid()?).await?; + Ok(provider.secret) + } + + async fn update_github_repository_provider_access_token( + &self, + id: ID, + access_token: String, + ) -> Result<()> { + self.db + .update_github_provider_access_token(id.as_rowid()?, access_token) + .await?; + Ok(()) + } + + async fn list_github_repository_providers( + &self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> Result> { + let (limit, skip_id, backwards) = graphql_pagination_to_filter(after, before, first, last)?; + let providers = self + .db + .list_github_repository_providers(limit, skip_id, backwards) + .await?; + Ok(providers + .into_iter() + .map(GithubRepositoryProvider::from) + .collect()) + } +} diff --git a/ee/tabby-webserver/src/service/mod.rs b/ee/tabby-webserver/src/service/mod.rs index b023f6fd5500..021d39e9fc9b 100644 --- a/ee/tabby-webserver/src/service/mod.rs +++ b/ee/tabby-webserver/src/service/mod.rs @@ -3,6 +3,7 @@ mod auth; mod dao; mod email; pub mod event_logger; +mod github_repository_provider; mod job; mod license; mod proxy; @@ -30,12 +31,14 @@ use tracing::{info, warn}; use self::{ analytic::new_analytic_service, auth::new_authentication_service, email::new_email_service, + github_repository_provider::new_github_repository_provider_service, license::new_license_service, }; use crate::schema::{ analytic::AnalyticService, auth::AuthenticationService, email::EmailService, + github_repository_provider::GithubRepositoryProviderService, job::JobService, license::{IsLicenseValid, LicenseService}, repository::RepositoryService, @@ -52,6 +55,7 @@ struct ServerContext { mail: Arc, auth: Arc, license: Arc, + github_repository_provider: Arc, logger: Arc, code: Arc, @@ -87,6 +91,9 @@ impl ServerContext { license.clone(), )), license, + github_repository_provider: Arc::new(new_github_repository_provider_service( + db_conn.clone(), + )), db_conn, logger, code, @@ -294,6 +301,12 @@ impl ServiceLocator for Arc { fn analytic(&self) -> Arc { new_analytic_service(self.db_conn.clone()) } + + fn github_repository_provider( + &self, + ) -> Arc { + self.github_repository_provider.clone() + } } pub async fn create_service_locator(