From 1b1cb4a1de3aa1e342f5486a7924cb82da9814ab Mon Sep 17 00:00:00 2001 From: Aashish-Upadhyay-101 Date: Thu, 13 Apr 2023 12:20:31 +0545 Subject: [PATCH 1/2] supabase updated setup --- .../public/images/integrations/Supabase.png | Bin 0 -> 7668 bytes .../pages/integrations/supabase/authorize.tsx | 65 ++++++++ .../pages/integrations/supabase/create.tsx | 142 ++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 frontend/public/images/integrations/Supabase.png create mode 100644 frontend/src/pages/integrations/supabase/authorize.tsx create mode 100644 frontend/src/pages/integrations/supabase/create.tsx diff --git a/frontend/public/images/integrations/Supabase.png b/frontend/public/images/integrations/Supabase.png new file mode 100644 index 0000000000000000000000000000000000000000..843be59ca501f34caa48c061e592235cd00a64a2 GIT binary patch literal 7668 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 ogo}1>&L8gdX|BzO9 { + try { + setApiKeyErrorText(''); + if (apiKey.length === 0) { + setApiKeyErrorText('API Key cannot be blank'); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem('projectData.id'), + integration: 'supabase', + accessToken: apiKey, + accessId: null + }); + + setIsLoading(false); + + router.push(`/integrations/supabase/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Supabase Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +} + +SupabaseCreateIntegrationPage.requireAuth = true; + +export const getServerSideProps = getTranslatedServerSideProps(['integrations']); \ No newline at end of file diff --git a/frontend/src/pages/integrations/supabase/create.tsx b/frontend/src/pages/integrations/supabase/create.tsx new file mode 100644 index 000000000..16e35e36e --- /dev/null +++ b/frontend/src/pages/integrations/supabase/create.tsx @@ -0,0 +1,142 @@ +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import queryString from 'query-string'; + +import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps'; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from '../../../components/v2'; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from '../../../hooks/api/integrationAuth'; +import { useGetWorkspaceById } from '../../../hooks/api/workspace'; +import createIntegration from '../../api/integrations/createIntegration'; + +export default function SupabaseCreateIntegrationPage() { + const router = useRouter(); + + const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]); + + const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? ''); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ''); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? '' + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); + const [targetApp, setTargetApp] = useState(''); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp('none'); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?._id) return; + + setIsLoading(true); + + await createIntegration({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp, + appId: + integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp) + ?.appId ?? null, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: null, + targetEnvironmentId: null, + targetService: null, + targetServiceId: null, + owner: null, + path: null, + region: null + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem('projectData.id')}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Supabase Integration + + + + + + + + +
+ ) : ( +
+ ); +} + +SupabaseCreateIntegrationPage.requireAuth = true; + +export const getServerSideProps = getTranslatedServerSideProps(['integrations']); \ No newline at end of file From 1fe1afbb8e856dc05ff821c24f25031665cb72d9 Mon Sep 17 00:00:00 2001 From: Aashish-Upadhyay-101 Date: Thu, 13 Apr 2023 12:39:19 +0545 Subject: [PATCH 2/2] updated PR supabase-integration --- backend/src/integrations/apps.ts | 43 ++++++++++++ backend/src/integrations/sync.ts | 84 +++++++++++++++++++++++ backend/src/models/integration.ts | 5 +- backend/src/models/integrationAuth.ts | 4 +- backend/src/variables/index.ts | 4 ++ backend/src/variables/integration.ts | 14 ++++ frontend/public/data/frequentConstants.ts | 3 +- frontend/src/pages/integrations/[id].tsx | 6 ++ 8 files changed, 160 insertions(+), 3 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 6929726e9..58568145d 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -16,6 +16,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, INTEGRATION_HEROKU_API_URL, INTEGRATION_GITLAB_API_URL, INTEGRATION_VERCEL_API_URL, @@ -25,6 +26,7 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, + INTEGRATION_SUPABASE_API_URL } from "../variables"; interface App { @@ -116,6 +118,11 @@ const getApps = async ({ accessToken, }) break; + case INTEGRATION_SUPABASE: + apps = await getAppsSupabase({ + accessToken + }); + break; } } catch (err) { Sentry.setUser(null); @@ -608,4 +615,40 @@ const getAppsGitlab = async ({ return apps; } + +/** + * Return list of projects for Supabase integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Supabase API + * @returns {Object[]} apps - names of Supabase apps + * @returns {String} apps.name - name of Supabase app + */ +const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { + let apps: any; + try { + const { data } = await request.get( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } + } + ); + + apps = data.map((a: any) => { + return { + name: a.name, + appId: a.id + }; + }); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to get Supabase projects'); + } + + return apps; +}; + export { getApps }; diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 9a998d6f5..285f31ca7 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -25,6 +25,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, INTEGRATION_HEROKU_API_URL, INTEGRATION_GITLAB_API_URL, INTEGRATION_VERCEL_API_URL, @@ -34,6 +35,7 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, + INTEGRATION_SUPABASE_API_URL } from "../variables"; import request from '../config/request'; import axios from "axios"; @@ -157,6 +159,13 @@ const syncSecrets = async ({ accessToken, }); break; + case INTEGRATION_SUPABASE: + await syncSecretsSupabase({ + integration, + secrets, + accessToken + }); + break; } } catch (err) { Sentry.setUser(null); @@ -1618,4 +1627,79 @@ const syncSecretsGitLab = async ({ } } +/** + * Sync/push [secrets] to Supabase with name [integration.app] + * @param {Object} obj + * @param {IIntegration} obj.integration - integration details + * @param {IIntegrationAuth} obj.integrationAuth - integration auth details + * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) + * @param {String} obj.accessToken - access token for Supabase integration + */ +const syncSecretsSupabase = async ({ + integration, + secrets, + accessToken +}: { + integration: IIntegration; + secrets: any; + accessToken: string; +}) => { + try { + const { data: getSecretsRes } = await request.get( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } + } + ); + + // convert the secrets to [{}] format + const modifiedFormatForSecretInjection = Object.keys(secrets).map( + (key) => { + return { + name: key, + value: secrets[key] + }; + } + ); + + await request.post( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + modifiedFormatForSecretInjection, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' + } + } + ); + + const secretsToDelete: any = []; + getSecretsRes?.forEach((secretObj: any) => { + if (!(secretObj.name in secrets)) { + secretsToDelete.push(secretObj.name); + } + }); + + await request.delete( + `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Accept-Encoding': 'application/json' + }, + data: secretsToDelete + } + ); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to sync secrets to Supabase'); + } +}; + + export { syncSecrets }; diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index 22a8713e0..2a77c95e9 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -13,6 +13,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE } from "../variables"; export interface IIntegration { @@ -42,7 +43,8 @@ export interface IIntegration { | 'railway' | 'flyio' | 'circleci' - | 'travisci'; + | 'travisci' + | 'supabase'; integrationAuth: Types.ObjectId; } @@ -122,6 +124,7 @@ const integrationSchema = new Schema( INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE ], required: true, }, diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index 64c4f2d69..e6014c675 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -13,12 +13,13 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, } from "../variables"; export interface IIntegrationAuth { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'aws-parameter-store' | 'aws-secret-manager'; + integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager'; teamId: string; accountId: string; refreshCiphertext?: string; @@ -56,6 +57,7 @@ const integrationAuthSchema = new Schema( INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE ], required: true, }, diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index 3029b265f..979de21ab 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -19,6 +19,7 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, INTEGRATION_SET, INTEGRATION_OAUTH2, INTEGRATION_AZURE_TOKEN_URL, @@ -36,6 +37,7 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, + INTEGRATION_SUPABASE_API_URL, getIntegrationOptions } from "./integration"; import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from "./organization"; @@ -102,6 +104,7 @@ export { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, INTEGRATION_SET, INTEGRATION_OAUTH2, INTEGRATION_AZURE_TOKEN_URL, @@ -119,6 +122,7 @@ export { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, + INTEGRATION_SUPABASE_API_URL, EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS, ACTION_LOGIN, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 3c25c79da..bbfd7f107 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -21,6 +21,7 @@ const INTEGRATION_RAILWAY = "railway"; const INTEGRATION_FLYIO = "flyio"; const INTEGRATION_CIRCLECI = "circleci"; const INTEGRATION_TRAVISCI = "travisci"; +const INTEGRATION_SUPABASE = 'supabase'; const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -32,6 +33,7 @@ const INTEGRATION_SET = new Set([ INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE ]); // integration types @@ -57,6 +59,7 @@ const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2"; const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; +const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; const getIntegrationOptions = () => { const INTEGRATION_OPTIONS = [ @@ -178,6 +181,15 @@ const getIntegrationOptions = () => { clientId: '', docsLink: '' }, + { + name: 'Supabase', + slug: 'supabase', + image: 'Supabase.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, { name: 'Google Cloud Platform', slug: 'gcp', @@ -207,6 +219,7 @@ export { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, + INTEGRATION_SUPABASE, INTEGRATION_SET, INTEGRATION_OAUTH2, INTEGRATION_AZURE_TOKEN_URL, @@ -224,5 +237,6 @@ export { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, + INTEGRATION_SUPABASE_API_URL, getIntegrationOptions }; diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index f5e5e5016..40c88f65b 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -15,7 +15,8 @@ const integrationSlugNameMapping: Mapping = { 'railway': 'Railway', 'flyio': 'Fly.io', 'circleci': 'CircleCI', - 'travisci': 'TravisCI' + 'travisci': 'TravisCI', + 'supabase': 'Supabase' } const envMapping: Mapping = { diff --git a/frontend/src/pages/integrations/[id].tsx b/frontend/src/pages/integrations/[id].tsx index be9e3718f..46870805d 100644 --- a/frontend/src/pages/integrations/[id].tsx +++ b/frontend/src/pages/integrations/[id].tsx @@ -207,6 +207,9 @@ export default function Integrations() { case 'travisci': link = `${window.location.origin}/integrations/travisci/authorize`; break; + case 'supabase': + link = `${window.location.origin}/integrations/supabase/authorize`; + break; case 'railway': link = `${window.location.origin}/integrations/railway/authorize`; break; @@ -262,6 +265,9 @@ export default function Integrations() { case 'travisci': link = `${window.location.origin}/integrations/travisci/create?integrationAuthId=${integrationAuth._id}`; break; + case 'supabase': + link = `${window.location.origin}/integrations/supabase/create?integrationAuthId=${integrationAuth._id}`; + break; case 'railway': link = `${window.location.origin}/integrations/railway/create?integrationAuthId=${integrationAuth._id}`; break;