From 530ad31aaad7d78c52e952b6a1ec30a96b7ce938 Mon Sep 17 00:00:00 2001 From: Jason Parks Date: Thu, 20 Mar 2025 16:48:59 -0600 Subject: [PATCH] Simplify Gitea authorization flow with shared utilities --- .../general/generic/save-gitea-provider.tsx | 6 +- .../generic/save-gitea-provider-compose.tsx | 26 +- .../compose/general/generic/show.tsx | 281 ++++++------ .../settings/git/gitea/add-gitea-provider.tsx | 78 +++- .../git/gitea/edit-gitea-provider.tsx | 49 +-- apps/dokploy/server/api/routers/gitea.ts | 399 ++++++++++-------- apps/dokploy/utils/gitea-utils.ts | 87 ++++ packages/server/src/services/gitea.ts | 18 +- packages/server/src/utils/providers/gitea.ts | 2 + 9 files changed, 558 insertions(+), 388 deletions(-) create mode 100644 apps/dokploy/utils/gitea-utils.ts diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx index 60565824..90de6b6b 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -1,4 +1,5 @@ import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -35,7 +36,6 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -433,7 +433,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
- {field.value && field.value.map((path: string, index: number) => ( + {field.value?.map((path: string, index: number) => ( {
); -}; \ No newline at end of file +}; diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx index 5059672e..dce6e4f7 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitea-provider-compose.tsx @@ -39,6 +39,7 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; +import type { Branch, Repository } from "@/utils/gitea-utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import Link from "next/link"; @@ -47,31 +48,6 @@ import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -interface Repository { - name: string; - url: string; - id: number; - owner: { - username: string; - }; -} - -interface Branch { - name: string; -} - -interface GiteaProviderType { - giteaId: string; - gitProvider: { - name: string; - gitProviderId: string; - providerType: "github" | "gitlab" | "bitbucket" | "gitea"; - createdAt: string; - organizationId: string; - }; - name: string; -} - const GiteaProviderSchema = z.object({ composePath: z.string().min(1), repository: z diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index 340037ad..939befac 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -1,16 +1,16 @@ import { BitbucketIcon, + GitIcon, GiteaIcon, GithubIcon, GitlabIcon, - GitIcon, - } from "@/components/icons/data-tools-icons"; +} from "@/components/icons/data-tools-icons"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { api } from "@/utils/api"; import { CodeIcon, GitBranch } from "lucide-react"; import Link from "next/link"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { ComposeFileEditor } from "../compose-file-editor"; import { ShowConvertedCompose } from "../show-converted-compose"; import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose"; @@ -18,141 +18,174 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose"; import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose"; - - type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; // Adding gitea to the TabState - interface Props { + +type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; // Adding gitea to the TabState +interface Props { composeId: string; - } - - export const ShowProviderFormCompose = ({ composeId }: Props) => { +} + +export const ShowProviderFormCompose = ({ composeId }: Props) => { const { data: githubProviders } = api.github.githubProviders.useQuery(); const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data: bitbucketProviders } = api.bitbucket.bitbucketProviders.useQuery(); + const { data: bitbucketProviders } = + api.bitbucket.bitbucketProviders.useQuery(); const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); // Fetching Gitea providers - + const { data: compose } = api.compose.one.useQuery({ composeId }); const [tab, setSab] = useState(compose?.sourceType || "github"); - + // Ensure we fall back to empty arrays if the data is undefined const safeGithubProviders = githubProviders || []; const safeGitlabProviders = gitlabProviders || []; const safeBitbucketProviders = bitbucketProviders || []; const safeGiteaProviders = giteaProviders || []; - - const renderProviderContent = (providers: any[], providerType: string, ProviderComponent: React.ComponentType) => { - if (providers.length > 0) { - return ; - } else { + + const renderProviderContent = ( + providers: any[], + providerType: string, + ProviderComponent: React.ComponentType, + ) => { + if (providers.length > 0) { + return ; + } + return ( -
- {providerType === "github" && } - {providerType === "gitlab" && } - {providerType === "bitbucket" && } - {providerType === "gitea" && } - - To deploy using {providerType.charAt(0).toUpperCase() + providerType.slice(1)}, you need to configure your account first. - Please, go to{" "} - - Settings - {" "} - to do so. - -
+
+ {providerType === "github" && ( + + )} + {providerType === "gitlab" && ( + + )} + {providerType === "bitbucket" && ( + + )} + {providerType === "gitea" && ( + + )} + + To deploy using{" "} + {providerType.charAt(0).toUpperCase() + providerType.slice(1)}, you + need to configure your account first. Please, go to{" "} + + Settings + {" "} + to do so. + +
); - } }; - + return ( - - - -
- Provider -

- Select the source of your code -

-
-
- - -
-
-
- - { - setSab(e as TabState); - }} - > -
- - + + +
+ Provider +

+ Select the source of your code +

+
+
+ + +
+
+
+ + { + setSab(e as TabState); + }} > - - GitHub -
- - - GitLab - - - - Bitbucket - - - Gitea - - - - Git - - - - Raw - -
-
- - - {renderProviderContent(safeGithubProviders, "github", SaveGithubProviderCompose)} - - - {renderProviderContent(safeGiteaProviders, "gitea", SaveGiteaProviderCompose)} - - - {renderProviderContent(safeGitlabProviders, "gitlab", SaveGitlabProviderCompose)} - - - {renderProviderContent(safeBitbucketProviders, "bitbucket", SaveBitbucketProviderCompose)} - - - - - - - - -
-
-
+
+ + + + GitHub + + + + GitLab + + + + Bitbucket + + + Gitea + + + + Git + + + + Raw + + +
+ + + {renderProviderContent( + safeGithubProviders, + "github", + SaveGithubProviderCompose, + )} + + + {renderProviderContent( + safeGiteaProviders, + "gitea", + SaveGiteaProviderCompose, + )} + + + {renderProviderContent( + safeGitlabProviders, + "gitlab", + SaveGitlabProviderCompose, + )} + + + {renderProviderContent( + safeBitbucketProviders, + "bitbucket", + SaveBitbucketProviderCompose, + )} + + + + + + + + + + + ); - }; \ No newline at end of file +}; diff --git a/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx index 407486c0..13c65bdf 100644 --- a/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/gitea/add-gitea-provider.tsx @@ -1,4 +1,4 @@ -import { GiteaIcon } from "@/components/icons/data-tools-icons"; // Use GiteaIcon for Gitea +import { GiteaIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { CardContent } from "@/components/ui/card"; @@ -19,9 +19,12 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { + type GiteaProviderResponse, + getGiteaOAuthUrl, +} from "@/utils/gitea-utils"; import { useUrl } from "@/utils/hooks/use-url"; import { zodResolver } from "@hookform/resolvers/zod"; - import { ExternalLink } from "lucide-react"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -51,11 +54,14 @@ const Schema = z.object({ type Schema = z.infer; export const AddGiteaProvider = () => { - const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); - const url = useUrl(); + + const urlObj = useUrl(); + const baseUrl = + typeof urlObj === "string" ? urlObj : (urlObj as any)?.url || ""; + const { mutateAsync, error, isError } = api.gitea.create.useMutation(); - const webhookUrl = `${url}/api/providers/gitea/callback`; + const webhookUrl = `${baseUrl}/api/providers/gitea/callback`; const form = useForm({ defaultValues: { @@ -78,24 +84,52 @@ export const AddGiteaProvider = () => { name: "", giteaUrl: "https://gitea.com", }); - }, [form, isOpen]); + }, [form, webhookUrl, isOpen]); const onSubmit = async (data: Schema) => { - await mutateAsync({ - clientId: data.clientId || "", - clientSecret: data.clientSecret || "", - name: data.name || "", - redirectUri: data.redirectUri || "", - giteaUrl: data.giteaUrl || "https://gitea.com", - }) - .then(async () => { - await utils.gitProvider.getAll.invalidate(); - toast.success("Gitea provider created successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error configuring Gitea"); - }); + try { + // Send the form data to create the Gitea provider + const result = (await mutateAsync({ + clientId: data.clientId, + clientSecret: data.clientSecret, + name: data.name, + redirectUri: data.redirectUri, + giteaUrl: data.giteaUrl, + organizationName: data.organizationName, + })) as unknown as GiteaProviderResponse; + + // Check if we have a giteaId from the response + if (!result || !result.giteaId) { + toast.error("Failed to get Gitea ID from response"); + return; + } + + // Generate OAuth URL using the shared utility + const authUrl = getGiteaOAuthUrl( + result.giteaId, + data.clientId, + data.giteaUrl, + baseUrl, + ); + + // Open the Gitea OAuth URL + if (authUrl !== "#") { + window.open(authUrl, "_blank"); + } else { + toast.error("Configuration Incomplete", { + description: "Please fill in Client ID and Gitea URL first.", + }); + } + + toast.success("Gitea provider created successfully"); + setIsOpen(false); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Error configuring Gitea: ${error.message}`); + } else { + toast.error("An unknown error occurred."); + } + } }; return ( @@ -109,7 +143,7 @@ export const AddGiteaProvider = () => { Gitea - + Gitea Provider diff --git a/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx index b56c4845..1b05b82d 100644 --- a/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/gitea/edit-gitea-provider.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { getGiteaOAuthUrl } from "@/utils/gitea-utils"; import { useUrl } from "@/utils/hooks/use-url"; import { zodResolver } from "@hookform/resolvers/zod"; import { PenBoxIcon } from "lucide-react"; @@ -55,7 +56,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { useEffect(() => { const { connected, error } = router.query; - // Only process if router is ready and query parameters exist if (!router.isReady) return; if (connected) { @@ -64,7 +64,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { id: "gitea-connection-success", }); refetch(); - // Clear the query parameters to prevent re-triggering router.replace( { pathname: router.pathname, @@ -80,7 +79,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { description: decodeURIComponent(error as string), id: "gitea-connection-error", }); - // Clear the query parameters to prevent re-triggering router.replace( { pathname: router.pathname, @@ -102,7 +100,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { }, }); - // Update form values when data is loaded useEffect(() => { if (gitea) { form.reset({ @@ -141,7 +138,15 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { description: result, }); } catch (error: any) { - const authUrl = error.authorizationUrl || getGiteaOAuthUrl(); + const formValues = form.getValues(); + const authUrl = + error.authorizationUrl || + getGiteaOAuthUrl( + giteaId, + formValues.clientId, + formValues.giteaUrl, + typeof url === "string" ? url : (url as any).url || "", + ); toast.error("Gitea Not Connected", { description: @@ -157,32 +162,6 @@ export const EditGiteaProvider = ({ giteaId }: Props) => { } }; - // Generate Gitea OAuth URL with state parameter - const getGiteaOAuthUrl = () => { - const clientId = form.getValues().clientId; - const giteaUrl = form.getValues().giteaUrl; - - if (!clientId || !giteaUrl) { - toast.error("Configuration Incomplete", { - description: "Please fill in Client ID and Gitea URL first.", - }); - return "#"; - } - - const redirectUri = `${url}/api/providers/gitea/callback`; - - // Use the scopes from the gitea data (if available), else fallback to default scopes - const scopes = - gitea?.scopes?.split(",").join(" ") || - "repo repo:status read:user read:org"; - //const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org'; - - const state = giteaId; - - return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scopes)}&state=${encodeURIComponent(state)}`; - }; - - // Show loading state if data is being fetched if (isLoading) { return (