From 6c1ccc17b312b8d87eeb23658fbe7600343db1c5 Mon Sep 17 00:00:00 2001 From: Andrew Atimapre Date: Tue, 4 Jul 2023 19:28:42 +0100 Subject: [PATCH] laravel forge integration --- backend/src/integrations/apps.ts | 46 +++++- backend/src/models/integration.ts | 5 +- backend/src/models/integrationAuth.ts | 6 +- backend/src/variables/integration.ts | 46 ++++-- frontend/public/data/frequentConstants.ts | 1 + .../images/integrations/Laravel Forge.png | Bin 0 -> 15566 bytes .../integrations/laravel-forge/authorize.tsx | 76 +++++++++ .../integrations/laravel-forge/create.tsx | 155 ++++++++++++++++++ .../IntegrationPage.utils.tsx | 3 + 9 files changed, 316 insertions(+), 22 deletions(-) create mode 100644 frontend/public/images/integrations/Laravel Forge.png create mode 100644 frontend/src/pages/integrations/laravel-forge/authorize.tsx create mode 100644 frontend/src/pages/integrations/laravel-forge/create.tsx diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index bc8f01a80..b2fec9292 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -9,15 +9,17 @@ import { INTEGRATION_CHECKLY_API_URL, INTEGRATION_CIRCLECI, INTEGRATION_CIRCLECI_API_URL, + INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_CLOUDFLARE_PAGES_API_URL, INTEGRATION_FLYIO, INTEGRATION_FLYIO_API_URL, INTEGRATION_GITHUB, INTEGRATION_GITLAB, - INTEGRATION_CLOUDFLARE_PAGES, - INTEGRATION_CLOUDFLARE_PAGES_API_URL, INTEGRATION_GITLAB_API_URL, INTEGRATION_HEROKU, INTEGRATION_HEROKU_API_URL, + INTEGRATION_LARAVELFORGE, + INTEGRATION_LARAVELFORGE_API_URL, INTEGRATION_NETLIFY, INTEGRATION_NETLIFY_API_URL, INTEGRATION_RAILWAY, @@ -116,6 +118,12 @@ const getApps = async ({ accessToken, }); break; + case INTEGRATION_LARAVELFORGE: + apps = await getAppsLaravelForge({ + accessToken, + serverId: accessId + }); + break; case INTEGRATION_TRAVISCI: apps = await getAppsTravisCI({ accessToken, @@ -398,6 +406,40 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => { return apps; }; +/** + * Return list of sites for Laravel Forge integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Laravel Forge API + * @param {String} obj.serverId - server id of Laravel Forge + * @returns {Object[]} apps - names and ids of Laravel Forge sites + * @returns {String} apps.name - name of Laravel Forge sites + * @returns {String} apps.appId - id of Laravel Forge sites + */ +const getAppsLaravelForge = async ({ + accessToken, + serverId +}: { + accessToken: string; + serverId?: string; +}) => { + const res = ( + await standardRequest.get(`${INTEGRATION_LARAVELFORGE_API_URL}/api/v1/servers/${serverId}/sites`, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + "Content-Type": "application/json", + }, + }) + ).data.sites; + + const apps = res.map((a: any) => ({ + name: a.name, + appId: a.id, + })); + + return apps; +}; + /** * Return list of apps for Fly.io integration * @param {Object} obj diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index d94cc9faa..c4021977d 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -5,16 +5,17 @@ import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_CHECKLY, INTEGRATION_CIRCLECI, + INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_FLYIO, INTEGRATION_GITHUB, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HEROKU, + INTEGRATION_LARAVELFORGE, INTEGRATION_NETLIFY, INTEGRATION_RAILWAY, INTEGRATION_RENDER, INTEGRATION_SUPABASE, - INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_TRAVISCI, INTEGRATION_VERCEL, } from "../variables"; @@ -48,6 +49,7 @@ export interface IIntegration { | "railway" | "flyio" | "circleci" + | "laravel-forge" | "travisci" | "supabase" | "checkly" @@ -136,6 +138,7 @@ const integrationSchema = new Schema( INTEGRATION_RAILWAY, INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, + INTEGRATION_LARAVELFORGE, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index 2d56c2063..4cc7ef530 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -7,16 +7,17 @@ import { INTEGRATION_AWS_SECRET_MANAGER, INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_CIRCLECI, + INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_FLYIO, INTEGRATION_GITHUB, INTEGRATION_GITLAB, INTEGRATION_HASHICORP_VAULT, INTEGRATION_HEROKU, + INTEGRATION_LARAVELFORGE, INTEGRATION_NETLIFY, INTEGRATION_RAILWAY, INTEGRATION_RENDER, INTEGRATION_SUPABASE, - INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_TRAVISCI, INTEGRATION_VERCEL, } from "../variables"; @@ -24,7 +25,7 @@ import { export interface IIntegrationAuth extends Document { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly' | 'cloudflare-pages'; + integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'laravel-forge' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly' | 'cloudflare-pages'; teamId: string; accountId: string; url: string; @@ -65,6 +66,7 @@ const integrationAuthSchema = new Schema( INTEGRATION_RAILWAY, INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, + INTEGRATION_LARAVELFORGE, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_HASHICORP_VAULT, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index c87d2eb97..18eaeb049 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -19,12 +19,13 @@ export const INTEGRATION_GITLAB = "gitlab"; export const INTEGRATION_RENDER = "render"; export const INTEGRATION_RAILWAY = "railway"; export const INTEGRATION_FLYIO = "flyio"; +export const INTEGRATION_LARAVELFORGE = "laravel-forge" export const INTEGRATION_CIRCLECI = "circleci"; export const INTEGRATION_TRAVISCI = "travisci"; -export const INTEGRATION_SUPABASE = 'supabase'; -export const INTEGRATION_CHECKLY = 'checkly'; -export const INTEGRATION_HASHICORP_VAULT = 'hashicorp-vault'; -export const INTEGRATION_CLOUDFLARE_PAGES = 'cloudflare-pages'; +export const INTEGRATION_SUPABASE = "supabase"; +export const INTEGRATION_CHECKLY = "checkly"; +export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault"; +export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages"; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -35,6 +36,7 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_RENDER, INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, + INTEGRATION_LARAVELFORGE, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, @@ -65,9 +67,10 @@ export const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphq export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; -export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; -export const INTEGRATION_CHECKLY_API_URL = 'https://api.checklyhq.com'; -export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = 'https://api.cloudflare.com'; +export const INTEGRATION_SUPABASE_API_URL = "https://api.supabase.com"; +export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com"; +export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com"; +export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com"; export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ @@ -144,6 +147,15 @@ export const getIntegrationOptions = async () => { clientId: "", docsLink: "", }, + { + name: "Laravel Forge", + slug: "laravel-forge", + image: "Laravel Forge.png", + isAvailable: true, + type: "pat", + clientId: "", + docsLink: "", + }, { name: "AWS Secret Manager", slug: "aws-secret-manager", @@ -221,20 +233,20 @@ export const getIntegrationOptions = async () => { slug: "gcp", image: "Google Cloud Platform.png", isAvailable: false, - type: '', - clientId: '', - docsLink: '' + type: "", + clientId: "", + docsLink: "" }, { - name: 'Cloudflare Pages', - slug: 'cloudflare-pages', - image: 'Cloudflare.png', + name: "Cloudflare Pages", + slug: "cloudflare-pages", + image: "Cloudflare.png", isAvailable: true, - type: 'pat', - clientId: '', - docsLink: '' + type: "pat", + clientId: "", + docsLink: "" } ] return INTEGRATION_OPTIONS; -} \ No newline at end of file +} diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index 480061e1a..c3c74091d 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -12,6 +12,7 @@ const integrationSlugNameMapping: Mapping = { 'github': 'GitHub', 'gitlab': 'GitLab', 'render': 'Render', + 'laravel-forge': "Laravel Forge", 'railway': 'Railway', 'flyio': 'Fly.io', 'circleci': 'CircleCI', diff --git a/frontend/public/images/integrations/Laravel Forge.png b/frontend/public/images/integrations/Laravel Forge.png new file mode 100644 index 0000000000000000000000000000000000000000..a911390043cfea3841ed8af424062b3bb6959b78 GIT binary patch literal 15566 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 literal 0 HcmV?d00001 diff --git a/frontend/src/pages/integrations/laravel-forge/authorize.tsx b/frontend/src/pages/integrations/laravel-forge/authorize.tsx new file mode 100644 index 000000000..db47dc250 --- /dev/null +++ b/frontend/src/pages/integrations/laravel-forge/authorize.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; + +import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2"; +import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken"; + +export default function LaravelForgeCreateIntegrationPage() { + const router = useRouter(); + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [serverId, setServerId] = useState(""); + const [serverIdErrorText, setServerIdErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + setServerIdErrorText(""); + + if (apiKey.length === 0 || serverId.length === 0) { + if (apiKey.length === 0) setApiKeyErrorText("Access Token cannot be blank"); + if (serverId.length === 0) setServerIdErrorText("Server Id cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem("projectData.id"), + integration: "laravel-forge", + accessId: serverId, + accessToken: apiKey, + url: null, + namespace: null + }); + + setIsLoading(false); + + router.push(`/integrations/laravel-forge/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Laravel Forge Integration + + setApiKey(e.target.value)} /> + + + setServerId(e.target.value)} /> + + + +
+ ); +} + +LaravelForgeCreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/pages/integrations/laravel-forge/create.tsx b/frontend/src/pages/integrations/laravel-forge/create.tsx new file mode 100644 index 000000000..acfeb6932 --- /dev/null +++ b/frontend/src/pages/integrations/laravel-forge/create.tsx @@ -0,0 +1,155 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import queryString from "query-string"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + 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 LaravelForgeCreateIntegrationPage() { + 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 [secretPath, setSecretPath] = 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, + secretPath + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem("projectData.id")}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Laravel Forge Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +} + +LaravelForgeCreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index fc60ead6d..fd82d1fa7 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -71,6 +71,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => case "circleci": link = `${window.location.origin}/integrations/circleci/authorize`; break; + case "laravel-forge": + link = `${window.location.origin}/integrations/laravel-forge/authorize`; + break; case "travisci": link = `${window.location.origin}/integrations/travisci/authorize`; break;