diff --git a/ee/tabby-db/migrations/0024_github-provided-repos.up.sql b/ee/tabby-db/migrations/0024_github-provided-repos.up.sql index 7ec9760211bd..dc11e6c1f8d0 100644 --- a/ee/tabby-db/migrations/0024_github-provided-repos.up.sql +++ b/ee/tabby-db/migrations/0024_github-provided-repos.up.sql @@ -5,7 +5,6 @@ CREATE TABLE github_provided_repositories( vendor_id TEXT NOT NULL, name TEXT NOT NULL, git_url TEXT NOT NULL, - active BOOLEAN NOT NULL DEFAULT TRUE, - FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id), - CONSTRAINT provided_repository_unique_name UNIQUE (name) + active BOOLEAN NOT NULL DEFAULT FALSE, + FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE ); diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index c333d74bcc97..09e420b504d3 100644 Binary files a/ee/tabby-db/schema.sqlite and b/ee/tabby-db/schema.sqlite differ diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index 148b45e3f2d3..3efa0bcd2eac 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -131,11 +131,16 @@ impl DbConn { pub async fn list_github_provided_repositories( &self, - provider_id: i64, + provider_ids: Vec, limit: Option, skip_id: Option, backwards: bool, ) -> Result> { + let provider_ids = provider_ids + .into_iter() + .map(|id| id.to_string()) + .collect::>() + .join(", "); let repos = query_paged_as!( GithubProvidedRepositoryDAO, "github_provided_repositories", @@ -150,7 +155,8 @@ impl DbConn { limit, skip_id, backwards, - Some(format!("github_repository_provider_id = {provider_id}")) + (!provider_ids.is_empty()) + .then(|| format!("github_repository_provider_id IN ({provider_ids})")) ) .fetch_all(&self.pool) .await?; diff --git a/ee/tabby-webserver/src/hub/mod.rs b/ee/tabby-webserver/src/hub/mod.rs index ea6e1814aacd..5e3bd9de2681 100644 --- a/ee/tabby-webserver/src/hub/mod.rs +++ b/ee/tabby-webserver/src/hub/mod.rs @@ -144,7 +144,10 @@ impl Hub for Arc { } } } + async fn list_repositories(self, _context: tarpc::context::Context) -> Vec { + let mut repositories = vec![]; + let result = self .ctx .repository() @@ -156,9 +159,78 @@ impl Hub for Arc { .map(|r| RepositoryConfig::new_named(r.name, r.git_url)) .collect() }); - result.unwrap_or_else(|e| { + repositories.extend(result.unwrap_or_else(|e| { warn!("Failed to fetch repositories: {e}"); vec![] - }) + })); + + let provider_service = self.ctx.github_repository_provider(); + let repository_providers = provider_service + .list_github_repository_providers(None, None, Some(1024), None) + .await + .unwrap_or_else(|e| { + warn!("Failed to fetch GitHub repository providers: {e}"); + vec![] + }); + + for provider in repository_providers { + let Ok(access_token) = provider_service + .read_github_repository_provider_access_token(provider.id.clone()) + .await + else { + continue; + }; + + let repos = match provider_service + .list_github_provided_repositories_by_provider( + vec![provider.id.clone()], + None, + None, + Some(1024), + None, + ) + .await + { + Ok(repos) => repos, + Err(e) => { + warn!( + "Failed to retrieve repositories provided by {name}: {e}", + name = provider.display_name + ); + continue; + } + }; + repositories.extend(repos.into_iter().filter(|repo| repo.active).map(|repo| { + RepositoryConfig::new_named( + repo.name, + format_authenticated_git_url(repo.git_url, &access_token), + ) + })) + } + + repositories + } +} + +fn format_authenticated_git_url(mut git_url: String, access_token: &str) -> String { + let split_pos = git_url.find("://").map(|i| i + 3).unwrap_or(0); + git_url.insert_str(split_pos, &format!("{access_token}@")); + git_url +} + +#[cfg(test)] +mod tests { + use crate::hub::format_authenticated_git_url; + + #[test] + fn test_format_authenticated_git_url() { + assert_eq!( + format_authenticated_git_url("https://github.com/TabbyML/tabby".into(), "token".into()), + "https://token@github.com/TabbyML/tabby" + ); + assert_eq!( + format_authenticated_git_url("github.com/TabbyML/tabby".into(), "token".into()), + "token@github.com/TabbyML/tabby" + ); } } diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs index 040c03e61221..a65dd3cb0098 100644 --- a/ee/tabby-webserver/src/schema/github_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -37,6 +37,7 @@ pub struct GithubProvidedRepository { pub github_repository_provider_id: ID, pub name: String, pub git_url: String, + pub active: bool, } impl NodeType for GithubProvidedRepository { @@ -65,6 +66,7 @@ pub trait GithubRepositoryProviderService: Send + Sync { ) -> Result; async fn get_github_repository_provider(&self, id: ID) -> Result; async fn read_github_repository_provider_secret(&self, id: ID) -> Result; + async fn read_github_repository_provider_access_token(&self, id: ID) -> Result; async fn update_github_repository_provider_access_token( &self, id: ID, @@ -81,7 +83,7 @@ pub trait GithubRepositoryProviderService: Send + Sync { async fn list_github_provided_repositories_by_provider( &self, - provider: ID, + provider: Vec, after: Option, before: Option, first: Option, diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index f28cb475df67..d4316ff70fad 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -256,9 +256,9 @@ impl Query { .await } - async fn github_repositories_by_provider( + async fn github_repositories( ctx: &Context, - github_repository_provider_id: ID, + provider_ids: Vec, after: Option, before: Option, first: Option, @@ -275,7 +275,7 @@ impl Query { .locator .github_repository_provider() .list_github_provided_repositories_by_provider( - github_repository_provider_id, + provider_ids, after, before, first, diff --git a/ee/tabby-webserver/src/service/dao.rs b/ee/tabby-webserver/src/service/dao.rs index 91bdbb305084..08542cf39d2b 100644 --- a/ee/tabby-webserver/src/service/dao.rs +++ b/ee/tabby-webserver/src/service/dao.rs @@ -138,6 +138,7 @@ impl From for GithubProvidedRepository { name: value.name, git_url: value.git_url, vendor_id: value.vendor_id, + active: value.active, } } } diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index 122ffec727a2..1c9bf110b3b7 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use async_trait::async_trait; use juniper::ID; use tabby_db::DbConn; @@ -46,6 +47,14 @@ impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { Ok(provider.secret) } + async fn read_github_repository_provider_access_token(&self, id: ID) -> Result { + let provider = self.db.get_github_provider(id.as_rowid()?).await?; + let Some(access_token) = provider.access_token else { + return Err(anyhow!("Provider has no access token").into()); + }; + Ok(access_token) + } + async fn update_github_repository_provider_access_token( &self, id: ID, @@ -77,16 +86,20 @@ impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { async fn list_github_provided_repositories_by_provider( &self, - provider: ID, + providers: Vec, after: Option, before: Option, first: Option, last: Option, ) -> Result> { + let providers = providers + .into_iter() + .map(|i| i.as_rowid()) + .collect::, _>>()?; let (limit, skip_id, backwards) = graphql_pagination_to_filter(after, before, last, first)?; let repos = self .db - .list_github_provided_repositories(provider.as_rowid()?, limit, skip_id, backwards) + .list_github_provided_repositories(providers, limit, skip_id, backwards) .await?; Ok(repos