From 09c2940aba278bcc2ab2b5e43acd773d73354c43 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Thu, 9 May 2024 16:10:50 -0400 Subject: [PATCH] feat(db): create merged integration tables (#2075) * feat(db): create merged integration tables * [autofix.ci] apply automated fixes * Apply suggestions * [autofix.ci] apply automated fixes * Apply suggestions * [autofix.ci] apply automated fixes * Change valid to error * [autofix.ci] apply automated fixes * Revert "Change valid to error" This reverts commit e9ac4636274fc2350b7c16f56c7fdc1a2e37e5d3. * [autofix.ci] apply automated fixes * Make access_token in update_integration_access_token optional * Reapply "Change valid to error" This reverts commit f97dd9a55492c80d93b795589f80ab6ab8ca6946. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../0029_merged-provider-tables.down.sql | 2 + .../0029_merged-provider-tables.up.sql | 22 + ee/tabby-db/schema.sqlite | Bin 176128 -> 188416 bytes ee/tabby-db/schema/schema.sql | 21 + ee/tabby-db/schema/schema.svg | 672 ++++++++++-------- ee/tabby-db/src/integration_access_tokens.rs | 155 ++++ ee/tabby-db/src/lib.rs | 2 + ee/tabby-db/src/provided_repositories.rs | 130 ++++ 8 files changed, 707 insertions(+), 297 deletions(-) create mode 100644 ee/tabby-db/migrations/0029_merged-provider-tables.down.sql create mode 100644 ee/tabby-db/migrations/0029_merged-provider-tables.up.sql create mode 100644 ee/tabby-db/src/integration_access_tokens.rs create mode 100644 ee/tabby-db/src/provided_repositories.rs diff --git a/ee/tabby-db/migrations/0029_merged-provider-tables.down.sql b/ee/tabby-db/migrations/0029_merged-provider-tables.down.sql new file mode 100644 index 000000000000..54650d10f26f --- /dev/null +++ b/ee/tabby-db/migrations/0029_merged-provider-tables.down.sql @@ -0,0 +1,2 @@ +DROP TABLE integration_access_tokens; +DROP TABLE provided_repositories; diff --git a/ee/tabby-db/migrations/0029_merged-provider-tables.up.sql b/ee/tabby-db/migrations/0029_merged-provider-tables.up.sql new file mode 100644 index 000000000000..f272586b97e7 --- /dev/null +++ b/ee/tabby-db/migrations/0029_merged-provider-tables.up.sql @@ -0,0 +1,22 @@ +CREATE TABLE integration_access_tokens( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + kind TEXT NOT NULL, + display_name TEXT NOT NULL, + access_token TEXT, + error TEXT, + created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')) +); + +CREATE TABLE provided_repositories( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + integration_access_token_id INTEGER NOT NULL, + vendor_id TEXT NOT NULL, + name TEXT NOT NULL, + git_url TEXT NOT NULL, + active BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')), + FOREIGN KEY (integration_access_token_id) REFERENCES integration_access_tokens(id) ON DELETE CASCADE, + CONSTRAINT idx_unique_provider_id_vendor_id UNIQUE (integration_access_token_id, vendor_id) +); diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index e9dce0a00e756915273eafa406f8c83c877073fb..4d5fbbb52d80a75a04ca3449f42ee68fee5619d7 100644 GIT binary patch delta 1615 zcmaKse@q)?7{_~WOOIY#4w2GAd+p(L45jT`26P>!3`tSU#0^Te;jp&7xAmZ0u|IGL zGcepjW@1M28ZL`YkSv<<&oYQZml%nfxJ5^@xF!A}(Wn?>j4>Lc*~sEs7H9TKy+7Vu z@_F9p`+dG|-gn{~Zeqna?J|2b|9nyJNj z-H42(+yeK6;RnN{!Ctyj8fR~_v#dvdMX%`jl9>{}?%Gm&`6=DFB(O|iry->khEXPo zB=<-rJt|8mFWQ@@ZqzE7Cvfu z{nX_%Bi7%)U)go?@=Fo_w|~7kb>=vacdQ;>XD`aV=LQ@s^GHF(k(6_(XzZ>E-e=L9 z@9B=?7N~t*&EXkKfFfTJl6> ztUijQtWXwH!q?~UWzR=OLsK{xto@R>@MsVfXxIhc;QGkL`=D-t@gMT+=U z8`1Au#Va=2^N%*zf)Y1S#hs(kRphheJVv6~?2GA)NJx#KgP$Z zAEc&QtR@*P9K-9?4^X7C25N^I-^P#E&}5X5xcewRSL3AiTrZH$pD}9}DB`n2t&}uk z$?m1d93ex#efSGItpgm;gPztL=2)h_kRC?{9wjqpQ7$Jd$*m@R&H+uyrmM)nGOdLz z_zef`@lyu`u?>9Fv5hkFus|%HKZ^$h+WZtD=)TYKC83Vm^9v!dKZCf|Nz3eYk|^*8 zc>F(Pp+KCgc+FW$bu+q%g9~5dpo>2I1R*KoEI#d`9b9!m4xJd|$TJ}DVa$pFz=xOG z#R^_uqGcFCc)8y@)$e7N`@P^DxQ&#RfgTNydBOa?`KT#w+HP#&I@x))OFvU0f*#!s z@&tGQe?qg9wfgU^{+FcfUziN@n7n~mhsLrB3swnj~)m@)jW*`l_^nUM~0N3r6FEY*uWq0UfGsJkQ7D;BHE zs_9{-?w~9kiR2aeg*=M<^G=Ztkx`^b=?wW3`?|aK_l5pfuR*+9UeDEcHR-Jbe$15G z)X6Ag-JDVOOlBKdYg-$0Jii%9QCCGl!#&9rEmNHe2&jumU{en$z|WyfCY=$(p@U(3 z-fS;~7Xe(osvQJN-~d!3QO2c?4TD|F7YD&vpgtJ`zp6{aWa*VaaJe-Osu}eU1RhsnazUadgRct;`g)ceaY%y(#fzDxNqUfPqR8{=4TD8{X#YWi)LgXZ~!1!A8yF zcp*oJ^R)_ldlKDZSG1?6ud_QYrg~*L+A1ghSM*>pWV)71(Z%Ae#8O!-Qs_GRSiG4K zvmG*x=YFFDc2fGVG<~2S^dpVKNl3tft9?kKgu_S34J@rkuAvtWvJWFb zCjIaX3JPSOr~viSS85Vhb*94PU0Aq(pG z2L_2})^90A8Ki7OfeR@6+QCXLCIdT7mn-KxfS~M^fhRZB0b;arwgc?aZ~~>tClzNW zSYnOi;8X6&33>@AnKqEkjU~ZN0FFYXB}%)M_9WP$1W$n#mZIq9+(e3wc*k}Z;P)>{ CIu`-} diff --git a/ee/tabby-db/schema/schema.sql b/ee/tabby-db/schema/schema.sql index 30bee964deb0..cd1904ffcd34 100644 --- a/ee/tabby-db/schema/schema.sql +++ b/ee/tabby-db/schema/schema.sql @@ -171,3 +171,24 @@ CREATE TABLE gitlab_provided_repositories( CREATE INDEX gitlab_provided_repositories_updated_at ON gitlab_provided_repositories( updated_at ); +CREATE TABLE integration_access_tokens( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + kind TEXT NOT NULL, + display_name TEXT NOT NULL, + access_token TEXT, + error TEXT, + created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')) +); +CREATE TABLE provided_repositories( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + integration_access_token_id INTEGER NOT NULL, + vendor_id TEXT NOT NULL, + name TEXT NOT NULL, + git_url TEXT NOT NULL, + active BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + FOREIGN KEY(integration_access_token_id) REFERENCES integration_access_tokens(id) ON DELETE CASCADE, + CONSTRAINT idx_unique_provider_id_vendor_id UNIQUE(integration_access_token_id, vendor_id) +); diff --git a/ee/tabby-db/schema/schema.svg b/ee/tabby-db/schema/schema.svg index adb349a8a540..37fb06b985e1 100644 --- a/ee/tabby-db/schema/schema.svg +++ b/ee/tabby-db/schema/schema.svg @@ -4,11 +4,11 @@ - - + + structs - + _sqlx_migrations @@ -201,347 +201,425 @@ - + +integration_access_tokens + +integration_access_tokens + +🔑 + +id + +  + +kind + +  + +display_name + +  + +access_token + +  + +error + +  + +created_at + +  + +updated_at + + + invitations - -invitations - -🔑 - -id - -  - -email - -  - -code - -  - -created_at + +invitations + +🔑 + +id + +  + +email + +  + +code + +  + +created_at - + job_runs - -job_runs - -🔑 - -id - -  - -job - -  - -start_ts - -  - -end_ts - -  - -exit_code - -  - -stdout - -  - -stderr - -  - -created_at - -  - -updated_at + +job_runs + +🔑 + +id + +  + +job + +  + +start_ts + +  + +end_ts + +  + +exit_code + +  + +stdout + +  + +stderr + +  + +created_at + +  + +updated_at - + oauth_credential - -oauth_credential - -🔑 - -id - -  - -provider - -  - -client_id - -  - -client_secret - -  - -created_at - -  - -updated_at + +oauth_credential + +🔑 + +id + +  + +provider + +  + +client_id + +  + +client_secret + +  + +created_at + +  + +updated_at - + password_reset - -password_reset - -🔑 - -id - -  - -user_id - -  - -code - -  - -created_at + +password_reset + +🔑 + +id + +  + +user_id + +  + +code + +  + +created_at - + users - -users - -🔑 - -id - -  - -email - -  - -is_admin - -  - -created_at - -  - -updated_at - -  - -auth_token - -  - -active - -  - -password_encrypted - -  - -avatar + +users + +🔑 + +id + +  + +email + +  + +is_admin + +  + +created_at + +  + +updated_at + +  + +auth_token + +  + +active + +  + +password_encrypted + +  + +avatar password_reset:e->users:w - - + + + + + +provided_repositories + +provided_repositories + +🔑 + +id + +  + +integration_access_token_id + +  + +vendor_id + +  + +name + +  + +git_url + +  + +active + +  + +created_at + +  + +updated_at + + + +provided_repositories:e->integration_access_tokens:w + + - + refresh_tokens - -refresh_tokens - -🔑 - -id - -  - -user_id - -  - -token - -  - -expires_at - -  - -created_at + +refresh_tokens + +🔑 + +id + +  + +user_id + +  + +token + +  + +expires_at + +  + +created_at refresh_tokens:e->users:w - - + + - + registration_token - -registration_token - -🔑 - -id - -  - -token - -  - -created_at - -  - -updated_at + +registration_token + +🔑 + +id + +  + +token + +  + +created_at + +  + +updated_at - + repositories - -repositories - -🔑 - -id - -  - -name - -  - -git_url + +repositories + +🔑 + +id + +  + +name + +  + +git_url - + server_setting - -server_setting - -🔑 - -id - -  - -security_allowed_register_domain_list - -  - -security_disable_client_side_telemetry - -  - -network_external_url - -  - -billing_enterprise_license + +server_setting + +🔑 + +id + +  + +security_allowed_register_domain_list + +  + +security_disable_client_side_telemetry + +  + +network_external_url + +  + +billing_enterprise_license - + user_completions - -user_completions - -🔑 - -id - -  - -user_id - -  - -completion_id - -  - -language - -  - -views - -  - -selects - -  - -dismisses - -  - -created_at - -  - -updated_at + +user_completions + +🔑 + +id + +  + +user_id + +  + +completion_id + +  + +language + +  + +views + +  + +selects + +  + +dismisses + +  + +created_at + +  + +updated_at user_completions:e->users:w - - + + - + user_events - -user_events - -🔑 - -id - -  - -user_id - -  - -kind - -  - -created_at - -  - -payload + +user_events + +🔑 + +id + +  + +user_id + +  + +kind + +  + +created_at + +  + +payload user_events:e->users:w - - + + diff --git a/ee/tabby-db/src/integration_access_tokens.rs b/ee/tabby-db/src/integration_access_tokens.rs new file mode 100644 index 000000000000..160d9ab41f5e --- /dev/null +++ b/ee/tabby-db/src/integration_access_tokens.rs @@ -0,0 +1,155 @@ +use anyhow::{anyhow, Result}; +use sqlx::{prelude::FromRow, query, query_as}; +use tabby_db_macros::query_paged_as; + +use crate::{DateTimeUtc, DbConn}; + +#[derive(FromRow)] +pub struct IntegrationAccessTokenDAO { + pub id: i64, + pub kind: String, + pub error: Option, + pub display_name: String, + pub access_token: Option, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, +} + +impl DbConn { + pub async fn create_integration_access_token( + &self, + kind: &str, + name: &str, + access_token: &str, + ) -> Result { + let res = query!( + "INSERT INTO integration_access_tokens(kind, display_name, access_token) VALUES (?, ?, ?);", + kind, + name, + access_token + ) + .execute(&self.pool) + .await?; + Ok(res.last_insert_rowid()) + } + + pub async fn get_integration_access_token(&self, id: i64) -> Result { + let provider = query_as!( + IntegrationAccessTokenDAO, + r#"SELECT + id, + kind, + error, + display_name, + access_token, + created_at AS "created_at: DateTimeUtc", + updated_at AS "updated_at: DateTimeUtc" + FROM integration_access_tokens WHERE id = ?;"#, + id + ) + .fetch_one(&self.pool) + .await?; + Ok(provider) + } + + pub async fn delete_integration_access_token(&self, id: i64) -> Result<()> { + let res = query!("DELETE FROM integration_access_tokens WHERE id = ?;", id) + .execute(&self.pool) + .await?; + if res.rows_affected() != 1 { + return Err(anyhow!("No integration access token to delete")); + } + Ok(()) + } + + pub async fn update_integration_access_token( + &self, + id: i64, + display_name: String, + access_token: Option, + ) -> Result<()> { + let access_token = match access_token { + Some(access_token) => Some(access_token), + None => self.get_integration_access_token(id).await?.access_token, + }; + + let res = query!( + "UPDATE integration_access_tokens SET display_name = ?, access_token=? WHERE id = ?;", + display_name, + access_token, + id + ) + .execute(&self.pool) + .await?; + + if res.rows_affected() != 1 { + return Err(anyhow!( + "The specified integration access token does not exist" + )); + } + + Ok(()) + } + + pub async fn update_integration_access_token_error( + &self, + id: i64, + error: Option, + ) -> Result<()> { + query!( + "UPDATE integration_access_tokens SET updated_at = DATETIME('now'), error = ? WHERE id = ?", + error, + id + ) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn list_integration_access_tokens( + &self, + ids: Vec, + kind: Option, + limit: Option, + skip_id: Option, + backwards: bool, + ) -> Result> { + let mut conditions = vec![]; + + let id_condition = (!ids.is_empty()).then(|| { + let ids = ids + .into_iter() + .map(|id| id.to_string()) + .collect::>() + .join(", "); + format!("id in ({ids})") + }); + conditions.extend(id_condition); + + let kind_condition = kind.map(|kind| format!("kind = {kind}")); + conditions.extend(kind_condition); + + let condition = (!conditions.is_empty()).then(|| conditions.join(" AND ")); + + let providers = query_paged_as!( + IntegrationAccessTokenDAO, + "integration_access_tokens", + [ + "id", + "kind", + "error", + "display_name", + "access_token", + "created_at" as "created_at: DateTimeUtc", + "updated_at" as "updated_at: DateTimeUtc" + ], + limit, + skip_id, + backwards, + condition + ) + .fetch_all(&self.pool) + .await?; + Ok(providers) + } +} diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 20efaa6b171e..f76160b30633 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -30,10 +30,12 @@ pub mod cache; mod email_setting; mod github_repository_provider; mod gitlab_repository_provider; +mod integration_access_tokens; mod invitations; mod job_runs; mod oauth_credential; mod password_reset; +mod provided_repositories; mod refresh_tokens; mod repositories; mod server_setting; diff --git a/ee/tabby-db/src/provided_repositories.rs b/ee/tabby-db/src/provided_repositories.rs new file mode 100644 index 000000000000..4ed8630f1f9d --- /dev/null +++ b/ee/tabby-db/src/provided_repositories.rs @@ -0,0 +1,130 @@ +use anyhow::{anyhow, Result}; +use sqlx::{prelude::FromRow, query, query_as}; +use tabby_db_macros::query_paged_as; + +use crate::{DateTimeUtc, DbConn}; + +#[derive(FromRow)] +pub struct ProvidedRepositoryDAO { + pub id: i64, + pub vendor_id: String, + pub integration_access_token_id: i64, + pub name: String, + pub git_url: String, + pub active: bool, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, +} + +impl DbConn { + pub async fn upsert_provided_repository( + &self, + integration_access_token_id: i64, + vendor_id: String, + name: String, + git_url: String, + ) -> Result { + let res = query!( + "INSERT INTO provided_repositories (integration_access_token_id, vendor_id, name, git_url) VALUES ($1, $2, $3, $4) + ON CONFLICT(integration_access_token_id, vendor_id) DO UPDATE SET name = $3, git_url = $4, updated_at = DATETIME('now')", + integration_access_token_id, + vendor_id, + name, + git_url + ).execute(&self.pool).await?; + Ok(res.last_insert_rowid()) + } + + pub async fn delete_outdated_provided_repositories( + &self, + integration_access_token_id: i64, + cutoff_timestamp: DateTimeUtc, + ) -> Result { + let res = query!( + "DELETE FROM provided_repositories WHERE integration_access_token_id = ? AND updated_at < ?;", + integration_access_token_id, + cutoff_timestamp + ).execute(&self.pool).await?; + Ok(res.rows_affected() as usize) + } + + pub async fn get_provided_repository(&self, id: i64) -> Result { + let repo = query_as!( + ProvidedRepositoryDAO, + "SELECT id, vendor_id, name, git_url, active, integration_access_token_id, created_at, updated_at FROM provided_repositories WHERE id = ?", + id + ) + .fetch_one(&self.pool) + .await?; + Ok(repo) + } + + pub async fn list_provided_repositories( + &self, + provider_ids: Vec, + kind: Option, + active: Option, + limit: Option, + skip_id: Option, + backwards: bool, + ) -> Result> { + let mut conditions = vec![]; + + let provider_ids = provider_ids + .into_iter() + .map(|id| id.to_string()) + .collect::>() + .join(", "); + if !provider_ids.is_empty() { + conditions.push(format!("access_token_provider_id IN ({provider_ids})")); + } + + let active_filter = active.map(|active| format!("active = {active}")); + conditions.extend(active_filter); + + let kind_filter = kind.map(|kind| format!("kind = {kind}")); + conditions.extend(kind_filter); + + let condition = (!conditions.is_empty()).then(|| conditions.join(" AND ")); + + let repos = query_paged_as!( + ProvidedRepositoryDAO, + "provided_repositories", + [ + "id", + "vendor_id", + "name", + "git_url", + "active", + "integration_access_token_id", + "created_at" as "created_at: DateTimeUtc", + "updated_at" as "updated_at: DateTimeUtc" + ], + limit, + skip_id, + backwards, + condition + ) + .fetch_all(&self.pool) + .await?; + Ok(repos) + } + + pub async fn update_provided_repository_active(&self, id: i64, active: bool) -> Result<()> { + let not_active = !active; + let res = query!( + "UPDATE 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(()) + } +}