Skip to content

Commit

Permalink
refactor(webserver): support browsing github / gitlab repositories. (#…
Browse files Browse the repository at this point in the history
…1968)

* refactor(webserver): support browsing github / gitlab repositories

* update

* simplify
  • Loading branch information
wsxiaoys authored Apr 26, 2024
1 parent cfe715b commit 23248a2
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 120 deletions.
14 changes: 14 additions & 0 deletions ee/tabby-db/src/github_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ impl DbConn {
Ok(())
}

pub async fn get_github_provided_repository(
&self,
id: i64,
) -> Result<GithubProvidedRepositoryDAO> {
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<i64>,
Expand Down
14 changes: 14 additions & 0 deletions ee/tabby-db/src/gitlab_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ impl DbConn {
Ok(())
}

pub async fn get_gitlab_provided_repository(
&self,
id: i64,
) -> Result<GitlabProvidedRepositoryDAO> {
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<i64>,
Expand Down
10 changes: 3 additions & 7 deletions ee/tabby-db/src/repositories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ impl DbConn {
}
}

pub async fn get_repository_by_name(&self, name: &str) -> Result<RepositoryDAO> {
pub async fn get_repository(&self, id: i64) -> Result<RepositoryDAO> {
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?;
Expand Down Expand Up @@ -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
);
}
}
15 changes: 14 additions & 1 deletion ee/tabby-webserver/graphql/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
type Repository {
id: ID!
name: String!
kind: RepositoryKind!
}

input UpdateRepositoryProviderInput {
id: ID!
displayName: String!
Expand Down Expand Up @@ -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!
Expand All @@ -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 {
Expand Down Expand Up @@ -336,6 +343,12 @@ type RepositoryConnection {
pageInfo: PageInfo!
}

enum RepositoryKind {
GIT
GITHUB
GITLAB
}

input EmailSettingInput {
smtpUsername: String!
fromAddress: String!
Expand Down
3 changes: 1 addition & 2 deletions ee/tabby-webserver/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 9 additions & 15 deletions ee/tabby-webserver/src/repositories/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn GitRepositoryService>,
repository: Arc<dyn RepositoryService>,
auth: Arc<dyn AuthenticationService>,
) -> 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))
Expand All @@ -44,11 +42,11 @@ async fn resolve_path(
State(rs): State<Arc<ResolveState>>,
Path(repo): Path<ResolveParams>,
) -> Result<Response, StatusCode> {
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())
Expand All @@ -72,7 +70,3 @@ async fn resolve_path(
}
}
}

async fn resolve(State(rs): State<Arc<ResolveState>>) -> Result<Response, StatusCode> {
rs.resolve_all().await.map_err(|_| StatusCode::NOT_FOUND)
}
44 changes: 13 additions & 31 deletions ee/tabby-webserver/src/repositories/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

impl ResolveParams {
pub fn name_str(&self) -> &str {
self.name.as_str()
}

pub fn path_str(&self) -> &str {
self.path.as_deref().unwrap_or("")
}
Expand Down Expand Up @@ -60,11 +57,11 @@ struct DirEntry {
}

pub(super) struct ResolveState {
service: Arc<dyn GitRepositoryService>,
service: Arc<dyn RepositoryService>,
}

impl ResolveState {
pub fn new(service: Arc<dyn GitRepositoryService>) -> Self {
pub fn new(service: Arc<dyn RepositoryService>) -> Self {
Self { service }
}

Expand Down Expand Up @@ -128,27 +125,12 @@ impl ResolveState {
Ok(resp.map(boxed))
}

pub async fn resolve_all(&self) -> Result<Response> {
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<RepositoryConfig> {
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<PathBuf> {
let repository = self
.service
.resolve_repository(&params.kind, &params.id)
.await
.ok()?;
Some(repository.dir)
}
}
12 changes: 2 additions & 10 deletions ee/tabby-webserver/src/schema/git_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<String>,
Expand All @@ -52,14 +52,6 @@ pub trait GitRepositoryService: Send + Sync {
) -> Result<Vec<GitRepository>>;

async fn create(&self, name: String, git_url: String) -> Result<ID>;
async fn get_by_name(&self, name: &str) -> Result<GitRepository>;
async fn delete(&self, id: &ID) -> Result<bool>;
async fn update(&self, id: &ID, name: String, git_url: String) -> Result<bool>;

async fn search_files(
&self,
name: &str,
pattern: &str,
top_n: usize,
) -> Result<Vec<FileEntrySearchResult>>;
}
4 changes: 2 additions & 2 deletions ee/tabby-webserver/src/schema/github_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions ee/tabby-webserver/src/schema/gitlab_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 12 additions & 11 deletions ee/tabby-webserver/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -421,14 +418,14 @@ impl Query {

async fn repository_search(
ctx: &Context,
repository_name: String,
kind: RepositoryKind,
id: ID,
pattern: String,
) -> Result<Vec<FileEntrySearchResult>> {
check_claims(ctx)?;
ctx.locator
.repository()
.git()
.search_files(&repository_name, &pattern, 40)
.search_files(&kind, &id, &pattern, 40)
.await
}

Expand Down Expand Up @@ -526,8 +523,12 @@ impl Query {

async fn disk_usage_stats(ctx: &Context) -> Result<DiskUsageStats> {
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<Vec<Repository>> {
check_admin(ctx).await?;
ctx.locator.repository().repository_list().await
}
}

Expand Down
Loading

0 comments on commit 23248a2

Please sign in to comment.