diff --git a/apps/dokploy/__test__/templates/config.template.test.ts b/apps/dokploy/__test__/templates/config.template.test.ts index 902e3163b..9516e9d1f 100644 --- a/apps/dokploy/__test__/templates/config.template.test.ts +++ b/apps/dokploy/__test__/templates/config.template.test.ts @@ -1,7 +1,7 @@ -import { describe, expect, it } from "vitest"; +import type { Schema } from "@dokploy/server/templates"; import type { CompleteTemplate } from "@dokploy/server/templates/processors"; import { processTemplate } from "@dokploy/server/templates/processors"; -import type { Schema } from "@dokploy/server/templates"; +import { describe, expect, it } from "vitest"; describe("processTemplate", () => { // Mock schema for testing diff --git a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx index 2a3f2f43a..0e848fece 100644 --- a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx @@ -1,3 +1,4 @@ +import { AlertBlock } from "@/components/shared/alert-block"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { @@ -32,7 +33,6 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { AlertBlock } from "@/components/shared/alert-block"; const ImportSchema = z.object({ base64: z.string(), diff --git a/apps/dokploy/components/dashboard/application/environment/show.tsx b/apps/dokploy/components/dashboard/application/environment/show.tsx index b574ce092..6f504959c 100644 --- a/apps/dokploy/components/dashboard/application/environment/show.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show.tsx @@ -4,10 +4,10 @@ import { Form } from "@/components/ui/form"; import { Secrets } from "@/components/ui/secrets"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { useEffect } from "react"; const addEnvironmentSchema = z.object({ env: z.string(), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index ca1bf823f..b506fbac5 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -1,4 +1,6 @@ +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { BitbucketIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const BitbucketProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx index 6f51af3d6..a1f3367dd 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx @@ -115,7 +115,11 @@ export const SaveDockerProvider = ({ applicationId }: Props) => { Username - + @@ -130,7 +134,12 @@ export const SaveDockerProvider = ({ applicationId }: Props) => { Password - + diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx index 3d6f6a388..a7020c59d 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx @@ -26,15 +26,15 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { KeyRoundIcon, LockIcon, X } from "lucide-react"; -import { useRouter } from "next/router"; import Link from "next/link"; +import { useRouter } from "next/router"; +import { GitIcon } from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { GitIcon } from "@/components/icons/data-tools-icons"; const GitProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), 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 b6bf21ca6..2351c8fba 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 @@ -235,7 +235,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { ? "Loading...." : field.value.owner ? repositories?.find( - (repo: GiteaRepository) => repo.name === field.value.repo, + (repo: GiteaRepository) => + repo.name === field.value.repo, )?.name : "Select repository"} @@ -327,7 +328,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { ? "Loading...." : field.value ? branches?.find( - (branch: GiteaBranch) => branch.name === field.value, + (branch: GiteaBranch) => + branch.name === field.value, )?.name : "Select branch"} @@ -411,4 +413,4 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { ); -}; \ No newline at end of file +}; diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx index 202c7f880..2a267a185 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx @@ -1,3 +1,5 @@ +import { GithubIcon } from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -34,17 +36,15 @@ 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"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import Link from "next/link"; -import { GithubIcon } from "@/components/icons/data-tools-icons"; const GithubProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index c0c90a016..0f8bb849e 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -1,4 +1,6 @@ +import { GitlabIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -35,17 +37,15 @@ 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"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import Link from "next/link"; -import { GitlabIcon } from "@/components/icons/data-tools-icons"; const GitlabProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index c2d86f86f..3f8854888 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -3,12 +3,12 @@ import { SaveGitProvider } from "@/components/dashboard/application/general/gene import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider"; import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider"; import { - BitbucketIcon, - DockerIcon, - GitIcon, - GiteaIcon, - GithubIcon, - GitlabIcon, + BitbucketIcon, + DockerIcon, + GitIcon, + GiteaIcon, + GithubIcon, + GitlabIcon, } from "@/components/icons/data-tools-icons"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -20,190 +20,197 @@ import { SaveBitbucketProvider } from "./save-bitbucket-provider"; import { SaveDragNDrop } from "./save-drag-n-drop"; import { SaveGitlabProvider } from "./save-gitlab-provider"; -type TabState = "github" | "docker" | "git" | "drop" | "gitlab" | "bitbucket" | "gitea"; +type TabState = + | "github" + | "docker" + | "git" + | "drop" + | "gitlab" + | "bitbucket" + | "gitea"; interface Props { - applicationId: string; + applicationId: string; } export const ShowProviderForm = ({ applicationId }: Props) => { - const { data: githubProviders } = api.github.githubProviders.useQuery(); - const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data: bitbucketProviders } = - api.bitbucket.bitbucketProviders.useQuery(); - const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); + const { data: githubProviders } = api.github.githubProviders.useQuery(); + const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); + const { data: bitbucketProviders } = + api.bitbucket.bitbucketProviders.useQuery(); + const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); - const { data: application } = api.application.one.useQuery({ applicationId }); - const [tab, setSab] = useState(application?.sourceType || "github"); - return ( - - - -
- Provider -

- Select the source of your code -

-
-
- -
-
-
- - { - setSab(e as TabState); - }} - > -
- - - - Github - - - - Gitlab - - - - Bitbucket - - - - Gitea - - - - Docker - - - - Git - - - - Drop - - -
+ const { data: application } = api.application.one.useQuery({ applicationId }); + const [tab, setSab] = useState(application?.sourceType || "github"); + return ( + + + +
+ Provider +

+ Select the source of your code +

+
+
+ +
+
+
+ + { + setSab(e as TabState); + }} + > +
+ + + + Github + + + + Gitlab + + + + Bitbucket + + + + Gitea + + + + Docker + + + + Git + + + + Drop + + +
- - {githubProviders && githubProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using GitHub, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - {gitlabProviders && gitlabProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using GitLab, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - {bitbucketProviders && bitbucketProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using Bitbucket, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - {giteaProviders && giteaProviders?.length > 0 ? ( - - ) : ( -
- - - To deploy using Gitea, you need to configure your account - first. Please, go to{" "} - - Settings - {" "} - to do so. - -
- )} -
- - - + + {githubProviders && githubProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using GitHub, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ + {gitlabProviders && gitlabProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using GitLab, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ + {bitbucketProviders && bitbucketProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using Bitbucket, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ + {giteaProviders && giteaProviders?.length > 0 ? ( + + ) : ( +
+ + + To deploy using Gitea, you need to configure your account + first. Please, go to{" "} + + Settings + {" "} + to do so. + +
+ )} +
+ + + - - - - - - -
-
-
- ); -}; \ No newline at end of file + + + + + + +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx index 2f0662805..41961c152 100644 --- a/apps/dokploy/components/dashboard/application/general/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/show.tsx @@ -10,8 +10,8 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { Ban, CheckCircle2, diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index f77619204..de5aa50df 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -7,8 +7,8 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react"; import { useRouter } from "next/router"; import { toast } from "sonner"; diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx index 6dc99b267..ff329a0af 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx @@ -1,4 +1,6 @@ +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { BitbucketIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const BitbucketProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx index ebe998923..68891e456 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-git-provider-compose.tsx @@ -1,3 +1,4 @@ +import { GitIcon } from "@/components/icons/data-tools-icons"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -27,13 +28,12 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { KeyRoundIcon, LockIcon, X } from "lucide-react"; +import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { GitIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GitProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx index b58347dc3..4f4c1d5ad 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx @@ -1,3 +1,4 @@ +import { GithubIcon } from "@/components/icons/data-tools-icons"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -39,12 +40,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { GithubIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GithubProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx index 693fea718..c191248ea 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-gitlab-provider-compose.tsx @@ -1,4 +1,6 @@ +import { GitlabIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -39,13 +41,11 @@ import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { Badge } from "@/components/ui/badge"; -import { GitlabIcon } from "@/components/icons/data-tools-icons"; -import Link from "next/link"; const GitlabProviderSchema = z.object({ composePath: z.string().min(1), diff --git a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx index 1eb13cadf..8ee9c786b 100644 --- a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx +++ b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx @@ -147,7 +147,9 @@ export const IsolatedDeployment = ({ composeId }: Props) => { render={({ field }) => (
- Enable Isolated Deployment ({data?.appName}) + + Enable Isolated Deployment ({data?.appName}) + Enable isolated deployment to the compose file. diff --git a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx b/apps/dokploy/components/dashboard/database/backups/add-backup.tsx index 219e42184..5b963a4c6 100644 --- a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/add-backup.tsx @@ -286,16 +286,21 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => { Keep the latest - + - Optional. If provided, only keeps the latest N backups in the cloud. + Optional. If provided, only keeps the latest N backups + in the cloud. ); }} - /> + /> ; } export const ShowBackups = ({ id, type }: Props) => { - const [activeManualBackup, setActiveManualBackup] = useState(); + const [activeManualBackup, setActiveManualBackup] = useState< + string | undefined + >(); const queryMap = { postgres: () => api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), @@ -142,7 +144,7 @@ export const ShowBackups = ({ id, type }: Props) => {
Keep Latest - {backup.keepLatestCount || 'All'} + {backup.keepLatestCount || "All"}
@@ -153,7 +155,10 @@ export const ShowBackups = ({ id, type }: Props) => { - ); - } + toast.error("Gitea Not Connected", { + description: + error.message || "Please complete the OAuth authorization process.", + action: + authUrl && authUrl !== "#" + ? { + label: "Authorize Now", + onClick: () => window.open(authUrl, "_blank"), + } + : undefined, + }); + } + }; - return ( - - - - - - - Edit Gitea Provider - - Update your Gitea provider details. - - -
- - ( - - Name - - - - - - )} - /> - ( - - Gitea URL - - - - - - )} - /> - ( - - Client ID - - - - - - )} - /> - ( - - Client Secret - - - - - - )} - /> + // Generate Gitea OAuth URL with state parameter + const getGiteaOAuthUrl = () => { + const clientId = form.getValues().clientId; + const giteaUrl = form.getValues().giteaUrl; -
- - - - - -
- - -
-
- ); -}; \ No newline at end of file + 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 ( + + ); + } + + return ( + + + + + + + Edit Gitea Provider + + Update your Gitea provider details. + + +
+ + ( + + Name + + + + + + )} + /> + ( + + Gitea URL + + + + + + )} + /> + ( + + Client ID + + + + + + )} + /> + ( + + Client Secret + + + + + + )} + /> + +
+ + + + + +
+ + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx index aeda489da..c63efa933 100644 --- a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx +++ b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx @@ -1,273 +1,285 @@ import { BitbucketIcon, + GiteaIcon, GithubIcon, GitlabIcon, - GiteaIcon, - } from "@/components/icons/data-tools-icons"; - import { DialogAction } from "@/components/shared/dialog-action"; - import { Button, buttonVariants } from "@/components/ui/button"; - import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; - import { api } from "@/utils/api"; - import { useUrl } from "@/utils/hooks/use-url"; - import { formatDate } from "date-fns"; - import { +} from "@/components/icons/data-tools-icons"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { api } from "@/utils/api"; +import { useUrl } from "@/utils/hooks/use-url"; +import { formatDate } from "date-fns"; +import { ExternalLinkIcon, GitBranch, ImportIcon, Loader2, Trash2, - } from "lucide-react"; - import Link from "next/link"; - import { toast } from "sonner"; - import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider"; - import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider"; - import { AddGithubProvider } from "./github/add-github-provider"; - import { EditGithubProvider } from "./github/edit-github-provider"; - import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; - import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; - import { AddGiteaProvider } from "./gitea/add-gitea-provider"; - import { EditGiteaProvider } from "./gitea/edit-gitea-provider"; - - export const ShowGitProviders = () => { - const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); - const { mutateAsync, isLoading: isRemoving } = api.gitProvider.remove.useMutation(); - const url = useUrl(); - - const getGitlabUrl = ( - clientId: string, - gitlabId: string, - gitlabUrl: string, - ) => { - const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`; - const scope = "api read_user read_repository"; - const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; - return authUrl; - }; - - const getGiteaUrl = ( - clientId: string, - giteaId: string, - giteaUrl: string, - ) => { - const redirectUri = `${url}/api/providers/gitea/callback?giteaId=${giteaId}`; - const scope = "repo"; - const authUrl = `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; - return authUrl; - }; - - return ( -
- -
- - - - Git Providers - - - Connect your Git provider for authentication. - - - - {isLoading ? ( -
- Loading... - -
- ) : ( - <> - {data?.length === 0 ? ( -
- - - Create your first Git Provider - -
-
-
- - - - -
-
-
-
- ) : ( -
-
- - Available Providers - -
-
- - - - -
-
-
- -
- {data?.map((gitProvider, _index) => { - const isGithub = gitProvider.providerType === "github"; - const isGitlab = gitProvider.providerType === "gitlab"; - const isBitbucket = gitProvider.providerType === "bitbucket"; - const isGitea = gitProvider.providerType === "gitea"; - - const haveGithubRequirements = isGithub && - gitProvider.github?.githubPrivateKey && - gitProvider.github?.githubAppId && - gitProvider.github?.githubInstallationId; - - const haveGitlabRequirements = isGitlab && - gitProvider.gitlab?.accessToken && - gitProvider.gitlab?.refreshToken; - - const haveGiteaRequirements = isGitea && - gitProvider.gitea?.accessToken && - gitProvider.gitea?.refreshToken; - - return ( -
-
-
-
- {isGithub && ( - - )} - {isGitlab && ( - - )} - {isBitbucket && ( - - )} - {isGitea && ( - - )} -
- - {gitProvider.name} - - - {formatDate( - gitProvider.createdAt, - "yyyy-MM-dd hh:mm:ss a" - )} - -
-
-
- -
- {!haveGithubRequirements && isGithub && ( -
- - - -
- )} - {haveGithubRequirements && isGithub && ( -
- - - -
- )} - {!haveGitlabRequirements && isGitlab && ( -
- - - -
- )} - {isGithub && haveGithubRequirements && ( - - )} - - {isGitlab && ( - - )} - - {isBitbucket && ( - - )} +} from "lucide-react"; +import Link from "next/link"; +import { toast } from "sonner"; +import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider"; +import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider"; +import { AddGiteaProvider } from "./gitea/add-gitea-provider"; +import { EditGiteaProvider } from "./gitea/edit-gitea-provider"; +import { AddGithubProvider } from "./github/add-github-provider"; +import { EditGithubProvider } from "./github/edit-github-provider"; +import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; +import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; - {isGitea && ( - - )} - - { - await mutateAsync({ - gitProviderId: gitProvider.gitProviderId, - }) - .then(() => { - toast.success("Git Provider deleted successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error deleting Git Provider"); - }); - }} - > - - -
-
+export const ShowGitProviders = () => { + const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); + const { mutateAsync, isLoading: isRemoving } = + api.gitProvider.remove.useMutation(); + const url = useUrl(); + + const getGitlabUrl = ( + clientId: string, + gitlabId: string, + gitlabUrl: string, + ) => { + const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`; + const scope = "api read_user read_repository"; + const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; + return authUrl; + }; + + return ( +
+ +
+ + + + Git Providers + + + Connect your Git provider for authentication. + + + + {isLoading ? ( +
+ Loading... +
- ); - })} -
- -
- {/* */} -
-
- )} - - )} - -
- -
+ ) : ( + <> + {data?.length === 0 ? ( +
+ + + Create your first Git Provider + +
+
+
+ + + + +
+
+
+
+ ) : ( +
+
+ + Available Providers + +
+
+ + + + +
+
+
+ +
+ {data?.map((gitProvider, _index) => { + const isGithub = gitProvider.providerType === "github"; + const isGitlab = gitProvider.providerType === "gitlab"; + const isBitbucket = + gitProvider.providerType === "bitbucket"; + const isGitea = gitProvider.providerType === "gitea"; + + const haveGithubRequirements = + isGithub && + gitProvider.github?.githubPrivateKey && + gitProvider.github?.githubAppId && + gitProvider.github?.githubInstallationId; + + const haveGitlabRequirements = + isGitlab && + gitProvider.gitlab?.accessToken && + gitProvider.gitlab?.refreshToken; + + const haveGiteaRequirements = + isGitea && + gitProvider.gitea?.accessToken && + gitProvider.gitea?.refreshToken; + + return ( +
+
+
+
+ {isGithub && ( + + )} + {isGitlab && ( + + )} + {isBitbucket && ( + + )} + {isGitea && } +
+ + {gitProvider.name} + + + {formatDate( + gitProvider.createdAt, + "yyyy-MM-dd hh:mm:ss a", + )} + +
+
+
+ +
+ {!haveGithubRequirements && isGithub && ( +
+ + + +
+ )} + {haveGithubRequirements && isGithub && ( +
+ + + +
+ )} + {!haveGitlabRequirements && isGitlab && ( +
+ + + +
+ )} + {isGithub && haveGithubRequirements && ( + + )} + + {isGitlab && ( + + )} + + {isBitbucket && ( + + )} + + {isGitea && ( + + )} + + { + await mutateAsync({ + gitProviderId: gitProvider.gitProviderId, + }) + .then(() => { + toast.success( + "Git Provider deleted successfully", + ); + refetch(); + }) + .catch(() => { + toast.error( + "Error deleting Git Provider", + ); + }); + }} + > + + +
+
+
+ ); + })} +
+ +
+ {/* */} +
+
+ )} + + )} + +
+ +
); - }; \ No newline at end of file +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 4b6516634..4771edd95 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -33,6 +33,7 @@ import { useTranslation } from "next-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; import { toast } from "sonner"; +import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; import { TerminalModal } from "../web-server/terminal-modal"; import { ShowServerActions } from "./actions/show-server-actions"; import { HandleServers } from "./handle-servers"; @@ -42,7 +43,6 @@ import { ShowMonitoringModal } from "./show-monitoring-modal"; import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal"; import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription"; -import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; export const ShowServers = () => { const { t } = useTranslation("settings"); diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx index a19d4fb78..aa7a33979 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, @@ -22,7 +23,6 @@ import dynamic from "next/dynamic"; import type React from "react"; import { useEffect, useState } from "react"; import { badgeStateColor } from "../../application/logs/show"; -import { Badge } from "@/components/ui/badge"; export const DockerLogsId = dynamic( () => diff --git a/apps/dokploy/components/dashboard/shared/rebuild-database.tsx b/apps/dokploy/components/dashboard/shared/rebuild-database.tsx index e3628130f..cb6a643df 100644 --- a/apps/dokploy/components/dashboard/shared/rebuild-database.tsx +++ b/apps/dokploy/components/dashboard/shared/rebuild-database.tsx @@ -12,7 +12,7 @@ import { import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { DatabaseIcon, AlertTriangle } from "lucide-react"; +import { AlertTriangle, DatabaseIcon } from "lucide-react"; import { toast } from "sonner"; interface Props { diff --git a/apps/dokploy/components/dashboard/shared/show-database-advanced-settings.tsx b/apps/dokploy/components/dashboard/shared/show-database-advanced-settings.tsx index c6c4c4142..5d7b6f6b6 100644 --- a/apps/dokploy/components/dashboard/shared/show-database-advanced-settings.tsx +++ b/apps/dokploy/components/dashboard/shared/show-database-advanced-settings.tsx @@ -1,6 +1,6 @@ -import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command"; import { ShowResources } from "@/components/dashboard/application/advanced/show-resources"; import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes"; +import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command"; import { RebuildDatabase } from "./rebuild-database"; interface Props { diff --git a/apps/dokploy/components/icons/data-tools-icons.tsx b/apps/dokploy/components/icons/data-tools-icons.tsx index 785b47e9b..806be0a6e 100644 --- a/apps/dokploy/components/icons/data-tools-icons.tsx +++ b/apps/dokploy/components/icons/data-tools-icons.tsx @@ -241,34 +241,34 @@ export const BitbucketIcon = ({ className }: Props) => { export const GiteaIcon = ({ className }: Props) => { return ( - - - - + + + + + + - - ); }; diff --git a/apps/dokploy/components/layouts/update-server.tsx b/apps/dokploy/components/layouts/update-server.tsx index 9e37c80b2..42cac69f4 100644 --- a/apps/dokploy/components/layouts/update-server.tsx +++ b/apps/dokploy/components/layouts/update-server.tsx @@ -1,10 +1,10 @@ import { api } from "@/utils/api"; import type { IUpdateData } from "@dokploy/server/index"; +import { Download } from "lucide-react"; import { useRouter } from "next/router"; import { useEffect, useRef, useState } from "react"; import UpdateServer from "../dashboard/settings/web-server/update-server"; import { Button } from "../ui/button"; -import { Download } from "lucide-react"; import { Tooltip, TooltipContent, diff --git a/apps/dokploy/components/shared/breadcrumb-sidebar.tsx b/apps/dokploy/components/shared/breadcrumb-sidebar.tsx index 5f6add3ec..f9448733a 100644 --- a/apps/dokploy/components/shared/breadcrumb-sidebar.tsx +++ b/apps/dokploy/components/shared/breadcrumb-sidebar.tsx @@ -37,7 +37,9 @@ export const BreadcrumbSidebar = ({ list }: Props) => { )} - {_index + 1 < list.length && } + {_index + 1 < list.length && ( + + )} ))} diff --git a/apps/dokploy/components/shared/code-editor.tsx b/apps/dokploy/components/shared/code-editor.tsx index 4acd2a6fc..d8eeffe55 100644 --- a/apps/dokploy/components/shared/code-editor.tsx +++ b/apps/dokploy/components/shared/code-editor.tsx @@ -3,18 +3,18 @@ import { json } from "@codemirror/lang-json"; import { yaml } from "@codemirror/lang-yaml"; import { StreamLanguage } from "@codemirror/language"; +import { + type Completion, + type CompletionContext, + type CompletionResult, + autocompletion, +} from "@codemirror/autocomplete"; import { properties } from "@codemirror/legacy-modes/mode/properties"; import { shell } from "@codemirror/legacy-modes/mode/shell"; import { EditorView } from "@codemirror/view"; import { githubDark, githubLight } from "@uiw/codemirror-theme-github"; import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror"; import { useTheme } from "next-themes"; -import { - autocompletion, - type CompletionContext, - type CompletionResult, - type Completion, -} from "@codemirror/autocomplete"; // Docker Compose completion options const dockerComposeServices = [ diff --git a/apps/dokploy/components/ui/calendar.tsx b/apps/dokploy/components/ui/calendar.tsx index 5141bae94..6e05e4807 100644 --- a/apps/dokploy/components/ui/calendar.tsx +++ b/apps/dokploy/components/ui/calendar.tsx @@ -1,9 +1,9 @@ -import type * as React from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; +import type * as React from "react"; import { DayPicker } from "react-day-picker"; -import { cn } from "@/lib/utils"; import { buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; export type CalendarProps = React.ComponentProps; diff --git a/apps/dokploy/drizzle/0076_tough_iron_patriot.sql b/apps/dokploy/drizzle/0076_tough_iron_patriot.sql new file mode 100644 index 000000000..aec64e484 --- /dev/null +++ b/apps/dokploy/drizzle/0076_tough_iron_patriot.sql @@ -0,0 +1,157 @@ +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND + enumtypid = (SELECT oid FROM pg_type WHERE typname = 'sourceType')) THEN + ALTER TYPE "public"."sourceType" ADD VALUE 'gitea' BEFORE 'drop'; + END IF; +END +$$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND + enumtypid = (SELECT oid FROM pg_type WHERE typname = 'sourceTypeCompose')) THEN + ALTER TYPE "public"."sourceTypeCompose" ADD VALUE 'gitea' BEFORE 'raw'; + END IF; +END +$$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND + enumtypid = (SELECT oid FROM pg_type WHERE typname = 'gitProviderType')) THEN + ALTER TYPE "public"."gitProviderType" ADD VALUE 'gitea'; + END IF; +END +$$; +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'gitea') THEN + CREATE TABLE "gitea" ( + "giteaId" text PRIMARY KEY NOT NULL, + "giteaUrl" text DEFAULT 'https://gitea.com' NOT NULL, + "redirect_uri" text, + "client_id" text, + "client_secret" text, + "gitProviderId" text NOT NULL, + "gitea_username" text, + "access_token" text, + "refresh_token" text, + "expires_at" integer, + "scopes" text DEFAULT 'repo,repo:status,read:user,read:org', + "last_authenticated_at" integer + ); + END IF; +END +$$; +--> statement-breakpoint +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaProjectId') THEN + ALTER TABLE "application" ADD COLUMN "giteaProjectId" integer; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaRepository') THEN + ALTER TABLE "application" ADD COLUMN "giteaRepository" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaOwner') THEN + ALTER TABLE "application" ADD COLUMN "giteaOwner" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaBranch') THEN + ALTER TABLE "application" ADD COLUMN "giteaBranch" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaBuildPath') THEN + ALTER TABLE "application" ADD COLUMN "giteaBuildPath" text DEFAULT '/'; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaPathNamespace') THEN + ALTER TABLE "application" ADD COLUMN "giteaPathNamespace" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaId') THEN + ALTER TABLE "application" ADD COLUMN "giteaId" text; + END IF; +END $$; +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaRepository') THEN + ALTER TABLE "compose" ADD COLUMN "giteaRepository" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaOwner') THEN + ALTER TABLE "compose" ADD COLUMN "giteaOwner" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaBranch') THEN + ALTER TABLE "compose" ADD COLUMN "giteaBranch" text; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaId') THEN + ALTER TABLE "compose" ADD COLUMN "giteaId" text; + END IF; +END $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'gitea_gitProviderId_git_provider_gitProviderId_fk' + ) THEN + ALTER TABLE "gitea" ADD CONSTRAINT "gitea_gitProviderId_git_provider_gitProviderId_fk" + FOREIGN KEY ("gitProviderId") REFERENCES "public"."git_provider"("gitProviderId") + ON DELETE cascade ON UPDATE no action; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'application_giteaId_gitea_giteaId_fk' + ) THEN + ALTER TABLE "application" ADD CONSTRAINT "application_giteaId_gitea_giteaId_fk" + FOREIGN KEY ("giteaId") REFERENCES "public"."gitea"("giteaId") + ON DELETE set null ON UPDATE no action; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'compose_giteaId_gitea_giteaId_fk' + ) THEN + ALTER TABLE "compose" ADD CONSTRAINT "compose_giteaId_gitea_giteaId_fk" + FOREIGN KEY ("giteaId") REFERENCES "public"."gitea"("giteaId") + ON DELETE set null ON UPDATE no action; + END IF; +END $$; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0076_snapshot.json b/apps/dokploy/drizzle/meta/0076_snapshot.json index 56817ca7c..92289ab85 100644 --- a/apps/dokploy/drizzle/meta/0076_snapshot.json +++ b/apps/dokploy/drizzle/meta/0076_snapshot.json @@ -1,6 +1,6 @@ { - "id": "27ba0f3d-859f-4233-a179-8aee11ad9179", - "prevId": "dd51ff04-a160-4d0d-b72f-4916493b740f", + "id": "7c8508ba-01eb-487e-88cf-0abe10916904", + "prevId": "f74127dc-3cda-4091-988f-97b60eb22427", "version": "7", "dialect": "postgresql", "tables": { @@ -179,6 +179,13 @@ "notNull": true, "default": "'github'" }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, "repository": { "name": "repository", "type": "text", @@ -247,6 +254,43 @@ "primaryKey": false, "notNull": false }, + "giteaProjectId": { + "name": "giteaProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBuildPath": { + "name": "giteaBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "giteaPathNamespace": { + "name": "giteaPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, "bitbucketRepository": { "name": "bitbucketRepository", "type": "text", @@ -458,6 +502,12 @@ "primaryKey": false, "notNull": false }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, "bitbucketId": { "name": "bitbucketId", "type": "text", @@ -538,6 +588,19 @@ "onDelete": "set null", "onUpdate": "no action" }, + "application_giteaId_gitea_giteaId_fk": { + "name": "application_giteaId_gitea_giteaId_fk", + "tableFrom": "application", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, "application_bitbucketId_bitbucket_bitbucketId_fk": { "name": "application_bitbucketId_bitbucket_bitbucketId_fk", "tableFrom": "application", @@ -1703,6 +1766,12 @@ "primaryKey": false, "notNull": true }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, "databaseType": { "name": "databaseType", "type": "databaseType", @@ -2908,6 +2977,24 @@ "primaryKey": false, "notNull": false }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, "customGitUrl": { "name": "customGitUrl", "type": "text", @@ -3005,6 +3092,12 @@ "primaryKey": false, "notNull": false }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, "serverId": { "name": "serverId", "type": "text", @@ -3079,6 +3172,19 @@ "onDelete": "set null", "onUpdate": "no action" }, + "compose_giteaId_gitea_giteaId_fk": { + "name": "compose_giteaId_gitea_giteaId_fk", + "tableFrom": "compose", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, "compose_serverId_server_serverId_fk": { "name": "compose_serverId_server_serverId_fk", "tableFrom": "compose", @@ -3937,6 +4043,107 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.gitea": { + "name": "gitea", + "schema": "", + "columns": { + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "giteaUrl": { + "name": "giteaUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitea.com'" + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gitea_username": { + "name": "gitea_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'repo,repo:status,read:user,read:org'" + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gitea_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitea_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitea", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.server": { "name": "server", "schema": "", @@ -5001,6 +5208,7 @@ "github", "gitlab", "bitbucket", + "gitea", "drop" ] }, @@ -5097,6 +5305,7 @@ "github", "gitlab", "bitbucket", + "gitea", "raw" ] }, @@ -5125,7 +5334,8 @@ "values": [ "github", "gitlab", - "bitbucket" + "bitbucket", + "gitea" ] }, "public.serverStatus": { diff --git a/apps/dokploy/drizzle/meta/0077_snapshot.json b/apps/dokploy/drizzle/meta/0077_snapshot.json index d59a7b4dd..5bb31889d 100644 --- a/apps/dokploy/drizzle/meta/0077_snapshot.json +++ b/apps/dokploy/drizzle/meta/0077_snapshot.json @@ -1,6 +1,6 @@ { - "id": "d4d95ab9-16bd-4583-949e-d6ae0a0bb7a0", - "prevId": "27ba0f3d-859f-4233-a179-8aee11ad9179", + "id": "c8f3ee9b-66c6-4a23-a151-78e04363bdf9", + "prevId": "f74127dc-3cda-4091-988f-97b60eb22427", "version": "7", "dialect": "postgresql", "tables": { @@ -179,6 +179,13 @@ "notNull": true, "default": "'github'" }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, "repository": { "name": "repository", "type": "text", @@ -247,6 +254,43 @@ "primaryKey": false, "notNull": false }, + "giteaProjectId": { + "name": "giteaProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBuildPath": { + "name": "giteaBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "giteaPathNamespace": { + "name": "giteaPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, "bitbucketRepository": { "name": "bitbucketRepository", "type": "text", @@ -458,6 +502,12 @@ "primaryKey": false, "notNull": false }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, "bitbucketId": { "name": "bitbucketId", "type": "text", @@ -538,6 +588,19 @@ "onDelete": "set null", "onUpdate": "no action" }, + "application_giteaId_gitea_giteaId_fk": { + "name": "application_giteaId_gitea_giteaId_fk", + "tableFrom": "application", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, "application_bitbucketId_bitbucket_bitbucketId_fk": { "name": "application_bitbucketId_bitbucket_bitbucketId_fk", "tableFrom": "application", @@ -2914,6 +2977,24 @@ "primaryKey": false, "notNull": false }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, "customGitUrl": { "name": "customGitUrl", "type": "text", @@ -3011,6 +3092,12 @@ "primaryKey": false, "notNull": false }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, "serverId": { "name": "serverId", "type": "text", @@ -3085,6 +3172,19 @@ "onDelete": "set null", "onUpdate": "no action" }, + "compose_giteaId_gitea_giteaId_fk": { + "name": "compose_giteaId_gitea_giteaId_fk", + "tableFrom": "compose", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, "compose_serverId_server_serverId_fk": { "name": "compose_serverId_server_serverId_fk", "tableFrom": "compose", @@ -3943,6 +4043,107 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.gitea": { + "name": "gitea", + "schema": "", + "columns": { + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "giteaUrl": { + "name": "giteaUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitea.com'" + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gitea_username": { + "name": "gitea_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'repo,repo:status,read:user,read:org'" + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gitea_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitea_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitea", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.server": { "name": "server", "schema": "", @@ -5007,6 +5208,7 @@ "github", "gitlab", "bitbucket", + "gitea", "drop" ] }, @@ -5103,6 +5305,7 @@ "github", "gitlab", "bitbucket", + "gitea", "raw" ] }, @@ -5131,7 +5334,8 @@ "values": [ "github", "gitlab", - "bitbucket" + "bitbucket", + "gitea" ] }, "public.serverStatus": { diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index 5ebe55654..3baae60bf 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -534,10 +534,31 @@ "tag": "0075_wild_xorn", "breakpoints": true }, + { + "idx": 76, + "version": "7", + "when": 1742237840762, + "tag": "0076_tough_iron_patriot", + "breakpoints": true + }, + { + "idx": 77, + "version": "7", + "when": 1742241730000, + "tag": "0076_young_sharon_ventura", + "breakpoints": true + }, { "idx": 78, "version": "7", - "when": 1742112194375, + "when": 1742241730001, + "tag": "0077_chemical_dreadnoughts", + "breakpoints": true + }, + { + "idx": 79, + "version": "7", + "when": 17422417300002, "tag": "0078_uneven_omega_sentinel", "breakpoints": true } diff --git a/apps/dokploy/drizzle/meta/_journal.json.backup b/apps/dokploy/drizzle/meta/_journal.json.backup new file mode 100644 index 000000000..670349d20 --- /dev/null +++ b/apps/dokploy/drizzle/meta/_journal.json.backup @@ -0,0 +1,552 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1713262741218, + "tag": "0000_reflective_puck", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1713761637676, + "tag": "0001_striped_tattoo", + "breakpoints": true + }, + { + "idx": 2, + "version": "5", + "when": 1713763492341, + "tag": "0002_ambiguous_carlie_cooper", + "breakpoints": true + }, + { + "idx": 3, + "version": "5", + "when": 1713947141424, + "tag": "0003_square_lightspeed", + "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1714004732716, + "tag": "0004_nice_tenebrous", + "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1715551130605, + "tag": "0005_cute_terror", + "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1715563165991, + "tag": "0006_oval_jimmy_woo", + "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1715563497100, + "tag": "0007_cute_guardsmen", + "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1715564143641, + "tag": "0008_lazy_sage", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1715564774423, + "tag": "0009_majestic_spencer_smythe", + "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1715574037832, + "tag": "0010_lean_black_widow", + "breakpoints": true + }, + { + "idx": 11, + "version": "6", + "when": 1715574230599, + "tag": "0011_petite_calypso", + "breakpoints": true + }, + { + "idx": 12, + "version": "6", + "when": 1716015716708, + "tag": "0012_chubby_umar", + "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1716076179443, + "tag": "0013_blushing_starjammers", + "breakpoints": true + }, + { + "idx": 14, + "version": "6", + "when": 1716715367982, + "tag": "0014_same_hammerhead", + "breakpoints": true + }, + { + "idx": 15, + "version": "6", + "when": 1717564517104, + "tag": "0015_fearless_callisto", + "breakpoints": true + }, + { + "idx": 16, + "version": "6", + "when": 1719109196484, + "tag": "0016_chunky_leopardon", + "breakpoints": true + }, + { + "idx": 17, + "version": "6", + "when": 1719547174326, + "tag": "0017_minor_post", + "breakpoints": true + }, + { + "idx": 18, + "version": "6", + "when": 1719928377858, + "tag": "0018_careful_killmonger", + "breakpoints": true + }, + { + "idx": 19, + "version": "6", + "when": 1721110706912, + "tag": "0019_heavy_freak", + "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1721363861686, + "tag": "0020_fantastic_slapstick", + "breakpoints": true + }, + { + "idx": 21, + "version": "6", + "when": 1721370423752, + "tag": "0021_premium_sebastian_shaw", + "breakpoints": true + }, + { + "idx": 22, + "version": "6", + "when": 1721531163852, + "tag": "0022_warm_colonel_america", + "breakpoints": true + }, + { + "idx": 23, + "version": "6", + "when": 1721542782659, + "tag": "0023_icy_maverick", + "breakpoints": true + }, + { + "idx": 24, + "version": "6", + "when": 1721603595092, + "tag": "0024_dapper_supernaut", + "breakpoints": true + }, + { + "idx": 25, + "version": "6", + "when": 1721633853118, + "tag": "0025_lying_mephisto", + "breakpoints": true + }, + { + "idx": 26, + "version": "6", + "when": 1721979220929, + "tag": "0026_known_dormammu", + "breakpoints": true + }, + { + "idx": 27, + "version": "6", + "when": 1722445099203, + "tag": "0027_red_lady_bullseye", + "breakpoints": true + }, + { + "idx": 28, + "version": "6", + "when": 1722503439951, + "tag": "0028_jittery_eternity", + "breakpoints": true + }, + { + "idx": 29, + "version": "6", + "when": 1722578386823, + "tag": "0029_colossal_zodiak", + "breakpoints": true + }, + { + "idx": 30, + "version": "6", + "when": 1723608499147, + "tag": "0030_little_kabuki", + "breakpoints": true + }, + { + "idx": 31, + "version": "6", + "when": 1723701656243, + "tag": "0031_steep_vulture", + "breakpoints": true + }, + { + "idx": 32, + "version": "6", + "when": 1723705257806, + "tag": "0032_flashy_shadow_king", + "breakpoints": true + }, + { + "idx": 33, + "version": "6", + "when": 1725250322137, + "tag": "0033_white_hawkeye", + "breakpoints": true + }, + { + "idx": 34, + "version": "6", + "when": 1725256397019, + "tag": "0034_aspiring_secret_warriors", + "breakpoints": true + }, + { + "idx": 35, + "version": "6", + "when": 1725429324584, + "tag": "0035_cool_gravity", + "breakpoints": true + }, + { + "idx": 36, + "version": "6", + "when": 1725519351871, + "tag": "0036_tired_ronan", + "breakpoints": true + }, + { + "idx": 37, + "version": "6", + "when": 1726988289562, + "tag": "0037_legal_namor", + "breakpoints": true + }, + { + "idx": 38, + "version": "6", + "when": 1727942090102, + "tag": "0038_rapid_landau", + "breakpoints": true + }, + { + "idx": 39, + "version": "6", + "when": 1728021127765, + "tag": "0039_many_tiger_shark", + "breakpoints": true + }, + { + "idx": 40, + "version": "6", + "when": 1728780577084, + "tag": "0040_graceful_wolfsbane", + "breakpoints": true + }, + { + "idx": 41, + "version": "6", + "when": 1729667438853, + "tag": "0041_huge_bruce_banner", + "breakpoints": true + }, + { + "idx": 42, + "version": "6", + "when": 1729984439862, + "tag": "0042_fancy_havok", + "breakpoints": true + }, + { + "idx": 43, + "version": "6", + "when": 1731873965888, + "tag": "0043_closed_naoko", + "breakpoints": true + }, + { + "idx": 44, + "version": "6", + "when": 1731875539532, + "tag": "0044_sour_true_believers", + "breakpoints": true + }, + { + "idx": 45, + "version": "6", + "when": 1732644181718, + "tag": "0045_smiling_blur", + "breakpoints": true + }, + { + "idx": 46, + "version": "6", + "when": 1732851191048, + "tag": "0046_purple_sleeper", + "breakpoints": true + }, + { + "idx": 47, + "version": "6", + "when": 1733599090582, + "tag": "0047_tidy_revanche", + "breakpoints": true + }, + { + "idx": 48, + "version": "6", + "when": 1733599163710, + "tag": "0048_flat_expediter", + "breakpoints": true + }, + { + "idx": 49, + "version": "6", + "when": 1733628762978, + "tag": "0049_dark_leopardon", + "breakpoints": true + }, + { + "idx": 50, + "version": "6", + "when": 1733889104203, + "tag": "0050_nappy_wrecker", + "breakpoints": true + }, + { + "idx": 51, + "version": "6", + "when": 1734241482851, + "tag": "0051_hard_gorgon", + "breakpoints": true + }, + { + "idx": 52, + "version": "6", + "when": 1734809337308, + "tag": "0052_bumpy_luckman", + "breakpoints": true + }, + { + "idx": 53, + "version": "6", + "when": 1735118844878, + "tag": "0053_broken_kulan_gath", + "breakpoints": true + }, + { + "idx": 54, + "version": "6", + "when": 1736669421560, + "tag": "0054_nervous_spencer_smythe", + "breakpoints": true + }, + { + "idx": 55, + "version": "6", + "when": 1736669623831, + "tag": "0055_next_serpent_society", + "breakpoints": true + }, + { + "idx": 56, + "version": "6", + "when": 1736789918294, + "tag": "0056_majestic_skaar", + "breakpoints": true + }, + { + "idx": 57, + "version": "6", + "when": 1737306063563, + "tag": "0057_tricky_living_tribunal", + "breakpoints": true + }, + { + "idx": 58, + "version": "6", + "when": 1737612903012, + "tag": "0058_brown_sharon_carter", + "breakpoints": true + }, + { + "idx": 59, + "version": "6", + "when": 1737615160768, + "tag": "0059_striped_bill_hollister", + "breakpoints": true + }, + { + "idx": 60, + "version": "6", + "when": 1737929896838, + "tag": "0060_disable-aggressive-cache", + "breakpoints": true + }, + { + "idx": 61, + "version": "7", + "when": 1738481304953, + "tag": "0061_many_molten_man", + "breakpoints": true + }, + { + "idx": 62, + "version": "7", + "when": 1738482795112, + "tag": "0062_slippery_white_tiger", + "breakpoints": true + }, + { + "idx": 63, + "version": "7", + "when": 1738522845992, + "tag": "0063_panoramic_dreadnoughts", + "breakpoints": true + }, + { + "idx": 64, + "version": "7", + "when": 1738564387043, + "tag": "0064_previous_agent_brand", + "breakpoints": true + }, + { + "idx": 65, + "version": "7", + "when": 1739087857244, + "tag": "0065_daily_zaladane", + "breakpoints": true + }, + { + "idx": 66, + "version": "7", + "when": 1739426913392, + "tag": "0066_yielding_echo", + "breakpoints": true + }, + { + "idx": 67, + "version": "7", + "when": 1740892043121, + "tag": "0067_condemned_sugar_man", + "breakpoints": true + }, + { + "idx": 68, + "version": "7", + "when": 1740897756774, + "tag": "0068_complex_rhino", + "breakpoints": true + }, + { + "idx": 69, + "version": "7", + "when": 1741152916611, + "tag": "0069_legal_bill_hollister", + "breakpoints": true + }, + { + "idx": 70, + "version": "7", + "when": 1741322697251, + "tag": "0070_useful_serpent_society", + "breakpoints": true + }, + { + "idx": 71, + "version": "7", + "when": 1741559743256, + "tag": "0071_flimsy_plazm", + "breakpoints": true + }, + { + "idx": 72, + "version": "7", + "when": 1741593124105, + "tag": "0072_low_redwing", + "breakpoints": true + }, + { + "idx": 73, + "version": "7", + "when": 1741645208694, + "tag": "0073_dark_tigra", + "breakpoints": true + }, + { + "idx": 74, + "version": "7", + "when": 1741673569715, + "tag": "0074_military_miss_america", + "breakpoints": true + }, + { + "idx": 75, + "version": "7", + "when": 1742018928109, + "tag": "0075_wild_xorn", + "breakpoints": true + }, + { + "idx": 76, + "version": "7", + "when": 1742237840762, + "tag": "0076_tough_iron_patriot", + "breakpoints": true + }, + { + "idx": 77, + "version": "7", + "when": 1742238314349, + "tag": "0077_mature_tomorrow_man", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/dokploy/next.config.mjs b/apps/dokploy/next.config.mjs index 016bc13df..1ebd4f22a 100644 --- a/apps/dokploy/next.config.mjs +++ b/apps/dokploy/next.config.mjs @@ -5,23 +5,23 @@ /** @type {import("next").NextConfig} */ const nextConfig = { - reactStrictMode: true, - eslint: { - ignoreDuringBuilds: true, - }, - typescript: { - ignoreBuildErrors: true, - }, - transpilePackages: ["@dokploy/server"], - /** - * If you are using `appDir` then you must comment the below `i18n` config out. - * - * @see https://github.com/vercel/next.js/issues/41980 - */ - i18n: { - locales: ["en"], - defaultLocale: "en", - }, + reactStrictMode: true, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + transpilePackages: ["@dokploy/server"], + /** + * If you are using `appDir` then you must comment the below `i18n` config out. + * + * @see https://github.com/vercel/next.js/issues/41980 + */ + i18n: { + locales: ["en"], + defaultLocale: "en", + }, }; export default nextConfig; diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 21465adfc..44201c119 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -84,7 +84,7 @@ "@trpc/client": "^10.43.6", "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", - "@trpc/server": "^10.43.6", + "@trpc/server": "^10.45.2", "@uiw/codemirror-theme-github": "^4.22.1", "@uiw/react-codemirror": "^4.22.1", "@xterm/addon-attach": "0.10.0", @@ -162,7 +162,7 @@ "@types/js-yaml": "4.0.9", "@types/lodash": "4.17.4", "@types/micromatch": "4.0.9", - "@types/node": "^18.17.0", + "@types/node": "^18.19.42", "@types/node-os-utils": "1.3.4", "@types/node-schedule": "2.1.6", "@types/nodemailer": "^6.4.15", @@ -197,8 +197,6 @@ ] }, "commitlint": { - "extends": [ - "@commitlint/config-conventional" - ] + "extends": ["@commitlint/config-conventional"] } } diff --git a/apps/dokploy/pages/api/deploy/[refreshToken].ts b/apps/dokploy/pages/api/deploy/[refreshToken].ts index bb453d201..d54c8f40b 100644 --- a/apps/dokploy/pages/api/deploy/[refreshToken].ts +++ b/apps/dokploy/pages/api/deploy/[refreshToken].ts @@ -112,13 +112,6 @@ export default async function handler( res.status(301).json({ message: "Branch Not Match" }); return; } -<<<<<<< HEAD - } else if (sourceType === "gitea") { - const branchName = extractBranchName(req.headers, req.body); - if (!branchName || branchName !== application.giteaBranch) { - res.status(301).json({ message: "Branch Not Match" }); - return; -======= const commitedPaths = await extractCommitedPaths( req.body, @@ -134,7 +127,12 @@ export default async function handler( if (!shouldDeployPaths) { res.status(301).json({ message: "Watch Paths Not Match" }); return; ->>>>>>> fork/canary + } + } else if (sourceType === "gitea") { + const branchName = extractBranchName(req.headers, req.body); + if (!branchName || branchName !== application.giteaBranch) { + res.status(301).json({ message: "Branch Not Match" }); + return; } } diff --git a/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts b/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts index 951f7a71d..ab28cfac5 100644 --- a/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts +++ b/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts @@ -8,8 +8,8 @@ import { eq } from "drizzle-orm"; import type { NextApiRequest, NextApiResponse } from "next"; import { extractBranchName, - extractCommitedPaths, extractCommitMessage, + extractCommitedPaths, extractHash, } from "../[refreshToken]"; diff --git a/apps/dokploy/pages/api/providers/gitea/authorize.ts b/apps/dokploy/pages/api/providers/gitea/authorize.ts index 1bd5c1ba4..8ab0e70f9 100644 --- a/apps/dokploy/pages/api/providers/gitea/authorize.ts +++ b/apps/dokploy/pages/api/providers/gitea/authorize.ts @@ -1,52 +1,41 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import { findGiteaById } from '@dokploy/server'; +import type { NextApiRequest, NextApiResponse } from "next"; +import { findGitea, redirectWithError } from "./helper"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - try { - if (req.method !== 'GET') { - return res.status(405).json({ error: 'Method not allowed' }); - } + try { + if (req.method !== "GET") { + return res.status(405).json({ error: "Method not allowed" }); + } - const { giteaId } = req.query; + const { giteaId } = req.query; - if (!giteaId || Array.isArray(giteaId)) { - return res.status(400).json({ error: 'Invalid Gitea provider ID' }); - } + if (!giteaId || Array.isArray(giteaId)) { + return res.status(400).json({ error: "Invalid Gitea provider ID" }); + } - let gitea; - try { - gitea = await findGiteaById(giteaId); - } catch (findError) { - console.error('Error finding Gitea provider:', findError); - return res.status(404).json({ error: 'Failed to find Gitea provider' }); - } + const gitea = await findGitea(giteaId as string); + if (!gitea || !gitea.clientId || !gitea.redirectUri) { + return redirectWithError(res, "Incomplete OAuth configuration"); + } - if (!gitea.clientId || !gitea.redirectUri) { - return res.status(400).json({ - error: 'Incomplete OAuth configuration', - missingClientId: !gitea.clientId, - missingRedirectUri: !gitea.redirectUri - }); - } + // Generate the Gitea authorization URL + const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`); + authorizationUrl.searchParams.append("client_id", gitea.clientId as string); + authorizationUrl.searchParams.append("response_type", "code"); + authorizationUrl.searchParams.append( + "redirect_uri", + gitea.redirectUri as string, + ); + authorizationUrl.searchParams.append("scope", "read:user repo"); + authorizationUrl.searchParams.append("state", giteaId as string); - // Use the state parameter to pass the giteaId - // This is more secure than adding it to the redirect URI - const state = giteaId; - - const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`); - authorizationUrl.searchParams.append('client_id', gitea.clientId); - authorizationUrl.searchParams.append('response_type', 'code'); - authorizationUrl.searchParams.append('redirect_uri', gitea.redirectUri); - authorizationUrl.searchParams.append('scope', 'read:user repo'); - authorizationUrl.searchParams.append('state', state); - - // Redirect the user to the Gitea authorization page - res.redirect(307, authorizationUrl.toString()); - } catch (error) { - console.error('Error initiating Gitea OAuth flow:', error); - return res.status(500).json({ error: 'Internal server error' }); - } -} \ No newline at end of file + // Redirect user to Gitea authorization URL + return res.redirect(307, authorizationUrl.toString()); + } catch (error) { + console.error("Error initiating Gitea OAuth flow:", error); + return res.status(500).json({ error: "Internal server error" }); + } +} diff --git a/apps/dokploy/pages/api/providers/gitea/callback.ts b/apps/dokploy/pages/api/providers/gitea/callback.ts index 08aec3892..bce152a9d 100644 --- a/apps/dokploy/pages/api/providers/gitea/callback.ts +++ b/apps/dokploy/pages/api/providers/gitea/callback.ts @@ -1,186 +1,94 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import { findGiteaById, updateGitea } from '@dokploy/server'; +import { updateGitea } from "@dokploy/server"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { type Gitea, findGitea, redirectWithError } from "./helper"; + +// Helper to parse the state parameter +const parseState = (state: string): string | null => { + try { + const stateObj = + state.startsWith("{") && state.endsWith("}") ? JSON.parse(state) : {}; + return stateObj.giteaId || state || null; + } catch { + return null; + } +}; + +// Helper to fetch access token from Gitea +const fetchAccessToken = async (gitea: Gitea, code: string) => { + const response = await fetch(`${gitea.giteaUrl}/login/oauth/access_token`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: new URLSearchParams({ + client_id: gitea.clientId as string, + client_secret: gitea.clientSecret as string, + code, + grant_type: "authorization_code", + redirect_uri: gitea.redirectUri || "", + }), + }); + + const responseText = await response.text(); + return response.ok + ? JSON.parse(responseText) + : { error: "Token exchange failed", responseText }; +}; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - try { - console.log('Full Callback Request:', { - query: req.query, - method: req.method, - timestamp: new Date().toISOString() - }); + const { code, state } = req.query; - const { code, state } = req.query; + if (!code || Array.isArray(code) || !state || Array.isArray(state)) { + return redirectWithError( + res, + "Invalid authorization code or state parameter", + ); + } - // Verify received parameters - console.log('Received Parameters:', { - code: code ? 'Present' : 'Missing', - state: state ? 'Present' : 'Missing' - }); + const giteaId = parseState(state as string); + if (!giteaId) return redirectWithError(res, "Invalid state format"); - if (!code || Array.isArray(code)) { - console.error('Invalid code:', code); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid authorization code')}` - ); - } + const gitea = await findGitea(giteaId); + if (!gitea) return redirectWithError(res, "Failed to find Gitea provider"); - // The state parameter now contains the giteaId - if (!state || Array.isArray(state)) { - console.error('Invalid state parameter:', state); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state parameter')}` - ); - } + // Fetch the access token from Gitea + const result = await fetchAccessToken(gitea, code as string); - // Extract the giteaId from the state parameter - let giteaId: string; - try { - // The state could be a simple string or a JSON object - if (state.startsWith('{') && state.endsWith('}')) { - const stateObj = JSON.parse(state); - giteaId = stateObj.giteaId; - } else { - giteaId = state; - } + if (result.error) { + console.error("Token exchange failed:", result); + return redirectWithError(res, result.error); + } - if (!giteaId) { - throw new Error('giteaId not found in state parameter'); - } - } catch (parseError) { - console.error('Error parsing state parameter:', parseError); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state format')}` - ); - } + if (!result.access_token) { + console.error("Missing access token:", result); + return redirectWithError(res, "No access token received"); + } - let gitea; - try { - gitea = await findGiteaById(giteaId); - } catch (findError) { - console.error('Error finding Gitea provider:', findError); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to find Gitea provider')}` - ); - } + const expiresAt = result.expires_in + ? Math.floor(Date.now() / 1000) + result.expires_in + : null; - // Extensive logging of Gitea provider details - console.log('Gitea Provider Details:', { - id: gitea.giteaId, - url: gitea.giteaUrl, - clientId: gitea.clientId ? 'Present' : 'Missing', - clientSecret: gitea.clientSecret ? 'Present' : 'Missing', - redirectUri: gitea.redirectUri - }); + try { + const updatedGitea = await updateGitea(gitea.giteaId, { + accessToken: result.access_token, + refreshToken: result.refresh_token, + expiresAt, + ...(result.organizationName + ? { organizationName: result.organizationName } + : {}), + }); - // Validate required OAuth parameters - if (!gitea.clientId || !gitea.clientSecret) { - console.error('Missing OAuth configuration:', { - hasClientId: !!gitea.clientId, - hasClientSecret: !!gitea.clientSecret - }); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Incomplete OAuth configuration')}` - ); - } - - const response = await fetch(`${gitea.giteaUrl}/login/oauth/access_token`, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "Accept": "application/json", - }, - body: new URLSearchParams({ - client_id: gitea.clientId as string, - client_secret: gitea.clientSecret as string, - code: code as string, - grant_type: "authorization_code", - redirect_uri: gitea.redirectUri || '', - }), - }); - - // Log raw response details - const responseText = await response.text(); - - let result; - try { - result = JSON.parse(responseText); - } catch (parseError) { - console.error('Failed to parse response:', { - error: parseError, - responseText - }); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to parse token response')}` - ); - } - - if (!response.ok) { - console.error('Gitea token exchange failed:', { - result, - responseStatus: response.status - }); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent(result.error || 'Token exchange failed')}` - ); - } - - // Validate token response - if (!result.access_token) { - console.error('Missing access token in response:', { - fullResponse: result - }); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('No access token received')}` - ); - } - - const expiresAt = result.expires_in - ? Math.floor(Date.now() / 1000) + result.expires_in - : null; - - try { - // Perform the update - const updatedGitea = await updateGitea(gitea.giteaId, { - accessToken: result.access_token, - refreshToken: result.refresh_token, - expiresAt, - ...(result.organizationName ? { organizationName: result.organizationName } : {}), - }); - - // Log successful update - console.log('Gitea provider updated successfully:', { - hasAccessToken: !!updatedGitea.accessToken, - hasRefreshToken: !!updatedGitea.refreshToken, - expiresAt: updatedGitea.expiresAt - }); - - return res.redirect(307, "/dashboard/settings/git-providers?connected=true"); - } catch (updateError) { - console.error('Failed to update Gitea provider:', { - error: updateError, - giteaId: gitea.giteaId - }); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to store access token')}` - ); - } - } catch (error) { - console.error('Comprehensive Callback Error:', error); - return res.redirect( - 307, - `/dashboard/settings/git-providers?error=${encodeURIComponent('Internal server error')}` - ); - } -} \ No newline at end of file + console.log("Gitea provider updated successfully:", updatedGitea); + return res.redirect( + 307, + "/dashboard/settings/git-providers?connected=true", + ); + } catch (updateError) { + console.error("Failed to update Gitea provider:", updateError); + return redirectWithError(res, "Failed to store access token"); + } +} diff --git a/apps/dokploy/pages/api/providers/gitea/helper.ts b/apps/dokploy/pages/api/providers/gitea/helper.ts new file mode 100644 index 000000000..eabe6441b --- /dev/null +++ b/apps/dokploy/pages/api/providers/gitea/helper.ts @@ -0,0 +1,42 @@ +import { findGiteaById } from "@dokploy/server"; +import type { NextApiResponse } from "next"; + +// Shared Gitea interface +export interface Gitea { + giteaId: string; + gitProviderId: string; + redirectUri: string | null; + accessToken: string | null; + refreshToken: string | null; + expiresAt: number | null; + giteaUrl: string; + clientId: string | null; + clientSecret: string | null; + organizationName?: string; + gitProvider: { + name: string; + gitProviderId: string; + providerType: "github" | "gitlab" | "bitbucket" | "gitea"; + createdAt: string; + organizationId: string; + }; +} + +// Shared function to find Gitea by ID +export const findGitea = async (giteaId: string): Promise => { + try { + const gitea = await findGiteaById(giteaId); + return gitea; + } catch (findError) { + console.error("Error finding Gitea provider:", findError); + return null; + } +}; + +// Helper for redirecting with error message +export const redirectWithError = (res: NextApiResponse, error: string) => { + return res.redirect( + 307, + `/dashboard/settings/git-providers?error=${encodeURIComponent(error)}`, + ); +}; diff --git a/apps/dokploy/pages/dashboard/project/[projectId].tsx b/apps/dokploy/pages/dashboard/project/[projectId].tsx index 275b235f7..d6fc9dcbf 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId].tsx @@ -34,6 +34,15 @@ import { CommandInput, CommandItem, } from "@/components/ui/command"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -47,6 +56,13 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; @@ -64,8 +80,8 @@ import { Loader2, PlusIcon, Search, - X, Trash2, + X, } from "lucide-react"; import type { GetServerSidePropsContext, @@ -73,25 +89,9 @@ import type { } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; -import { type ReactElement, useMemo, useState, useEffect } from "react"; +import { type ReactElement, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import superjson from "superjson"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; export type Services = { appName: string; diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index 9ff527ab3..12b894a48 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -1,3 +1,4 @@ +import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import"; import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes"; import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment"; import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; @@ -47,7 +48,6 @@ import { useRouter } from "next/router"; import { type ReactElement, useEffect, useState } from "react"; import { toast } from "sonner"; import superjson from "superjson"; -import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import"; type TabState = | "projects" diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx index 753374644..ef5912f1c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -8,11 +8,11 @@ import { ShowExternalPostgresCredentials } from "@/components/dashboard/postgres import { ShowGeneralPostgres } from "@/components/dashboard/postgres/general/show-general-postgres"; import { ShowInternalPostgresCredentials } from "@/components/dashboard/postgres/general/show-internal-postgres-credentials"; import { UpdatePostgres } from "@/components/dashboard/postgres/update-postgres"; +import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings"; import { PostgresqlIcon } from "@/components/icons/data-tools-icons"; import { ProjectLayout } from "@/components/layouts/project-layout"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { StatusTooltip } from "@/components/shared/status-tooltip"; -import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings"; import { Badge } from "@/components/ui/badge"; import { Card, diff --git a/apps/dokploy/pages/dashboard/settings/ai.tsx b/apps/dokploy/pages/dashboard/settings/ai.tsx index 92ca2fabb..8195ce626 100644 --- a/apps/dokploy/pages/dashboard/settings/ai.tsx +++ b/apps/dokploy/pages/dashboard/settings/ai.tsx @@ -5,7 +5,7 @@ import { getLocale, serverSideTranslations } from "@/utils/i18n"; import { validateRequest } from "@dokploy/server"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; -import React, { type ReactElement } from "react"; +import type { ReactElement } from "react"; import superjson from "superjson"; const Page = () => { diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 4f8088598..cd5671bd7 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -13,9 +13,9 @@ import { destinationRouter } from "./routers/destination"; import { dockerRouter } from "./routers/docker"; import { domainRouter } from "./routers/domain"; import { gitProviderRouter } from "./routers/git-provider"; +import { giteaRouter } from "./routers/gitea"; import { githubRouter } from "./routers/github"; import { gitlabRouter } from "./routers/gitlab"; -import { giteaRouter } from "./routers/gitea"; import { mariadbRouter } from "./routers/mariadb"; import { mongoRouter } from "./routers/mongo"; import { mountRouter } from "./routers/mount"; diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index b4a0e83c2..c64a77052 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -26,8 +26,8 @@ import { checkServiceAccess, } from "@dokploy/server/services/user"; import { - getProviderHeaders, type Model, + getProviderHeaders, } from "@dokploy/server/utils/ai/select-ai-provider"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 951998d63..a0b1a964e 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -14,9 +14,9 @@ import { apiSaveDockerProvider, apiSaveEnvironmentVariables, apiSaveGitProvider, + apiSaveGiteaProvider, apiSaveGithubProvider, apiSaveGitlabProvider, - apiSaveGiteaProvider, apiUpdateApplication, applications, } from "@/server/db/schema"; @@ -425,7 +425,7 @@ export const applicationRouter = createTRPCRouter({ giteaProjectId: input.giteaProjectId, giteaPathNamespace: input.giteaPathNamespace, }); - + return true; }), saveDockerProvider: protectedProcedure diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index cab9bc411..9c4658234 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -9,23 +9,10 @@ import { apiUpdateCompose, compose as composeTable, } from "@/server/db/schema"; -import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup"; -import { generatePassword } from "@/templates/utils"; -import { - type CompleteTemplate, - fetchTemplateFiles, - fetchTemplatesList, -} from "@dokploy/server/templates/github"; -import { processTemplate } from "@dokploy/server/templates/processors"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { dump, load } from "js-yaml"; -import _ from "lodash"; -import { nanoid } from "nanoid"; -import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; -import { z } from "zod"; import type { DeploymentJob } from "@/server/queues/queue-types"; +import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup"; import { deploy } from "@/server/utils/deploy"; +import { generatePassword } from "@/templates/utils"; import { IS_CLOUD, addDomainToCompose, @@ -55,6 +42,19 @@ import { stopCompose, updateCompose, } from "@dokploy/server"; +import { + type CompleteTemplate, + fetchTemplateFiles, + fetchTemplatesList, +} from "@dokploy/server/templates/github"; +import { processTemplate } from "@dokploy/server/templates/processors"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; +import { dump, load } from "js-yaml"; +import _ from "lodash"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; export const composeRouter = createTRPCRouter({ create: protectedProcedure diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts index 70e2c53d2..c104f9fec 100644 --- a/apps/dokploy/server/api/routers/destination.ts +++ b/apps/dokploy/server/api/routers/destination.ts @@ -21,7 +21,7 @@ import { updateDestinationById, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; -import { eq, desc } from "drizzle-orm"; +import { desc, eq } from "drizzle-orm"; export const destinationRouter = createTRPCRouter({ create: adminProcedure diff --git a/apps/dokploy/server/api/routers/gitea.ts b/apps/dokploy/server/api/routers/gitea.ts index 798484da2..89d21a77b 100644 --- a/apps/dokploy/server/api/routers/gitea.ts +++ b/apps/dokploy/server/api/routers/gitea.ts @@ -1,238 +1,240 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { - apiCreateGitea, - apiFindOneGitea, - apiGiteaTestConnection, - apiUpdateGitea, - apiFindGiteaBranches, + apiCreateGitea, + apiFindGiteaBranches, + apiFindOneGitea, + apiGiteaTestConnection, + apiUpdateGitea, } from "@/server/db/schema"; import { db } from "@/server/db"; import { - createGitea, - findGiteaById, - haveGiteaRequirements, - testGiteaConnection, - updateGitProvider, - updateGitea, - getGiteaBranches, - getGiteaRepositories, + createGitea, + findGiteaById, + getGiteaBranches, + getGiteaRepositories, + haveGiteaRequirements, + testGiteaConnection, + updateGitProvider, + updateGitea, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; // Gitea Router export const giteaRouter = createTRPCRouter({ - // Create a new Gitea provider - create: protectedProcedure - .input(apiCreateGitea) - .mutation(async ({ input, ctx }) => { - try { - return await createGitea(input, ctx.session.activeOrganizationId); - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating this Gitea provider", - cause: error, - }); - } - }), + // Create a new Gitea provider + create: protectedProcedure + .input(apiCreateGitea) + .mutation(async ({ input, ctx }) => { + try { + return await createGitea(input, ctx.session.activeOrganizationId); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating this Gitea provider", + cause: error, + }); + } + }), - // Fetch a specific Gitea provider by ID - one: protectedProcedure - .input(apiFindOneGitea) - .query(async ({ input, ctx }) => { - const giteaProvider = await findGiteaById(input.giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - return giteaProvider; - }), + // Fetch a specific Gitea provider by ID + one: protectedProcedure + .input(apiFindOneGitea) + .query(async ({ input, ctx }) => { + const giteaProvider = await findGiteaById(input.giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } + return giteaProvider; + }), - // Fetch all Gitea providers for the active organization - giteaProviders: protectedProcedure.query(async ({ ctx }) => { - let result = await db.query.gitea.findMany({ - with: { - gitProvider: true, - }, - }); + // Fetch all Gitea providers for the active organization + giteaProviders: protectedProcedure.query(async ({ ctx }) => { + let result = await db.query.gitea.findMany({ + with: { + gitProvider: true, + }, + }); - // Filter by organization ID - result = result.filter( - (provider) => - provider.gitProvider.organizationId === ctx.session.activeOrganizationId - ); - - // Filter providers that meet the requirements - const filtered = result - .filter((provider) => haveGiteaRequirements(provider)) - .map((provider) => { - return { - giteaId: provider.giteaId, - gitProvider: { - ...provider.gitProvider, - }, - }; - }); + // Filter by organization ID + result = result.filter( + (provider) => + provider.gitProvider.organizationId === + ctx.session.activeOrganizationId, + ); - return filtered; - }), + // Filter providers that meet the requirements + const filtered = result + .filter((provider) => haveGiteaRequirements(provider)) + .map((provider) => { + return { + giteaId: provider.giteaId, + gitProvider: { + ...provider.gitProvider, + }, + }; + }); - // Fetch repositories from Gitea provider - getGiteaRepositories: protectedProcedure - .input(apiFindOneGitea) - .query(async ({ input, ctx }) => { - const { giteaId } = input; + return filtered; + }), - if (!giteaId) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Gitea provider ID is required.", - }); - } + // Fetch repositories from Gitea provider + getGiteaRepositories: protectedProcedure + .input(apiFindOneGitea) + .query(async ({ input, ctx }) => { + const { giteaId } = input; - const giteaProvider = await findGiteaById(giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - - try { - // Call the service layer function to get repositories - console.log('Calling getGiteaRepositories with giteaId:', giteaId); - return await getGiteaRepositories(giteaId); - } catch (error) { - console.error('Error fetching Gitea repositories:', error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }), + if (!giteaId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Gitea provider ID is required.", + }); + } - // Fetch branches of a specific Gitea repository - getGiteaBranches: protectedProcedure - .input(apiFindGiteaBranches) - .query(async ({ input, ctx }) => { - const { giteaId, owner, repositoryName } = input; + const giteaProvider = await findGiteaById(giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } - if (!giteaId || !owner || !repositoryName) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Gitea provider ID, owner, and repository name are required.", - }); - } + try { + // Call the service layer function to get repositories + console.log("Calling getGiteaRepositories with giteaId:", giteaId); + return await getGiteaRepositories(giteaId); + } catch (error) { + console.error("Error fetching Gitea repositories:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), - const giteaProvider = await findGiteaById(giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - - try { - // Call the service layer function with the required parameters - console.log('Calling getGiteaBranches with:', { - giteaId, - owner, - repo: repositoryName - }); - - return await getGiteaBranches({ - giteaId, - owner, - repo: repositoryName, - id: 0 // Provide a default value for the optional id - }); - } catch (error) { - console.error('Error fetching Gitea branches:', error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }), + // Fetch branches of a specific Gitea repository + getGiteaBranches: protectedProcedure + .input(apiFindGiteaBranches) + .query(async ({ input, ctx }) => { + const { giteaId, owner, repositoryName } = input; - // Test connection to Gitea provider - testConnection: protectedProcedure - .input(apiGiteaTestConnection) - .mutation(async ({ input, ctx }) => { - // Ensure giteaId is always a non-empty string - const giteaId = input.giteaId ?? ''; + if (!giteaId || !owner || !repositoryName) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Gitea provider ID, owner, and repository name are required.", + }); + } - try { - const giteaProvider = await findGiteaById(giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - - const result = await testGiteaConnection({ - giteaId, - }); + const giteaProvider = await findGiteaById(giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } - return `Found ${result} repositories`; - } catch (error) { - console.error('Gitea connection test error:', error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: error instanceof Error ? error.message : String(error), - }); - } - }), + try { + // Call the service layer function with the required parameters + console.log("Calling getGiteaBranches with:", { + giteaId, + owner, + repo: repositoryName, + }); - // Update an existing Gitea provider - update: protectedProcedure - .input(apiUpdateGitea) - .mutation(async ({ input, ctx }) => { - const giteaProvider = await findGiteaById(input.giteaId); - if ( - giteaProvider.gitProvider.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to access this Gitea provider", - }); - } - - console.log('Updating Gitea provider:', input); - - if (input.name) { - await updateGitProvider(input.gitProviderId, { - name: input.name, - organizationId: ctx.session.activeOrganizationId, - }); + return await getGiteaBranches({ + giteaId, + owner, + repo: repositoryName, + id: 0, // Provide a default value for the optional id + }); + } catch (error) { + console.error("Error fetching Gitea branches:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), - await updateGitea(input.giteaId, { - ...input, - }); - } else { - await updateGitea(input.giteaId, { - ...input, - }); - } - - return { success: true }; - }), -}); \ No newline at end of file + // Test connection to Gitea provider + testConnection: protectedProcedure + .input(apiGiteaTestConnection) + .mutation(async ({ input, ctx }) => { + // Ensure giteaId is always a non-empty string + const giteaId = input.giteaId ?? ""; + + try { + const giteaProvider = await findGiteaById(giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } + + const result = await testGiteaConnection({ + giteaId, + }); + + return `Found ${result} repositories`; + } catch (error) { + console.error("Gitea connection test error:", error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : String(error), + }); + } + }), + + // Update an existing Gitea provider + update: protectedProcedure + .input(apiUpdateGitea) + .mutation(async ({ input, ctx }) => { + const giteaProvider = await findGiteaById(input.giteaId); + if ( + giteaProvider.gitProvider.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this Gitea provider", + }); + } + + console.log("Updating Gitea provider:", input); + + if (input.name) { + await updateGitProvider(input.gitProviderId, { + name: input.name, + organizationId: ctx.session.activeOrganizationId, + }); + + await updateGitea(input.giteaId, { + ...input, + }); + } else { + await updateGitea(input.giteaId, { + ...input, + }); + } + + return { success: true }; + }), +}); diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index c3ba8c13b..241274f16 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -1,14 +1,15 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { db } from "@/server/db"; import { apiChangeMariaDBStatus, apiCreateMariaDB, apiDeployMariaDB, apiFindOneMariaDB, + apiRebuildMariadb, apiResetMariadb, apiSaveEnvironmentVariablesMariaDB, apiSaveExternalPortMariaDB, apiUpdateMariaDB, - apiRebuildMariadb, mariadb as mariadbTable, } from "@/server/db/schema"; import { cancelJobs } from "@/server/utils/backup"; @@ -30,12 +31,11 @@ import { stopServiceRemote, updateMariadbById, } from "@dokploy/server"; +import { rebuildDatabase } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { observable } from "@trpc/server/observable"; -import { z } from "zod"; import { eq } from "drizzle-orm"; -import { db } from "@/server/db"; -import { rebuildDatabase } from "@dokploy/server"; +import { z } from "zod"; export const mariadbRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreateMariaDB) diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index de6c03e57..eb51522a0 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -1,4 +1,5 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { db } from "@/server/db"; import { apiChangeMongoStatus, apiCreateMongo, @@ -30,12 +31,11 @@ import { stopServiceRemote, updateMongoById, } from "@dokploy/server"; +import { rebuildDatabase } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { observable } from "@trpc/server/observable"; -import { z } from "zod"; import { eq } from "drizzle-orm"; -import { db } from "@/server/db"; -import { rebuildDatabase } from "@dokploy/server"; +import { z } from "zod"; export const mongoRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreateMongo) diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 0c66b71ca..910876013 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -14,6 +14,7 @@ import { import { TRPCError } from "@trpc/server"; +import { db } from "@/server/db"; import { cancelJobs } from "@/server/utils/backup"; import { IS_CLOUD, @@ -36,7 +37,6 @@ import { } from "@dokploy/server"; import { observable } from "@trpc/server/observable"; import { eq } from "drizzle-orm"; -import { db } from "@/server/db"; import { z } from "zod"; export const mysqlRouter = createTRPCRouter({ diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 8159447fe..28d5558a6 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -1,4 +1,5 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { db } from "@/server/db"; import { apiChangePostgresStatus, apiCreatePostgres, @@ -33,9 +34,8 @@ import { } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { observable } from "@trpc/server/observable"; -import { z } from "zod"; import { eq } from "drizzle-orm"; -import { db } from "@/server/db"; +import { z } from "zod"; export const postgresRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreatePostgres) diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index c310a6dd2..1de13fcb7 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -4,16 +4,17 @@ import { apiCreateRedis, apiDeployRedis, apiFindOneRedis, + apiRebuildRedis, apiResetRedis, apiSaveEnvironmentVariablesRedis, apiSaveExternalPortRedis, apiUpdateRedis, redis as redisTable, - apiRebuildRedis, } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; +import { db } from "@/server/db"; import { IS_CLOUD, addNewService, @@ -31,11 +32,10 @@ import { stopServiceRemote, updateRedisById, } from "@dokploy/server"; +import { rebuildDatabase } from "@dokploy/server"; import { observable } from "@trpc/server/observable"; import { eq } from "drizzle-orm"; -import { db } from "@/server/db"; import { z } from "zod"; -import { rebuildDatabase } from "@dokploy/server"; export const redisRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreateRedis) diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index abf0eb4bb..70f14ec39 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -26,6 +26,7 @@ import { findUserById, getDokployImage, getDokployImageTag, + getLogCleanupStatus, getUpdateData, initializeTraefik, parseRawConfig, @@ -41,6 +42,8 @@ import { recreateDirectory, sendDockerCleanupNotifications, spawnAsync, + startLogCleanup, + stopLogCleanup, updateLetsEncryptEmail, updateServerById, updateServerTraefik, @@ -48,9 +51,6 @@ import { writeConfig, writeMainConfig, writeTraefikConfigInPath, - startLogCleanup, - stopLogCleanup, - getLogCleanupStatus, } from "@dokploy/server"; import { checkGPUStatus, setupGPUSupport } from "@dokploy/server"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 0b740ab74..905ae1d51 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -1,11 +1,11 @@ import { IS_CLOUD, + createApiKey, findOrganizationById, findUserById, getUserByToken, removeUserById, updateUser, - createApiKey, } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { @@ -13,12 +13,12 @@ import { apiAssignPermissions, apiFindOneToken, apiUpdateUser, + apikey, invitation, member, - apikey, } from "@dokploy/server/db/schema"; -import * as bcrypt from "bcrypt"; import { TRPCError } from "@trpc/server"; +import * as bcrypt from "bcrypt"; import { and, asc, eq, gt } from "drizzle-orm"; import { z } from "zod"; import { diff --git a/apps/dokploy/templates/infisical/index.ts b/apps/dokploy/templates/infisical/index.ts index c932cd5e7..efdc96940 100644 --- a/apps/dokploy/templates/infisical/index.ts +++ b/apps/dokploy/templates/infisical/index.ts @@ -77,7 +77,7 @@ export function generate(schema: Schema): Template { "CLIENT_SECRET_GITLAB_LOGIN=", "", "CLIENT_ID_GITEA_LOGIN=", - "CLIENT_SECRET_GITEA_LOGIN=", + "CLIENT_SECRET_GITEA_LOGIN=", "", "CAPTCHA_SECRET=", "", diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index a810c1764..b5ca987e2 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -13,9 +13,9 @@ import { z } from "zod"; import { bitbucket } from "./bitbucket"; import { deployments } from "./deployment"; import { domains } from "./domain"; +import { gitea } from "./gitea"; import { github } from "./github"; import { gitlab } from "./gitlab"; -import { gitea } from "./gitea"; import { mounts } from "./mount"; import { ports } from "./port"; import { previewDeployments } from "./preview-deployments"; @@ -395,7 +395,9 @@ const createSchema = createInsertSchema(applications, { customGitUrl: z.string().optional(), buildPath: z.string().optional(), projectId: z.string(), - sourceType: z.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"]).optional(), + sourceType: z + .enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"]) + .optional(), applicationStatus: z.enum(["idle", "running", "done", "error"]), buildType: z.enum([ "dockerfile", @@ -556,4 +558,4 @@ export const apiUpdateApplication = createSchema .extend({ applicationId: z.string().min(1), }) - .omit({ serverId: true }); \ No newline at end of file + .omit({ serverId: true }); diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index 60107f99e..e8d0e0329 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { bitbucket } from "./bitbucket"; import { deployments } from "./deployment"; import { domains } from "./domain"; +import { gitea } from "./gitea"; import { github } from "./github"; import { gitlab } from "./gitlab"; import { mounts } from "./mount"; @@ -14,7 +15,6 @@ import { server } from "./server"; import { applicationStatus } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; -import { gitea } from "./gitea"; export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ "git", @@ -58,9 +58,9 @@ export const compose = pgTable("compose", { bitbucketOwner: text("bitbucketOwner"), bitbucketBranch: text("bitbucketBranch"), // Gitea - giteaRepository: text("giteaRepository"), - giteaOwner: text("giteaOwner"), - giteaBranch: text("giteaBranch"), + giteaRepository: text("giteaRepository"), + giteaOwner: text("giteaOwner"), + giteaBranch: text("giteaBranch"), // Git customGitUrl: text("customGitUrl"), customGitBranch: text("customGitBranch"), @@ -94,8 +94,8 @@ export const compose = pgTable("compose", { onDelete: "set null", }), giteaId: text("giteaId").references(() => gitea.giteaId, { - onDelete: "set null", - }), + onDelete: "set null", + }), serverId: text("serverId").references(() => server.serverId, { onDelete: "cascade", }), @@ -126,9 +126,9 @@ export const composeRelations = relations(compose, ({ one, many }) => ({ references: [bitbucket.bitbucketId], }), gitea: one(gitea, { - fields: [compose.giteaId], - references: [gitea.giteaId], - }), + fields: [compose.giteaId], + references: [gitea.giteaId], + }), server: one(server, { fields: [compose.serverId], references: [server.serverId], diff --git a/packages/server/src/db/schema/git-provider.ts b/packages/server/src/db/schema/git-provider.ts index 62e95c25a..792cd5fd2 100644 --- a/packages/server/src/db/schema/git-provider.ts +++ b/packages/server/src/db/schema/git-provider.ts @@ -5,9 +5,9 @@ import { nanoid } from "nanoid"; import { z } from "zod"; import { organization } from "./account"; import { bitbucket } from "./bitbucket"; +import { gitea } from "./gitea"; import { github } from "./github"; import { gitlab } from "./gitlab"; -import { gitea } from "./gitea"; export const gitProviderType = pgEnum("gitProviderType", [ "github", diff --git a/packages/server/src/db/schema/gitea.ts b/packages/server/src/db/schema/gitea.ts index dba7854bb..5022167bd 100644 --- a/packages/server/src/db/schema/gitea.ts +++ b/packages/server/src/db/schema/gitea.ts @@ -7,31 +7,31 @@ import { gitProvider } from "./git-provider"; // Gitea table definition export const gitea = pgTable("gitea", { - giteaId: text("giteaId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), // Using nanoid for unique ID - giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), // Default URL for Gitea - redirectUri: text("redirect_uri"), - clientId: text("client_id"), - clientSecret: text("client_secret"), - gitProviderId: text("gitProviderId") - .notNull() - .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), - giteaUsername: text("gitea_username"), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - expiresAt: integer("expires_at"), - scopes: text("scopes").default('repo,repo:status,read:user,read:org'), - lastAuthenticatedAt: integer("last_authenticated_at"), + giteaId: text("giteaId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), // Using nanoid for unique ID + giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), // Default URL for Gitea + redirectUri: text("redirect_uri"), + clientId: text("client_id"), + clientSecret: text("client_secret"), + gitProviderId: text("gitProviderId") + .notNull() + .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), + giteaUsername: text("gitea_username"), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + expiresAt: integer("expires_at"), + scopes: text("scopes").default("repo,repo:status,read:user,read:org"), + lastAuthenticatedAt: integer("last_authenticated_at"), }); // Gitea relations with gitProvider export const giteaProviderRelations = relations(gitea, ({ one }) => ({ - gitProvider: one(gitProvider, { - fields: [gitea.gitProviderId], - references: [gitProvider.gitProviderId], - }), + gitProvider: one(gitProvider, { + fields: [gitea.gitProviderId], + references: [gitProvider.gitProviderId], + }), })); // Create schema for Gitea @@ -39,58 +39,58 @@ const createSchema = createInsertSchema(gitea); // API schema for creating a Gitea instance export const apiCreateGitea = createSchema.extend({ - clientId: z.string().optional(), - clientSecret: z.string().optional(), - gitProviderId: z.string().optional(), - redirectUri: z.string().optional(), - name: z.string().min(1), - giteaUrl: z.string().min(1), - giteaUsername: z.string().optional(), - accessToken: z.string().optional(), - refreshToken: z.string().optional(), - expiresAt: z.number().optional(), - organizationName: z.string().optional(), - scopes: z.string().optional(), - lastAuthenticatedAt: z.number().optional(), + clientId: z.string().optional(), + clientSecret: z.string().optional(), + gitProviderId: z.string().optional(), + redirectUri: z.string().optional(), + name: z.string().min(1), + giteaUrl: z.string().min(1), + giteaUsername: z.string().optional(), + accessToken: z.string().optional(), + refreshToken: z.string().optional(), + expiresAt: z.number().optional(), + organizationName: z.string().optional(), + scopes: z.string().optional(), + lastAuthenticatedAt: z.number().optional(), }); // API schema for finding one Gitea instance export const apiFindOneGitea = createSchema - .extend({ - giteaId: z.string().min(1), - }) - .pick({ giteaId: true }); + .extend({ + giteaId: z.string().min(1), + }) + .pick({ giteaId: true }); // API schema for testing Gitea connection export const apiGiteaTestConnection = createSchema - .extend({ - organizationName: z.string().optional(), - }) - .pick({ giteaId: true, organizationName: true }); + .extend({ + organizationName: z.string().optional(), + }) + .pick({ giteaId: true, organizationName: true }); export type ApiGiteaTestConnection = z.infer; // API schema for finding branches in Gitea export const apiFindGiteaBranches = z.object({ - id: z.number().optional(), - owner: z.string().min(1), - repositoryName: z.string().min(1), - giteaId: z.string().optional(), + id: z.number().optional(), + owner: z.string().min(1), + repositoryName: z.string().min(1), + giteaId: z.string().optional(), }); // API schema for updating Gitea instance export const apiUpdateGitea = createSchema.extend({ - clientId: z.string().optional(), - clientSecret: z.string().optional(), - redirectUri: z.string().optional(), - name: z.string().min(1), - giteaId: z.string().min(1), - giteaUrl: z.string().min(1), - giteaUsername: z.string().optional(), - accessToken: z.string().optional(), - refreshToken: z.string().optional(), - expiresAt: z.number().optional(), - organizationName: z.string().optional(), - scopes: z.string().optional(), - lastAuthenticatedAt: z.number().optional(), -}); \ No newline at end of file + clientId: z.string().optional(), + clientSecret: z.string().optional(), + redirectUri: z.string().optional(), + name: z.string().min(1), + giteaId: z.string().min(1), + giteaUrl: z.string().min(1), + giteaUsername: z.string().optional(), + accessToken: z.string().optional(), + refreshToken: z.string().optional(), + expiresAt: z.number().optional(), + organizationName: z.string().optional(), + scopes: z.string().optional(), + lastAuthenticatedAt: z.number().optional(), +}); diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index b1303285f..aa1c726a2 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -10,7 +10,7 @@ import { import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; -import { account, organization, apikey } from "./account"; +import { account, apikey, organization } from "./account"; import { projects } from "./project"; import { certificateType } from "./shared"; /** diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 9043f2030..212a95133 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -2,12 +2,12 @@ import type { IncomingMessage } from "node:http"; import * as bcrypt from "bcrypt"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { organization, twoFactor, apiKey } from "better-auth/plugins"; +import { apiKey, organization, twoFactor } from "better-auth/plugins"; import { and, desc, eq } from "drizzle-orm"; +import { IS_CLOUD } from "../constants"; import { db } from "../db"; import * as schema from "../db/schema"; import { sendEmail } from "../verification/send-verification-email"; -import { IS_CLOUD } from "../constants"; const { handler, api } = betterAuth({ database: drizzleAdapter(db, { diff --git a/packages/server/src/services/ai.ts b/packages/server/src/services/ai.ts index 3b7750a98..61c448397 100644 --- a/packages/server/src/services/ai.ts +++ b/packages/server/src/services/ai.ts @@ -6,8 +6,8 @@ import { generateObject } from "ai"; import { desc, eq } from "drizzle-orm"; import { z } from "zod"; import { IS_CLOUD } from "../constants"; -import { findServerById } from "./server"; import { findOrganizationById } from "./admin"; +import { findServerById } from "./server"; export const getAiSettingsByOrganizationId = async (organizationId: string) => { const aiSettings = await db.query.ai.findMany({ diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index b3c2a72b3..179aa5be3 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -26,6 +26,10 @@ import { cloneGitRepository, getCustomGitCloneCommand, } from "@dokploy/server/utils/providers/git"; +import { + cloneGiteaRepository, + getGiteaCloneCommand, +} from "@dokploy/server/utils/providers/gitea"; import { cloneGithubRepository, getGithubCloneCommand, @@ -34,10 +38,6 @@ import { cloneGitlabRepository, getGitlabCloneCommand, } from "@dokploy/server/utils/providers/gitlab"; -import { - cloneGiteaRepository, - getGiteaCloneCommand, -} from "@dokploy/server/utils/providers/gitea"; import { createTraefikConfig } from "@dokploy/server/utils/traefik/application"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -318,10 +318,7 @@ export const deployRemoteApplication = async ({ deployment.logPath, ); } else if (application.sourceType === "gitea") { - command += await getGiteaCloneCommand( - application, - deployment.logPath, - ); + command += await getGiteaCloneCommand(application, deployment.logPath); } else if (application.sourceType === "git") { command += await getCustomGitCloneCommand( application, diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 076b1f502..3f014089e 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -29,6 +29,10 @@ import { cloneGitRepository, getCustomGitCloneCommand, } from "@dokploy/server/utils/providers/git"; +import { + cloneGiteaRepository, + getGiteaCloneCommand, +} from "@dokploy/server/utils/providers/gitea"; import { cloneGithubRepository, getGithubCloneCommand, @@ -37,10 +41,6 @@ import { cloneGitlabRepository, getGitlabCloneCommand, } from "@dokploy/server/utils/providers/gitlab"; -import { - cloneGiteaRepository, - getGiteaCloneCommand, -} from "@dokploy/server/utils/providers/gitea"; import { createComposeFile, getCreateComposeFileCommand, @@ -237,7 +237,7 @@ export const deployCompose = async ({ await cloneGiteaRepository(compose, deployment.logPath, true); } else if (compose.sourceType === "raw") { await createComposeFile(compose, deployment.logPath); - } + } await buildCompose(compose, deployment.logPath); await updateDeploymentStatus(deployment.deploymentId, "done"); await updateCompose(composeId, { @@ -360,9 +360,9 @@ export const deployRemoteCompose = async ({ command += getCreateComposeFileCommand(compose, deployment.logPath); } else if (compose.sourceType === "gitea") { command += await getGiteaCloneCommand( - compose, - deployment.logPath, - true + compose, + deployment.logPath, + true, ); } diff --git a/packages/server/src/services/gitea.ts b/packages/server/src/services/gitea.ts index 676197c7f..6b6dab7f8 100644 --- a/packages/server/src/services/gitea.ts +++ b/packages/server/src/services/gitea.ts @@ -1,104 +1,100 @@ import { db } from "@dokploy/server/db"; import { - type apiCreateGitea, - gitProvider, - gitea, + type apiCreateGitea, + gitProvider, + gitea, } from "@dokploy/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; export type Gitea = typeof gitea.$inferSelect; export const createGitea = async ( - input: typeof apiCreateGitea._type, - organizationId: string, + input: typeof apiCreateGitea._type, + organizationId: string, ) => { - return await db.transaction(async (tx) => { - // Insert new Git provider (Gitea) - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "gitea", // Set providerType to 'gitea' - organizationId: organizationId, - name: input.name, - }) - .returning() - .then((response) => response[0]); + return await db.transaction(async (tx) => { + // Insert new Git provider (Gitea) + const newGitProvider = await tx + .insert(gitProvider) + .values({ + providerType: "gitea", // Set providerType to 'gitea' + organizationId: organizationId, + name: input.name, + }) + .returning() + .then((response) => response[0]); - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating the Git provider", - }); - } + if (!newGitProvider) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the Git provider", + }); + } - // Insert the Gitea data into the `gitea` table - await tx - .insert(gitea) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); + // Insert the Gitea data into the `gitea` table + await tx + .insert(gitea) + .values({ + ...input, + gitProviderId: newGitProvider?.gitProviderId, + }) + .returning() + .then((response) => response[0]); + }); }; export const findGiteaById = async (giteaId: string) => { - try { - - const giteaProviderResult = await db.query.gitea.findFirst({ - where: eq(gitea.giteaId, giteaId), - with: { - gitProvider: true, - }, - }); - - if (!giteaProviderResult) { - console.error('No Gitea Provider found:', { giteaId }); - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea Provider not found", - }); - } - - return giteaProviderResult; - } catch (error) { - console.error('Error finding Gitea Provider:', error); - throw error; - } + try { + const giteaProviderResult = await db.query.gitea.findFirst({ + where: eq(gitea.giteaId, giteaId), + with: { + gitProvider: true, + }, + }); + + if (!giteaProviderResult) { + console.error("No Gitea Provider found:", { giteaId }); + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } + + return giteaProviderResult; + } catch (error) { + console.error("Error finding Gitea Provider:", error); + throw error; + } }; -export const updateGitea = async ( - giteaId: string, - input: Partial, -) => { - console.log('Updating Gitea Provider:', { - giteaId, - updateData: { - accessTokenPresent: !!input.accessToken, - refreshTokenPresent: !!input.refreshToken, - expiresAt: input.expiresAt, - } - }); +export const updateGitea = async (giteaId: string, input: Partial) => { + console.log("Updating Gitea Provider:", { + giteaId, + updateData: { + accessTokenPresent: !!input.accessToken, + refreshTokenPresent: !!input.refreshToken, + expiresAt: input.expiresAt, + }, + }); - try { - const updateResult = await db - .update(gitea) - .set(input) - .where(eq(gitea.giteaId, giteaId)) - .returning(); + try { + const updateResult = await db + .update(gitea) + .set(input) + .where(eq(gitea.giteaId, giteaId)) + .returning(); - // Explicitly type the result and handle potential undefined - const result = updateResult[0] as Gitea | undefined; + // Explicitly type the result and handle potential undefined + const result = updateResult[0] as Gitea | undefined; - if (!result) { - console.error('No rows were updated', { giteaId, input }); - throw new Error(`Failed to update Gitea provider with ID ${giteaId}`); - } + if (!result) { + console.error("No rows were updated", { giteaId, input }); + throw new Error(`Failed to update Gitea provider with ID ${giteaId}`); + } - return result; - } catch (error) { - console.error('Error updating Gitea provider:', error); - throw error; - } -}; \ No newline at end of file + return result; + } catch (error) { + console.error("Error updating Gitea provider:", error); + throw error; + } +}; diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index 677ce74ac..66f62e3da 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -6,10 +6,10 @@ import { } from "@dokploy/server/services/deployment"; import { findServerById } from "@dokploy/server/services/server"; import { + TRAEFIK_HTTP3_PORT, TRAEFIK_PORT, TRAEFIK_SSL_PORT, TRAEFIK_VERSION, - TRAEFIK_HTTP3_PORT, getDefaultMiddlewares, getDefaultServerTraefikConfig, } from "@dokploy/server/setup/traefik-setup"; diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts index fac187943..5e12e885c 100644 --- a/packages/server/src/utils/access-log/handler.ts +++ b/packages/server/src/utils/access-log/handler.ts @@ -1,8 +1,8 @@ import { paths } from "@dokploy/server/constants"; -import { execAsync } from "../process/execAsync"; import { findAdmin } from "@dokploy/server/services/admin"; import { updateUser } from "@dokploy/server/services/user"; import { scheduleJob, scheduledJobs } from "node-schedule"; +import { execAsync } from "../process/execAsync"; const LOG_CLEANUP_JOB_NAME = "access-log-cleanup"; diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 4b194efc6..96c131a52 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -2,19 +2,19 @@ import path from "node:path"; import { getAllServers } from "@dokploy/server/services/server"; import { scheduleJob } from "node-schedule"; import { db } from "../../db/index"; +import { findAdmin } from "../../services/admin"; import { cleanUpDockerBuilder, cleanUpSystemPrune, cleanUpUnusedImages, } from "../docker/utils"; import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup"; +import { execAsync, execAsyncRemote } from "../process/execAsync"; import { runMariadbBackup } from "./mariadb"; import { runMongoBackup } from "./mongo"; import { runMySqlBackup } from "./mysql"; import { runPostgresBackup } from "./postgres"; -import { findAdmin } from "../../services/admin"; import { getS3Credentials } from "./utils"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; import type { BackupSchedule } from "@dokploy/server/services/backup"; import { startLogCleanup } from "../access-log/handler"; diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index 81be92963..28a71833e 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -1,11 +1,11 @@ import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { Destination } from "@dokploy/server/services/destination"; import { scheduleJob, scheduledJobs } from "node-schedule"; +import { keepLatestNBackups } from "."; import { runMariadbBackup } from "./mariadb"; import { runMongoBackup } from "./mongo"; import { runMySqlBackup } from "./mysql"; import { runPostgresBackup } from "./postgres"; -import { keepLatestNBackups } from "."; export const scheduleBackup = (backup: BackupSchedule) => { const { schedule, backupId, databaseType, postgres, mysql, mongo, mariadb } = diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 22073a22e..9baf73a3f 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -112,118 +112,120 @@ export const getBuildCommand = ( export const mechanizeDockerContainer = async ( application: ApplicationNested, - ) => { - console.log(`Starting to mechanize Docker container for ${application.appName}`); - +) => { + console.log( + `Starting to mechanize Docker container for ${application.appName}`, + ); + const { - appName, - env, - mounts, - cpuLimit, - memoryLimit, - memoryReservation, - cpuReservation, - command, - ports, + appName, + env, + mounts, + cpuLimit, + memoryLimit, + memoryReservation, + cpuReservation, + command, + ports, } = application; - + const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, + memoryLimit, + memoryReservation, + cpuLimit, + cpuReservation, }); - + const volumesMount = generateVolumeMounts(mounts); - + const { - HealthCheck, - RestartPolicy, - Placement, - Labels, - Mode, - RollbackConfig, - UpdateConfig, - Networks, + HealthCheck, + RestartPolicy, + Placement, + Labels, + Mode, + RollbackConfig, + UpdateConfig, + Networks, } = generateConfigContainer(application); - + const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, application); const envVariables = prepareEnvironmentVariables( - env, - application.project.env, + env, + application.project.env, ); - + const image = getImageName(application); const authConfig = getAuthConfig(application); const docker = await getRemoteDocker(application.serverId); - + const settings: CreateServiceOptions = { - authconfig: authConfig, - Name: appName, - TaskTemplate: { - ContainerSpec: { - HealthCheck, - Image: image, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - Labels, - }, - Networks, - RestartPolicy, - Placement, - Resources: { - ...resources, - }, - }, - Mode, - RollbackConfig, - EndpointSpec: { - Ports: ports.map((port) => ({ - Protocol: port.protocol, - TargetPort: port.targetPort, - PublishedPort: port.publishedPort, - })), - }, - UpdateConfig, - }; - - try { - console.log(`Attempting to find existing service: ${appName}`); - const service = docker.getService(appName); - const inspect = await service.inspect(); - console.log(`Found existing service, updating: ${appName}`); - - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, + authconfig: authConfig, + Name: appName, TaskTemplate: { - ...settings.TaskTemplate, - ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, + ContainerSpec: { + HealthCheck, + Image: image, + Env: envVariables, + Mounts: [...volumesMount, ...bindsMount, ...filesMount], + ...(command + ? { + Command: ["/bin/sh"], + Args: ["-c", command], + } + : {}), + Labels, + }, + Networks, + RestartPolicy, + Placement, + Resources: { + ...resources, + }, }, - }); - console.log(`Service updated successfully: ${appName}`); + Mode, + RollbackConfig, + EndpointSpec: { + Ports: ports.map((port) => ({ + Protocol: port.protocol, + TargetPort: port.targetPort, + PublishedPort: port.publishedPort, + })), + }, + UpdateConfig, + }; + + try { + console.log(`Attempting to find existing service: ${appName}`); + const service = docker.getService(appName); + const inspect = await service.inspect(); + console.log(`Found existing service, updating: ${appName}`); + + await service.update({ + version: Number.parseInt(inspect.Version.Index), + ...settings, + TaskTemplate: { + ...settings.TaskTemplate, + ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, + }, + }); + console.log(`Service updated successfully: ${appName}`); } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.log(`Service not found or error: ${errorMessage}`); - console.log(`Creating new service: ${appName}`); - - try { - await docker.createService(settings); - console.log(`Service created successfully: ${appName}`); - } catch (createError: unknown) { - const createErrorMessage = createError instanceof Error - ? createError.message - : 'Unknown error'; - console.error(`Failed to create service: ${createErrorMessage}`); - throw createError; - } + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + console.log(`Service not found or error: ${errorMessage}`); + console.log(`Creating new service: ${appName}`); + + try { + await docker.createService(settings); + console.log(`Service created successfully: ${appName}`); + } catch (createError: unknown) { + const createErrorMessage = + createError instanceof Error ? createError.message : "Unknown error"; + console.error(`Failed to create service: ${createErrorMessage}`); + throw createError; + } } }; diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts index 91baeed49..2d110d41d 100644 --- a/packages/server/src/utils/builders/nixpacks.ts +++ b/packages/server/src/utils/builders/nixpacks.ts @@ -39,7 +39,7 @@ export const buildNixpacks = async ( for (const env of envVariables) { args.push("--env", env); } - + if (publishDirectory) { /* No need for any start command, since we'll use nginx later on */ args.push("--no-error-without-start"); @@ -90,10 +90,11 @@ export const buildNixpacks = async ( await spawnAsync("docker", ["rm", buildContainerId], writeToStream); } catch (rmError) { // Ignore errors from container removal - const errorMessage = rmError instanceof Error - ? rmError.message - : 'Unknown container cleanup error'; - + const errorMessage = + rmError instanceof Error + ? rmError.message + : "Unknown container cleanup error"; + // Just log it but don't let it cause another error writeToStream(`Container cleanup attempt: ${errorMessage}\n`); } diff --git a/packages/server/src/utils/builders/railpack.ts b/packages/server/src/utils/builders/railpack.ts index 737910abe..612e02cf6 100644 --- a/packages/server/src/utils/builders/railpack.ts +++ b/packages/server/src/utils/builders/railpack.ts @@ -1,11 +1,11 @@ +import { createHash } from "node:crypto"; import type { WriteStream } from "node:fs"; +import { nanoid } from "nanoid"; import type { ApplicationNested } from "."; import { prepareEnvironmentVariables } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; import { execAsync } from "../process/execAsync"; -import { nanoid } from "nanoid"; -import { createHash } from "node:crypto"; +import { spawnAsync } from "../process/spawnAsync"; const calculateSecretsHash = (envVariables: string[]): string => { const hash = createHash("sha256"); diff --git a/packages/server/src/utils/databases/rebuild.ts b/packages/server/src/utils/databases/rebuild.ts index 12c907b7f..523ea37e3 100644 --- a/packages/server/src/utils/databases/rebuild.ts +++ b/packages/server/src/utils/databases/rebuild.ts @@ -1,20 +1,20 @@ -import { deployPostgres } from "@dokploy/server/services/postgres"; -import { execAsyncRemote } from "../process/execAsync"; -import { execAsync } from "../process/execAsync"; -import { deployMySql } from "@dokploy/server/services/mysql"; -import { deployMariadb } from "@dokploy/server/services/mariadb"; -import { deployMongo } from "@dokploy/server/services/mongo"; -import { deployRedis } from "@dokploy/server/services/redis"; -import { removeService } from "../docker/utils"; import { db } from "@dokploy/server/db"; import { - postgres, - mysql, mariadb, mongo, + mysql, + postgres, redis, } from "@dokploy/server/db/schema"; +import { deployMariadb } from "@dokploy/server/services/mariadb"; +import { deployMongo } from "@dokploy/server/services/mongo"; +import { deployMySql } from "@dokploy/server/services/mysql"; +import { deployPostgres } from "@dokploy/server/services/postgres"; +import { deployRedis } from "@dokploy/server/services/redis"; import { eq } from "drizzle-orm"; +import { removeService } from "../docker/utils"; +import { execAsyncRemote } from "../process/execAsync"; +import { execAsync } from "../process/execAsync"; type DatabaseType = "postgres" | "mysql" | "mariadb" | "mongo" | "redis"; diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index d40746cae..4127248f9 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -14,6 +14,7 @@ import { cloneGitRawRepository, cloneRawGitRepositoryRemote, } from "../providers/git"; +import { cloneRawGiteaRepository } from "../providers/gitea"; import { cloneRawGithubRepository, cloneRawGithubRepositoryRemote, @@ -22,10 +23,6 @@ import { cloneRawGitlabRepository, cloneRawGitlabRepositoryRemote, } from "../providers/gitlab"; -import { - cloneRawGiteaRepository, - cloneRawGiteaRepositoryRemote, -} from "../providers/gitea"; import { createComposeFileRaw, createComposeFileRawRemote, diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts index 30cd3c37e..cd6cb95b5 100644 --- a/packages/server/src/utils/providers/gitea.ts +++ b/packages/server/src/utils/providers/gitea.ts @@ -1,6 +1,6 @@ import { createWriteStream } from "node:fs"; +import fs from "node:fs/promises"; import { join } from "node:path"; -import fs from "fs/promises"; import { paths } from "@dokploy/server/constants"; import { findGiteaById, updateGitea } from "@dokploy/server/services/gitea"; import { TRPCError } from "@trpc/server"; @@ -12,154 +12,172 @@ import { spawnAsync } from "../process/spawnAsync"; * Wrapper function to maintain compatibility with the existing implementation */ export const fetchGiteaBranches = async ( - giteaId: string, - repoFullName: string + giteaId: string, + repoFullName: string, ) => { - // Ensure owner and repo are non-empty strings - const parts = repoFullName.split('/'); - - // Validate that we have exactly two parts - if (parts.length !== 2 || !parts[0] || !parts[1]) { - throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); - } + // Ensure owner and repo are non-empty strings + const parts = repoFullName.split("/"); - const [owner, repo] = parts; + // Validate that we have exactly two parts + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new Error( + `Invalid repository name format: ${repoFullName}. Expected format: owner/repo`, + ); + } - // Call the existing getGiteaBranches function with the correct object structure - return await getGiteaBranches({ - giteaId, - owner, - repo, - id: 0 // Provide a default value for optional id - }); + const [owner, repo] = parts; + + // Call the existing getGiteaBranches function with the correct object structure + return await getGiteaBranches({ + giteaId, + owner, + repo, + id: 0, // Provide a default value for optional id + }); }; /** * Helper function to check if the required fields are filled for Gitea repository operations */ -export const getErrorCloneRequirements = (entity: { - giteaRepository?: string | null; - giteaOwner?: string | null; - giteaBranch?: string | null; - giteaPathNamespace?: string | null +export const getErrorCloneRequirements = (entity: { + giteaRepository?: string | null; + giteaOwner?: string | null; + giteaBranch?: string | null; + giteaPathNamespace?: string | null; }) => { - const reasons: string[] = []; - const { giteaBranch, giteaOwner, giteaRepository, giteaPathNamespace } = entity; + const reasons: string[] = []; + const { giteaBranch, giteaOwner, giteaRepository, giteaPathNamespace } = + entity; - if (!giteaRepository) reasons.push("1. Repository not assigned."); - if (!giteaOwner) reasons.push("2. Owner not specified."); - if (!giteaBranch) reasons.push("3. Branch not defined."); - if (!giteaPathNamespace) reasons.push("4. Path namespace not defined."); + if (!giteaRepository) reasons.push("1. Repository not assigned."); + if (!giteaOwner) reasons.push("2. Owner not specified."); + if (!giteaBranch) reasons.push("3. Branch not defined."); + if (!giteaPathNamespace) reasons.push("4. Path namespace not defined."); - return reasons; + return reasons; }; /** * Function to refresh the Gitea token if expired */ export const refreshGiteaToken = async (giteaProviderId: string) => { - try { - console.log('Attempting to refresh Gitea token:', { - giteaProviderId, - timestamp: new Date().toISOString() - }); + try { + console.log("Attempting to refresh Gitea token:", { + giteaProviderId, + timestamp: new Date().toISOString(), + }); - const giteaProvider = await findGiteaById(giteaProviderId); + const giteaProvider = await findGiteaById(giteaProviderId); - if (!giteaProvider?.clientId || !giteaProvider?.clientSecret || !giteaProvider?.refreshToken) { - console.warn('Missing credentials for token refresh'); - return giteaProvider?.accessToken || null; - } + if ( + !giteaProvider?.clientId || + !giteaProvider?.clientSecret || + !giteaProvider?.refreshToken + ) { + console.warn("Missing credentials for token refresh"); + return giteaProvider?.accessToken || null; + } - const tokenEndpoint = `${giteaProvider.giteaUrl}/login/oauth/access_token`; - const params = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: giteaProvider.refreshToken, - client_id: giteaProvider.clientId, - client_secret: giteaProvider.clientSecret, - }); + const tokenEndpoint = `${giteaProvider.giteaUrl}/login/oauth/access_token`; + const params = new URLSearchParams({ + grant_type: "refresh_token", + refresh_token: giteaProvider.refreshToken, + client_id: giteaProvider.clientId, + client_secret: giteaProvider.clientSecret, + }); - console.log('Token Endpoint:', tokenEndpoint); - console.log('Request Parameters:', params.toString()); + console.log("Token Endpoint:", tokenEndpoint); + console.log("Request Parameters:", params.toString()); - const response = await fetch(tokenEndpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Accept': 'application/json' - }, - body: params.toString(), - }); + const response = await fetch(tokenEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: params.toString(), + }); - console.log('Token Refresh Response:', { - status: response.status, - statusText: response.statusText - }); + console.log("Token Refresh Response:", { + status: response.status, + statusText: response.statusText, + }); - if (!response.ok) { - const errorText = await response.text(); - console.error('Token Refresh Failed:', errorText); - return giteaProvider?.accessToken || null; - } + if (!response.ok) { + const errorText = await response.text(); + console.error("Token Refresh Failed:", errorText); + return giteaProvider?.accessToken || null; + } - const data = await response.json(); - const { access_token, refresh_token, expires_in } = data; + const data = await response.json(); + const { access_token, refresh_token, expires_in } = data; - if (!access_token) { - console.error('Missing access token in refresh response'); - return giteaProvider?.accessToken || null; - } + if (!access_token) { + console.error("Missing access token in refresh response"); + return giteaProvider?.accessToken || null; + } - const expiresAt = Date.now() + ((expires_in || 3600) * 1000); - const expiresAtSeconds = Math.floor(expiresAt / 1000); + const expiresAt = Date.now() + (expires_in || 3600) * 1000; + const expiresAtSeconds = Math.floor(expiresAt / 1000); - await updateGitea(giteaProviderId, { - accessToken: access_token, - refreshToken: refresh_token || giteaProvider.refreshToken, - expiresAt: expiresAtSeconds, - }); + await updateGitea(giteaProviderId, { + accessToken: access_token, + refreshToken: refresh_token || giteaProvider.refreshToken, + expiresAt: expiresAtSeconds, + }); - console.log('Gitea token refreshed successfully.'); - return access_token; - } catch (error) { - console.error('Token Refresh Error:', error); - // Return the existing token if refresh fails - const giteaProvider = await findGiteaById(giteaProviderId); - return giteaProvider?.accessToken || null; - } + console.log("Gitea token refreshed successfully."); + return access_token; + } catch (error) { + console.error("Token Refresh Error:", error); + // Return the existing token if refresh fails + const giteaProvider = await findGiteaById(giteaProviderId); + return giteaProvider?.accessToken || null; + } }; /** * Generate a secure Git clone command with proper validation */ -export const getGiteaCloneCommand = async (entity: any, logPath: string, isCompose = false) => { - const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository, serverId } = entity; +export const getGiteaCloneCommand = async ( + entity: any, + logPath: string, + isCompose = false, +) => { + const { + appName, + giteaBranch, + giteaId, + giteaOwner, + giteaRepository, + serverId, + } = entity; - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } + if (!serverId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Server not found", + }); + } - if (!giteaId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea Provider not found", - }); - } + if (!giteaId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } - // Use paths(true) for remote operations - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + // Use paths(true) for remote operations + const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); + const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; + const outputPath = join(basePath, appName, "code"); - const giteaProvider = await findGiteaById(giteaId); - const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ''); - const repoClone = `${giteaOwner}/${giteaRepository}.git`; - const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + const giteaProvider = await findGiteaById(giteaId); + const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const repoClone = `${giteaOwner}/${giteaRepository}.git`; + const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; - const cloneCommand = ` + const cloneCommand = ` # Ensure output directory exists and is empty rm -rf ${outputPath}; mkdir -p ${outputPath}; @@ -185,193 +203,209 @@ export const getGiteaCloneCommand = async (entity: any, logPath: string, isCompo echo "Cloned ${repoClone} to ${outputPath}: ✅" >> ${logPath}; `; - return cloneCommand; + return cloneCommand; }; /** * Main function to clone a Gitea repository with improved validation and robust directory handling */ export const cloneGiteaRepository = async ( - entity: any, - logPath?: string, - isCompose: boolean = false + entity: any, + logPath?: string, + isCompose = false, ) => { - // If logPath is not provided, generate a default log path - const actualLogPath = logPath || join( - paths()[isCompose ? 'COMPOSE_PATH' : 'APPLICATIONS_PATH'], - entity.appName, - 'clone.log' - ); + // If logPath is not provided, generate a default log path + const actualLogPath = + logPath || + join( + paths()[isCompose ? "COMPOSE_PATH" : "APPLICATIONS_PATH"], + entity.appName, + "clone.log", + ); - const writeStream = createWriteStream(actualLogPath, { flags: "a" }); - const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository } = entity; + const writeStream = createWriteStream(actualLogPath, { flags: "a" }); + const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository } = entity; - try { - if (!giteaId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea Provider not found", - }); - } + try { + if (!giteaId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea Provider not found", + }); + } - // Refresh the access token - await refreshGiteaToken(giteaId); + // Refresh the access token + await refreshGiteaToken(giteaId); - // Fetch the Gitea provider - const giteaProvider = await findGiteaById(giteaId); - if (!giteaProvider) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea provider not found in the database", - }); - } + // Fetch the Gitea provider + const giteaProvider = await findGiteaById(giteaId); + if (!giteaProvider) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea provider not found in the database", + }); + } - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(); + const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; + const outputPath = join(basePath, appName, "code"); - // Log path information - writeStream.write(`\nPath Information:\n`); - writeStream.write(`Base Path: ${basePath}\n`); - writeStream.write(`Output Path: ${outputPath}\n`); - - writeStream.write(`\nRecreating directory: ${outputPath}\n`); - await recreateDirectory(outputPath); - - // Additional step - verify directory exists and is empty - try { - const filesCheck = await fs.readdir(outputPath); - writeStream.write(`Directory after cleanup - files: ${filesCheck.length}\n`); - - if (filesCheck.length > 0) { - writeStream.write(`WARNING: Directory not empty after cleanup!\n`); - - // Force remove with shell command if recreateDirectory didn't work - if (entity.serverId) { - writeStream.write(`Attempting forceful cleanup via shell command\n`); - await execAsyncRemote(entity.serverId, `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, - data => writeStream.write(`Cleanup output: ${data}\n`)); - } else { - // Fallback to direct fs operations if serverId not available - writeStream.write(`Attempting direct fs removal\n`); - await fs.rm(outputPath, { recursive: true, force: true }); - await fs.mkdir(outputPath, { recursive: true }); - } - } - } catch (verifyError) { - writeStream.write(`Error verifying directory: ${verifyError}\n`); - // Continue anyway - the clone operation might handle this - } + // Log path information + writeStream.write("\nPath Information:\n"); + writeStream.write(`Base Path: ${basePath}\n`); + writeStream.write(`Output Path: ${outputPath}\n`); - const repoClone = `${giteaOwner}/${giteaRepository}.git`; - const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ''); - const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; + writeStream.write(`\nRecreating directory: ${outputPath}\n`); + await recreateDirectory(outputPath); - writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`); - writeStream.write(`Clone URL (masked): https://oauth2:***@${baseUrl}/${repoClone}\n`); + // Additional step - verify directory exists and is empty + try { + const filesCheck = await fs.readdir(outputPath); + writeStream.write( + `Directory after cleanup - files: ${filesCheck.length}\n`, + ); - // First try standard git clone - try { - await spawnAsync( - "git", - [ - "clone", - "--branch", - giteaBranch, - "--depth", - "1", - "--recurse-submodules", - cloneUrl, - outputPath, - "--progress" - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - } - ); - writeStream.write(`\nStandard git clone succeeded\n`); - } catch (cloneError) { - writeStream.write(`\nStandard git clone failed: ${cloneError}\n`); - writeStream.write(`Falling back to git init + fetch approach...\n`); - - // Retry cleanup one more time - if (entity.serverId) { - await execAsyncRemote(entity.serverId, `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, - data => writeStream.write(`Cleanup retry: ${data}\n`)); - } else { - await fs.rm(outputPath, { recursive: true, force: true }); - await fs.mkdir(outputPath, { recursive: true }); - } - - // Initialize git repo - writeStream.write(`Initializing git repository...\n`); - await spawnAsync("git", ["init", outputPath], data => writeStream.write(data)); - - // Set remote origin - writeStream.write(`Setting remote origin...\n`); - await spawnAsync( - "git", - ["-C", outputPath, "remote", "add", "origin", cloneUrl], - data => writeStream.write(data) - ); - - // Fetch branch - writeStream.write(`Fetching branch: ${giteaBranch}...\n`); - await spawnAsync( - "git", - ["-C", outputPath, "fetch", "--depth", "1", "origin", giteaBranch], - data => writeStream.write(data) - ); - - // Checkout branch - writeStream.write(`Checking out branch: ${giteaBranch}...\n`); - await spawnAsync( - "git", - ["-C", outputPath, "checkout", "FETCH_HEAD"], - data => writeStream.write(data) - ); - - writeStream.write(`Git init and fetch completed successfully\n`); - } + if (filesCheck.length > 0) { + writeStream.write("WARNING: Directory not empty after cleanup!\n"); - // Verify clone - const files = await fs.readdir(outputPath); - writeStream.write(`\nClone Verification:\n`); - writeStream.write(`Files found: ${files.length}\n`); - if (files.length > 0) { - files.slice(0, 10).forEach(file => writeStream.write(`- ${file}\n`)); - } + // Force remove with shell command if recreateDirectory didn't work + if (entity.serverId) { + writeStream.write("Attempting forceful cleanup via shell command\n"); + await execAsyncRemote( + entity.serverId, + `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, + (data) => writeStream.write(`Cleanup output: ${data}\n`), + ); + } else { + // Fallback to direct fs operations if serverId not available + writeStream.write("Attempting direct fs removal\n"); + await fs.rm(outputPath, { recursive: true, force: true }); + await fs.mkdir(outputPath, { recursive: true }); + } + } + } catch (verifyError) { + writeStream.write(`Error verifying directory: ${verifyError}\n`); + // Continue anyway - the clone operation might handle this + } - if (files.length === 0) { - throw new Error("Repository clone failed - directory is empty"); - } + const repoClone = `${giteaOwner}/${giteaRepository}.git`; + const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ""); + const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; - writeStream.write(`\nCloned ${repoClone} successfully: ✅\n`); - } catch (error) { - writeStream.write(`\nClone Error: ${error}\n`); - throw error; - } finally { - writeStream.end(); - } + writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`); + writeStream.write( + `Clone URL (masked): https://oauth2:***@${baseUrl}/${repoClone}\n`, + ); + + // First try standard git clone + try { + await spawnAsync( + "git", + [ + "clone", + "--branch", + giteaBranch, + "--depth", + "1", + "--recurse-submodules", + cloneUrl, + outputPath, + "--progress", + ], + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + ); + writeStream.write("\nStandard git clone succeeded\n"); + } catch (cloneError) { + writeStream.write(`\nStandard git clone failed: ${cloneError}\n`); + writeStream.write("Falling back to git init + fetch approach...\n"); + + // Retry cleanup one more time + if (entity.serverId) { + await execAsyncRemote( + entity.serverId, + `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, + (data) => writeStream.write(`Cleanup retry: ${data}\n`), + ); + } else { + await fs.rm(outputPath, { recursive: true, force: true }); + await fs.mkdir(outputPath, { recursive: true }); + } + + // Initialize git repo + writeStream.write("Initializing git repository...\n"); + await spawnAsync("git", ["init", outputPath], (data) => + writeStream.write(data), + ); + + // Set remote origin + writeStream.write("Setting remote origin...\n"); + await spawnAsync( + "git", + ["-C", outputPath, "remote", "add", "origin", cloneUrl], + (data) => writeStream.write(data), + ); + + // Fetch branch + writeStream.write(`Fetching branch: ${giteaBranch}...\n`); + await spawnAsync( + "git", + ["-C", outputPath, "fetch", "--depth", "1", "origin", giteaBranch], + (data) => writeStream.write(data), + ); + + // Checkout branch + writeStream.write(`Checking out branch: ${giteaBranch}...\n`); + await spawnAsync( + "git", + ["-C", outputPath, "checkout", "FETCH_HEAD"], + (data) => writeStream.write(data), + ); + + writeStream.write("Git init and fetch completed successfully\n"); + } + + // Verify clone + const files = await fs.readdir(outputPath); + writeStream.write("\nClone Verification:\n"); + writeStream.write(`Files found: ${files.length}\n`); + if (files.length > 0) { + // Using a for loop instead of forEach + for (let i = 0; i < Math.min(files.length, 10); i++) { + writeStream.write(`- ${files[i]}\n`); + } + } + + if (files.length === 0) { + throw new Error("Repository clone failed - directory is empty"); + } + + writeStream.write(`\nCloned ${repoClone} successfully: ✅\n`); + } catch (error) { + writeStream.write(`\nClone Error: ${error}\n`); + throw error; + } finally { + writeStream.end(); + } }; - /** * Clone a Gitea repository locally for a Compose configuration * Leverages the existing comprehensive cloneGiteaRepository function */ export const cloneRawGiteaRepository = async (entity: any) => { - // Merge the existing entity with compose-specific properties - const composeEntity = { - ...entity, - sourceType: 'compose', - isCompose: true - }; - - // Call cloneGiteaRepository with the modified entity - await cloneGiteaRepository(composeEntity); + // Merge the existing entity with compose-specific properties + const composeEntity = { + ...entity, + sourceType: "compose", + isCompose: true, + }; + + // Call cloneGiteaRepository with the modified entity + await cloneGiteaRepository(composeEntity); }; /** @@ -379,204 +413,212 @@ export const cloneRawGiteaRepository = async (entity: any) => { * Uses the existing getGiteaCloneCommand function for remote cloning */ export const cloneRawGiteaRepositoryRemote = async (compose: any) => { - const { COMPOSE_PATH } = paths(true); - const logPath = join(COMPOSE_PATH, compose.appName, 'clone.log'); - - // Reuse the existing getGiteaCloneCommand function - const command = await getGiteaCloneCommand({ - ...compose, - isCompose: true - }, logPath, true); + const { COMPOSE_PATH } = paths(true); + const logPath = join(COMPOSE_PATH, compose.appName, "clone.log"); - if (!compose.serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } + // Reuse the existing getGiteaCloneCommand function + const command = await getGiteaCloneCommand( + { + ...compose, + isCompose: true, + }, + logPath, + true, + ); - // Execute the clone command on the remote server - await execAsyncRemote(compose.serverId, command); + if (!compose.serverId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Server not found", + }); + } + + // Execute the clone command on the remote server + await execAsyncRemote(compose.serverId, command); }; // Helper function to check if a Gitea provider meets the necessary requirements export const haveGiteaRequirements = (giteaProvider: any) => { - return !!(giteaProvider?.clientId && giteaProvider?.clientSecret); + return !!(giteaProvider?.clientId && giteaProvider?.clientSecret); }; /** * Function to test the connection to a Gitea provider */ export const testGiteaConnection = async (input: { giteaId: string }) => { - try { - const { giteaId } = input; + try { + const { giteaId } = input; - if (!giteaId) { - throw new Error("Gitea provider not found"); - } + if (!giteaId) { + throw new Error("Gitea provider not found"); + } - // Fetch the Gitea provider from the database - const giteaProvider = await findGiteaById(giteaId); - if (!giteaProvider) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitea provider not found in the database", - }); - } + // Fetch the Gitea provider from the database + const giteaProvider = await findGiteaById(giteaId); + if (!giteaProvider) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitea provider not found in the database", + }); + } - console.log('Gitea Provider Found:', { - id: giteaProvider.giteaId, - url: giteaProvider.giteaUrl, - hasAccessToken: !!giteaProvider.accessToken, - }); + console.log("Gitea Provider Found:", { + id: giteaProvider.giteaId, + url: giteaProvider.giteaUrl, + hasAccessToken: !!giteaProvider.accessToken, + }); - // Refresh the token if needed - await refreshGiteaToken(giteaId); + // Refresh the token if needed + await refreshGiteaToken(giteaId); - // Fetch the provider again in case the token was refreshed - const provider = await findGiteaById(giteaId); - if (!provider || !provider.accessToken) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "No access token available. Please authorize with Gitea.", - }); - } + // Fetch the provider again in case the token was refreshed + const provider = await findGiteaById(giteaId); + if (!provider || !provider.accessToken) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "No access token available. Please authorize with Gitea.", + }); + } - // Make API request to test connection - console.log('Making API request to test connection...'); - - // Construct proper URL for the API request - const baseUrl = provider.giteaUrl.replace(/\/+$/, ''); // Remove trailing slashes - const url = `${baseUrl}/api/v1/user/repos`; - - console.log(`Testing connection to: ${url}`); - - const response = await fetch(url, { - headers: { - 'Accept': 'application/json', - 'Authorization': `token ${provider.accessToken}` - } - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Repository API failed:', errorText); - throw new Error(`Failed to connect to Gitea API: ${response.status} ${response.statusText}`); - } - - const repos = await response.json(); - console.log(`Successfully connected to Gitea API. Found ${repos.length} repositories.`); - - // Update lastAuthenticatedAt - await updateGitea(giteaId, { - lastAuthenticatedAt: Math.floor(Date.now() / 1000) - }); + // Make API request to test connection + console.log("Making API request to test connection..."); - return repos.length; - } catch (error) { - console.error('Gitea Connection Test Error:', error); - throw error; - } + // Construct proper URL for the API request + const baseUrl = provider.giteaUrl.replace(/\/+$/, ""); // Remove trailing slashes + const url = `${baseUrl}/api/v1/user/repos`; + + console.log(`Testing connection to: ${url}`); + + const response = await fetch(url, { + headers: { + Accept: "application/json", + Authorization: `token ${provider.accessToken}`, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error("Repository API failed:", errorText); + throw new Error( + `Failed to connect to Gitea API: ${response.status} ${response.statusText}`, + ); + } + + const repos = await response.json(); + console.log( + `Successfully connected to Gitea API. Found ${repos.length} repositories.`, + ); + + // Update lastAuthenticatedAt + await updateGitea(giteaId, { + lastAuthenticatedAt: Math.floor(Date.now() / 1000), + }); + + return repos.length; + } catch (error) { + console.error("Gitea Connection Test Error:", error); + throw error; + } }; /** * Function to fetch repositories from a Gitea provider */ export const getGiteaRepositories = async (giteaId?: string) => { - if (!giteaId) { - return []; - } + if (!giteaId) { + return []; + } - // Refresh the token - await refreshGiteaToken(giteaId); + // Refresh the token + await refreshGiteaToken(giteaId); - // Fetch the Gitea provider - const giteaProvider = await findGiteaById(giteaId); + // Fetch the Gitea provider + const giteaProvider = await findGiteaById(giteaId); - // Construct the URL for fetching repositories - const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ''); - const url = `${baseUrl}/api/v1/user/repos`; + // Construct the URL for fetching repositories + const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ""); + const url = `${baseUrl}/api/v1/user/repos`; - const response = await fetch(url, { - headers: { - 'Accept': 'application/json', - 'Authorization': `token ${giteaProvider.accessToken}` - } - }); + const response = await fetch(url, { + headers: { + Accept: "application/json", + Authorization: `token ${giteaProvider.accessToken}`, + }, + }); - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Failed to fetch repositories: ${response.statusText}`, - }); - } + if (!response.ok) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Failed to fetch repositories: ${response.statusText}`, + }); + } - const repositories = await response.json(); + const repositories = await response.json(); - // Map repositories to a consistent format - const mappedRepositories = repositories.map((repo: any) => ({ - id: repo.id, - name: repo.name, - url: repo.full_name, - owner: { - username: repo.owner.login - } - })); + // Map repositories to a consistent format + const mappedRepositories = repositories.map((repo: any) => ({ + id: repo.id, + name: repo.name, + url: repo.full_name, + owner: { + username: repo.owner.login, + }, + })); - return mappedRepositories; + return mappedRepositories; }; /** * Function to fetch branches for a specific Gitea repository */ export const getGiteaBranches = async (input: { - id?: number; - giteaId?: string; - owner: string; - repo: string; + id?: number; + giteaId?: string; + owner: string; + repo: string; }) => { - if (!input.giteaId) { - return []; - } + if (!input.giteaId) { + return []; + } - // Fetch the Gitea provider - const giteaProvider = await findGiteaById(input.giteaId); + // Fetch the Gitea provider + const giteaProvider = await findGiteaById(input.giteaId); - // Construct the URL for fetching branches - const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ''); - const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`; + // Construct the URL for fetching branches + const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ""); + const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`; - const response = await fetch(url, { - headers: { - 'Accept': 'application/json', - 'Authorization': `token ${giteaProvider.accessToken}` - } - }); + const response = await fetch(url, { + headers: { + Accept: "application/json", + Authorization: `token ${giteaProvider.accessToken}`, + }, + }); - if (!response.ok) { - throw new Error(`Failed to fetch branches: ${response.statusText}`); - } + if (!response.ok) { + throw new Error(`Failed to fetch branches: ${response.statusText}`); + } - const branches = await response.json(); + const branches = await response.json(); - // Map branches to a consistent format - return branches.map((branch: any) => ({ - id: branch.name, - name: branch.name, - commit: { - id: branch.commit.id - } - })); + // Map branches to a consistent format + return branches.map((branch: any) => ({ + id: branch.name, + name: branch.name, + commit: { + id: branch.commit.id, + }, + })); }; export default { - cloneGiteaRepository, - cloneRawGiteaRepository, - cloneRawGiteaRepositoryRemote, - refreshGiteaToken, - haveGiteaRequirements, - testGiteaConnection, - getGiteaRepositories, - getGiteaBranches, - fetchGiteaBranches -}; \ No newline at end of file + cloneGiteaRepository, + cloneRawGiteaRepository, + cloneRawGiteaRepositoryRemote, + refreshGiteaToken, + haveGiteaRequirements, + testGiteaConnection, + getGiteaRepositories, + getGiteaBranches, + fetchGiteaBranches, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 286802e6e..23ff3b0d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,7 +242,7 @@ importers: specifier: ^10.43.6 version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@trpc/server': - specifier: ^10.43.6 + specifier: ^10.45.2 version: 10.45.2 '@uiw/codemirror-theme-github': specifier: ^4.22.1 @@ -471,7 +471,7 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: ^18.17.0 + specifier: ^18.19.42 version: 18.19.42 '@types/node-os-utils': specifier: 1.3.4