Skip to content

Commit

Permalink
feat(webserver, db): Implement github provided repositories (#1800)
Browse files Browse the repository at this point in the history
* feat(webserver, db): Implement github provided repositories

* Split PR

* Implement suggestions and RepositoryAccess listing github-provided repos

* [autofix.ci] apply automated fixes

* Finish splitting PRs

* [autofix.ci] apply automated fixes

* Apply suggestion, add back authentication middleware

* Revert RepositoryAccess changes

* Update schema after rebasing

* Add test case

* [autofix.ci] apply automated fixes

* Document access_token stripping

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
boxbeam and autofix-ci[bot] authored Apr 16, 2024
1 parent 141d2cd commit fc2efd7
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 11 deletions.
1 change: 1 addition & 0 deletions ee/tabby-db/migrations/0024_github-provided-repos.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE github_provided_repositories;
10 changes: 10 additions & 0 deletions ee/tabby-db/migrations/0024_github-provided-repos.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE github_provided_repositories(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
github_repository_provider_id INTEGER NOT NULL,
-- vendor_id from https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user
vendor_id TEXT NOT NULL,
name TEXT NOT NULL,
git_url TEXT NOT NULL,
active BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE
);
Binary file modified ee/tabby-db/schema.sqlite
Binary file not shown.
89 changes: 89 additions & 0 deletions ee/tabby-db/src/github_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ pub struct GithubRepositoryProviderDAO {
pub access_token: Option<String>,
}

#[derive(FromRow)]
pub struct GithubProvidedRepositoryDAO {
pub id: i64,
pub vendor_id: String,
pub github_repository_provider_id: i64,
pub name: String,
pub git_url: String,
pub active: bool,
}

impl DbConn {
pub async fn create_github_provider(
&self,
Expand Down Expand Up @@ -95,4 +105,83 @@ impl DbConn {
.await?;
Ok(providers)
}

pub async fn create_github_provided_repository(
&self,
github_provider_id: i64,
vendor_id: String,
name: String,
git_url: String,
) -> Result<i64> {
let res = query!("INSERT INTO github_provided_repositories (github_repository_provider_id, vendor_id, name, git_url) VALUES (?, ?, ?, ?)",
github_provider_id, vendor_id, name, git_url).execute(&self.pool).await?;
Ok(res.last_insert_rowid())
}

pub async fn delete_github_provided_repository(&self, id: i64) -> Result<()> {
let res = query!("DELETE FROM github_provided_repositories WHERE id = ?", id)
.execute(&self.pool)
.await?;

if res.rows_affected() != 1 {
return Err(anyhow!("Repository not found"));
}
Ok(())
}

pub async fn list_github_provided_repositories(
&self,
provider_ids: Vec<i64>,
limit: Option<usize>,
skip_id: Option<i32>,
backwards: bool,
) -> Result<Vec<GithubProvidedRepositoryDAO>> {
let provider_ids = provider_ids
.into_iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(", ");
let repos = query_paged_as!(
GithubProvidedRepositoryDAO,
"github_provided_repositories",
[
"id",
"vendor_id",
"name",
"git_url",
"active",
"github_repository_provider_id"
],
limit,
skip_id,
backwards,
(!provider_ids.is_empty())
.then(|| format!("github_repository_provider_id IN ({provider_ids})"))
)
.fetch_all(&self.pool)
.await?;
Ok(repos)
}

pub async fn update_github_provided_repository_active(
&self,
id: i64,
active: bool,
) -> Result<()> {
let not_active = !active;
let res = query!(
"UPDATE github_provided_repositories SET active = ? WHERE id = ? AND active = ?",
active,
id,
not_active
)
.execute(&self.pool)
.await?;

if res.rows_affected() != 1 {
return Err(anyhow!("Repository active status was not changed"));
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion ee/tabby-db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cache::Cache;
use cached::TimedSizedCache;
use chrono::{DateTime, NaiveDateTime, Utc};
pub use email_setting::EmailSettingDAO;
pub use github_repository_provider::GithubRepositoryProviderDAO;
pub use github_repository_provider::{GithubProvidedRepositoryDAO, GithubRepositoryProviderDAO};
pub use invitations::InvitationDAO;
pub use job_runs::JobRunDAO;
pub use oauth_credential::OAuthCredentialDAO;
Expand Down
25 changes: 23 additions & 2 deletions ee/tabby-webserver/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ enum AuthMethod {
LOGIN
}

type GithubProvidedRepositoryConnection {
edges: [GithubProvidedRepositoryEdge!]!
pageInfo: PageInfo!
}

type RegisterResponse {
accessToken: String!
refreshToken: String!
Expand Down Expand Up @@ -175,6 +180,7 @@ type Mutation {
deleteEmailSetting: Boolean!
uploadLicense(license: String!): Boolean!
resetLicense: Boolean!
updateGithubProvidedRepositoryActive(id: ID!, active: Boolean!): Boolean!
}

type RepositoryEdge {
Expand All @@ -192,8 +198,13 @@ type FileEntrySearchResult {
indices: [Int!]!
}

input NetworkSettingInput {
externalUrl: String!
type GithubProvidedRepository {
id: ID!
vendorId: String!
githubRepositoryProviderId: ID!
name: String!
gitUrl: String!
active: Boolean!
}

type Query {
Expand All @@ -204,6 +215,7 @@ type Query {
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!
githubRepositories(providerIds: [ID!]!, after: String, before: String, first: Int, last: Int): GithubProvidedRepositoryConnection!
jobRuns(ids: [ID!], jobs: [String!], after: String, before: String, first: Int, last: Int): JobRunConnection!
jobRunStats(jobs: [String!]): JobStats!
emailSetting: EmailSetting
Expand All @@ -221,6 +233,10 @@ type Query {
dailyStats(start: DateTimeUtc!, end: DateTimeUtc!, users: [ID!], languages: [Language!]): [CompletionStats!]!
}

input NetworkSettingInput {
externalUrl: String!
}

enum Encryption {
START_TLS
SSL_TLS
Expand Down Expand Up @@ -328,6 +344,11 @@ type PageInfo {
endCursor: String
}

type GithubProvidedRepositoryEdge {
node: GithubProvidedRepository!
cursor: String!
}

type Repository {
id: ID!
name: String!
Expand Down
6 changes: 5 additions & 1 deletion ee/tabby-webserver/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ impl WebserverHandle {
)
.nest(
"/integrations/github",
integrations::github::routes(ctx.setting(), ctx.github_repository_provider()),
integrations::github::routes(
ctx.auth(),
ctx.setting(),
ctx.github_repository_provider(),
),
)
.route(
"/avatar/:id",
Expand Down
11 changes: 9 additions & 2 deletions ee/tabby-webserver/src/integrations/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;
use anyhow::Result;
use axum::{
extract::{Path, Query, State},
middleware::from_fn_with_state,
response::Redirect,
routing, Router,
};
Expand All @@ -12,8 +13,12 @@ use serde::Deserialize;
use tracing::error;
use url::Url;

use crate::schema::{
github_repository_provider::GithubRepositoryProviderService, setting::SettingService,
use crate::{
handler::require_login_middleware,
schema::{
auth::AuthenticationService, github_repository_provider::GithubRepositoryProviderService,
setting::SettingService,
},
};

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -47,6 +52,7 @@ struct IntegrationState {
}

pub fn routes(
auth: Arc<dyn AuthenticationService>,
settings: Arc<dyn SettingService>,
github_repository_provider: Arc<dyn GithubRepositoryProviderService>,
) -> Router {
Expand All @@ -57,6 +63,7 @@ pub fn routes(
Router::new()
.route("/connect/:id", routing::get(connect))
.route("/callback", routing::get(callback))
.layer(from_fn_with_state(auth, require_login_middleware))
.with_state(state)
}

Expand Down
40 changes: 40 additions & 0 deletions ee/tabby-webserver/src/schema/github_repository_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct GithubRepositoryProvider {
pub id: ID,
pub display_name: String,
pub application_id: String,
#[graphql(skip)]
pub access_token: Option<String>,
}

impl NodeType for GithubRepositoryProvider {
Expand All @@ -28,6 +30,33 @@ impl NodeType for GithubRepositoryProvider {
}
}

#[derive(GraphQLObject, Debug)]
#[graphql(context = Context)]
pub struct GithubProvidedRepository {
pub id: ID,
pub vendor_id: String,
pub github_repository_provider_id: ID,
pub name: String,
pub git_url: String,
pub active: bool,
}

impl NodeType for GithubProvidedRepository {
type Cursor = String;

fn cursor(&self) -> Self::Cursor {
self.id.to_string()
}

fn connection_type_name() -> &'static str {
"GithubProvidedRepositoryConnection"
}

fn edge_type_name() -> &'static str {
"GithubProvidedRepositoryEdge"
}
}

#[async_trait]
pub trait GithubRepositoryProviderService: Send + Sync {
async fn get_github_repository_provider(&self, id: ID) -> Result<GithubRepositoryProvider>;
Expand All @@ -45,4 +74,15 @@ pub trait GithubRepositoryProviderService: Send + Sync {
first: Option<usize>,
last: Option<usize>,
) -> Result<Vec<GithubRepositoryProvider>>;

async fn list_github_provided_repositories_by_provider(
&self,
provider: Vec<ID>,
after: Option<String>,
before: Option<String>,
first: Option<usize>,
last: Option<usize>,
) -> Result<Vec<GithubProvidedRepository>>;

async fn update_github_provided_repository_active(&self, id: ID, active: bool) -> Result<()>;
}
47 changes: 46 additions & 1 deletion ee/tabby-webserver/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ use self::{
RequestInvitationInput, RequestPasswordResetEmailInput, UpdateOAuthCredentialInput,
},
email::{EmailService, EmailSetting, EmailSettingInput},
github_repository_provider::{GithubRepositoryProvider, GithubRepositoryProviderService},
github_repository_provider::{
GithubProvidedRepository, GithubRepositoryProvider, GithubRepositoryProviderService,
},
job::JobStats,
license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType},
repository::{Repository, RepositoryService},
Expand Down Expand Up @@ -254,6 +256,37 @@ impl Query {
.await
}

async fn github_repositories(
ctx: &Context,
provider_ids: Vec<ID>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<GithubProvidedRepository>> {
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_provided_repositories_by_provider(
provider_ids,
after,
before,
first,
last,
)
.await?)
},
)
.await
}

async fn job_runs(
ctx: &Context,
ids: Option<Vec<ID>>,
Expand Down Expand Up @@ -669,6 +702,18 @@ impl Mutation {
ctx.locator.license().reset_license().await?;
Ok(true)
}

async fn update_github_provided_repository_active(
ctx: &Context,
id: ID,
active: bool,
) -> Result<bool> {
ctx.locator
.github_repository_provider()
.update_github_provided_repository_active(id, active)
.await?;
Ok(true)
}
}

async fn check_analytic_access(ctx: &Context, users: &[ID]) -> Result<(), CoreError> {
Expand Down
Loading

0 comments on commit fc2efd7

Please sign in to comment.