From 23248a27102f0beb84e3bcefd4e8456bc1abf2b3 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 25 Apr 2024 21:32:22 -0700 Subject: [PATCH] refactor(webserver): support browsing github / gitlab repositories. (#1968) * refactor(webserver): support browsing github / gitlab repositories * update * simplify --- ee/tabby-db/src/github_repository_provider.rs | 14 ++++ ee/tabby-db/src/gitlab_repository_provider.rs | 14 ++++ ee/tabby-db/src/repositories.rs | 10 +-- ee/tabby-webserver/graphql/schema.graphql | 15 +++- ee/tabby-webserver/src/handler.rs | 3 +- ee/tabby-webserver/src/repositories/mod.rs | 24 +++--- .../src/repositories/resolve.rs | 44 +++------- .../src/schema/git_repository.rs | 12 +-- .../src/schema/github_repository_provider.rs | 4 +- .../src/schema/gitlab_repository_provider.rs | 4 +- ee/tabby-webserver/src/schema/mod.rs | 23 +++--- ee/tabby-webserver/src/schema/repository.rs | 81 +++++++++++++++++-- .../src/service/git_repository.rs | 45 +++++------ .../src/service/github_repository_provider.rs | 22 +++++ .../src/service/gitlab_repository_provider.rs | 22 +++++ ee/tabby-webserver/src/service/repository.rs | 56 +++++++++++-- 16 files changed, 273 insertions(+), 120 deletions(-) diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index a984204eb787..f6c666783a0c 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -166,6 +166,20 @@ impl DbConn { Ok(()) } + pub async fn get_github_provided_repository( + &self, + id: i64, + ) -> Result { + let repo = query_as!( + GithubProvidedRepositoryDAO, + "SELECT id, vendor_id, name, git_url, active, github_repository_provider_id FROM github_provided_repositories WHERE id = ?", + id + ) + .fetch_one(&self.pool) + .await?; + Ok(repo) + } + pub async fn list_github_provided_repositories( &self, provider_ids: Vec, diff --git a/ee/tabby-db/src/gitlab_repository_provider.rs b/ee/tabby-db/src/gitlab_repository_provider.rs index a894ae55f006..509498333185 100644 --- a/ee/tabby-db/src/gitlab_repository_provider.rs +++ b/ee/tabby-db/src/gitlab_repository_provider.rs @@ -166,6 +166,20 @@ impl DbConn { Ok(()) } + pub async fn get_gitlab_provided_repository( + &self, + id: i64, + ) -> Result { + let repo = query_as!( + GitlabProvidedRepositoryDAO, + "SELECT id, vendor_id, name, git_url, active, gitlab_repository_provider_id FROM gitlab_provided_repositories WHERE id = ?", + id + ) + .fetch_one(&self.pool) + .await?; + Ok(repo) + } + pub async fn list_gitlab_provided_repositories( &self, provider_ids: Vec, diff --git a/ee/tabby-db/src/repositories.rs b/ee/tabby-db/src/repositories.rs index 6c75d3b7d590..0eca8f965d7c 100644 --- a/ee/tabby-db/src/repositories.rs +++ b/ee/tabby-db/src/repositories.rs @@ -67,11 +67,11 @@ impl DbConn { } } - pub async fn get_repository_by_name(&self, name: &str) -> Result { + pub async fn get_repository(&self, id: i64) -> Result { let repository = sqlx::query_as!( RepositoryDAO, - "SELECT id as 'id!: i64', name, git_url FROM repositories WHERE name = ?", - name + "SELECT id as 'id!: i64', name, git_url FROM repositories WHERE id = ?", + id ) .fetch_one(&self.pool) .await?; @@ -112,9 +112,5 @@ mod tests { .unwrap()[0]; assert_eq!(repository.git_url, "testurl2"); assert_eq!(repository.name, "test2"); - assert_eq!( - conn.get_repository_by_name("test2").await.unwrap().git_url, - repository.git_url - ); } } diff --git a/ee/tabby-webserver/graphql/schema.graphql b/ee/tabby-webserver/graphql/schema.graphql index dd35fc094ae0..0f940e9425bd 100644 --- a/ee/tabby-webserver/graphql/schema.graphql +++ b/ee/tabby-webserver/graphql/schema.graphql @@ -1,3 +1,9 @@ +type Repository { + id: ID! + name: String! + kind: RepositoryKind! +} + input UpdateRepositoryProviderInput { id: ID! displayName: String! @@ -283,7 +289,7 @@ type Query { networkSetting: NetworkSetting! securitySetting: SecuritySetting! gitRepositories(after: String, before: String, first: Int, last: Int): RepositoryConnection! - repositorySearch(repositoryName: String!, pattern: String!): [FileEntrySearchResult!]! + repositorySearch(kind: RepositoryKind!, id: ID!, pattern: String!): [FileEntrySearchResult!]! oauthCredential(provider: OAuthProvider!): OAuthCredential oauthCallbackUrl(provider: OAuthProvider!): String! serverInfo: ServerInfo! @@ -293,6 +299,7 @@ type Query { dailyStats(start: DateTimeUtc!, end: DateTimeUtc!, users: [ID!], languages: [Language!]): [CompletionStats!]! userEvents(after: String, before: String, first: Int, last: Int, users: [ID!], start: DateTimeUtc!, end: DateTimeUtc!): UserEventConnection! diskUsageStats: DiskUsageStats! + repositoryList: [Repository!]! } input NetworkSettingInput { @@ -336,6 +343,12 @@ type RepositoryConnection { pageInfo: PageInfo! } +enum RepositoryKind { + GIT + GITHUB + GITLAB +} + input EmailSettingInput { smtpUsername: String! fromAddress: String! diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 98d536682a9d..5e9e9a687e04 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -118,8 +118,7 @@ impl WebserverHandle { ) .nest( "/repositories", - // FIXME(boxbeam): repositories routes should support both git / github repositories, but currently only git repositories are supported. - repositories::routes(ctx.repository().git(), ctx.auth()), + repositories::routes(ctx.repository(), ctx.auth()), ) .route( "/avatar/:id", diff --git a/ee/tabby-webserver/src/repositories/mod.rs b/ee/tabby-webserver/src/repositories/mod.rs index 48cd91f57957..d4b43df4aa3c 100644 --- a/ee/tabby-webserver/src/repositories/mod.rs +++ b/ee/tabby-webserver/src/repositories/mod.rs @@ -16,20 +16,18 @@ use self::resolve::ResolveState; use crate::{ handler::require_login_middleware, repositories::resolve::ResolveParams, - schema::{auth::AuthenticationService, git_repository::GitRepositoryService}, + schema::{auth::AuthenticationService, repository::RepositoryService}, }; pub fn routes( - repository: Arc, + repository: Arc, auth: Arc, ) -> Router { Router::new() - .route("/resolve", routing::get(resolve)) - .route("/resolve/", routing::get(resolve)) - .route("/:name/resolve/.git/", routing::get(not_found)) - .route("/:name/resolve/.git/*path", routing::get(not_found)) - .route("/:name/resolve/", routing::get(resolve_path)) - .route("/:name/resolve/*path", routing::get(resolve_path)) + .route("/:kind/:id/resolve/.git/", routing::get(not_found)) + .route("/:kind/:id/resolve/.git/*path", routing::get(not_found)) + .route("/:kind/:id/resolve/", routing::get(resolve_path)) + .route("/:kind/:id/resolve/*path", routing::get(resolve_path)) .with_state(Arc::new(ResolveState::new(repository))) .fallback(not_found) .layer(from_fn_with_state(auth, require_login_middleware)) @@ -44,11 +42,11 @@ async fn resolve_path( State(rs): State>, Path(repo): Path, ) -> Result { - let Some(conf) = rs.find_repository(repo.name_str()).await else { + let relpath = repo.os_path(); + let Some(root) = rs.find_repository(&repo).await else { return Err(StatusCode::NOT_FOUND); }; - let root = conf.dir(); - let full_path = root.join(repo.os_path()); + let full_path = root.join(relpath); let is_dir = tokio::fs::metadata(full_path.clone()) .await .map(|m| m.is_dir()) @@ -72,7 +70,3 @@ async fn resolve_path( } } } - -async fn resolve(State(rs): State>) -> Result { - 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 dcd715d72e22..e95b9c814d04 100644 --- a/ee/tabby-webserver/src/repositories/resolve.rs +++ b/ee/tabby-webserver/src/repositories/resolve.rs @@ -8,26 +8,23 @@ use axum::{ Json, }; use hyper::Body; +use juniper::ID; use serde::{Deserialize, Serialize}; -use tabby_common::config::RepositoryConfig; use tower::ServiceExt; use tower_http::services::ServeDir; -use crate::schema::git_repository::GitRepositoryService; +use crate::schema::repository::{RepositoryKind, RepositoryService}; const DIRECTORY_MIME_TYPE: &str = "application/vnd.directory+json"; #[derive(Deserialize, Debug)] pub struct ResolveParams { - name: String, + pub kind: RepositoryKind, + pub id: ID, path: Option, } impl ResolveParams { - pub fn name_str(&self) -> &str { - self.name.as_str() - } - pub fn path_str(&self) -> &str { self.path.as_deref().unwrap_or("") } @@ -60,11 +57,11 @@ struct DirEntry { } pub(super) struct ResolveState { - service: Arc, + service: Arc, } impl ResolveState { - pub fn new(service: Arc) -> Self { + pub fn new(service: Arc) -> Self { Self { service } } @@ -128,27 +125,12 @@ impl ResolveState { Ok(resp.map(boxed)) } - pub async fn resolve_all(&self) -> Result { - let repositories = self.service.list(None, None, None, None).await?; - - let entries = repositories - .into_iter() - .map(|repo| DirEntry { - kind: DirEntryKind::Dir, - basename: repo.name.clone(), - }) - .collect(); - - let body = Json(ListDir { entries }).into_response(); - let resp = Response::builder() - .header(header::CONTENT_TYPE, DIRECTORY_MIME_TYPE) - .body(body.into_body())?; - - Ok(resp) - } - - pub async fn find_repository(&self, name: &str) -> Option { - let repository = self.service.get_by_name(name).await.ok()?; - Some(RepositoryConfig::new(repository.git_url.clone())) + pub async fn find_repository(&self, params: &ResolveParams) -> Option { + let repository = self + .service + .resolve_repository(¶ms.kind, ¶ms.id) + .await + .ok()?; + Some(repository.dir) } } diff --git a/ee/tabby-webserver/src/schema/git_repository.rs b/ee/tabby-webserver/src/schema/git_repository.rs index 9f41e2732029..18a27c9a9549 100644 --- a/ee/tabby-webserver/src/schema/git_repository.rs +++ b/ee/tabby-webserver/src/schema/git_repository.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use juniper::{GraphQLObject, ID}; use validator::Validate; -use super::{repository::FileEntrySearchResult, Context, Result}; +use super::{repository::RepositoryProvider, Context, Result}; use crate::juniper::relay::NodeType; #[derive(Validate)] @@ -42,7 +42,7 @@ impl NodeType for GitRepository { } #[async_trait] -pub trait GitRepositoryService: Send + Sync { +pub trait GitRepositoryService: Send + Sync + RepositoryProvider { async fn list( &self, after: Option, @@ -52,14 +52,6 @@ pub trait GitRepositoryService: Send + Sync { ) -> Result>; async fn create(&self, name: String, git_url: String) -> Result; - async fn get_by_name(&self, name: &str) -> Result; async fn delete(&self, id: &ID) -> Result; async fn update(&self, id: &ID, name: String, git_url: String) -> Result; - - async fn search_files( - &self, - name: &str, - pattern: &str, - top_n: usize, - ) -> Result>; } diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs index 83cabf889321..fdf4ebaf319e 100644 --- a/ee/tabby-webserver/src/schema/github_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use juniper::{GraphQLObject, ID}; -use super::Context; +use super::{repository::RepositoryProvider, Context}; use crate::{juniper::relay::NodeType, schema::Result}; #[derive(GraphQLObject, Debug, PartialEq)] @@ -61,7 +61,7 @@ impl NodeType for GithubProvidedRepository { } #[async_trait] -pub trait GithubRepositoryProviderService: Send + Sync { +pub trait GithubRepositoryProviderService: Send + Sync + RepositoryProvider { async fn create_github_repository_provider( &self, display_name: String, diff --git a/ee/tabby-webserver/src/schema/gitlab_repository_provider.rs b/ee/tabby-webserver/src/schema/gitlab_repository_provider.rs index c6bde56115e3..71316f44aa6f 100644 --- a/ee/tabby-webserver/src/schema/gitlab_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/gitlab_repository_provider.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use juniper::{GraphQLObject, ID}; -use super::Context; +use super::{repository::RepositoryProvider, Context}; use crate::{juniper::relay::NodeType, schema::Result}; #[derive(GraphQLObject, Debug, PartialEq)] @@ -61,7 +61,7 @@ impl NodeType for GitlabProvidedRepository { } #[async_trait] -pub trait GitlabRepositoryProviderService: Send + Sync { +pub trait GitlabRepositoryProviderService: Send + Sync + RepositoryProvider { async fn create_gitlab_repository_provider( &self, display_name: String, diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index 3d5ecfbd28f2..45caeeca312c 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -40,22 +40,19 @@ use self::{ email::{EmailService, EmailSetting, EmailSettingInput}, git_repository::GitRepository, github_repository_provider::{GithubProvidedRepository, GithubRepositoryProvider}, + gitlab_repository_provider::{GitlabProvidedRepository, GitlabRepositoryProvider}, job::JobStats, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, - repository::RepositoryService, + repository::{FileEntrySearchResult, Repository, RepositoryKind, RepositoryService}, setting::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, + types::{CreateRepositoryProviderInput, UpdateRepositoryProviderInput}, user_event::{UserEvent, UserEventService}, }; use crate::{ axum::FromAuth, juniper::relay::{self, Connection}, - schema::{ - gitlab_repository_provider::{GitlabProvidedRepository, GitlabRepositoryProvider}, - repository::FileEntrySearchResult, - types::{CreateRepositoryProviderInput, UpdateRepositoryProviderInput}, - }, }; pub trait ServiceLocator: Send + Sync { @@ -421,14 +418,14 @@ impl Query { async fn repository_search( ctx: &Context, - repository_name: String, + kind: RepositoryKind, + id: ID, pattern: String, ) -> Result> { check_claims(ctx)?; ctx.locator .repository() - .git() - .search_files(&repository_name, &pattern, 40) + .search_files(&kind, &id, &pattern, 40) .await } @@ -526,8 +523,12 @@ impl Query { async fn disk_usage_stats(ctx: &Context) -> Result { check_admin(ctx).await?; - let storage_stats = ctx.locator.analytic().disk_usage_stats().await?; - Ok(storage_stats) + ctx.locator.analytic().disk_usage_stats().await + } + + async fn repository_list(ctx: &Context) -> Result> { + check_admin(ctx).await?; + ctx.locator.repository().repository_list().await } } diff --git a/ee/tabby-webserver/src/schema/repository.rs b/ee/tabby-webserver/src/schema/repository.rs index 1d16993e744e..0a466c932341 100644 --- a/ee/tabby-webserver/src/schema/repository.rs +++ b/ee/tabby-webserver/src/schema/repository.rs @@ -1,14 +1,16 @@ -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use async_trait::async_trait; -use juniper::GraphQLObject; -use tabby_common::config::RepositoryAccess; +use juniper::{GraphQLEnum, GraphQLObject, ID}; +use serde::Deserialize; +use tabby_common::config::{RepositoryAccess, RepositoryConfig}; use tabby_search::FileSearch; use super::{ - git_repository::GitRepositoryService, - github_repository_provider::GithubRepositoryProviderService, - gitlab_repository_provider::GitlabRepositoryProviderService, + git_repository::{GitRepository, GitRepositoryService}, + github_repository_provider::{GithubProvidedRepository, GithubRepositoryProviderService}, + gitlab_repository_provider::{GitlabProvidedRepository, GitlabRepositoryProviderService}, + Result, }; #[derive(GraphQLObject, Debug)] @@ -30,8 +32,75 @@ impl From for FileEntrySearchResult { } } +#[derive(GraphQLEnum, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum RepositoryKind { + Git, + Github, + Gitlab, +} + +#[derive(GraphQLObject)] +pub struct Repository { + pub id: ID, + pub name: String, + pub kind: RepositoryKind, + + #[graphql(skip)] + pub dir: PathBuf, +} + +impl From for Repository { + fn from(value: GitRepository) -> Self { + Self { + id: value.id, + name: value.name, + kind: RepositoryKind::Git, + dir: RepositoryConfig::new(value.git_url).dir(), + } + } +} + +impl From for Repository { + fn from(value: GithubProvidedRepository) -> Self { + Self { + id: value.id, + name: value.name, + kind: RepositoryKind::Github, + dir: RepositoryConfig::new(value.git_url).dir(), + } + } +} + +impl From for Repository { + fn from(value: GitlabProvidedRepository) -> Self { + Self { + id: value.id, + name: value.name, + kind: RepositoryKind::Gitlab, + dir: RepositoryConfig::new(value.git_url).dir(), + } + } +} + +#[async_trait] +pub trait RepositoryProvider { + async fn repository_list(&self) -> Result>; + async fn get_repository(&self, id: &ID) -> Result; +} + #[async_trait] pub trait RepositoryService: Send + Sync + RepositoryAccess { + async fn repository_list(&self) -> Result>; + async fn resolve_repository(&self, kind: &RepositoryKind, id: &ID) -> Result; + async fn search_files( + &self, + kind: &RepositoryKind, + id: &ID, + pattern: &str, + top_n: usize, + ) -> Result>; + fn git(&self) -> Arc; fn github(&self) -> Arc; fn gitlab(&self) -> Arc; diff --git a/ee/tabby-webserver/src/service/git_repository.rs b/ee/tabby-webserver/src/service/git_repository.rs index 4cac5c789b36..72185f04bc7e 100644 --- a/ee/tabby-webserver/src/service/git_repository.rs +++ b/ee/tabby-webserver/src/service/git_repository.rs @@ -1,12 +1,11 @@ use async_trait::async_trait; use juniper::ID; -use tabby_common::config::RepositoryConfig; use tabby_db::DbConn; use super::{graphql_pagination_to_filter, AsID, AsRowid}; use crate::schema::{ git_repository::{GitRepository, GitRepositoryService}, - repository::FileEntrySearchResult, + repository::{Repository, RepositoryProvider}, Result, }; @@ -30,10 +29,6 @@ impl GitRepositoryService for DbConn { Ok(self.create_repository(name, git_url).await?.as_id()) } - async fn get_by_name(&self, name: &str) -> Result { - Ok(self.get_repository_by_name(name).await?.into()) - } - async fn delete(&self, id: &ID) -> Result { Ok(self.delete_repository(id.as_rowid()?).await?) } @@ -43,29 +38,25 @@ impl GitRepositoryService for DbConn { .await?; Ok(true) } +} - async fn search_files( - &self, - name: &str, - pattern: &str, - top_n: usize, - ) -> Result> { - if pattern.trim().is_empty() { - return Ok(vec![]); - } - let git_url = self.get_repository_by_name(name).await?.git_url; - let config = RepositoryConfig::new(git_url); - - let pattern = pattern.to_owned(); - let matching = tokio::task::spawn_blocking(move || async move { - tabby_search::FileSearch::search(&config.dir(), &pattern, top_n) - .map(|x| x.into_iter().map(|f| f.into()).collect()) - }) - .await - .map_err(anyhow::Error::from)? - .await?; +#[async_trait] +impl RepositoryProvider for DbConn { + async fn repository_list(&self) -> Result> { + Ok(self + .list(None, None, None, None) + .await? + .into_iter() + .map(|x| x.into()) + .collect()) + } - Ok(matching) + async fn get_repository(&self, id: &ID) -> Result { + let git_repo: GitRepository = (self as &DbConn) + .get_repository(id.as_rowid()?) + .await? + .into(); + Ok(git_repo.into()) } } diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index 425b85f46b6b..544a9e8c51b6 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -12,6 +12,7 @@ use crate::{ github_repository_provider::{ GithubProvidedRepository, GithubRepositoryProvider, GithubRepositoryProviderService, }, + repository::{Repository, RepositoryProvider}, Result, }, service::graphql_pagination_to_filter, @@ -184,6 +185,27 @@ impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { } } +#[async_trait] +impl RepositoryProvider for GithubRepositoryProviderServiceImpl { + async fn repository_list(&self) -> Result> { + Ok(self + .list_github_provided_repositories_by_provider(vec![], None, None, None, None) + .await? + .into_iter() + .map(|x| x.into()) + .collect()) + } + + async fn get_repository(&self, id: &ID) -> Result { + let repo: GithubProvidedRepository = self + .db + .get_github_provided_repository(id.as_rowid()?) + .await? + .into(); + Ok(repo.into()) + } +} + fn deduplicate_github_repositories(repositories: &mut Vec) { let mut vendor_ids = HashSet::new(); repositories.retain(|repo| vendor_ids.insert(repo.vendor_id.clone())); diff --git a/ee/tabby-webserver/src/service/gitlab_repository_provider.rs b/ee/tabby-webserver/src/service/gitlab_repository_provider.rs index a9b5b0c78705..505603ee6b1f 100644 --- a/ee/tabby-webserver/src/service/gitlab_repository_provider.rs +++ b/ee/tabby-webserver/src/service/gitlab_repository_provider.rs @@ -12,6 +12,7 @@ use crate::{ gitlab_repository_provider::{ GitlabProvidedRepository, GitlabRepositoryProvider, GitlabRepositoryProviderService, }, + repository::{Repository, RepositoryProvider}, Result, }, service::graphql_pagination_to_filter, @@ -192,6 +193,27 @@ fn deduplicate_gitlab_repositories(repositories: &mut Vec Result> { + Ok(self + .list_gitlab_provided_repositories_by_provider(vec![], None, None, None, None) + .await? + .into_iter() + .map(|x| x.into()) + .collect()) + } + + async fn get_repository(&self, id: &ID) -> Result { + let repo: GitlabProvidedRepository = self + .db + .get_gitlab_provided_repository(id.as_rowid()?) + .await? + .into(); + Ok(repo.into()) + } +} + #[cfg(test)] mod tests { use chrono::Duration; diff --git a/ee/tabby-webserver/src/service/repository.rs b/ee/tabby-webserver/src/service/repository.rs index 00dbf2def023..e8b643240626 100644 --- a/ee/tabby-webserver/src/service/repository.rs +++ b/ee/tabby-webserver/src/service/repository.rs @@ -1,14 +1,16 @@ use std::sync::Arc; use async_trait::async_trait; +use juniper::ID; use tabby_common::config::{RepositoryAccess, RepositoryConfig}; use tabby_db::DbConn; -use super::{github_repository_provider, gitlab_repository_provider}; +use super::{github_repository_provider, gitlab_repository_provider, Result}; use crate::schema::{ git_repository::GitRepositoryService, github_repository_provider::GithubRepositoryProviderService, - gitlab_repository_provider::GitlabRepositoryProviderService, repository::RepositoryService, + gitlab_repository_provider::GitlabRepositoryProviderService, + repository::{FileEntrySearchResult, Repository, RepositoryKind, RepositoryService}, }; struct RepositoryServiceImpl { @@ -58,11 +60,8 @@ impl RepositoryAccess for RepositoryServiceImpl { } } +#[async_trait] impl RepositoryService for RepositoryServiceImpl { - fn access(self: Arc) -> Arc { - self.clone() - } - fn git(&self) -> Arc { self.git.clone() } @@ -74,6 +73,51 @@ impl RepositoryService for RepositoryServiceImpl { fn gitlab(&self) -> Arc { self.gitlab.clone() } + + fn access(self: Arc) -> Arc { + self.clone() + } + + async fn repository_list(&self) -> Result> { + let mut all = vec![]; + all.append(&mut self.git().repository_list().await?); + all.append(&mut self.github().repository_list().await?); + all.append(&mut self.gitlab().repository_list().await?); + + Ok(all) + } + + async fn resolve_repository(&self, kind: &RepositoryKind, id: &ID) -> Result { + match kind { + RepositoryKind::Git => self.git().get_repository(id).await, + RepositoryKind::Github => self.github().get_repository(id).await, + RepositoryKind::Gitlab => self.gitlab().get_repository(id).await, + } + } + + async fn search_files( + &self, + kind: &RepositoryKind, + id: &ID, + pattern: &str, + top_n: usize, + ) -> Result> { + if pattern.trim().is_empty() { + return Ok(vec![]); + } + let dir = self.resolve_repository(kind, id).await?.dir; + + let pattern = pattern.to_owned(); + let matching = tokio::task::spawn_blocking(move || async move { + tabby_search::FileSearch::search(&dir, &pattern, top_n) + .map(|x| x.into_iter().map(|f| f.into()).collect()) + }) + .await + .map_err(anyhow::Error::from)? + .await?; + + Ok(matching) + } } #[cfg(test)]