From 0814898b3e1eadf02c4609bfac19a18cd702d51e Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Sat, 20 Apr 2024 14:43:54 -0700 Subject: [PATCH] refactor(webserver): extract RepositoryService (#1897) * refactor(webserver): extract RepositoryService * add unit test * update * update naming * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- ee/tabby-webserver/src/handler.rs | 76 ++++------------- .../src/schema/git_repository.rs | 23 +---- ee/tabby-webserver/src/schema/mod.rs | 36 +++++--- ee/tabby-webserver/src/schema/repository.rs | 36 ++++++++ .../src/service/git_repository.rs | 3 +- .../src/service/github_repository_provider.rs | 8 +- ee/tabby-webserver/src/service/mod.rs | 30 ++----- ee/tabby-webserver/src/service/repository.rs | 83 +++++++++++++++++++ 8 files changed, 175 insertions(+), 120 deletions(-) create mode 100644 ee/tabby-webserver/src/schema/repository.rs create mode 100644 ee/tabby-webserver/src/service/repository.rs diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 148d0bcf064e..c9c4fd09a267 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use async_trait::async_trait; use axum::{ extract::{Path, State}, http::Request, @@ -17,7 +16,7 @@ use tabby_common::{ event::{ComposedLogger, EventLogger}, server_setting::ServerSetting, }, - config::{RepositoryAccess, RepositoryConfig}, + config::RepositoryAccess, }; use tabby_db::DbConn; use tracing::{error, warn}; @@ -30,51 +29,17 @@ use crate::{ path::db_file, repositories, schema::{ - auth::AuthenticationService, create_schema, git_repository::GitRepositoryService, - github_repository_provider::GithubRepositoryProviderService, Schema, ServiceLocator, - }, - service::{ - create_service_locator, event_logger::create_event_logger, - new_github_repository_provider_service, + auth::AuthenticationService, create_schema, repository::RepositoryService, Schema, + ServiceLocator, }, + service::{create_service_locator, event_logger::create_event_logger, repository}, ui, }; -struct RepositoryAccessImpl { - git_repository_service: Arc, - github_repository_service: Arc, -} - -#[async_trait] -impl RepositoryAccess for RepositoryAccessImpl { - async fn list_repositories(&self) -> anyhow::Result> { - let mut repos: Vec = self - .git_repository_service - .list(None, None, None, None) - .await? - .into_iter() - .map(|repo| RepositoryConfig::new(repo.git_url)) - .collect(); - - repos.extend( - self.github_repository_service - .list_provided_git_urls() - .await - .unwrap_or_default() - .into_iter() - .map(RepositoryConfig::new), - ); - - Ok(repos) - } -} - pub struct WebserverHandle { db: DbConn, logger: Arc, - git_repository_service: Arc, - github_repository_service: Arc, - repository_access: Arc, + repository: Arc, } impl WebserverHandle { @@ -82,21 +47,15 @@ impl WebserverHandle { let db = DbConn::new(db_file().as_path()) .await .expect("Must be able to initialize db"); - let git_repository_service = Arc::new(db.clone()); - let github_repository_service = - Arc::new(new_github_repository_provider_service(db.clone())); + + let repository = repository::create(db.clone()); let logger2 = create_event_logger(db.clone()); let logger = Arc::new(ComposedLogger::new(logger1, logger2)); WebserverHandle { db, logger, - git_repository_service: git_repository_service.clone(), - github_repository_service: github_repository_service.clone(), - repository_access: Arc::new(RepositoryAccessImpl { - git_repository_service, - github_repository_service, - }), + repository, } } @@ -105,7 +64,7 @@ impl WebserverHandle { } pub fn repository_access(&self) -> Arc { - self.repository_access.clone() + self.repository.clone().access() } pub async fn attach_webserver( @@ -119,8 +78,7 @@ impl WebserverHandle { let ctx = create_service_locator( self.logger(), code, - self.git_repository_service.clone(), - self.github_repository_service.clone(), + self.repository.clone(), self.db.clone(), is_chat_enabled, ) @@ -147,20 +105,18 @@ impl WebserverHandle { .layer(Extension(schema)) .route( "/hub", - routing::get(hub::ws_handler) - .with_state(HubState::new(ctx.clone(), self.repository_access.clone()).into()), + routing::get(hub::ws_handler).with_state( + HubState::new(ctx.clone(), self.repository_access().clone()).into(), + ), ) .nest( "/repositories", - repositories::routes(ctx.repository(), ctx.auth()), + // FIXME(boxbeam): repositories routes should support both git / github repositories, but currently only git repositories are supported. + repositories::routes(ctx.repository().git(), ctx.auth()), ) .nest( "/integrations/github", - integrations::github::routes( - ctx.auth(), - ctx.setting(), - ctx.github_repository_provider(), - ), + integrations::github::routes(ctx.auth(), ctx.setting(), ctx.repository().github()), ) .route( "/avatar/:id", diff --git a/ee/tabby-webserver/src/schema/git_repository.rs b/ee/tabby-webserver/src/schema/git_repository.rs index fb4cf04f8441..b70ddb8e5905 100644 --- a/ee/tabby-webserver/src/schema/git_repository.rs +++ b/ee/tabby-webserver/src/schema/git_repository.rs @@ -4,11 +4,11 @@ use lazy_static::lazy_static; use regex::Regex; use validator::Validate; -use super::{Context, Result}; +use super::{repository::FileEntrySearchResult, Context, Result}; use crate::juniper::relay::NodeType; lazy_static! { - static ref REPOSITORY_NAME_REGEX: Regex = Regex::new("").unwrap(); + static ref REPOSITORY_NAME_REGEX: Regex = Regex::new("^[a-zA-Z][\\w.-]+$").unwrap(); } #[derive(Validate)] @@ -31,25 +31,6 @@ pub struct GitRepository { pub git_url: String, } -#[derive(GraphQLObject, Debug)] -pub struct FileEntrySearchResult { - pub r#type: String, - pub path: String, - - /// matched indices for fuzzy search query. - pub indices: Vec, -} - -impl FileEntrySearchResult { - pub fn new(r#type: String, path: String, indices: Vec) -> Self { - Self { - r#type, - path, - indices: indices.into_iter().map(|i| i as i32).collect(), - } - } -} - impl NodeType for GitRepository { type Cursor = String; diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index d00dcd1005a1..aeca2303a746 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -5,6 +5,7 @@ pub mod git_repository; pub mod github_repository_provider; pub mod job; pub mod license; +pub mod repository; pub mod setting; pub mod worker; @@ -40,6 +41,7 @@ use self::{ }, job::JobStats, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, + repository::RepositoryService, setting::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, @@ -47,7 +49,7 @@ use self::{ use crate::{ axum::FromAuth, juniper::relay::{self, Connection}, - schema::git_repository::FileEntrySearchResult, + schema::repository::FileEntrySearchResult, }; pub trait ServiceLocator: Send + Sync { @@ -56,12 +58,11 @@ pub trait ServiceLocator: Send + Sync { fn code(&self) -> Arc; fn logger(&self) -> Arc; fn job(&self) -> Arc; - fn repository(&self) -> Arc; + fn repository(&self) -> Arc; fn email(&self) -> Arc; fn setting(&self) -> Arc; fn license(&self) -> Arc; fn analytic(&self) -> Arc; - fn github_repository_provider(&self) -> Arc; } pub struct Context { @@ -234,7 +235,8 @@ impl Query { last, |after, before, first, last| async move { ctx.locator - .github_repository_provider() + .repository() + .github() .list_github_repository_providers( ids.unwrap_or_default(), after, @@ -264,7 +266,8 @@ impl Query { last, |after, before, first, last| async move { ctx.locator - .github_repository_provider() + .repository() + .github() .list_github_provided_repositories_by_provider( provider_ids, after, @@ -339,6 +342,7 @@ impl Query { |after, before, first, last| async move { ctx.locator .repository() + .git() .list(after, before, first, last) .await }, @@ -354,6 +358,7 @@ impl Query { check_claims(ctx)?; ctx.locator .repository() + .git() .search_files(&repository_name, &pattern, 40) .await } @@ -594,13 +599,14 @@ impl Mutation { input.validate()?; ctx.locator .repository() + .git() .create(input.name, input.git_url) .await } async fn delete_git_repository(ctx: &Context, id: ID) -> Result { check_admin(ctx).await?; - ctx.locator.repository().delete(&id).await + ctx.locator.repository().git().delete(&id).await } async fn update_git_repository( @@ -610,7 +616,11 @@ impl Mutation { git_url: String, ) -> Result { check_admin(ctx).await?; - ctx.locator.repository().update(&id, name, git_url).await + ctx.locator + .repository() + .git() + .update(&id, name, git_url) + .await } async fn delete_invitation(ctx: &Context, id: ID) -> Result { @@ -682,7 +692,8 @@ impl Mutation { check_admin(ctx).await?; input.validate()?; ctx.locator - .github_repository_provider() + .repository() + .github() .create_github_repository_provider( input.display_name, input.application_id, @@ -695,7 +706,8 @@ impl Mutation { async fn delete_github_repository_provider(ctx: &Context, id: ID) -> Result { check_admin(ctx).await?; ctx.locator - .github_repository_provider() + .repository() + .github() .delete_github_repository_provider(id) .await?; Ok(true) @@ -708,7 +720,8 @@ impl Mutation { check_admin(ctx).await?; input.validate()?; ctx.locator - .github_repository_provider() + .repository() + .github() .update_github_repository_provider( input.id, input.display_name, @@ -725,7 +738,8 @@ impl Mutation { active: bool, ) -> Result { ctx.locator - .github_repository_provider() + .repository() + .github() .update_github_provided_repository_active(id, active) .await?; Ok(true) diff --git a/ee/tabby-webserver/src/schema/repository.rs b/ee/tabby-webserver/src/schema/repository.rs new file mode 100644 index 000000000000..71b8a2626e66 --- /dev/null +++ b/ee/tabby-webserver/src/schema/repository.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use juniper::GraphQLObject; +use tabby_common::config::RepositoryAccess; + +use super::{ + git_repository::GitRepositoryService, + github_repository_provider::GithubRepositoryProviderService, +}; + +#[derive(GraphQLObject, Debug)] +pub struct FileEntrySearchResult { + pub r#type: String, + pub path: String, + + /// matched indices for fuzzy search query. + pub indices: Vec, +} + +impl FileEntrySearchResult { + pub fn new(r#type: String, path: String, indices: Vec) -> Self { + Self { + r#type, + path, + indices: indices.into_iter().map(|i| i as i32).collect(), + } + } +} + +#[async_trait] +pub trait RepositoryService: Send + Sync + RepositoryAccess { + fn git(&self) -> Arc; + fn github(&self) -> Arc; + fn access(self: Arc) -> Arc; +} diff --git a/ee/tabby-webserver/src/service/git_repository.rs b/ee/tabby-webserver/src/service/git_repository.rs index 50ecacdbe529..fb6e9bfcdf28 100644 --- a/ee/tabby-webserver/src/service/git_repository.rs +++ b/ee/tabby-webserver/src/service/git_repository.rs @@ -8,7 +8,8 @@ use tabby_db::DbConn; use super::{graphql_pagination_to_filter, AsID, AsRowid}; use crate::schema::{ - git_repository::{FileEntrySearchResult, GitRepository, GitRepositoryService}, + git_repository::{GitRepository, GitRepositoryService}, + repository::FileEntrySearchResult, Result, }; diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index 7bcdc56a1fbf..8f5a3471d8ac 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -20,7 +20,7 @@ struct GithubRepositoryProviderServiceImpl { db: DbConn, } -pub fn new_github_repository_provider_service(db: DbConn) -> impl GithubRepositoryProviderService { +pub fn create(db: DbConn) -> impl GithubRepositoryProviderService { GithubRepositoryProviderServiceImpl { db } } @@ -166,7 +166,7 @@ mod tests { #[tokio::test] async fn test_github_provided_repositories() { let db = DbConn::new_in_memory().await.unwrap(); - let service = new_github_repository_provider_service(db.clone()); + let service = create(db.clone()); let provider_id1 = db .create_github_provider( @@ -252,7 +252,7 @@ mod tests { #[tokio::test] async fn test_github_repository_provider_crud() { let db = DbConn::new_in_memory().await.unwrap(); - let service = new_github_repository_provider_service(db.clone()); + let service = super::create(db.clone()); let id = service .create_github_repository_provider("example".into(), "id".into(), "secret".into()) @@ -354,7 +354,7 @@ mod tests { #[tokio::test] async fn test_provided_git_urls() { let db = DbConn::new_in_memory().await.unwrap(); - let service = new_github_repository_provider_service(db.clone()); + let service = create(db.clone()); let provider_id = db .create_github_provider( diff --git a/ee/tabby-webserver/src/service/mod.rs b/ee/tabby-webserver/src/service/mod.rs index 6e8974e04b42..ed5e0756dba8 100644 --- a/ee/tabby-webserver/src/service/mod.rs +++ b/ee/tabby-webserver/src/service/mod.rs @@ -8,6 +8,7 @@ mod github_repository_provider; mod job; mod license; mod proxy; +pub mod repository; mod setting; mod worker; @@ -20,7 +21,6 @@ use axum::{ response::IntoResponse, }; pub(in crate::service) use dao::{AsID, AsRowid}; -pub(crate) use github_repository_provider::new_github_repository_provider_service; use hyper::{client::HttpConnector, Body, Client, StatusCode}; use juniper::ID; use tabby_common::{ @@ -41,9 +41,9 @@ use crate::{ auth::AuthenticationService, email::EmailService, git_repository::GitRepositoryService, - github_repository_provider::GithubRepositoryProviderService, job::JobService, license::{IsLicenseValid, LicenseService}, + repository::RepositoryService, setting::SettingService, worker::{RegisterWorkerError, Worker, WorkerKind, WorkerService}, CoreError, Result, ServiceLocator, @@ -58,8 +58,7 @@ struct ServerContext { mail: Arc, auth: Arc, license: Arc, - github_repository_provider: Arc, - repository: Arc, + repository: Arc, logger: Arc, code: Arc, @@ -71,8 +70,7 @@ impl ServerContext { pub async fn new( logger: Arc, code: Arc, - repository: Arc, - github_repository_provider: Arc, + repository: Arc, db_conn: DbConn, is_chat_enabled_locally: bool, ) -> Self { @@ -97,7 +95,6 @@ impl ServerContext { license.clone(), )), license, - github_repository_provider, repository, db_conn, logger, @@ -288,7 +285,7 @@ impl ServiceLocator for Arc { Arc::new(self.db_conn.clone()) } - fn repository(&self) -> Arc { + fn repository(&self) -> Arc { self.repository.clone() } @@ -307,30 +304,17 @@ 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( logger: Arc, code: Arc, - repository: Arc, - github_repository_provider: Arc, + repository: Arc, db: DbConn, is_chat_enabled: bool, ) -> Arc { Arc::new(Arc::new( - ServerContext::new( - logger, - code, - repository, - github_repository_provider, - db, - is_chat_enabled, - ) - .await, + ServerContext::new(logger, code, repository, db, is_chat_enabled).await, )) } diff --git a/ee/tabby-webserver/src/service/repository.rs b/ee/tabby-webserver/src/service/repository.rs new file mode 100644 index 000000000000..4a2bf9fcfb1f --- /dev/null +++ b/ee/tabby-webserver/src/service/repository.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use tabby_common::config::{RepositoryAccess, RepositoryConfig}; +use tabby_db::DbConn; + +use super::github_repository_provider; +use crate::schema::{ + git_repository::GitRepositoryService, + github_repository_provider::GithubRepositoryProviderService, repository::RepositoryService, +}; + +struct RepositoryServiceImpl { + git: Arc, + github: Arc, +} + +pub fn create(db: DbConn) -> Arc { + Arc::new(RepositoryServiceImpl { + git: Arc::new(db.clone()), + github: Arc::new(github_repository_provider::create(db.clone())), + }) +} + +#[async_trait] +impl RepositoryAccess for RepositoryServiceImpl { + async fn list_repositories(&self) -> anyhow::Result> { + let mut repos: Vec = self + .git + .list(None, None, None, None) + .await? + .into_iter() + .map(|repo| RepositoryConfig::new(repo.git_url)) + .collect(); + + repos.extend( + self.github + .list_provided_git_urls() + .await + .unwrap_or_default() + .into_iter() + .map(RepositoryConfig::new), + ); + + Ok(repos) + } +} + +impl RepositoryService for RepositoryServiceImpl { + fn access(self: Arc) -> Arc { + self.clone() + } + + fn git(&self) -> Arc { + self.git.clone() + } + + fn github(&self) -> Arc { + self.github.clone() + } +} + +#[cfg(test)] +mod tests { + use tabby_db::DbConn; + + use super::*; + + #[tokio::test] + async fn test_list_repositories() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + service + .git() + .create("test_git_repo".into(), "http://test_git_repo".into()) + .await + .unwrap(); + + // FIXME(boxbeam): add repo with github service once there's syncing logic. + let repos = service.list_repositories().await.unwrap(); + assert_eq!(repos.len(), 1); + } +}