From 99f63597a890079c48b4d1b8996bce77652e9156 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 1 Sep 2024 21:49:59 -0600 Subject: [PATCH] feat: add test connection bitbucket and gitlab --- .../generic/save-bitbucket-provider.tsx | 15 +- .../general/generic/save-github-provider.tsx | 15 +- .../general/generic/save-gitlab-provider.tsx | 15 +- .../application/general/generic/show.tsx | 6 +- .../save-bitbucket-provider-compose.tsx | 15 +- .../generic/save-github-provider-compose.tsx | 15 +- .../generic/save-gitlab-provider-compose.tsx | 15 +- .../compose/general/generic/show.tsx | 6 +- .../git/bitbucket/add-bitbucket-provider.tsx | 3 +- .../git/bitbucket/edit-bitbucket-provider.tsx | 142 ++++++-------- .../git/gitlab/add-gitlab-provider.tsx | 3 +- .../git/gitlab/edit-gitlab-provider.tsx | 180 ++++++++++++++++++ .../settings/git/show-git-providers.tsx | 44 ++++- apps/dokploy/server/api/root.ts | 10 +- apps/dokploy/server/api/routers/bitbucket.ts | 82 ++++++++ .../server/api/routers/git-provider.ts | 163 +--------------- apps/dokploy/server/api/routers/github.ts | 49 +++++ apps/dokploy/server/api/routers/gitlab.ts | 86 +++++++++ apps/dokploy/server/api/services/bitbucket.ts | 88 +++++++++ .../server/api/services/git-provider.ts | 143 +------------- apps/dokploy/server/api/services/github.ts | 58 ++++++ apps/dokploy/server/api/services/gitlab.ts | 104 ++++++++++ apps/dokploy/server/db/schema/application.ts | 2 +- apps/dokploy/server/db/schema/bitbucket.ts | 64 +++++++ apps/dokploy/server/db/schema/compose.ts | 2 +- apps/dokploy/server/db/schema/git-provider.ts | 147 +------------- apps/dokploy/server/db/schema/github.ts | 54 ++++++ apps/dokploy/server/db/schema/gitlab.ts | 70 +++++++ apps/dokploy/server/db/schema/index.ts | 3 + .../server/utils/providers/bitbucket.ts | 54 +++++- apps/dokploy/server/utils/providers/github.ts | 6 +- apps/dokploy/server/utils/providers/gitlab.ts | 57 +++++- 32 files changed, 1120 insertions(+), 596 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx create mode 100644 apps/dokploy/server/api/routers/bitbucket.ts create mode 100644 apps/dokploy/server/api/routers/github.ts create mode 100644 apps/dokploy/server/api/routers/gitlab.ts create mode 100644 apps/dokploy/server/api/services/bitbucket.ts create mode 100644 apps/dokploy/server/api/services/github.ts create mode 100644 apps/dokploy/server/api/services/gitlab.ts create mode 100644 apps/dokploy/server/db/schema/bitbucket.ts create mode 100644 apps/dokploy/server/db/schema/github.ts create mode 100644 apps/dokploy/server/db/schema/gitlab.ts 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 fe6f7d66e..28aaec464 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 @@ -58,7 +58,7 @@ interface Props { export const SaveBitbucketProvider = ({ applicationId }: Props) => { const { data: bitbucketProviders } = - api.gitProvider.bitbucketProviders.useQuery(); + api.bitbucket.bitbucketProviders.useQuery(); const { data, refetch } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isLoading: isSavingBitbucketProvider } = @@ -85,15 +85,20 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { isLoading: isLoadingRepositories, error, isError, - } = api.gitProvider.getBitbucketRepositories.useQuery({ - bitbucketId, - }); + } = api.bitbucket.getBitbucketRepositories.useQuery( + { + bitbucketId, + }, + { + enabled: !!bitbucketId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getBitbucketBranches.useQuery( + } = api.bitbucket.getBitbucketBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, 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 6bde55ed3..7f6ed46c0 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 @@ -56,7 +56,7 @@ interface Props { } export const SaveGithubProvider = ({ applicationId }: Props) => { - const { data: githubProviders } = api.gitProvider.githubProviders.useQuery(); + const { data: githubProviders } = api.github.githubProviders.useQuery(); const { data, refetch } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isLoading: isSavingGithubProvider } = @@ -79,15 +79,20 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { const githubId = form.watch("githubId"); const { data: repositories, isLoading: isLoadingRepositories } = - api.gitProvider.getGithubRepositories.useQuery({ - githubId, - }); + api.github.getGithubRepositories.useQuery( + { + githubId, + }, + { + enabled: !!githubId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getGithubBranches.useQuery( + } = api.github.getGithubBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, 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 b153ae8f1..89e92e4eb 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 @@ -59,7 +59,7 @@ interface Props { } export const SaveGitlabProvider = ({ applicationId }: Props) => { - const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery(); + const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data, refetch } = api.application.one.useQuery({ applicationId }); const { mutateAsync, isLoading: isSavingGitlabProvider } = @@ -87,15 +87,20 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { data: repositories, isLoading: isLoadingRepositories, error, - } = api.gitProvider.getGitlabRepositories.useQuery({ - gitlabId, - }); + } = api.gitlab.getGitlabRepositories.useQuery( + { + gitlabId, + }, + { + enabled: !!gitlabId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getGitlabBranches.useQuery( + } = api.gitlab.getGitlabBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index 9347152bf..252d1af7c 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -25,10 +25,10 @@ interface Props { } export const ShowProviderForm = ({ applicationId }: Props) => { - const { data: githubProviders } = api.gitProvider.githubProviders.useQuery(); - const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery(); + const { data: githubProviders } = api.github.githubProviders.useQuery(); + const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data: bitbucketProviders } = - api.gitProvider.bitbucketProviders.useQuery(); + api.bitbucket.bitbucketProviders.useQuery(); const { data: application } = api.application.one.useQuery({ applicationId }); const [tab, setSab] = useState(application?.sourceType || "github"); 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 076a178d2..0aa7afef8 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 @@ -58,7 +58,7 @@ interface Props { export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { const { data: bitbucketProviders } = - api.gitProvider.bitbucketProviders.useQuery(); + api.bitbucket.bitbucketProviders.useQuery(); const { data, refetch } = api.compose.one.useQuery({ composeId }); const { mutateAsync, isLoading: isSavingBitbucketProvider } = @@ -85,15 +85,20 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { isLoading: isLoadingRepositories, error, isError, - } = api.gitProvider.getBitbucketRepositories.useQuery({ - bitbucketId, - }); + } = api.bitbucket.getBitbucketRepositories.useQuery( + { + bitbucketId, + }, + { + enabled: !!bitbucketId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getBitbucketBranches.useQuery( + } = api.bitbucket.getBitbucketBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, 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 6674a45b7..b77fdeabf 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 @@ -56,7 +56,7 @@ interface Props { } export const SaveGithubProviderCompose = ({ composeId }: Props) => { - const { data: githubProviders } = api.gitProvider.githubProviders.useQuery(); + const { data: githubProviders } = api.github.githubProviders.useQuery(); const { data, refetch } = api.compose.one.useQuery({ composeId }); const { mutateAsync, isLoading: isSavingGithubProvider } = @@ -79,15 +79,20 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { const githubId = form.watch("githubId"); const { data: repositories, isLoading: isLoadingRepositories } = - api.gitProvider.getRepositories.useQuery({ - githubId, - }); + api.github.getGithubRepositories.useQuery( + { + githubId, + }, + { + enabled: !!githubId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getBranches.useQuery( + } = api.github.getGithubBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, 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 7fcf018d4..4aac89207 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 @@ -59,7 +59,7 @@ interface Props { } export const SaveGitlabProviderCompose = ({ composeId }: Props) => { - const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery(); + const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data, refetch } = api.compose.one.useQuery({ composeId }); const { mutateAsync, isLoading: isSavingGitlabProvider } = @@ -87,15 +87,20 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => { data: repositories, isLoading: isLoadingRepositories, error, - } = api.gitProvider.getGitlabRepositories.useQuery({ - gitlabId, - }); + } = api.gitlab.getGitlabRepositories.useQuery( + { + gitlabId, + }, + { + enabled: !!gitlabId, + }, + ); const { data: branches, fetchStatus, status, - } = api.gitProvider.getGitlabBranches.useQuery( + } = api.gitlab.getGitlabBranches.useQuery( { owner: repository?.owner, repo: repository?.repo, diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index bbbd42fc6..0e84fe492 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -23,10 +23,10 @@ interface Props { } export const ShowProviderFormCompose = ({ composeId }: Props) => { - const { data: githubProviders } = api.gitProvider.githubProviders.useQuery(); - const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery(); + const { data: githubProviders } = api.github.githubProviders.useQuery(); + const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data: bitbucketProviders } = - api.gitProvider.bitbucketProviders.useQuery(); + api.bitbucket.bitbucketProviders.useQuery(); const { data: compose } = api.compose.one.useQuery({ composeId }); const [tab, setSab] = useState(compose?.sourceType || "github"); diff --git a/apps/dokploy/components/dashboard/settings/git/bitbucket/add-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/settings/git/bitbucket/add-bitbucket-provider.tsx index 79b6deafe..82fbd0f84 100644 --- a/apps/dokploy/components/dashboard/settings/git/bitbucket/add-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/bitbucket/add-bitbucket-provider.tsx @@ -52,8 +52,7 @@ export const AddBitbucketProvider = () => { const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); const url = useUrl(); - const { mutateAsync, error, isError } = - api.gitProvider.createBitbucket.useMutation(); + const { mutateAsync, error, isError } = api.bitbucket.create.useMutation(); const { data: auth } = api.auth.get.useQuery(); const router = useRouter(); const form = useForm({ diff --git a/apps/dokploy/components/dashboard/settings/git/bitbucket/edit-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/settings/git/bitbucket/edit-bitbucket-provider.tsx index c3fcffea0..e7298de11 100644 --- a/apps/dokploy/components/dashboard/settings/git/bitbucket/edit-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/bitbucket/edit-bitbucket-provider.tsx @@ -1,8 +1,4 @@ -import { - BitbucketIcon, - GithubIcon, - GitlabIcon, -} from "@/components/icons/data-tools-icons"; +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { CardContent } from "@/components/ui/card"; @@ -23,11 +19,8 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { useUrl } from "@/utils/hooks/use-url"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ExternalLink } from "lucide-react"; -import Link from "next/link"; -import { useRouter } from "next/router"; +import { Edit } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -40,73 +33,77 @@ const Schema = z.object({ username: z.string().min(1, { message: "Username is required", }), - password: z.string().min(1, { - message: "App Password is required", - }), workspaceName: z.string().optional(), }); type Schema = z.infer; -export const AddBitbucketProvider = () => { - // const {data} = api.gitProvider. +interface Props { + bitbucketId: string; +} + +export const EditBitbucketProvider = ({ bitbucketId }: Props) => { + const { data: bitbucket } = api.bitbucket.one.useQuery( + { + bitbucketId, + }, + { + enabled: !!bitbucketId, + }, + ); const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); - const url = useUrl(); - const { mutateAsync, error, isError } = - api.gitProvider.createBitbucket.useMutation(); - const { data: auth } = api.auth.get.useQuery(); - const router = useRouter(); + const { mutateAsync, error, isError } = api.bitbucket.update.useMutation(); + const { mutateAsync: testConnection, isLoading } = + api.bitbucket.testConnection.useMutation(); const form = useForm({ defaultValues: { username: "", - password: "", workspaceName: "", }, resolver: zodResolver(Schema), }); + const username = form.watch("username"); + const workspaceName = form.watch("workspaceName"); + useEffect(() => { form.reset({ - username: "", - password: "", - workspaceName: "", + username: bitbucket?.bitbucketUsername || "", + workspaceName: bitbucket?.bitbucketWorkspaceName || "", + name: bitbucket?.gitProvider.name || "", }); }, [form, isOpen]); const onSubmit = async (data: Schema) => { await mutateAsync({ + bitbucketId, + gitProviderId: bitbucket?.gitProviderId || "", bitbucketUsername: data.username, - appPassword: data.password, bitbucketWorkspaceName: data.workspaceName || "", - authId: auth?.id || "", name: data.name || "", }) .then(async () => { await utils.gitProvider.getAll.invalidate(); - toast.success("Bitbucket configured successfully"); + toast.success("Bitbucket updated successfully"); setIsOpen(false); }) .catch(() => { - toast.error("Error configuring Bitbucket"); + toast.error("Error to update Bitbucket"); }); }; return ( - - Bitbucket Provider + Update Bitbucket Provider @@ -119,37 +116,6 @@ export const AddBitbucketProvider = () => { >
-

- To integrate your Bitbucket account, you need to create a new - App Password in your Bitbucket settings. Follow these steps: -

-
    -
  1. - Create new App Password{" "} - - - -
  2. -
  3. - When creating the App Password, ensure you grant the - following permissions: -
      -
    • Account: Read
    • -
    • Workspace membership: Read
    • -
    • Projects: Read
    • -
    • Repositories: Read
    • -
    • Pull requests: Read
    • -
    • Webhooks: Read and write
    • -
    -
  4. -
  5. - After creating, you'll receive an App Password. Copy it and - paste it below along with your Bitbucket username. -
  6. -
{ )} /> - ( - - App Password - - - - - - )} - /> - { )} /> - +
+ + +
diff --git a/apps/dokploy/components/dashboard/settings/git/gitlab/add-gitlab-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitlab/add-gitlab-provider.tsx index ed91a0523..11f5be22f 100644 --- a/apps/dokploy/components/dashboard/settings/git/gitlab/add-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/gitlab/add-gitlab-provider.tsx @@ -53,8 +53,7 @@ export const AddGitlabProvider = () => { const [isOpen, setIsOpen] = useState(false); const url = useUrl(); const { data: auth } = api.auth.get.useQuery(); - const { mutateAsync, error, isError } = - api.gitProvider.createGitlab.useMutation(); + const { mutateAsync, error, isError } = api.gitlab.create.useMutation(); const webhookUrl = `${url}/api/providers/gitlab/callback`; const form = useForm({ diff --git a/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx new file mode 100644 index 000000000..f004607aa --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx @@ -0,0 +1,180 @@ +import { GitlabIcon } from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api } from "@/utils/api"; +import { useUrl } from "@/utils/hooks/use-url"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Edit } from "lucide-react"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const Schema = z.object({ + name: z.string().min(1, { + message: "Name is required", + }), + groupName: z.string().optional(), +}); + +type Schema = z.infer; + +interface Props { + gitlabId: string; +} + +export const EditGitlabProvider = ({ gitlabId }: Props) => { + const { data: gitlab } = api.gitlab.one.useQuery( + { + gitlabId, + }, + { + enabled: !!gitlabId, + }, + ); + const utils = api.useUtils(); + const [isOpen, setIsOpen] = useState(false); + const { mutateAsync, error, isError } = api.gitlab.update.useMutation(); + const { mutateAsync: testConnection, isLoading } = + api.gitlab.testConnection.useMutation(); + const form = useForm({ + defaultValues: { + groupName: "", + name: "", + }, + resolver: zodResolver(Schema), + }); + + const groupName = form.watch("groupName"); + + useEffect(() => { + form.reset({ + groupName: gitlab?.groupName || "", + name: gitlab?.gitProvider.name || "", + }); + }, [form, isOpen]); + + const onSubmit = async (data: Schema) => { + await mutateAsync({ + gitlabId, + gitProviderId: gitlab?.gitProviderId || "", + groupName: data.groupName || "", + name: data.name || "", + }) + .then(async () => { + await utils.gitProvider.getAll.invalidate(); + toast.success("Gitlab updated successfully"); + setIsOpen(false); + }) + .catch(() => { + toast.error("Error to update Gitlab"); + }); + }; + + return ( + + + + + + + + Update GitLab Provider + + + + {isError && {error?.message}} +
+ + +
+ ( + + Name + + + + + + )} + /> + + ( + + Group Name (Optional) + + + + + + )} + /> + +
+ + +
+
+
+
+ +
+
+ ); +}; 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 b4b3ca8f7..9f0aad7a7 100644 --- a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx +++ b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx @@ -12,6 +12,9 @@ import { api } from "@/utils/api"; import Link from "next/link"; import { RemoveGitProvider } from "./remove-git-provider"; import { useUrl } from "@/utils/hooks/use-url"; +import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider"; +import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; +import { formatDate } from "date-fns"; export const ShowGitProviders = () => { const { data } = api.gitProvider.getAll.useQuery(); @@ -48,6 +51,7 @@ export const ShowGitProviders = () => { {data?.map((gitProvider, index) => { const isGithub = gitProvider.providerType === "github"; const isGitlab = gitProvider.providerType === "gitlab"; + const isBitbucket = gitProvider.providerType === "bitbucket"; const haveGithubRequirements = gitProvider.providerType === "github" && gitProvider.github?.githubPrivateKey && @@ -61,7 +65,7 @@ export const ShowGitProviders = () => { className="space-y-4" key={`${gitProvider.gitProviderId}-${index}`} > - +
{gitProvider.providerType === "github" && ( @@ -72,7 +76,7 @@ export const ShowGitProviders = () => { {gitProvider.providerType === "bitbucket" && ( )} -
+

{gitProvider.providerType === "github" ? "GitHub" @@ -83,6 +87,15 @@ export const ShowGitProviders = () => {

{gitProvider.name}

+ +

+ Created{" "} + {formatDate( + gitProvider.createdAt, + "yyyy-MM-dd hh:mm:ss a", + )} +

+
@@ -92,7 +105,7 @@ export const ShowGitProviders = () => { href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`} className={buttonVariants({ className: "w-fit" })} > - Install Github App + Install
)} @@ -107,7 +120,7 @@ export const ShowGitProviders = () => { variant: "secondary", })} > - Manage Github App + Manage
)} @@ -125,15 +138,26 @@ export const ShowGitProviders = () => { variant: "secondary", })} > - Install Gitlab App + Install )} - - +
+ {isBitbucket && ( + + )} + {isGitlab && haveGitlabRequirements && ( + + )} + +
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index d340514d6..81c61c02b 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -25,7 +25,10 @@ import { securityRouter } from "./routers/security"; import { settingsRouter } from "./routers/settings"; import { sshRouter } from "./routers/ssh-key"; import { userRouter } from "./routers/user"; -import { gitProvider } from "./routers/git-provider"; +import { gitProviderRouter } from "./routers/git-provider"; +import { bitbucketRouter } from "./routers/bitbucket"; +import { githubRouter } from "./routers/github"; +import { gitlabRouter } from "./routers/gitlab"; /** * This is the primary router for your server. @@ -59,7 +62,10 @@ export const appRouter = createTRPCRouter({ cluster: clusterRouter, notification: notificationRouter, sshKey: sshRouter, - gitProvider: gitProvider, + gitProvider: gitProviderRouter, + bitbucket: bitbucketRouter, + gitlab: gitlabRouter, + github: githubRouter, }); // export type definition of API diff --git a/apps/dokploy/server/api/routers/bitbucket.ts b/apps/dokploy/server/api/routers/bitbucket.ts new file mode 100644 index 000000000..598d0e591 --- /dev/null +++ b/apps/dokploy/server/api/routers/bitbucket.ts @@ -0,0 +1,82 @@ +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { + apiBitbucketTestConnection, + apiCreateBitbucket, + apiFindBitbucketBranches, + apiFindOneBitbucket, + apiUpdateBitbucket, +} from "@/server/db/schema"; +import { db } from "@/server/db"; +import { + getBitbucketRepositories, + getBitbucketBranches, + testBitbucketConnection, +} from "@/server/utils/providers/bitbucket"; +import { TRPCError } from "@trpc/server"; +import { + createBitbucket, + findBitbucketById, + updateBitbucket, +} from "../services/bitbucket"; + +export const bitbucketRouter = createTRPCRouter({ + create: protectedProcedure + .input(apiCreateBitbucket) + .mutation(async ({ input }) => { + try { + return await createBitbucket(input); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create this bitbucket provider", + cause: error, + }); + } + }), + one: protectedProcedure + .input(apiFindOneBitbucket) + .query(async ({ input }) => { + return await findBitbucketById(input.bitbucketId); + }), + bitbucketProviders: protectedProcedure.query(async () => { + const result = await db.query.bitbucket.findMany({ + with: { + gitProvider: true, + }, + columns: { + bitbucketId: true, + }, + }); + return result; + }), + + getBitbucketRepositories: protectedProcedure + .input(apiFindOneBitbucket) + .query(async ({ input }) => { + return await getBitbucketRepositories(input.bitbucketId); + }), + getBitbucketBranches: protectedProcedure + .input(apiFindBitbucketBranches) + .query(async ({ input }) => { + return await getBitbucketBranches(input); + }), + testConnection: protectedProcedure + .input(apiBitbucketTestConnection) + .mutation(async ({ input }) => { + try { + const result = await testBitbucketConnection(input); + + return `Found ${result} repositories`; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error?.message : `Error: ${error}`, + }); + } + }), + update: protectedProcedure + .input(apiUpdateBitbucket) + .mutation(async ({ input }) => { + return await updateBitbucket(input.bitbucketId, input); + }), +}); diff --git a/apps/dokploy/server/api/routers/git-provider.ts b/apps/dokploy/server/api/routers/git-provider.ts index 09b06d21e..3b47de1b5 100644 --- a/apps/dokploy/server/api/routers/git-provider.ts +++ b/apps/dokploy/server/api/routers/git-provider.ts @@ -1,42 +1,11 @@ import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { db } from "@/server/db"; -import { - apiCreateBitbucket, - apiCreateGitlab, - apiFindBitbucketBranches, - apiFindGithubBranches, - apiFindGitlabBranches, - apiFindOneBitbucket, - apiFindOneGithub, - apiFindOneGitlab, - apiRemoveGitProvider, -} from "@/server/db/schema"; +import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; -import { - createBitbucket, - createGitlab, - findBitbucketById, - findGithubById, - findGitlabById, - haveGithubRequirements, - removeGitProvider, -} from "../services/git-provider"; -import { z } from "zod"; -import { - getGitlabBranches, - getGitlabRepositories, - haveGitlabRequirements, -} from "@/server/utils/providers/gitlab"; -import { - getBitbucketBranches, - getBitbucketRepositories, -} from "@/server/utils/providers/bitbucket"; -import { - getGithubBranches, - getGithubRepositories, -} from "@/server/utils/providers/github"; +import { removeGitProvider } from "../services/git-provider"; +import { asc, desc } from "drizzle-orm"; -export const gitProvider = createTRPCRouter({ +export const gitProviderRouter = createTRPCRouter({ getAll: protectedProcedure.query(async () => { return await db.query.gitProvider.findMany({ with: { @@ -44,23 +13,9 @@ export const gitProvider = createTRPCRouter({ bitbucket: true, github: true, }, + orderBy: desc(gitProvider.createdAt), }); }), - oneGithub: protectedProcedure - .input(apiFindOneGithub) - .query(async ({ input }) => { - return await findGithubById(input.githubId); - }), - oneGitlab: protectedProcedure - .input(apiFindOneGitlab) - .query(async ({ input }) => { - return await findGitlabById(input.gitlabId); - }), - oneBitbucket: protectedProcedure - .input(apiFindOneBitbucket) - .query(async ({ input }) => { - return await findBitbucketById(input.bitbucketId); - }), remove: protectedProcedure .input(apiRemoveGitProvider) .mutation(async ({ input }) => { @@ -73,112 +28,4 @@ export const gitProvider = createTRPCRouter({ }); } }), - createGitlab: protectedProcedure - .input(apiCreateGitlab) - .mutation(async ({ input }) => { - try { - return await createGitlab(input); - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create this gitlab provider", - cause: error, - }); - } - }), - createBitbucket: protectedProcedure - .input(apiCreateBitbucket) - .mutation(async ({ input }) => { - try { - return await createBitbucket(input); - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create this bitbucket provider", - cause: error, - }); - } - }), - githubProviders: protectedProcedure.query(async () => { - const result = await db.query.github.findMany({ - with: { - gitProvider: true, - }, - }); - - const filtered = result - .filter((provider) => haveGithubRequirements(provider)) - .map((provider) => { - return { - githubId: provider.githubId, - gitProvider: { - ...provider.gitProvider, - }, - }; - }); - - return filtered; - }), - gitlabProviders: protectedProcedure.query(async () => { - const result = await db.query.gitlab.findMany({ - with: { - gitProvider: true, - }, - }); - const filtered = result - .filter((provider) => haveGitlabRequirements(provider)) - .map((provider) => { - return { - gitlabId: provider.gitlabId, - gitProvider: { - ...provider.gitProvider, - }, - }; - }); - - return filtered; - }), - bitbucketProviders: protectedProcedure.query(async () => { - const result = await db.query.bitbucket.findMany({ - with: { - gitProvider: true, - }, - columns: { - bitbucketId: true, - }, - }); - return result; - }), - - getGitlabRepositories: protectedProcedure - .input(apiFindOneGitlab) - .query(async ({ input }) => { - return await getGitlabRepositories(input.gitlabId); - }), - - getGitlabBranches: protectedProcedure - .input(apiFindGitlabBranches) - .query(async ({ input }) => { - return await getGitlabBranches(input); - }), - getBitbucketRepositories: protectedProcedure - .input(apiFindOneBitbucket) - .query(async ({ input }) => { - return await getBitbucketRepositories(input.bitbucketId); - }), - getBitbucketBranches: protectedProcedure - .input(apiFindBitbucketBranches) - .query(async ({ input }) => { - return await getBitbucketBranches(input); - }), - getGithubRepositories: protectedProcedure - .input(apiFindOneGithub) - .query(async ({ input }) => { - return await getGithubRepositories(input.githubId); - }), - getGithubBranches: protectedProcedure - .input(apiFindGithubBranches) - .query(async ({ input }) => { - return await getGithubBranches(input); - }), }); diff --git a/apps/dokploy/server/api/routers/github.ts b/apps/dokploy/server/api/routers/github.ts new file mode 100644 index 000000000..add130bf2 --- /dev/null +++ b/apps/dokploy/server/api/routers/github.ts @@ -0,0 +1,49 @@ +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { apiFindGithubBranches, apiFindOneGithub } from "@/server/db/schema"; +import { db } from "@/server/db"; +import { findGithubById, haveGithubRequirements } from "../services/github"; +import { + getGithubRepositories, + getGithubBranches, +} from "@/server/utils/providers/github"; + +export const githubRouter = createTRPCRouter({ + one: protectedProcedure.input(apiFindOneGithub).query(async ({ input }) => { + return await findGithubById(input.githubId); + }), + getGithubRepositories: protectedProcedure + .input(apiFindOneGithub) + .query(async ({ input }) => { + return await getGithubRepositories(input.githubId); + }), + getGithubBranches: protectedProcedure + .input(apiFindGithubBranches) + .query(async ({ input }) => { + return await getGithubBranches(input); + }), + githubProviders: protectedProcedure.query(async () => { + const result = await db.query.github.findMany({ + with: { + gitProvider: true, + }, + }); + + const filtered = result + .filter((provider) => haveGithubRequirements(provider)) + .map((provider) => { + return { + githubId: provider.githubId, + gitProvider: { + ...provider.gitProvider, + }, + }; + }); + + return filtered; + }), + testConnection: protectedProcedure + .input(apiFindOneGithub) + .query(async ({ input }) => { + return await findGithubById(input.githubId); + }), +}); diff --git a/apps/dokploy/server/api/routers/gitlab.ts b/apps/dokploy/server/api/routers/gitlab.ts new file mode 100644 index 000000000..d83d69193 --- /dev/null +++ b/apps/dokploy/server/api/routers/gitlab.ts @@ -0,0 +1,86 @@ +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { + apiCreateGitlab, + apiFindGitlabBranches, + apiFindOneGitlab, + apiGitlabTestConnection, + apiUpdateGitlab, +} from "@/server/db/schema"; + +import { + haveGitlabRequirements, + getGitlabRepositories, + getGitlabBranches, + testGitlabConnection, +} from "@/server/utils/providers/gitlab"; +import { TRPCError } from "@trpc/server"; +import { createGitlab, findGitlabById, updateGitlab } from "../services/gitlab"; +import { db } from "@/server/db"; + +export const gitlabRouter = createTRPCRouter({ + create: protectedProcedure + .input(apiCreateGitlab) + .mutation(async ({ input }) => { + try { + return await createGitlab(input); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create this gitlab provider", + cause: error, + }); + } + }), + one: protectedProcedure.input(apiFindOneGitlab).query(async ({ input }) => { + return await findGitlabById(input.gitlabId); + }), + gitlabProviders: protectedProcedure.query(async () => { + const result = await db.query.gitlab.findMany({ + with: { + gitProvider: true, + }, + }); + const filtered = result + .filter((provider) => haveGitlabRequirements(provider)) + .map((provider) => { + return { + gitlabId: provider.gitlabId, + gitProvider: { + ...provider.gitProvider, + }, + }; + }); + + return filtered; + }), + getGitlabRepositories: protectedProcedure + .input(apiFindOneGitlab) + .query(async ({ input }) => { + return await getGitlabRepositories(input.gitlabId); + }), + + getGitlabBranches: protectedProcedure + .input(apiFindGitlabBranches) + .query(async ({ input }) => { + return await getGitlabBranches(input); + }), + testConnection: protectedProcedure + .input(apiGitlabTestConnection) + .mutation(async ({ input }) => { + try { + const result = await testGitlabConnection(input); + + return `Found ${result} repositories`; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error?.message : `Error: ${error}`, + }); + } + }), + update: protectedProcedure + .input(apiUpdateGitlab) + .mutation(async ({ input }) => { + return await updateGitlab(input.gitlabId, input); + }), +}); diff --git a/apps/dokploy/server/api/services/bitbucket.ts b/apps/dokploy/server/api/services/bitbucket.ts new file mode 100644 index 000000000..11ae0903d --- /dev/null +++ b/apps/dokploy/server/api/services/bitbucket.ts @@ -0,0 +1,88 @@ +import { db } from "@/server/db"; +import { + type apiCreateBitbucket, + type apiUpdateBitbucket, + bitbucket, + gitProvider, +} from "@/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; + +export type Bitbucket = typeof bitbucket.$inferSelect; + +export const createBitbucket = async ( + input: typeof apiCreateBitbucket._type, +) => { + return await db.transaction(async (tx) => { + const newGitProvider = await tx + .insert(gitProvider) + .values({ + providerType: "bitbucket", + authId: input.authId, + name: input.name, + }) + .returning() + .then((response) => response[0]); + + if (!newGitProvider) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the git provider", + }); + } + + await tx + .insert(bitbucket) + .values({ + ...input, + gitProviderId: newGitProvider?.gitProviderId, + }) + .returning() + .then((response) => response[0]); + }); +}; + +export const findBitbucketById = async (bitbucketId: string) => { + const bitbucketProviderResult = await db.query.bitbucket.findFirst({ + where: eq(bitbucket.bitbucketId, bitbucketId), + with: { + gitProvider: true, + }, + }); + + if (!bitbucketProviderResult) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Bitbucket Provider not found", + }); + } + + return bitbucketProviderResult; +}; + +export const updateBitbucket = async ( + bitbucketId: string, + input: typeof apiUpdateBitbucket._type, +) => { + return await db.transaction(async (tx) => { + const result = await tx + .update(bitbucket) + .set({ + ...input, + }) + .where(eq(bitbucket.bitbucketId, bitbucketId)) + .returning(); + + if (input.name) { + await tx + .update(gitProvider) + .set({ + name: input.name, + }) + .where(eq(gitProvider.gitProviderId, input.gitProviderId)) + .returning(); + } + + return result[0]; + }); +}; diff --git a/apps/dokploy/server/api/services/git-provider.ts b/apps/dokploy/server/api/services/git-provider.ts index 8ef365953..66aed3acf 100644 --- a/apps/dokploy/server/api/services/git-provider.ts +++ b/apps/dokploy/server/api/services/git-provider.ts @@ -1,19 +1,8 @@ import { db } from "@/server/db"; -import { - type apiCreateBitbucket, - type apiCreateGithub, - type apiCreateGitlab, - bitbucket, - github, - gitlab, - gitProvider, -} from "@/server/db/schema"; +import { type apiCreateGithub, github, gitProvider } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -export type Github = typeof github.$inferSelect; - -export type Gitlab = typeof gitlab.$inferSelect; export const createGithub = async (input: typeof apiCreateGithub._type) => { return await db.transaction(async (tx) => { const newGitProvider = await tx @@ -44,68 +33,6 @@ export const createGithub = async (input: typeof apiCreateGithub._type) => { }); }; -export const createGitlab = async (input: typeof apiCreateGitlab._type) => { - return await db.transaction(async (tx) => { - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "gitlab", - authId: input.authId, - name: input.name, - }) - .returning() - .then((response) => response[0]); - - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the git provider", - }); - } - - await tx - .insert(gitlab) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); -}; - -export const createBitbucket = async ( - input: typeof apiCreateBitbucket._type, -) => { - return await db.transaction(async (tx) => { - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "bitbucket", - authId: input.authId, - name: input.name, - }) - .returning() - .then((response) => response[0]); - - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the git provider", - }); - } - - await tx - .insert(bitbucket) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); -}; - export const removeGitProvider = async (gitProviderId: string) => { const result = await db .delete(gitProvider) @@ -114,71 +41,3 @@ export const removeGitProvider = async (gitProviderId: string) => { return result[0]; }; - -export const findGithubById = async (githubId: string) => { - const githubProviderResult = await db.query.github.findFirst({ - where: eq(github.githubId, githubId), - }); - - if (!githubProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Github Provider not found", - }); - } - - return githubProviderResult; -}; - -export const haveGithubRequirements = (github: Github) => { - return !!( - github?.githubAppId && - github?.githubPrivateKey && - github?.githubInstallationId - ); -}; - -export const findGitlabById = async (gitlabId: string) => { - const gitlabProviderResult = await db.query.gitlab.findFirst({ - where: eq(gitlab.gitlabId, gitlabId), - }); - - if (!gitlabProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - - return gitlabProviderResult; -}; - -export const updateGitlab = async ( - gitlabId: string, - input: Partial, -) => { - const result = await db - .update(gitlab) - .set({ - ...input, - }) - .where(eq(gitlab.gitlabId, gitlabId)) - .returning(); - - return result[0]; -}; - -export const findBitbucketById = async (bitbucketId: string) => { - const bitbucketProviderResult = await db.query.bitbucket.findFirst({ - where: eq(bitbucket.bitbucketId, bitbucketId), - }); - - if (!bitbucketProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - return bitbucketProviderResult; -}; diff --git a/apps/dokploy/server/api/services/github.ts b/apps/dokploy/server/api/services/github.ts new file mode 100644 index 000000000..14980185a --- /dev/null +++ b/apps/dokploy/server/api/services/github.ts @@ -0,0 +1,58 @@ +import { db } from "@/server/db"; +import { type apiCreateGithub, github, gitProvider } from "@/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; + +export type Github = typeof github.$inferSelect; +export const createGithub = async (input: typeof apiCreateGithub._type) => { + return await db.transaction(async (tx) => { + const newGitProvider = await tx + .insert(gitProvider) + .values({ + providerType: "github", + authId: input.authId, + name: input.name, + }) + .returning() + .then((response) => response[0]); + + if (!newGitProvider) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the git provider", + }); + } + + return await tx + .insert(github) + .values({ + ...input, + gitProviderId: newGitProvider?.gitProviderId, + }) + .returning() + .then((response) => response[0]); + }); +}; + +export const findGithubById = async (githubId: string) => { + const githubProviderResult = await db.query.github.findFirst({ + where: eq(github.githubId, githubId), + }); + + if (!githubProviderResult) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Github Provider not found", + }); + } + + return githubProviderResult; +}; + +export const haveGithubRequirements = (github: Github) => { + return !!( + github?.githubAppId && + github?.githubPrivateKey && + github?.githubInstallationId + ); +}; diff --git a/apps/dokploy/server/api/services/gitlab.ts b/apps/dokploy/server/api/services/gitlab.ts new file mode 100644 index 000000000..acc117e60 --- /dev/null +++ b/apps/dokploy/server/api/services/gitlab.ts @@ -0,0 +1,104 @@ +import { db } from "@/server/db"; +import { + type apiCreateGitlab, + type apiUpdateGitlab, + type bitbucket, + type github, + gitlab, + gitProvider, +} from "@/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; + +export type Github = typeof github.$inferSelect; +export type Bitbucket = typeof bitbucket.$inferSelect; +export type Gitlab = typeof gitlab.$inferSelect; + +export const createGitlab = async (input: typeof apiCreateGitlab._type) => { + return await db.transaction(async (tx) => { + const newGitProvider = await tx + .insert(gitProvider) + .values({ + providerType: "gitlab", + authId: input.authId, + name: input.name, + }) + .returning() + .then((response) => response[0]); + + if (!newGitProvider) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the git provider", + }); + } + + await tx + .insert(gitlab) + .values({ + ...input, + gitProviderId: newGitProvider?.gitProviderId, + }) + .returning() + .then((response) => response[0]); + }); +}; + +export const findGitlabById = async (gitlabId: string) => { + const gitlabProviderResult = await db.query.gitlab.findFirst({ + where: eq(gitlab.gitlabId, gitlabId), + with: { + gitProvider: true, + }, + }); + + if (!gitlabProviderResult) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Gitlab Provider not found", + }); + } + + return gitlabProviderResult; +}; + +export const updateGitlab = async ( + gitlabId: string, + input: typeof apiUpdateGitlab._type, +) => { + return await db.transaction(async (tx) => { + const result = await tx + .update(gitlab) + .set({ + ...input, + }) + .where(eq(gitlab.gitlabId, gitlabId)) + .returning(); + + if (input.name) { + await tx + .update(gitProvider) + .set({ + name: input.name, + }) + .where(eq(gitProvider.gitProviderId, input.gitProviderId)) + .returning(); + } + + return result[0]; + }); +}; + +export const updateGitlabComplete = async ( + gitlabId: string, + input: Partial, +) => { + return await db + .update(gitlab) + .set({ + ...input, + }) + .where(eq(gitlab.gitlabId, gitlabId)) + .returning() + .then((response) => response[0]); +}; diff --git a/apps/dokploy/server/db/schema/application.ts b/apps/dokploy/server/db/schema/application.ts index 49ae0ce13..1158beba1 100644 --- a/apps/dokploy/server/db/schema/application.ts +++ b/apps/dokploy/server/db/schema/application.ts @@ -21,7 +21,7 @@ import { security } from "./security"; import { applicationStatus } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; -import { bitbucket, github, gitlab } from "./git-provider"; +import { bitbucket, github, gitlab } from "."; export const sourceType = pgEnum("sourceType", [ "docker", diff --git a/apps/dokploy/server/db/schema/bitbucket.ts b/apps/dokploy/server/db/schema/bitbucket.ts new file mode 100644 index 000000000..ee4d22024 --- /dev/null +++ b/apps/dokploy/server/db/schema/bitbucket.ts @@ -0,0 +1,64 @@ +import { relations } from "drizzle-orm"; +import { pgTable, text } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { gitProvider } from "./git-provider"; + +export const bitbucket = pgTable("bitbucket", { + bitbucketId: text("bitbucketId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + bitbucketUsername: text("bitbucketUsername"), + appPassword: text("appPassword"), + bitbucketWorkspaceName: text("bitbucketWorkspaceName"), + gitProviderId: text("gitProviderId") + .notNull() + .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), +}); + +export const bitbucketProviderRelations = relations(bitbucket, ({ one }) => ({ + gitProvider: one(gitProvider, { + fields: [bitbucket.gitProviderId], + references: [gitProvider.gitProviderId], + }), +})); + +const createSchema = createInsertSchema(bitbucket); + +export const apiCreateBitbucket = createSchema.extend({ + bitbucketUsername: z.string().optional(), + appPassword: z.string().optional(), + bitbucketWorkspaceName: z.string().optional(), + gitProviderId: z.string().optional(), + authId: z.string().min(1), + name: z.string().min(1), +}); + +export const apiFindOneBitbucket = createSchema + .extend({ + bitbucketId: z.string().min(1), + }) + .pick({ bitbucketId: true }); + +export const apiBitbucketTestConnection = createSchema + .extend({ + bitbucketId: z.string().min(1), + bitbucketUsername: z.string().optional(), + workspaceName: z.string().optional(), + }) + .pick({ bitbucketId: true, bitbucketUsername: true, workspaceName: true }); + +export const apiFindBitbucketBranches = z.object({ + owner: z.string(), + repo: z.string(), + bitbucketId: z.string().optional(), +}); + +export const apiUpdateBitbucket = createSchema.extend({ + bitbucketId: z.string().min(1), + name: z.string().min(1), + bitbucketUsername: z.string().optional(), + bitbucketWorkspaceName: z.string().optional(), +}); diff --git a/apps/dokploy/server/db/schema/compose.ts b/apps/dokploy/server/db/schema/compose.ts index 6af2357b2..07c55ff8f 100644 --- a/apps/dokploy/server/db/schema/compose.ts +++ b/apps/dokploy/server/db/schema/compose.ts @@ -10,7 +10,7 @@ import { mounts } from "./mount"; import { projects } from "./project"; import { applicationStatus } from "./shared"; import { generateAppName } from "./utils"; -import { bitbucket, github, gitlab } from "./git-provider"; +import { bitbucket, github, gitlab } from "."; export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ "git", diff --git a/apps/dokploy/server/db/schema/git-provider.ts b/apps/dokploy/server/db/schema/git-provider.ts index 68f678f07..4564797c7 100644 --- a/apps/dokploy/server/db/schema/git-provider.ts +++ b/apps/dokploy/server/db/schema/git-provider.ts @@ -1,9 +1,12 @@ import { relations } from "drizzle-orm"; -import { pgTable, text, pgEnum, integer } from "drizzle-orm/pg-core"; +import { pgTable, text, pgEnum } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; import { auth } from "./auth"; +import { bitbucket } from "./bitbucket"; +import { github } from "./github"; +import { gitlab } from "./gitlab"; export const gitProviderType = pgEnum("gitProviderType", [ "github", @@ -45,74 +48,6 @@ export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({ }), })); -export const github = pgTable("github", { - githubId: text("githubId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - githubAppName: text("githubAppName"), - githubAppId: integer("githubAppId"), - githubClientId: text("githubClientId"), - githubClientSecret: text("githubClientSecret"), - githubInstallationId: text("githubInstallationId"), - githubPrivateKey: text("githubPrivateKey"), - githubWebhookSecret: text("githubWebhookSecret"), - gitProviderId: text("gitProviderId") - .notNull() - .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), -}); - -export const githubProviderRelations = relations(github, ({ one }) => ({ - gitProvider: one(gitProvider, { - fields: [github.gitProviderId], - references: [gitProvider.gitProviderId], - }), -})); - -export const gitlab = pgTable("gitlab", { - gitlabId: text("gitlabId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - applicationId: text("application_id"), - redirectUri: text("redirect_uri"), - secret: text("secret"), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - groupName: text("group_name"), - expiresAt: integer("expires_at"), - gitProviderId: text("gitProviderId") - .notNull() - .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), -}); - -export const gitlabProviderRelations = relations(gitlab, ({ one }) => ({ - gitProvider: one(gitProvider, { - fields: [gitlab.gitProviderId], - references: [gitProvider.gitProviderId], - }), -})); - -export const bitbucket = pgTable("bitbucket", { - bitbucketId: text("bitbucketId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - bitbucketUsername: text("bitbucketUsername"), - appPassword: text("appPassword"), - bitbucketWorkspaceName: text("bitbucketWorkspaceName"), - gitProviderId: text("gitProviderId") - .notNull() - .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), -}); - -export const bitbucketProviderRelations = relations(bitbucket, ({ one }) => ({ - gitProvider: one(gitProvider, { - fields: [bitbucket.gitProviderId], - references: [gitProvider.gitProviderId], - }), -})); - const createSchema = createInsertSchema(gitProvider); export const apiRemoveGitProvider = createSchema @@ -120,77 +55,3 @@ export const apiRemoveGitProvider = createSchema gitProviderId: z.string().min(1), }) .pick({ gitProviderId: true }); - -export const apiCreateGithub = createSchema.extend({ - githubAppName: z.string().optional(), - githubAppId: z.number().optional(), - githubClientId: z.string().optional(), - githubClientSecret: z.string().optional(), - githubInstallationId: z.string().optional(), - githubPrivateKey: z.string().optional(), - githubWebhookSecret: z.string().nullable(), - gitProviderId: z.string().optional(), -}); - -export const apiFindGithubBranches = z.object({ - repo: z.string().min(1), - owner: z.string().min(1), - githubId: z.string().optional(), -}); - -export const apiFindOneGithub = createSchema - .extend({ - githubId: z.string().min(1), - }) - .pick({ githubId: true }); - -export const apiCreateGitlab = createSchema.extend({ - applicationId: z.string().optional(), - secret: z.string().optional(), - groupName: z.string().optional(), - gitProviderId: z.string().optional(), - redirectUri: z.string().optional(), -}); - -export const apiFindOneGitlab = createSchema - .extend({ - gitlabId: z.string().min(1), - }) - .pick({ gitlabId: true }); - -export const apiFindGitlabBranches = z.object({ - id: z.number().nullable(), - owner: z.string(), - repo: z.string(), - gitlabId: z.string().optional(), -}); -export const apiCreateBitbucket = createSchema.extend({ - bitbucketUsername: z.string().optional(), - appPassword: z.string().optional(), - bitbucketWorkspaceName: z.string().optional(), - gitProviderId: z.string().optional(), -}); - -export const apiFindOneBitbucket = createSchema - .extend({ - bitbucketId: z.string().min(1), - }) - .pick({ bitbucketId: true }); - -export const apiFindBitbucketBranches = z.object({ - owner: z.string(), - repo: z.string(), - bitbucketId: z.string().optional(), -}); - -export const apiUpdateBitbucket = createSchema.extend({ - bitbucketUsername: z.string().optional(), - bitbucketWorkspaceName: z.string().optional(), -}); - -export const apiUpdateGitlab = createSchema.extend({ - applicationId: z.string().optional(), - secret: z.string().optional(), - groupName: z.string().optional(), - redirectUri: z.string().optional(), -}); diff --git a/apps/dokploy/server/db/schema/github.ts b/apps/dokploy/server/db/schema/github.ts new file mode 100644 index 000000000..487a74dfd --- /dev/null +++ b/apps/dokploy/server/db/schema/github.ts @@ -0,0 +1,54 @@ +import { relations } from "drizzle-orm"; +import { pgTable, text, integer } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { gitProvider } from "./git-provider"; + +export const github = pgTable("github", { + githubId: text("githubId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + githubAppName: text("githubAppName"), + githubAppId: integer("githubAppId"), + githubClientId: text("githubClientId"), + githubClientSecret: text("githubClientSecret"), + githubInstallationId: text("githubInstallationId"), + githubPrivateKey: text("githubPrivateKey"), + githubWebhookSecret: text("githubWebhookSecret"), + gitProviderId: text("gitProviderId") + .notNull() + .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), +}); + +export const githubProviderRelations = relations(github, ({ one }) => ({ + gitProvider: one(gitProvider, { + fields: [github.gitProviderId], + references: [gitProvider.gitProviderId], + }), +})); + +const createSchema = createInsertSchema(github); +export const apiCreateGithub = createSchema.extend({ + githubAppName: z.string().optional(), + githubAppId: z.number().optional(), + githubClientId: z.string().optional(), + githubClientSecret: z.string().optional(), + githubInstallationId: z.string().optional(), + githubPrivateKey: z.string().optional(), + githubWebhookSecret: z.string().nullable(), + gitProviderId: z.string().optional(), +}); + +export const apiFindGithubBranches = z.object({ + repo: z.string().min(1), + owner: z.string().min(1), + githubId: z.string().optional(), +}); + +export const apiFindOneGithub = createSchema + .extend({ + githubId: z.string().min(1), + }) + .pick({ githubId: true }); diff --git a/apps/dokploy/server/db/schema/gitlab.ts b/apps/dokploy/server/db/schema/gitlab.ts new file mode 100644 index 000000000..086a19c85 --- /dev/null +++ b/apps/dokploy/server/db/schema/gitlab.ts @@ -0,0 +1,70 @@ +import { relations } from "drizzle-orm"; +import { pgTable, text, integer } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { gitProvider } from "./git-provider"; + +export const gitlab = pgTable("gitlab", { + gitlabId: text("gitlabId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + applicationId: text("application_id"), + redirectUri: text("redirect_uri"), + secret: text("secret"), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + groupName: text("group_name"), + expiresAt: integer("expires_at"), + gitProviderId: text("gitProviderId") + .notNull() + .references(() => gitProvider.gitProviderId, { onDelete: "cascade" }), +}); + +export const gitlabProviderRelations = relations(gitlab, ({ one }) => ({ + gitProvider: one(gitProvider, { + fields: [gitlab.gitProviderId], + references: [gitProvider.gitProviderId], + }), +})); + +const createSchema = createInsertSchema(gitlab); + +export const apiCreateGitlab = createSchema.extend({ + applicationId: z.string().optional(), + secret: z.string().optional(), + groupName: z.string().optional(), + gitProviderId: z.string().optional(), + redirectUri: z.string().optional(), + authId: z.string().min(1), + name: z.string().min(1), +}); + +export const apiFindOneGitlab = createSchema + .extend({ + gitlabId: z.string().min(1), + }) + .pick({ gitlabId: true }); + +export const apiGitlabTestConnection = createSchema + .extend({ + groupName: z.string().optional(), + }) + .pick({ gitlabId: true, groupName: true }); + +export const apiFindGitlabBranches = z.object({ + id: z.number().nullable(), + owner: z.string(), + repo: z.string(), + gitlabId: z.string().optional(), +}); + +export const apiUpdateGitlab = createSchema.extend({ + applicationId: z.string().optional(), + secret: z.string().optional(), + groupName: z.string().optional(), + redirectUri: z.string().optional(), + name: z.string().min(1), + gitlabId: z.string().min(1), +}); diff --git a/apps/dokploy/server/db/schema/index.ts b/apps/dokploy/server/db/schema/index.ts index 7729b7ac2..3b57b7a14 100644 --- a/apps/dokploy/server/db/schema/index.ts +++ b/apps/dokploy/server/db/schema/index.ts @@ -24,3 +24,6 @@ export * from "./registry"; export * from "./notification"; export * from "./ssh-key"; export * from "./git-provider"; +export * from "./bitbucket"; +export * from "./github"; +export * from "./gitlab"; diff --git a/apps/dokploy/server/utils/providers/bitbucket.ts b/apps/dokploy/server/utils/providers/bitbucket.ts index e59a97ae6..602700b63 100644 --- a/apps/dokploy/server/utils/providers/bitbucket.ts +++ b/apps/dokploy/server/utils/providers/bitbucket.ts @@ -5,9 +5,12 @@ import { TRPCError } from "@trpc/server"; import { recreateDirectory } from "../filesystem/directory"; import { spawnAsync } from "../process/spawnAsync"; import type { InferResultType } from "@/server/types/with"; -import { findBitbucketById } from "@/server/api/services/git-provider"; import type { Compose } from "@/server/api/services/compose"; -import type { apiFindBitbucketBranches } from "@/server/db/schema"; +import type { + apiBitbucketTestConnection, + apiFindBitbucketBranches, +} from "@/server/db/schema"; +import { findBitbucketById } from "@/server/api/services/bitbucket"; export type ApplicationWithBitbucket = InferResultType< "applications", @@ -210,3 +213,50 @@ export const getBitbucketBranches = async ( throw error; } }; + +export const testBitbucketConnection = async ( + input: typeof apiBitbucketTestConnection._type, +) => { + const bitbucketProvider = await findBitbucketById(input.bitbucketId); + + if (!bitbucketProvider) { + throw new Error("Bitbucket provider not found"); + } + + const { bitbucketUsername, workspaceName } = input; + + const username = workspaceName || bitbucketUsername; + + const url = `https://api.bitbucket.org/2.0/repositories/${username}`; + try { + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`, + }, + }); + + if (!response.ok) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Failed to fetch repositories: ${response.statusText}`, + }); + } + + const data = await response.json(); + + const mappedData = data.values.map((repo: any) => { + return { + name: repo.name, + url: repo.links.html.href, + owner: { + username: repo.workspace.slug, + }, + }; + }) as []; + + return mappedData.length; + } catch (error) { + throw error; + } +}; diff --git a/apps/dokploy/server/utils/providers/github.ts b/apps/dokploy/server/utils/providers/github.ts index 4d84ea01f..4d05f651c 100644 --- a/apps/dokploy/server/utils/providers/github.ts +++ b/apps/dokploy/server/utils/providers/github.ts @@ -7,12 +7,10 @@ import { Octokit } from "octokit"; import { recreateDirectory } from "../filesystem/directory"; import { spawnAsync } from "../process/spawnAsync"; import type { InferResultType } from "@/server/types/with"; -import { - findGithubById, - type Github, -} from "@/server/api/services/git-provider"; + import type { Compose } from "@/server/api/services/compose"; import type { apiFindGithubBranches } from "@/server/db/schema"; +import { type Github, findGithubById } from "@/server/api/services/github"; export const authGithub = (githubProvider: Github) => { if (!haveGithubRequirements(githubProvider)) { diff --git a/apps/dokploy/server/utils/providers/gitlab.ts b/apps/dokploy/server/utils/providers/gitlab.ts index 0443aa5cc..737f5e5b2 100644 --- a/apps/dokploy/server/utils/providers/gitlab.ts +++ b/apps/dokploy/server/utils/providers/gitlab.ts @@ -4,13 +4,14 @@ import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants"; import { TRPCError } from "@trpc/server"; import { recreateDirectory } from "../filesystem/directory"; import { spawnAsync } from "../process/spawnAsync"; -import { - findGitlabById, - type Gitlab, - updateGitlab, -} from "@/server/api/services/git-provider"; import type { InferResultType } from "@/server/types/with"; import type { Compose } from "@/server/api/services/compose"; +import { + findGitlabById, + updateGitlabComplete, + type Gitlab, +} from "@/server/api/services/gitlab"; +import type { apiGitlabTestConnection } from "@/server/db/schema"; export const refreshGitlabToken = async (gitlabProviderId: string) => { const gitlabProvider = await findGitlabById(gitlabProviderId); @@ -21,7 +22,6 @@ export const refreshGitlabToken = async (gitlabProviderId: string) => { gitlabProvider.expiresAt && currentTime + safetyMargin < gitlabProvider.expiresAt ) { - console.log("Token still valid, no need to refresh"); return; } @@ -48,7 +48,7 @@ export const refreshGitlabToken = async (gitlabProviderId: string) => { console.log("Refreshed token"); - await updateGitlab(gitlabProviderId, { + await updateGitlabComplete(gitlabProviderId, { accessToken: data.access_token, refreshToken: data.refresh_token, expiresAt, @@ -284,3 +284,46 @@ export const cloneRawGitlabRepository = async (entity: Compose) => { throw error; } }; + +export const testGitlabConnection = async ( + input: typeof apiGitlabTestConnection._type, +) => { + const { gitlabId, groupName } = input; + + if (!gitlabId) { + throw new Error("Gitlab provider not found"); + } + + await refreshGitlabToken(gitlabId); + + const gitlabProvider = await findGitlabById(gitlabId); + + const response = await fetch( + `https://gitlab.com/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`, + { + headers: { + Authorization: `Bearer ${gitlabProvider.accessToken}`, + }, + }, + ); + + if (!response.ok) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Failed to fetch repositories: ${response.statusText}`, + }); + } + + const repositories = await response.json(); + + const filteredRepos = repositories.filter((repo: any) => { + const { full_path, kind } = repo.namespace; + + if (groupName) { + return full_path.toLowerCase().includes(groupName) && kind === "group"; + } + return kind === "user"; + }); + + return filteredRepos.length; +};