diff --git a/Dockerfile b/Dockerfile index a9b5f951..1e104eeb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ WORKDIR /app # Set production ENV NODE_ENV=production -RUN apt-get update && apt-get install -y curl unzip zip apache2-utils iproute2 && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y curl unzip zip apache2-utils iproute2 rsync && rm -rf /var/lib/apt/lists/* # Copy only the necessary files COPY --from=build /prod/dokploy/.next ./.next diff --git a/LICENSE.MD b/LICENSE.MD index 8a508efb..7e49a35b 100644 --- a/LICENSE.MD +++ b/LICENSE.MD @@ -19,8 +19,8 @@ See the License for the specific language governing permissions and limitations The following additional terms apply to the multi-node support, Docker Compose file, Preview Deployments and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License: -- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Preview Deployments and Multi Server, will always be free to use in the self-hosted version. -- **Restriction on Resale**: The multi-node support, Docker Compose file support, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent. -- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service. +- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server, will always be free to use in the self-hosted version. +- **Restriction on Resale**: The multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent. +- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service. For further inquiries or permissions, please contact us directly. diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index 6c0dd981..74a4eb66 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.test.ts @@ -36,6 +36,7 @@ const baseApp: ApplicationNested = { watchPaths: [], enableSubmodules: false, applicationStatus: "done", + triggerType: "push", appName: "", autoDeploy: true, serverId: "", diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 9312151a..5437e64d 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -25,6 +25,7 @@ const baseApp: ApplicationNested = { buildArgs: null, isPreviewDeploymentsActive: false, previewBuildArgs: null, + triggerType: "push", previewCertificateType: "none", previewEnv: null, previewHttps: false, diff --git a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx index 5fe7ffb0..eb85f383 100644 --- a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx @@ -15,11 +15,15 @@ import { Paintbrush } from "lucide-react"; import { toast } from "sonner"; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const CancelQueues = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.application.cleanQueues.useMutation(); +export const CancelQueues = ({ id, type }: Props) => { + const { mutateAsync, isLoading } = + type === "application" + ? api.application.cleanQueues.useMutation() + : api.compose.cleanQueues.useMutation(); const { data: isCloud } = api.settings.isCloud.useQuery(); if (isCloud) { @@ -48,7 +52,8 @@ export const CancelQueues = ({ applicationId }: Props) => { { await mutateAsync({ - applicationId, + applicationId: id || "", + composeId: id || "", }) .then(() => { toast.success("Queues are being cleaned"); diff --git a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx index b80450f9..abfe37c3 100644 --- a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx @@ -14,10 +14,14 @@ import { RefreshCcw } from "lucide-react"; import { toast } from "sonner"; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const RefreshToken = ({ applicationId }: Props) => { - const { mutateAsync } = api.application.refreshToken.useMutation(); +export const RefreshToken = ({ id, type }: Props) => { + const { mutateAsync } = + type === "application" + ? api.application.refreshToken.useMutation() + : api.compose.refreshToken.useMutation(); const utils = api.useUtils(); return ( @@ -37,12 +41,19 @@ export const RefreshToken = ({ applicationId }: Props) => { { await mutateAsync({ - applicationId, + applicationId: id || "", + composeId: id || "", }) .then(() => { - utils.application.one.invalidate({ - applicationId, - }); + if (type === "application") { + utils.application.one.invalidate({ + applicationId: id, + }); + } else { + utils.compose.one.invalidate({ + composeId: id, + }); + } toast.success("Refresh updated"); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx new file mode 100644 index 00000000..c018a97c --- /dev/null +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx @@ -0,0 +1,69 @@ +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; + +import type { RouterOutputs } from "@/utils/api"; +import { useState } from "react"; +import { ShowDeployment } from "../deployments/show-deployment"; +import { ShowDeployments } from "./show-deployments"; + +interface Props { + id: string; + type: + | "application" + | "compose" + | "schedule" + | "server" + | "backup" + | "previewDeployment"; + serverId?: string; + refreshToken?: string; + children?: React.ReactNode; +} + +export const formatDuration = (seconds: number) => { + if (seconds < 60) return `${seconds}s`; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; +}; + +export const ShowDeploymentsModal = ({ + id, + type, + serverId, + refreshToken, + children, +}: Props) => { + const [activeLog, setActiveLog] = useState< + RouterOutputs["deployment"]["all"][number] | null + >(null); + const [isOpen, setIsOpen] = useState(false); + return ( + + + {children ? ( + children + ) : ( + + )} + + + + + setActiveLog(null)} + logPath={activeLog?.logPath || ""} + errorMessage={activeLog?.errorMessage || ""} + /> + + ); +}; diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 76e5bb26..199c1154 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -9,28 +9,53 @@ import { CardTitle, } from "@/components/ui/card"; import { type RouterOutputs, api } from "@/utils/api"; -import { RocketIcon } from "lucide-react"; +import { RocketIcon, Clock, Loader2 } from "lucide-react"; import React, { useEffect, useState } from "react"; import { CancelQueues } from "./cancel-queues"; import { RefreshToken } from "./refresh-token"; import { ShowDeployment } from "./show-deployment"; +import { Badge } from "@/components/ui/badge"; interface Props { - applicationId: string; + id: string; + type: + | "application" + | "compose" + | "schedule" + | "server" + | "backup" + | "previewDeployment"; + refreshToken?: string; + serverId?: string; } -export const ShowDeployments = ({ applicationId }: Props) => { +export const formatDuration = (seconds: number) => { + if (seconds < 60) return `${seconds}s`; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; +}; + +export const ShowDeployments = ({ + id, + type, + refreshToken, + serverId, +}: Props) => { const [activeLog, setActiveLog] = useState< RouterOutputs["deployment"]["all"][number] | null >(null); - const { data } = api.application.one.useQuery({ applicationId }); - const { data: deployments } = api.deployment.all.useQuery( - { applicationId }, - { - enabled: !!applicationId, - refetchInterval: 1000, - }, - ); + const { data: deployments, isLoading: isLoadingDeployments } = + api.deployment.allByType.useQuery( + { + id, + type, + }, + { + enabled: !!id, + refetchInterval: 1000, + }, + ); const [url, setUrl] = React.useState(""); useEffect(() => { @@ -38,34 +63,48 @@ export const ShowDeployments = ({ applicationId }: Props) => { }, []); return ( - +
Deployments - See all the 10 last deployments for this application + See all the 10 last deployments for this {type}
- + {(type === "application" || type === "compose") && ( + + )}
-
- - If you want to re-deploy this application use this URL in the config - of your git provider or docker - -
- Webhook URL: -
- - {`${url}/api/deploy/${data?.refreshToken}`} - - + {refreshToken && ( +
+ + If you want to re-deploy this application use this URL in the + config of your git provider or docker + +
+ Webhook URL: +
+ + {`${url}/api/deploy/${refreshToken}`} + + {(type === "application" || type === "compose") && ( + + )} +
-
- {data?.deployments?.length === 0 ? ( -
+ )} + + {isLoadingDeployments ? ( +
+ + + Loading deployments... + +
+ ) : deployments?.length === 0 ? ( +
No deployments found @@ -96,8 +135,23 @@ export const ShowDeployments = ({ applicationId }: Props) => { )}
-
+
+ {deployment.startedAt && deployment.finishedAt && ( + + + {formatDuration( + Math.floor( + (new Date(deployment.finishedAt).getTime() - + new Date(deployment.startedAt).getTime()) / + 1000, + ), + )} + + )}
)} setActiveLog(null)} logPath={activeLog?.logPath || ""} diff --git a/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx b/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx new file mode 100644 index 00000000..82c25d0f --- /dev/null +++ b/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx @@ -0,0 +1,109 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Copy, HelpCircle, Server } from "lucide-react"; +import { toast } from "sonner"; + +interface Props { + domain: { + host: string; + https: boolean; + path?: string; + }; + serverIp?: string; +} + +export const DnsHelperModal = ({ domain, serverIp }: Props) => { + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + toast.success("Copied to clipboard!"); + }; + + return ( + + + + + + + + + DNS Configuration Guide + + + Follow these steps to configure your DNS records for {domain.host} + + + +
+ + To make your domain accessible, you need to configure your DNS + records with your domain provider (e.g., Cloudflare, GoDaddy, + NameCheap). + + +
+
+

1. Add A Record

+
+

+ Create an A record that points your domain to the server's IP + address: +

+
+
+
+

Type: A

+

+ Name: @ or {domain.host.split(".")[0]} +

+

+ Value: {serverIp || "Your server IP"} +

+
+ +
+
+
+
+ +
+

2. Verify Configuration

+
+

+ After configuring your DNS records: +

+
    +
  • Wait for DNS propagation (usually 15-30 minutes)
  • +
  • + Test your domain by visiting:{" "} + {domain.https ? "https://" : "http://"} + {domain.host} + {domain.path || "/"} +
  • +
  • Use a DNS lookup tool to verify your records
  • +
+
+
+
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx similarity index 59% rename from apps/dokploy/components/dashboard/application/domains/add-domain.tsx rename to apps/dokploy/components/dashboard/application/domains/handle-domain.tsx index 8da85a87..c145afcf 100644 --- a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx @@ -38,26 +38,67 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { domain } from "@/server/db/validations/domain"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Dices } from "lucide-react"; +import { DatabaseZap, Dices, RefreshCw } from "lucide-react"; import Link from "next/link"; -import type z from "zod"; +import z from "zod"; + +export type CacheType = "fetch" | "cache"; + +export const domain = z + .object({ + host: z.string().min(1, { message: "Add a hostname" }), + path: z.string().min(1).optional(), + port: z + .number() + .min(1, { message: "Port must be at least 1" }) + .max(65535, { message: "Port must be 65535 or below" }) + .optional(), + https: z.boolean().optional(), + certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(), + customCertResolver: z.string().optional(), + serviceName: z.string().optional(), + domainType: z.enum(["application", "compose", "preview"]).optional(), + }) + .superRefine((input, ctx) => { + if (input.https && !input.certificateType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["certificateType"], + message: "Required", + }); + } + + if (input.certificateType === "custom" && !input.customCertResolver) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["customCertResolver"], + message: "Required", + }); + } + + if (input.domainType === "compose" && !input.serviceName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["serviceName"], + message: "Required", + }); + } + }); type Domain = z.infer; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; domainId?: string; children: React.ReactNode; } -export const AddDomain = ({ - applicationId, - domainId = "", - children, -}: Props) => { +export const AddDomain = ({ id, type, domainId = "", children }: Props) => { const [isOpen, setIsOpen] = useState(false); + const [cacheType, setCacheType] = useState("cache"); + const utils = api.useUtils(); const { data, refetch } = api.domain.one.useQuery( { @@ -68,14 +109,24 @@ export const AddDomain = ({ }, ); - const { data: application } = api.application.one.useQuery( - { - applicationId, - }, - { - enabled: !!applicationId, - }, - ); + const { data: application } = + type === "application" + ? api.application.one.useQuery( + { + applicationId: id, + }, + { + enabled: !!id, + }, + ) + : api.compose.one.useQuery( + { + composeId: id, + }, + { + enabled: !!id, + }, + ); const { mutateAsync, isError, error, isLoading } = domainId ? api.domain.update.useMutation() @@ -89,7 +140,22 @@ export const AddDomain = ({ serverId: application?.serverId || "", }); - console.log("canGenerateTraefikMeDomains", canGenerateTraefikMeDomains); + const { + data: services, + isFetching: isLoadingServices, + error: errorServices, + refetch: refetchServices, + } = api.compose.loadServices.useQuery( + { + composeId: id, + type: cacheType, + }, + { + retry: false, + refetchOnWindowFocus: false, + enabled: type === "compose" && !!id, + }, + ); const form = useForm({ resolver: zodResolver(domain), @@ -100,12 +166,15 @@ export const AddDomain = ({ https: false, certificateType: undefined, customCertResolver: undefined, + serviceName: undefined, + domainType: type, }, mode: "onChange", }); const certificateType = form.watch("certificateType"); const https = form.watch("https"); + const domainType = form.watch("domainType"); useEffect(() => { if (data) { @@ -116,6 +185,8 @@ export const AddDomain = ({ port: data?.port || undefined, certificateType: data?.certificateType || undefined, customCertResolver: data?.customCertResolver || undefined, + serviceName: data?.serviceName || undefined, + domainType: data?.domainType || type, }); } @@ -127,6 +198,7 @@ export const AddDomain = ({ https: false, certificateType: undefined, customCertResolver: undefined, + domainType: type, }); } }, [form, data, isLoading, domainId]); @@ -150,22 +222,37 @@ export const AddDomain = ({ const onSubmit = async (data: Domain) => { await mutateAsync({ domainId, - applicationId, + ...(data.domainType === "application" && { + applicationId: id, + }), + ...(data.domainType === "compose" && { + composeId: id, + }), ...data, }) .then(async () => { toast.success(dictionary.success); - await utils.domain.byApplicationId.invalidate({ - applicationId, - }); - await utils.application.readTraefikConfig.invalidate({ applicationId }); + + if (data.domainType === "application") { + await utils.domain.byApplicationId.invalidate({ + applicationId: id, + }); + await utils.application.readTraefikConfig.invalidate({ + applicationId: id, + }); + } else if (data.domainType === "compose") { + await utils.domain.byComposeId.invalidate({ + composeId: id, + }); + } if (domainId) { refetch(); } setIsOpen(false); }) - .catch(() => { + .catch((e) => { + console.log(e); toast.error(dictionary.error); }); }; @@ -189,6 +276,119 @@ export const AddDomain = ({ >
+
+ {domainType === "compose" && ( +
+ {errorServices && ( + + {errorServices?.message} + + )} + ( + + Service Name +
+ + + + + + + +

+ Fetch: Will clone the repository and load + the services +

+
+
+
+ + + + + + +

+ Cache: If you previously deployed this + compose, it will read the services from + the last deployment/fetch from the + repository +

+
+
+
+
+ + +
+ )} + /> +
+ )} +
Container Port + + The port where your application is running inside the + container (e.g., 3000 for Node.js, 80 for Nginx, 8080 + for Java) + diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 17dbc91f..26df7b69 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -8,28 +8,133 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react"; +import { + CheckCircle2, + ExternalLink, + GlobeIcon, + InfoIcon, + Loader2, + PenBoxIcon, + RefreshCw, + Server, + Trash2, + XCircle, +} from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; -import { AddDomain } from "./add-domain"; +import { AddDomain } from "./handle-domain"; +import { useState } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { DnsHelperModal } from "./dns-helper-modal"; +import { Badge } from "@/components/ui/badge"; + +export type ValidationState = { + isLoading: boolean; + isValid?: boolean; + error?: string; + resolvedIp?: string; + message?: string; +}; + +export type ValidationStates = Record; interface Props { - applicationId: string; + id: string; + type: "application" | "compose"; } -export const ShowDomains = ({ applicationId }: Props) => { - const { data, refetch } = api.domain.byApplicationId.useQuery( - { - applicationId, - }, - { - enabled: !!applicationId, - }, +export const ShowDomains = ({ id, type }: Props) => { + const { data: application } = + type === "application" + ? api.application.one.useQuery( + { + applicationId: id, + }, + { + enabled: !!id, + }, + ) + : api.compose.one.useQuery( + { + composeId: id, + }, + { + enabled: !!id, + }, + ); + const [validationStates, setValidationStates] = useState( + {}, ); + const { data: ip } = api.settings.getIp.useQuery(); + const { + data, + refetch, + isLoading: isLoadingDomains, + } = type === "application" + ? api.domain.byApplicationId.useQuery( + { + applicationId: id, + }, + { + enabled: !!id, + }, + ) + : api.domain.byComposeId.useQuery( + { + composeId: id, + }, + { + enabled: !!id, + }, + ); + + const { mutateAsync: validateDomain } = + api.domain.validateDomain.useMutation(); const { mutateAsync: deleteDomain, isLoading: isRemoving } = api.domain.delete.useMutation(); + const handleValidateDomain = async (host: string) => { + setValidationStates((prev) => ({ + ...prev, + [host]: { isLoading: true }, + })); + + try { + const result = await validateDomain({ + domain: host, + serverIp: + application?.server?.ipAddress?.toString() || ip?.toString() || "", + }); + + setValidationStates((prev) => ({ + ...prev, + [host]: { + isLoading: false, + isValid: result.isValid, + error: result.error, + resolvedIp: result.resolvedIp, + message: result.error && result.isValid ? result.error : undefined, + }, + })); + } catch (err) { + const error = err as Error; + setValidationStates((prev) => ({ + ...prev, + [host]: { + isLoading: false, + isValid: false, + error: error.message || "Failed to validate domain", + }, + })); + } + }; + return (
@@ -43,7 +148,7 @@ export const ShowDomains = ({ applicationId }: Props) => {
{data && data?.length > 0 && ( - + @@ -52,15 +157,22 @@ export const ShowDomains = ({ applicationId }: Props) => {
- {data?.length === 0 ? ( -
+ {isLoadingDomains ? ( +
+ + + Loading domains... + +
+ ) : data?.length === 0 ? ( +
To access the application it is required to set at least 1 domain
- + @@ -68,73 +180,216 @@ export const ShowDomains = ({ applicationId }: Props) => {
) : ( -
+
{data?.map((item) => { + const validationState = validationStates[item.host]; return ( -
- - - {item.host} - - - + +
+ {/* Service & Domain Info */} +
+
+ {item.serviceName && ( + + + {item.serviceName} + + )} -
-
- {item.path} - {item.port} - {item.https ? "HTTPS" : "HTTP"} -
+ + {item.host} + + +
+
+ {!item.host.includes("traefik.me") && ( + + )} + + + + { + await deleteDomain({ + domainId: item.domainId, + }) + .then((_data) => { + refetch(); + toast.success( + "Domain deleted successfully", + ); + }) + .catch(() => { + toast.error("Error deleting domain"); + }); + }} + > + + +
+
-
- - - - { - await deleteDomain({ - domainId: item.domainId, - }) - .then(() => { - refetch(); - toast.success("Domain deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting domain"); - }); - }} - > - - + {/* Domain Details */} +
+ + + + + + Path: {item.path || "/"} + + + +

URL path for this service

+
+
+
+ + + + + + + Port: {item.port} + + + +

Container port exposed

+
+
+
+ + + + + + {item.https ? "HTTPS" : "HTTP"} + + + +

+ {item.https + ? "Secure HTTPS connection" + : "Standard HTTP connection"} +

+
+
+
+ + {item.certificateType && ( + + + + + Cert: {item.certificateType} + + + +

SSL Certificate Provider

+
+
+
+ )} + + + + + + handleValidateDomain(item.host) + } + > + {validationState?.isLoading ? ( + <> + + Checking DNS... + + ) : validationState?.isValid ? ( + <> + + {validationState.message + ? "Behind Cloudflare" + : "DNS Valid"} + + ) : validationState?.error ? ( + <> + + {validationState.error} + + ) : ( + <> + + Validate DNS + + )} + + + + {validationState?.error ? ( +
+

+ Error: +

+

{validationState.error}

+
+ ) : ( + "Click to validate DNS configuration" + )} +
+
+
+
-
-
+
+ ); })}
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 98d8cfd7..531ace12 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 @@ -381,6 +381,9 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => { No branch found. + {branches && branches.length === 0 && ( + No branches found. + )} {branches?.map((branch: GiteaBranch) => ( { }, githubId: "", branch: "", + triggerType: "push", enableSubmodules: false, }, resolver: zodResolver(GithubProviderSchema), @@ -90,6 +92,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { const repository = form.watch("repository"); const githubId = form.watch("githubId"); + const triggerType = form.watch("triggerType"); const { data: repositories, isLoading: isLoadingRepositories } = api.github.getGithubRepositories.useQuery( @@ -127,6 +130,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { buildPath: data.buildPath || "/", githubId: data.githubId || "", watchPaths: data.watchPaths || [], + triggerType: data.triggerType || "push", enableSubmodules: data.enableSubmodules ?? false, }); } @@ -141,6 +145,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { buildPath: data.buildPath, githubId: data.githubId, watchPaths: data.watchPaths || [], + triggerType: data.triggerType, enableSubmodules: data.enableSubmodules, }) .then(async () => { @@ -386,11 +391,11 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { /> ( -
- Watch Paths +
+ Trigger Type @@ -398,71 +403,114 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {

- Add paths to watch for changes. When files in these - paths change, a new deployment will be triggered. + Choose when to trigger deployments: on push to the + selected branch or when a new tag is created.

-
- {field.value?.map((path, index) => ( - - {path} - { - const newPaths = [...(field.value || [])]; - newPaths.splice(index, 1); - field.onChange(newPaths); - }} - /> - - ))} -
-
+ { - if (e.key === "Enter") { - e.preventDefault(); - const input = e.currentTarget; - const path = input.value.trim(); - if (path) { - field.onChange([...(field.value || []), path]); - input.value = ""; - } - } - }} - /> + + + - -
+ + On Push + On Tag + + )} /> + {triggerType === "push" && ( + ( + +
+ Watch Paths + + + + + + +

+ Add paths to watch for changes. When files in + these paths change, a new deployment will be + triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + field.onChange(newPaths); + }} + /> + + ))} +
+
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const path = input.value.trim(); + if (path) { + field.onChange([...(field.value || []), path]); + input.value = ""; + } + } + }} + /> + + +
+ +
+ )} + /> + )} { - const { data: githubProviders } = api.github.githubProviders.useQuery(); - const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data: bitbucketProviders } = + const { data: githubProviders, isLoading: isLoadingGithub } = + api.github.githubProviders.useQuery(); + const { data: gitlabProviders, isLoading: isLoadingGitlab } = + api.gitlab.gitlabProviders.useQuery(); + const { data: bitbucketProviders, isLoading: isLoadingBitbucket } = api.bitbucket.bitbucketProviders.useQuery(); - const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); + const { data: giteaProviders, isLoading: isLoadingGitea } = + api.gitea.giteaProviders.useQuery(); const { data: application } = api.application.one.useQuery({ applicationId }); const [tab, setSab] = useState(application?.sourceType || "github"); + + const isLoading = + isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea; + + if (isLoading) { + return ( + + + +
+ Provider +

+ Select the source of your code +

+
+
+ +
+
+
+ +
+
+ + Loading providers... +
+
+
+
+ ); + } + return ( @@ -123,7 +158,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { {githubProviders && githubProviders?.length > 0 ? ( ) : ( -
+
To deploy using GitHub, you need to configure your account @@ -143,7 +178,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { {gitlabProviders && gitlabProviders?.length > 0 ? ( ) : ( -
+
To deploy using GitLab, you need to configure your account @@ -163,7 +198,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { {bitbucketProviders && bitbucketProviders?.length > 0 ? ( ) : ( -
+
To deploy using Bitbucket, you need to configure your account @@ -183,7 +218,7 @@ export const ShowProviderForm = ({ applicationId }: Props) => { {giteaProviders && giteaProviders?.length > 0 ? ( ) : ( -
+
To deploy using Gitea, you need to configure your account diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx deleted file mode 100644 index 90800f75..00000000 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-builds.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { DateTooltip } from "@/components/shared/date-tooltip"; -import { StatusTooltip } from "@/components/shared/status-tooltip"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; - -import type { RouterOutputs } from "@/utils/api"; -import { useState } from "react"; -import { ShowDeployment } from "../deployments/show-deployment"; - -interface Props { - deployments: RouterOutputs["deployment"]["all"]; - serverId?: string; - trigger?: React.ReactNode; -} - -export const ShowPreviewBuilds = ({ - deployments, - serverId, - trigger, -}: Props) => { - const [activeLog, setActiveLog] = useState< - RouterOutputs["deployment"]["all"][number] | null - >(null); - const [isOpen, setIsOpen] = useState(false); - return ( - - - {trigger ? ( - trigger - ) : ( - - )} - - - - Preview Builds - - See all the preview builds for this application on this Pull Request - - -
- {deployments?.map((deployment) => ( -
-
- - {deployment.status} - - - - - {deployment.title} - - {deployment.description && ( - - {deployment.description} - - )} -
-
-
- -
- - -
-
- ))} -
-
- setActiveLog(null)} - logPath={activeLog?.logPath || ""} - errorMessage={activeLog?.errorMessage || ""} - /> -
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx index ec3680f1..d436055e 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx @@ -17,7 +17,7 @@ import { ExternalLink, FileText, GitPullRequest, - Layers, + Loader2, PenSquare, RocketIcon, Trash2, @@ -25,8 +25,8 @@ import { import { toast } from "sonner"; import { ShowModalLogs } from "../../settings/web-server/show-modal-logs"; import { AddPreviewDomain } from "./add-preview-domain"; -import { ShowPreviewBuilds } from "./show-preview-builds"; import { ShowPreviewSettings } from "./show-preview-settings"; +import { ShowDeploymentsModal } from "../deployments/show-deployments-modal"; interface Props { applicationId: string; @@ -38,13 +38,16 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => { const { mutateAsync: deletePreviewDeployment, isLoading } = api.previewDeployment.delete.useMutation(); - const { data: previewDeployments, refetch: refetchPreviewDeployments } = - api.previewDeployment.all.useQuery( - { applicationId }, - { - enabled: !!applicationId, - }, - ); + const { + data: previewDeployments, + refetch: refetchPreviewDeployments, + isLoading: isLoadingPreviewDeployments, + } = api.previewDeployment.all.useQuery( + { applicationId }, + { + enabled: !!applicationId, + }, + ); const handleDeletePreviewDeployment = async (previewDeploymentId: string) => { deletePreviewDeployment({ @@ -80,8 +83,15 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => { each pull request you create.
- {!previewDeployments?.length ? ( -
+ {isLoadingPreviewDeployments ? ( +
+ + + Loading preview deployments... + +
+ ) : !previewDeployments?.length ? ( +
No preview deployments found @@ -168,19 +178,10 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => { - - - Builds - - } /> { + if (data.scheduleType === "compose" && !data.serviceName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Service name is required", + path: ["serviceName"], + }); + } + + if ( + (data.scheduleType === "dokploy-server" || + data.scheduleType === "server") && + !data.script + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Script is required", + path: ["script"], + }); + } + + if ( + (data.scheduleType === "application" || + data.scheduleType === "compose") && + !data.command + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Command is required", + path: ["command"], + }); + } + }); + +interface Props { + id?: string; + scheduleId?: string; + scheduleType?: "application" | "compose" | "server" | "dokploy-server"; +} + +export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const [cacheType, setCacheType] = useState("cache"); + + const utils = api.useUtils(); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + cronExpression: "", + shellType: "bash", + command: "", + enabled: true, + serviceName: "", + scheduleType: scheduleType || "application", + script: "", + }, + }); + + const scheduleTypeForm = form.watch("scheduleType"); + + const { data: schedule } = api.schedule.one.useQuery( + { scheduleId: scheduleId || "" }, + { enabled: !!scheduleId }, + ); + + const { + data: services, + isFetching: isLoadingServices, + error: errorServices, + refetch: refetchServices, + } = api.compose.loadServices.useQuery( + { + composeId: id || "", + type: cacheType, + }, + { + retry: false, + refetchOnWindowFocus: false, + enabled: !!id && scheduleType === "compose", + }, + ); + + useEffect(() => { + if (scheduleId && schedule) { + form.reset({ + name: schedule.name, + cronExpression: schedule.cronExpression, + shellType: schedule.shellType, + command: schedule.command, + enabled: schedule.enabled, + serviceName: schedule.serviceName || "", + scheduleType: schedule.scheduleType, + script: schedule.script || "", + }); + } + }, [form, schedule, scheduleId]); + + const { mutateAsync, isLoading } = scheduleId + ? api.schedule.update.useMutation() + : api.schedule.create.useMutation(); + + const onSubmit = async (values: z.infer) => { + if (!id && !scheduleId) return; + + await mutateAsync({ + ...values, + scheduleId: scheduleId || "", + ...(scheduleType === "application" && { + applicationId: id || "", + }), + ...(scheduleType === "compose" && { + composeId: id || "", + }), + ...(scheduleType === "server" && { + serverId: id || "", + }), + ...(scheduleType === "dokploy-server" && { + userId: id || "", + }), + }) + .then(() => { + toast.success( + `Schedule ${scheduleId ? "updated" : "created"} successfully`, + ); + utils.schedule.list.invalidate({ + id, + scheduleType, + }); + setIsOpen(false); + }) + .catch((error) => { + toast.error( + error instanceof Error ? error.message : "An unknown error occurred", + ); + }); + }; + + return ( + + + {scheduleId ? ( + + ) : ( + + )} + + + + {scheduleId ? "Edit" : "Create"} Schedule + +
+ + {scheduleTypeForm === "compose" && ( +
+ {errorServices && ( + + {errorServices?.message} + + )} + ( + + Service Name +
+ + + + + + + +

+ Fetch: Will clone the repository and load the + services +

+
+
+
+ + + + + + +

+ Cache: If you previously deployed this compose, + it will read the services from the last + deployment/fetch from the repository +

+
+
+
+
+ + +
+ )} + /> +
+ )} + + ( + + + Task Name + + + + + + A descriptive name for your scheduled task + + + + )} + /> + + ( + + + Schedule + + + + + + +

+ Cron expression format: minute hour day month + weekday +

+

Example: 0 0 * * * (daily at midnight)

+
+
+
+
+
+ +
+ + + +
+
+ + Choose a predefined schedule or enter a custom cron + expression + + +
+ )} + /> + + {(scheduleTypeForm === "application" || + scheduleTypeForm === "compose") && ( + <> + ( + + + Shell Type + + + + Choose the shell to execute your command + + + + )} + /> + ( + + + Command + + + + + + The command to execute in your container + + + + )} + /> + + )} + + {(scheduleTypeForm === "dokploy-server" || + scheduleTypeForm === "server") && ( + ( + + Script + + + + + + + + )} + /> + )} + + ( + + + + Enabled + + + )} + /> + + + + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx new file mode 100644 index 00000000..bb62eb3e --- /dev/null +++ b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx @@ -0,0 +1,239 @@ +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; +import { HandleSchedules } from "./handle-schedules"; +import { + Clock, + Play, + Terminal, + Trash2, + ClipboardList, + Loader2, +} from "lucide-react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { toast } from "sonner"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { ShowDeploymentsModal } from "../deployments/show-deployments-modal"; + +interface Props { + id: string; + scheduleType?: "application" | "compose" | "server" | "dokploy-server"; +} + +export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { + const { + data: schedules, + isLoading: isLoadingSchedules, + refetch: refetchSchedules, + } = api.schedule.list.useQuery( + { + id: id || "", + scheduleType, + }, + { + enabled: !!id, + }, + ); + + const utils = api.useUtils(); + + const { mutateAsync: deleteSchedule, isLoading: isDeleting } = + api.schedule.delete.useMutation(); + + const { mutateAsync: runManually, isLoading } = + api.schedule.runManually.useMutation(); + + return ( + + +
+
+ + Scheduled Tasks + + + Schedule tasks to run automatically at specified intervals. + +
+ + {schedules && schedules.length > 0 && ( + + )} +
+
+ + {isLoadingSchedules ? ( +
+ + + Loading scheduled tasks... + +
+ ) : schedules && schedules.length > 0 ? ( +
+ {schedules.map((schedule) => { + const serverId = + schedule.serverId || + schedule.application?.serverId || + schedule.compose?.serverId; + return ( +
+
+
+ +
+
+
+

+ {schedule.name} +

+ + {schedule.enabled ? "Enabled" : "Disabled"} + +
+
+ + Cron: {schedule.cronExpression} + + {schedule.scheduleType !== "server" && + schedule.scheduleType !== "dokploy-server" && ( + <> + + • + + + {schedule.shellType} + + + )} +
+ {schedule.command && ( +
+ + + {schedule.command} + +
+ )} +
+
+ +
+ + + + + + + + + + Run Manual Schedule + + + + + + { + await deleteSchedule({ + scheduleId: schedule.scheduleId, + }) + .then(() => { + utils.schedule.list.invalidate({ + id, + scheduleType, + }); + toast.success("Schedule deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting schedule"); + }); + }} + > + + +
+
+ ); + })} +
+ ) : ( +
+ +

+ No scheduled tasks +

+

+ Create your first scheduled task to automate your workflows +

+ +
+ )} +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx deleted file mode 100644 index a430ae18..00000000 --- a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Paintbrush } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const CancelQueuesCompose = ({ composeId }: Props) => { - const { mutateAsync, isLoading } = api.compose.cleanQueues.useMutation(); - const { data: isCloud } = api.settings.isCloud.useQuery(); - - if (isCloud) { - return null; - } - return ( - - - - - - - - Are you sure to cancel the incoming deployments? - - - This will cancel all the incoming deployments - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(() => { - toast.success("Queues are being cleaned"); - }) - .catch((err) => { - toast.error(err.message); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx deleted file mode 100644 index b062b099..00000000 --- a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} -export const RefreshTokenCompose = ({ composeId }: Props) => { - const { mutateAsync } = api.compose.refreshToken.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently change the token - and all the previous tokens will be invalidated - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(() => { - utils.compose.one.invalidate({ - composeId, - }); - toast.success("Refresh Token updated"); - }) - .catch(() => { - toast.error("Error updating the refresh token"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx deleted file mode 100644 index 7c191a14..00000000 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Loader2 } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; -import { TerminalLine } from "../../docker/logs/terminal-line"; -import { type LogLine, parseLogs } from "../../docker/logs/utils"; - -interface Props { - logPath: string | null; - serverId?: string; - open: boolean; - onClose: () => void; - errorMessage?: string; -} -export const ShowDeploymentCompose = ({ - logPath, - open, - onClose, - serverId, - errorMessage, -}: Props) => { - const [data, setData] = useState(""); - const [filteredLogs, setFilteredLogs] = useState([]); - const [showExtraLogs, setShowExtraLogs] = useState(false); - const wsRef = useRef(null); // Ref to hold WebSocket instance - const [autoScroll, setAutoScroll] = useState(true); - const scrollRef = useRef(null); - - const scrollToBottom = () => { - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }; - - const handleScroll = () => { - if (!scrollRef.current) return; - - const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; - const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; - setAutoScroll(isAtBottom); - }; - - useEffect(() => { - if (!open || !logPath) return; - - setData(""); - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - - const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}&serverId=${serverId}`; - const ws = new WebSocket(wsUrl); - - wsRef.current = ws; // Store WebSocket instance in ref - - ws.onmessage = (e) => { - setData((currentData) => currentData + e.data); - }; - - ws.onerror = (error) => { - console.error("WebSocket error: ", error); - }; - - ws.onclose = () => { - wsRef.current = null; - }; - - return () => { - if (wsRef.current?.readyState === WebSocket.OPEN) { - ws.close(); - wsRef.current = null; - } - }; - }, [logPath, open]); - - useEffect(() => { - const logs = parseLogs(data); - let filteredLogsResult = logs; - if (serverId) { - let hideSubsequentLogs = false; - filteredLogsResult = logs.filter((log) => { - if ( - log.message.includes( - "===================================EXTRA LOGS============================================", - ) - ) { - hideSubsequentLogs = true; - return showExtraLogs; - } - return showExtraLogs ? true : !hideSubsequentLogs; - }); - } - - setFilteredLogs(filteredLogsResult); - }, [data, showExtraLogs]); - - useEffect(() => { - scrollToBottom(); - - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [filteredLogs, autoScroll]); - - const optionalErrors = parseLogs(errorMessage || ""); - - return ( - { - onClose(); - if (!e) { - setData(""); - } - - if (wsRef.current) { - if (wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.close(); - } - } - }} - > - - - Deployment - - - See all the details of this deployment |{" "} - - {filteredLogs.length} lines - - - {serverId && ( -
- - setShowExtraLogs(checked as boolean) - } - /> - -
- )} -
-
- -
- {filteredLogs.length > 0 ? ( - filteredLogs.map((log: LogLine, index: number) => ( - - )) - ) : ( - <> - {optionalErrors.length > 0 ? ( - optionalErrors.map((log: LogLine, index: number) => ( - - )) - ) : ( -
- -
- )} - - )} -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx deleted file mode 100644 index fce4f33f..00000000 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { DateTooltip } from "@/components/shared/date-tooltip"; -import { StatusTooltip } from "@/components/shared/status-tooltip"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { type RouterOutputs, api } from "@/utils/api"; -import { RocketIcon } from "lucide-react"; -import React, { useEffect, useState } from "react"; -import { CancelQueuesCompose } from "./cancel-queues-compose"; -import { RefreshTokenCompose } from "./refresh-token-compose"; -import { ShowDeploymentCompose } from "./show-deployment-compose"; - -interface Props { - composeId: string; -} -export const ShowDeploymentsCompose = ({ composeId }: Props) => { - const [activeLog, setActiveLog] = useState< - RouterOutputs["deployment"]["all"][number] | null - >(null); - const { data } = api.compose.one.useQuery({ composeId }); - const { data: deployments } = api.deployment.allByCompose.useQuery( - { composeId }, - { - enabled: !!composeId, - refetchInterval: 5000, - }, - ); - const [url, setUrl] = React.useState(""); - useEffect(() => { - setUrl(document.location.origin); - }, []); - - return ( - - -
- Deployments - - See all the 10 last deployments for this compose - -
- - {/* */} -
- -
- - If you want to re-deploy this application use this URL in the config - of your git provider or docker - -
- Webhook URL: -
- - {`${url}/api/deploy/compose/${data?.refreshToken}`} - - -
-
-
- {data?.deployments?.length === 0 ? ( -
- - - No deployments found - -
- ) : ( -
- {deployments?.map((deployment) => ( -
-
- - {deployment.status} - - - - - {deployment.title} - - {deployment.description && ( - - {deployment.description} - - )} -
-
-
- -
- - -
-
- ))} -
- )} - setActiveLog(null)} - logPath={activeLog?.logPath || ""} - errorMessage={activeLog?.errorMessage || ""} - /> -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx deleted file mode 100644 index 6089c99f..00000000 --- a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx +++ /dev/null @@ -1,503 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input, NumberInput } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { api } from "@/utils/api"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; - -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { domainCompose } from "@/server/db/validations/domain"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { DatabaseZap, Dices, RefreshCw } from "lucide-react"; -import Link from "next/link"; -import type z from "zod"; - -type Domain = z.infer; - -export type CacheType = "fetch" | "cache"; - -interface Props { - composeId: string; - domainId?: string; - children: React.ReactNode; -} - -export const AddDomainCompose = ({ - composeId, - domainId = "", - children, -}: Props) => { - const [isOpen, setIsOpen] = useState(false); - const [cacheType, setCacheType] = useState("cache"); - const utils = api.useUtils(); - const { data, refetch } = api.domain.one.useQuery( - { - domainId, - }, - { - enabled: !!domainId, - }, - ); - - const { data: compose } = api.compose.one.useQuery( - { - composeId, - }, - { - enabled: !!composeId, - }, - ); - - const { - data: services, - isFetching: isLoadingServices, - error: errorServices, - refetch: refetchServices, - } = api.compose.loadServices.useQuery( - { - composeId, - type: cacheType, - }, - { - retry: false, - refetchOnWindowFocus: false, - }, - ); - - const { mutateAsync: generateDomain, isLoading: isLoadingGenerate } = - api.domain.generateDomain.useMutation(); - - const { mutateAsync, isError, error, isLoading } = domainId - ? api.domain.update.useMutation() - : api.domain.create.useMutation(); - - const { data: canGenerateTraefikMeDomains } = - api.domain.canGenerateTraefikMeDomains.useQuery({ - serverId: compose?.serverId || "", - }); - - const form = useForm({ - resolver: zodResolver(domainCompose), - defaultValues: { - host: "", - path: undefined, - port: undefined, - https: false, - certificateType: undefined, - customCertResolver: undefined, - serviceName: "", - }, - }); - - const https = form.watch("https"); - - useEffect(() => { - if (data) { - form.reset({ - ...data, - /* Convert null to undefined */ - path: data?.path || undefined, - port: data?.port || undefined, - serviceName: data?.serviceName || undefined, - certificateType: data?.certificateType || undefined, - customCertResolver: data?.customCertResolver || undefined, - }); - } - - if (!domainId) { - form.reset({ - host: "", - path: undefined, - port: undefined, - https: false, - certificateType: undefined, - customCertResolver: undefined, - serviceName: "", - }); - } - }, [form, form.reset, data, isLoading]); - - const dictionary = { - success: domainId ? "Domain Updated" : "Domain Created", - error: domainId ? "Error updating the domain" : "Error creating the domain", - submit: domainId ? "Update" : "Create", - dialogDescription: domainId - ? "In this section you can edit a domain" - : "In this section you can add domains", - }; - - const onSubmit = async (data: Domain) => { - await mutateAsync({ - domainId, - composeId, - domainType: "compose", - ...data, - }) - .then(async () => { - await utils.domain.byComposeId.invalidate({ - composeId, - }); - toast.success(dictionary.success); - if (domainId) { - refetch(); - } - setIsOpen(false); - }) - .catch(() => { - toast.error(dictionary.error); - }); - }; - return ( - - - {children} - - - - Domain - {dictionary.dialogDescription} - -
- - Deploy is required to apply changes after creating or updating a - domain. - - {isError && {error?.message}} -
- -
- -
-
- {errorServices && ( - - {errorServices?.message} - - )} -
- ( - - Service Name -
- - - - - - - -

- Fetch: Will clone the repository and load the - services -

-
-
-
- - - - - - -

- Cache: If you previously deployed this - compose, it will read the services from the - last deployment/fetch from the repository -

-
-
-
-
- - -
- )} - /> -
- - ( - - {!canGenerateTraefikMeDomains && - field.value.includes("traefik.me") && ( - - You need to set an IP address in your{" "} - - {compose?.serverId - ? "Remote Servers -> Server -> Edit Server -> Update IP Address" - : "Web Server -> Server -> Update Server IP"} - {" "} - to make your traefik.me domain work. - - )} - Host -
- - - - - - - - - -

Generate traefik.me domain

-
-
-
-
- - -
- )} - /> - - { - return ( - - Path - - - - - - ); - }} - /> - - { - return ( - - Container Port - - - - - - ); - }} - /> - - ( - -
- HTTPS - - Automatically provision SSL Certificate. - - -
- - - -
- )} - /> - - {https && ( - <> - ( - - Certificate Provider - - - - )} - /> - - {form.getValues().certificateType === "custom" && ( - ( - - Custom Certificate Resolver - - - - - - )} - /> - )} - - )} -
-
-
- - - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx deleted file mode 100644 index e6468d6f..00000000 --- a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { DialogAction } from "@/components/shared/dialog-action"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react"; -import Link from "next/link"; -import { toast } from "sonner"; -import { AddDomainCompose } from "./add-domain"; - -interface Props { - composeId: string; -} - -export const ShowDomainsCompose = ({ composeId }: Props) => { - const { data, refetch } = api.domain.byComposeId.useQuery( - { - composeId, - }, - { - enabled: !!composeId, - }, - ); - - const { mutateAsync: deleteDomain, isLoading: isRemoving } = - api.domain.delete.useMutation(); - - return ( -
- - -
- Domains - - Domains are used to access to the application - -
- -
- {data && data?.length > 0 && ( - - - - )} -
-
- - {data?.length === 0 ? ( -
- - - To access to the application it is required to set at least 1 - domain - -
- - - -
-
- ) : ( -
- {data?.map((item) => { - return ( -
-
- - {item.serviceName} - - - - {item.host} - - -
- -
-
- {item.path} - {item.port} - {item.https ? "HTTPS" : "HTTP"} -
- -
- - - - { - await deleteDomain({ - domainId: item.domainId, - }) - .then((_data) => { - refetch(); - toast.success("Domain deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting domain"); - }); - }} - > - - -
-
-
- ); - })} -
- )} -
-
-
- ); -}; 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 6e9b0a03..97b57f0b 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 @@ -40,7 +40,7 @@ import { import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react"; import Link from "next/link"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; @@ -58,6 +58,7 @@ const GithubProviderSchema = z.object({ branch: z.string().min(1, "Branch is required"), githubId: z.string().min(1, "Github Provider is required"), watchPaths: z.array(z.string()).optional(), + triggerType: z.enum(["push", "tag"]).default("push"), enableSubmodules: z.boolean().default(false), }); @@ -84,6 +85,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { githubId: "", branch: "", watchPaths: [], + triggerType: "push", enableSubmodules: false, }, resolver: zodResolver(GithubProviderSchema), @@ -91,7 +93,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { const repository = form.watch("repository"); const githubId = form.watch("githubId"); - + const triggerType = form.watch("triggerType"); const { data: repositories, isLoading: isLoadingRepositories } = api.github.getGithubRepositories.useQuery( { @@ -128,6 +130,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { composePath: data.composePath, githubId: data.githubId || "", watchPaths: data.watchPaths || [], + triggerType: data.triggerType || "push", enableSubmodules: data.enableSubmodules ?? false, }); } @@ -145,6 +148,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { composeStatus: "idle", watchPaths: data.watchPaths, enableSubmodules: data.enableSubmodules, + triggerType: data.triggerType, }) .then(async () => { toast.success("Service Provided Saved"); @@ -389,82 +393,128 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { /> (
- Watch Paths + Trigger Type - -
- ? -
+ +

- Add paths to watch for changes. When files in these - paths change, a new deployment will be triggered. + Choose when to trigger deployments: on push to the + selected branch or when a new tag is created.

-
- {field.value?.map((path, index) => ( - - {path} - { - const newPaths = [...(field.value || [])]; - newPaths.splice(index, 1); - form.setValue("watchPaths", newPaths); + + + + )} + /> + {triggerType === "push" && ( + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in + these paths change, a new deployment will be + triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [ + ...(field.value || []), + value, + ]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } }} /> - - ))} -
- -
- { - if (e.key === "Enter") { - e.preventDefault(); - const input = e.currentTarget; + -
-
- -
- )} - /> + }} + > + Add + +
+ + +
+ )} + /> + )} { - const { data: githubProviders } = api.github.githubProviders.useQuery(); - const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); - const { data: bitbucketProviders } = + const { data: githubProviders, isLoading: isLoadingGithub } = + api.github.githubProviders.useQuery(); + const { data: gitlabProviders, isLoading: isLoadingGitlab } = + api.gitlab.gitlabProviders.useQuery(); + const { data: bitbucketProviders, isLoading: isLoadingBitbucket } = api.bitbucket.bitbucketProviders.useQuery(); - const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); + const { data: giteaProviders, isLoading: isLoadingGitea } = + api.gitea.giteaProviders.useQuery(); const { data: compose } = api.compose.one.useQuery({ composeId }); const [tab, setSab] = useState(compose?.sourceType || "github"); + const isLoading = + isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea; + + if (isLoading) { + return ( + + + +
+ Provider +

+ Select the source of your code +

+
+
+ +
+
+
+ +
+
+ + Loading providers... +
+
+
+
+ ); + } + return ( @@ -108,7 +142,7 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { {githubProviders && githubProviders?.length > 0 ? ( ) : ( -
+
To deploy using GitHub, you need to configure your account @@ -128,7 +162,7 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { {gitlabProviders && gitlabProviders?.length > 0 ? ( ) : ( -
+
To deploy using GitLab, you need to configure your account @@ -148,7 +182,7 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { {bitbucketProviders && bitbucketProviders?.length > 0 ? ( ) : ( -
+
To deploy using Bitbucket, you need to configure your account @@ -168,7 +202,7 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { {giteaProviders && giteaProviders?.length > 0 ? ( ) : ( -
+
To deploy using Gitea, you need to configure your account diff --git a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx b/apps/dokploy/components/dashboard/database/backups/add-backup.tsx deleted file mode 100644 index 0fa568a9..00000000 --- a/apps/dokploy/components/dashboard/database/backups/add-backup.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Switch } from "@/components/ui/switch"; -import { cn } from "@/lib/utils"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const AddPostgresBackup1Schema = z.object({ - destinationId: z.string().min(1, "Destination required"), - schedule: z.string().min(1, "Schedule (Cron) required"), - // .regex( - // new RegExp( - // /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$/, - // ), - // "Invalid Cron", - // ), - prefix: z.string().min(1, "Prefix required"), - enabled: z.boolean(), - database: z.string().min(1, "Database required"), - keepLatestCount: z.coerce.number().optional(), -}); - -type AddPostgresBackup = z.infer; - -interface Props { - databaseId: string; - databaseType: "postgres" | "mariadb" | "mysql" | "mongo" | "web-server"; - refetch: () => void; -} - -export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => { - const { data, isLoading } = api.destination.all.useQuery(); - - const { mutateAsync: createBackup, isLoading: isCreatingPostgresBackup } = - api.backup.create.useMutation(); - - const form = useForm({ - defaultValues: { - database: "", - destinationId: "", - enabled: true, - prefix: "/", - schedule: "", - keepLatestCount: undefined, - }, - resolver: zodResolver(AddPostgresBackup1Schema), - }); - - useEffect(() => { - form.reset({ - database: databaseType === "web-server" ? "dokploy" : "", - destinationId: "", - enabled: true, - prefix: "/", - schedule: "", - keepLatestCount: undefined, - }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); - - const onSubmit = async (data: AddPostgresBackup) => { - const getDatabaseId = - databaseType === "postgres" - ? { - postgresId: databaseId, - } - : databaseType === "mariadb" - ? { - mariadbId: databaseId, - } - : databaseType === "mysql" - ? { - mysqlId: databaseId, - } - : databaseType === "mongo" - ? { - mongoId: databaseId, - } - : databaseType === "web-server" - ? { - userId: databaseId, - } - : undefined; - - await createBackup({ - destinationId: data.destinationId, - prefix: data.prefix, - schedule: data.schedule, - enabled: data.enabled, - database: data.database, - keepLatestCount: data.keepLatestCount, - databaseType, - ...getDatabaseId, - }) - .then(async () => { - toast.success("Backup Created"); - refetch(); - }) - .catch(() => { - toast.error("Error creating a backup"); - }); - }; - return ( - - - - - - - Create a backup - Add a new backup - - -
- -
- ( - - Destination - - - - - - - - - - {isLoading && ( - - Loading Destinations.... - - )} - No destinations found. - - - {data?.map((destination) => ( - { - form.setValue( - "destinationId", - destination.destinationId, - ); - }} - > - {destination.name} - - - ))} - - - - - - - - - )} - /> - { - return ( - - Database - - - - - - ); - }} - /> - { - return ( - - Schedule (Cron) - - - - - - ); - }} - /> - { - return ( - - Prefix Destination - - - - - Use if you want to back up in a specific path of your - destination/bucket - - - - - ); - }} - /> - { - return ( - - Keep the latest - - - - - Optional. If provided, only keeps the latest N backups - in the cloud. - - - - ); - }} - /> - ( - -
- Enabled - - Enable or disable the backup - -
- - - -
- )} - /> -
- - - -
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx b/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx new file mode 100644 index 00000000..ca2cd27f --- /dev/null +++ b/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx @@ -0,0 +1,828 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + DatabaseZap, + Info, + PenBoxIcon, + PlusIcon, + RefreshCw, +} from "lucide-react"; +import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { commonCronExpressions } from "../../application/schedules/handle-schedules"; + +type CacheType = "cache" | "fetch"; + +type DatabaseType = "postgres" | "mariadb" | "mysql" | "mongo" | "web-server"; + +const Schema = z + .object({ + destinationId: z.string().min(1, "Destination required"), + schedule: z.string().min(1, "Schedule (Cron) required"), + prefix: z.string().min(1, "Prefix required"), + enabled: z.boolean(), + database: z.string().min(1, "Database required"), + keepLatestCount: z.coerce.number().optional(), + serviceName: z.string().nullable(), + databaseType: z + .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) + .optional(), + backupType: z.enum(["database", "compose"]), + metadata: z + .object({ + postgres: z + .object({ + databaseUser: z.string(), + }) + .optional(), + mariadb: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mongo: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mysql: z + .object({ + databaseRootPassword: z.string(), + }) + .optional(), + }) + .optional(), + }) + .superRefine((data, ctx) => { + if (data.backupType === "compose" && !data.databaseType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database type is required for compose backups", + path: ["databaseType"], + }); + } + + if (data.backupType === "compose" && !data.serviceName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Service name is required for compose backups", + path: ["serviceName"], + }); + } + + if (data.backupType === "compose" && data.databaseType) { + if (data.databaseType === "postgres") { + if (!data.metadata?.postgres?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for PostgreSQL", + path: ["metadata", "postgres", "databaseUser"], + }); + } + } else if (data.databaseType === "mariadb") { + if (!data.metadata?.mariadb?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for MariaDB", + path: ["metadata", "mariadb", "databaseUser"], + }); + } + if (!data.metadata?.mariadb?.databasePassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database password is required for MariaDB", + path: ["metadata", "mariadb", "databasePassword"], + }); + } + } else if (data.databaseType === "mongo") { + if (!data.metadata?.mongo?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for MongoDB", + path: ["metadata", "mongo", "databaseUser"], + }); + } + if (!data.metadata?.mongo?.databasePassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database password is required for MongoDB", + path: ["metadata", "mongo", "databasePassword"], + }); + } + } else if (data.databaseType === "mysql") { + if (!data.metadata?.mysql?.databaseRootPassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Root password is required for MySQL", + path: ["metadata", "mysql", "databaseRootPassword"], + }); + } + } + } + }); + +interface Props { + id?: string; + backupId?: string; + databaseType?: DatabaseType; + refetch: () => void; + backupType: "database" | "compose"; +} + +export const HandleBackup = ({ + id, + backupId, + databaseType = "postgres", + refetch, + backupType = "database", +}: Props) => { + const [isOpen, setIsOpen] = useState(false); + + const { data, isLoading } = api.destination.all.useQuery(); + const { data: backup } = api.backup.one.useQuery( + { + backupId: backupId ?? "", + }, + { + enabled: !!backupId, + }, + ); + const [cacheType, setCacheType] = useState("cache"); + const { mutateAsync: createBackup, isLoading: isCreatingPostgresBackup } = + backupId + ? api.backup.update.useMutation() + : api.backup.create.useMutation(); + + const form = useForm>({ + defaultValues: { + database: databaseType === "web-server" ? "dokploy" : "", + destinationId: "", + enabled: true, + prefix: "/", + schedule: "", + keepLatestCount: undefined, + serviceName: null, + databaseType: backupType === "compose" ? undefined : databaseType, + backupType: backupType, + metadata: {}, + }, + resolver: zodResolver(Schema), + }); + + const { + data: services, + isFetching: isLoadingServices, + error: errorServices, + refetch: refetchServices, + } = api.compose.loadServices.useQuery( + { + composeId: backup?.composeId ?? id ?? "", + type: cacheType, + }, + { + retry: false, + refetchOnWindowFocus: false, + enabled: backupType === "compose" && !!backup?.composeId && !!id, + }, + ); + + useEffect(() => { + form.reset({ + database: backup?.database + ? backup?.database + : databaseType === "web-server" + ? "dokploy" + : "", + destinationId: backup?.destinationId ?? "", + enabled: backup?.enabled ?? true, + prefix: backup?.prefix ?? "/", + schedule: backup?.schedule ?? "", + keepLatestCount: backup?.keepLatestCount ?? undefined, + serviceName: backup?.serviceName ?? null, + databaseType: backup?.databaseType ?? databaseType, + backupType: backup?.backupType ?? backupType, + metadata: backup?.metadata ?? {}, + }); + }, [form, form.reset, backupId, backup]); + + const onSubmit = async (data: z.infer) => { + const getDatabaseId = + backupType === "compose" + ? { + composeId: id, + } + : databaseType === "postgres" + ? { + postgresId: id, + } + : databaseType === "mariadb" + ? { + mariadbId: id, + } + : databaseType === "mysql" + ? { + mysqlId: id, + } + : databaseType === "mongo" + ? { + mongoId: id, + } + : databaseType === "web-server" + ? { + userId: id, + } + : undefined; + + await createBackup({ + destinationId: data.destinationId, + prefix: data.prefix, + schedule: data.schedule, + enabled: data.enabled, + database: data.database, + keepLatestCount: data.keepLatestCount ?? null, + databaseType: data.databaseType || databaseType, + serviceName: data.serviceName, + ...getDatabaseId, + backupId: backupId ?? "", + backupType, + metadata: data.metadata, + }) + .then(async () => { + toast.success(`Backup ${backupId ? "Updated" : "Created"}`); + refetch(); + setIsOpen(false); + }) + .catch(() => { + toast.error(`Error ${backupId ? "updating" : "creating"} a backup`); + }); + }; + + return ( + + + {backupId ? ( + + ) : ( + + )} + + + + + {backupId ? "Update Backup" : "Create Backup"} + + + {backupId ? "Update a backup" : "Add a new backup"} + + + +
+ +
+ {errorServices && ( + + {errorServices?.message} + + )} + {backupType === "compose" && ( + ( + + Database Type + + + + )} + /> + )} + ( + + Destination + + + + + + + + + + {isLoading && ( + + Loading Destinations.... + + )} + No destinations found. + + + {data?.map((destination) => ( + { + form.setValue( + "destinationId", + destination.destinationId, + ); + }} + > + {destination.name} + + + ))} + + + + + + + + + )} + /> + {backupType === "compose" && ( +
+ ( + + Service Name +
+ + + + + + + +

+ Fetch: Will clone the repository and load the + services +

+
+
+
+ + + + + + +

+ Cache: If you previously deployed this + compose, it will read the services from the + last deployment/fetch from the repository +

+
+
+
+
+ + +
+ )} + /> +
+ )} + { + return ( + + Database + + + + + + ); + }} + /> + { + return ( + + + Schedule + + + + + + +

+ Cron expression format: minute hour day month + weekday +

+

Example: 0 0 * * * (daily at midnight)

+
+
+
+
+
+ +
+ + + +
+
+ + Choose a predefined schedule or enter a custom cron + expression + + +
+ ); + }} + /> + { + return ( + + Prefix Destination + + + + + Use if you want to back up in a specific path of your + destination/bucket + + + + + ); + }} + /> + { + return ( + + Keep the latest + + + + + Optional. If provided, only keeps the latest N backups + in the cloud. + + + + ); + }} + /> + ( + +
+ Enabled + + Enable or disable the backup + +
+ + + +
+ )} + /> + {backupType === "compose" && ( + <> + {form.watch("databaseType") === "postgres" && ( + ( + + Database User + + + + + + )} + /> + )} + + {form.watch("databaseType") === "mariadb" && ( + <> + ( + + Database User + + + + + + )} + /> + ( + + Database Password + + + + + + )} + /> + + )} + + {form.watch("databaseType") === "mongo" && ( + <> + ( + + Database User + + + + + + )} + /> + ( + + Database Password + + + + + + )} + /> + + )} + + {form.watch("databaseType") === "mysql" && ( + ( + + Root Password + + + + + + )} + /> + )} + + )} +
+ + + +
+ +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx index 69d37935..a8a2f1ae 100644 --- a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx @@ -32,50 +32,172 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import copy from "copy-to-clipboard"; import { debounce } from "lodash"; -import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react"; +import { + CheckIcon, + ChevronsUpDown, + Copy, + RotateCcw, + RefreshCw, + DatabaseZap, +} from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import type { ServiceType } from "../../application/advanced/show-resources"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +type DatabaseType = + | Exclude + | "web-server"; interface Props { - databaseId: string; - databaseType: Exclude | "web-server"; + id: string; + databaseType?: DatabaseType; serverId?: string | null; + backupType?: "database" | "compose"; } -const RestoreBackupSchema = z.object({ - destinationId: z - .string({ - required_error: "Please select a destination", - }) - .min(1, { - message: "Destination is required", - }), - backupFile: z - .string({ - required_error: "Please select a backup file", - }) - .min(1, { - message: "Backup file is required", - }), - databaseName: z - .string({ - required_error: "Please enter a database name", - }) - .min(1, { - message: "Database name is required", - }), -}); +const RestoreBackupSchema = z + .object({ + destinationId: z + .string({ + required_error: "Please select a destination", + }) + .min(1, { + message: "Destination is required", + }), + backupFile: z + .string({ + required_error: "Please select a backup file", + }) + .min(1, { + message: "Backup file is required", + }), + databaseName: z + .string({ + required_error: "Please enter a database name", + }) + .min(1, { + message: "Database name is required", + }), + databaseType: z + .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) + .optional(), + backupType: z.enum(["database", "compose"]).default("database"), + metadata: z + .object({ + postgres: z + .object({ + databaseUser: z.string(), + }) + .optional(), + mariadb: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mongo: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mysql: z + .object({ + databaseRootPassword: z.string(), + }) + .optional(), + serviceName: z.string().optional(), + }) + .optional(), + }) + .superRefine((data, ctx) => { + if (data.backupType === "compose" && !data.databaseType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database type is required for compose backups", + path: ["databaseType"], + }); + } -type RestoreBackup = z.infer; + if (data.backupType === "compose" && !data.metadata?.serviceName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Service name is required for compose backups", + path: ["metadata", "serviceName"], + }); + } + + if (data.backupType === "compose" && data.databaseType) { + if (data.databaseType === "postgres") { + if (!data.metadata?.postgres?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for PostgreSQL", + path: ["metadata", "postgres", "databaseUser"], + }); + } + } else if (data.databaseType === "mariadb") { + if (!data.metadata?.mariadb?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for MariaDB", + path: ["metadata", "mariadb", "databaseUser"], + }); + } + if (!data.metadata?.mariadb?.databasePassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database password is required for MariaDB", + path: ["metadata", "mariadb", "databasePassword"], + }); + } + } else if (data.databaseType === "mongo") { + if (!data.metadata?.mongo?.databaseUser) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database user is required for MongoDB", + path: ["metadata", "mongo", "databaseUser"], + }); + } + if (!data.metadata?.mongo?.databasePassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Database password is required for MongoDB", + path: ["metadata", "mongo", "databasePassword"], + }); + } + } else if (data.databaseType === "mysql") { + if (!data.metadata?.mysql?.databaseRootPassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Root password is required for MySQL", + path: ["metadata", "mysql", "databaseRootPassword"], + }); + } + } + } + }); const formatBytes = (bytes: number): string => { if (bytes === 0) return "0 Bytes"; @@ -86,9 +208,10 @@ const formatBytes = (bytes: number): string => { }; export const RestoreBackup = ({ - databaseId, + id, databaseType, serverId, + backupType = "database", }: Props) => { const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(""); @@ -96,16 +219,22 @@ export const RestoreBackup = ({ const { data: destinations = [] } = api.destination.all.useQuery(); - const form = useForm({ + const form = useForm>({ defaultValues: { destinationId: "", backupFile: "", databaseName: databaseType === "web-server" ? "dokploy" : "", + databaseType: + backupType === "compose" ? ("postgres" as DatabaseType) : databaseType, + backupType: backupType, + metadata: {}, }, resolver: zodResolver(RestoreBackupSchema), }); const destionationId = form.watch("destinationId"); + const currentDatabaseType = form.watch("databaseType"); + const metadata = form.watch("metadata"); const debouncedSetSearch = debounce((value: string) => { setDebouncedSearchTerm(value); @@ -131,16 +260,15 @@ export const RestoreBackup = ({ const [filteredLogs, setFilteredLogs] = useState([]); const [isDeploying, setIsDeploying] = useState(false); - // const { mutateAsync: restore, isLoading: isRestoring } = - // api.backup.restoreBackup.useMutation(); - api.backup.restoreBackupWithLogs.useSubscription( { - databaseId, - databaseType, + databaseId: id, + databaseType: currentDatabaseType as DatabaseType, databaseName: form.watch("databaseName"), backupFile: form.watch("backupFile"), destinationId: form.watch("destinationId"), + backupType: backupType, + metadata: metadata, }, { enabled: isDeploying, @@ -162,10 +290,32 @@ export const RestoreBackup = ({ }, ); - const onSubmit = async (_data: RestoreBackup) => { + const onSubmit = async (data: z.infer) => { + if (backupType === "compose" && !data.databaseType) { + toast.error("Please select a database type"); + return; + } + console.log({ data }); setIsDeploying(true); }; + const [cacheType, setCacheType] = useState<"fetch" | "cache">("cache"); + const { + data: services = [], + isLoading: isLoadingServices, + refetch: refetchServices, + } = api.compose.loadServices.useQuery( + { + composeId: id, + type: cacheType, + }, + { + retry: false, + refetchOnWindowFocus: false, + enabled: backupType === "compose", + }, + ); + return ( @@ -174,7 +324,7 @@ export const RestoreBackup = ({ Restore Backup - + @@ -377,25 +527,274 @@ export const RestoreBackup = ({ control={form.control} name="databaseName" render={({ field }) => ( - + Database Name )} /> + + {backupType === "compose" && ( + <> + ( + + Database Type + + + + )} + /> + + ( + + Service Name +
+ + + + + + + +

+ Fetch: Will clone the repository and load the + services +

+
+
+
+ + + + + + +

+ Cache: If you previously deployed this compose, + it will read the services from the last + deployment/fetch from the repository +

+
+
+
+
+ + +
+ )} + /> + + {currentDatabaseType === "postgres" && ( + ( + + Database User + + + + + + )} + /> + )} + + {currentDatabaseType === "mariadb" && ( + <> + ( + + Database User + + + + + + )} + /> + ( + + Database Password + + + + + + )} + /> + + )} + + {currentDatabaseType === "mongo" && ( + <> + ( + + Database User + + + + + + )} + /> + ( + + Database Password + + + + + + )} + /> + + )} + + {currentDatabaseType === "mysql" && ( + ( + + Root Password + + + + + + )} + /> + )} + + )} + diff --git a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx index 1c2b527b..bb3128cf 100644 --- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx +++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx @@ -14,49 +14,83 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { Database, DatabaseBackup, Play, Trash2 } from "lucide-react"; +import { + ClipboardList, + Database, + DatabaseBackup, + Play, + Trash2, +} from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; import type { ServiceType } from "../../application/advanced/show-resources"; -import { AddBackup } from "./add-backup"; import { RestoreBackup } from "./restore-backup"; -import { UpdateBackup } from "./update-backup"; +import { HandleBackup } from "./handle-backup"; +import { cn } from "@/lib/utils"; +import { + MariadbIcon, + MongodbIcon, + MysqlIcon, + PostgresqlIcon, +} from "@/components/icons/data-tools-icons"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { ShowDeploymentsModal } from "../../application/deployments/show-deployments-modal"; interface Props { id: string; - type: Exclude | "web-server"; + databaseType?: Exclude | "web-server"; + backupType?: "database" | "compose"; } -export const ShowBackups = ({ id, type }: Props) => { +export const ShowBackups = ({ + id, + databaseType, + backupType = "database", +}: Props) => { const [activeManualBackup, setActiveManualBackup] = useState< string | undefined >(); - const queryMap = { - postgres: () => - api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), - mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), - mariadb: () => - api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), - mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), - "web-server": () => api.user.getBackups.useQuery(), - }; + const queryMap = + backupType === "database" + ? { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + mysql: () => + api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + mongo: () => + api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + "web-server": () => api.user.getBackups.useQuery(), + } + : { + compose: () => + api.compose.one.useQuery({ composeId: id }, { enabled: !!id }), + }; const { data } = api.destination.all.useQuery(); - const { data: postgres, refetch } = queryMap[type] - ? queryMap[type]() + const key = backupType === "database" ? databaseType : "compose"; + const query = queryMap[key as keyof typeof queryMap]; + const { data: postgres, refetch } = query + ? query() : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); - const mutationMap = { - postgres: () => api.backup.manualBackupPostgres.useMutation(), - mysql: () => api.backup.manualBackupMySql.useMutation(), - mariadb: () => api.backup.manualBackupMariadb.useMutation(), - mongo: () => api.backup.manualBackupMongo.useMutation(), - "web-server": () => api.backup.manualBackupWebServer.useMutation(), - }; + const mutationMap = + backupType === "database" + ? { + postgres: api.backup.manualBackupPostgres.useMutation(), + mysql: api.backup.manualBackupMySql.useMutation(), + mariadb: api.backup.manualBackupMariadb.useMutation(), + mongo: api.backup.manualBackupMongo.useMutation(), + "web-server": api.backup.manualBackupWebServer.useMutation(), + } + : { + compose: api.backup.manualBackupCompose.useMutation(), + }; - const { mutateAsync: manualBackup, isLoading: isManualBackup } = mutationMap[ - type - ] - ? mutationMap[type]() + const mutation = mutationMap[key as keyof typeof mutationMap]; + + const { mutateAsync: manualBackup, isLoading: isManualBackup } = mutation + ? mutation : api.backup.manualBackupMongo.useMutation(); const { mutateAsync: deleteBackup, isLoading: isRemoving } = @@ -78,16 +112,18 @@ export const ShowBackups = ({ id, type }: Props) => { {postgres && postgres?.backups?.length > 0 && (
- {type !== "web-server" && ( - )}
@@ -95,7 +131,7 @@ export const ShowBackups = ({ id, type }: Props) => { {data?.length === 0 ? ( -
+
To create a backup it is required to set at least 1 provider. @@ -110,7 +146,7 @@ export const ShowBackups = ({ id, type }: Props) => {
) : ( -
+
{postgres?.backups.length === 0 ? (
@@ -118,14 +154,16 @@ export const ShowBackups = ({ id, type }: Props) => { No backups configured
- {
) : ( -
+
+ {backupType === "compose" && ( + + Make sure the compose is running before creating a backup. + + )}
- {postgres?.backups.map((backup) => ( -
-
-
-
- Destination - - {backup.destination.name} - -
-
- Database - - {backup.database} - -
-
- Scheduled - - {backup.schedule} - -
-
- Prefix Storage - - {backup.prefix} - -
-
- Enabled - - {backup.enabled ? "Yes" : "No"} - -
-
- Keep Latest - - {backup.keepLatestCount || "All"} - -
-
-
- - - - - - Run Manual Backup - - + {postgres?.backups.map((backup) => { + const serverId = + "serverId" in postgres ? postgres.serverId : undefined; - - { - await deleteBackup({ - backupId: backup.backupId, - }) - .then(() => { - refetch(); - toast.success("Backup deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting backup"); - }); - }} - > - - + + + + + + + + + Run Manual Backup + + + + + + { + await deleteBackup({ + backupId: backup.backupId, + }) + .then(() => { + refetch(); + toast.success( + "Backup deleted successfully", + ); + }) + .catch(() => { + toast.error("Error deleting backup"); + }); + }} + > + + +
-
- ))} + ); + })}
)} diff --git a/apps/dokploy/components/dashboard/database/backups/update-backup.tsx b/apps/dokploy/components/dashboard/database/backups/update-backup.tsx deleted file mode 100644 index 2cf7b7a5..00000000 --- a/apps/dokploy/components/dashboard/database/backups/update-backup.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Switch } from "@/components/ui/switch"; -import { cn } from "@/lib/utils"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, PenBoxIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const UpdateBackupSchema = z.object({ - destinationId: z.string().min(1, "Destination required"), - schedule: z.string().min(1, "Schedule (Cron) required"), - prefix: z.string().min(1, "Prefix required"), - enabled: z.boolean(), - database: z.string().min(1, "Database required"), - keepLatestCount: z.coerce.number().optional(), -}); - -type UpdateBackup = z.infer; - -interface Props { - backupId: string; - refetch: () => void; -} - -export const UpdateBackup = ({ backupId, refetch }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { data, isLoading } = api.destination.all.useQuery(); - const { data: backup } = api.backup.one.useQuery( - { - backupId, - }, - { - enabled: !!backupId, - }, - ); - - const { mutateAsync, isLoading: isLoadingUpdate } = - api.backup.update.useMutation(); - - const form = useForm({ - defaultValues: { - database: "", - destinationId: "", - enabled: true, - prefix: "/", - schedule: "", - keepLatestCount: undefined, - }, - resolver: zodResolver(UpdateBackupSchema), - }); - - useEffect(() => { - if (backup) { - form.reset({ - database: backup.database, - destinationId: backup.destinationId, - enabled: backup.enabled || false, - prefix: backup.prefix, - schedule: backup.schedule, - keepLatestCount: backup.keepLatestCount - ? Number(backup.keepLatestCount) - : undefined, - }); - } - }, [form, form.reset, backup]); - - const onSubmit = async (data: UpdateBackup) => { - await mutateAsync({ - backupId, - destinationId: data.destinationId, - prefix: data.prefix, - schedule: data.schedule, - enabled: data.enabled, - database: data.database, - keepLatestCount: data.keepLatestCount as number | null, - }) - .then(async () => { - toast.success("Backup Updated"); - refetch(); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating the Backup"); - }); - }; - - return ( - - - - - - - Update Backup - Update the backup - - -
- -
- ( - - Destination - - - - - - - - - - {isLoading && ( - - Loading Destinations.... - - )} - No destinations found. - - - {data?.map((destination) => ( - { - form.setValue( - "destinationId", - destination.destinationId, - ); - }} - > - {destination.name} - - - ))} - - - - - - - - - )} - /> - { - return ( - - Database - - - - - - ); - }} - /> - { - return ( - - Schedule (Cron) - - - - - - ); - }} - /> - { - return ( - - Prefix Destination - - - - - Use if you want to back up in a specific path of your - destination/bucket - - - - - ); - }} - /> - { - return ( - - Keep the latest - - - - - Optional. If provided, only keeps the latest N backups - in the cloud. - - - - ); - }} - /> - ( - -
- Enabled - - Enable or disable the backup - -
- - - -
- )} - /> -
- - - -
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index 861b4bbe..611af355 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -140,7 +140,14 @@ export const DockerLogsId: React.FC = ({ ws.onmessage = (e) => { if (!isCurrentConnection) return; - setRawLogs((prev) => prev + e.data); + setRawLogs((prev) => { + const updated = prev + e.data; + const splitLines = updated.split("\n"); + if (splitLines.length > lines) { + return splitLines.slice(-lines).join("\n"); + } + return updated; + }); setIsLoading(false); if (noDataTimeout) clearTimeout(noDataTimeout); }; diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 16c56917..c93de251 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -145,10 +145,8 @@ export const AddApplication = ({ projectId, projectName }: Props) => { {...field} onChange={(e) => { const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase().replaceAll(" ", "-")}`, - ); + const serviceName = slugify(val); + form.setValue("appName", `${slug}-${serviceName}`); field.onChange(val); }} /> diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index ea8690a8..5f2bb137 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -152,10 +152,8 @@ export const AddCompose = ({ projectId, projectName }: Props) => { {...field} onChange={(e) => { const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase()}`, - ); + const serviceName = slugify(val); + form.setValue("appName", `${slug}-${serviceName}`); field.onChange(val); }} /> diff --git a/apps/dokploy/components/dashboard/project/add-database.tsx b/apps/dokploy/components/dashboard/project/add-database.tsx index a58aef2c..2420e603 100644 --- a/apps/dokploy/components/dashboard/project/add-database.tsx +++ b/apps/dokploy/components/dashboard/project/add-database.tsx @@ -363,10 +363,8 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { {...field} onChange={(e) => { const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase()}`, - ); + const serviceName = slugify(val); + form.setValue("appName", `${slug}-${serviceName}`); field.onChange(val); }} /> diff --git a/apps/dokploy/components/dashboard/projects/handle-project.tsx b/apps/dokploy/components/dashboard/projects/handle-project.tsx index dcb81241..ddc1303e 100644 --- a/apps/dokploy/components/dashboard/projects/handle-project.tsx +++ b/apps/dokploy/components/dashboard/projects/handle-project.tsx @@ -33,12 +33,23 @@ import { z } from "zod"; const AddProjectSchema = z.object({ name: z .string() - .min(1, { - message: "Name is required", - }) - .regex(/^[a-zA-Z]/, { + .min(1, "Project name is required") + .refine( + (name) => { + const trimmedName = name.trim(); + const validNameRegex = + /^[\p{L}\p{N}_-][\p{L}\p{N}\s_-]*[\p{L}\p{N}_-]$/u; + return validNameRegex.test(trimmedName); + }, + { + message: + "Project name must start and end with a letter, number, hyphen or underscore. Spaces are allowed in between.", + }, + ) + .refine((name) => !/^\d/.test(name.trim()), { message: "Project name cannot start with a number", - }), + }) + .transform((name) => name.trim()), description: z.string().optional(), }); diff --git a/apps/dokploy/components/dashboard/settings/servers/show-schedules-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/show-schedules-modal.tsx new file mode 100644 index 00000000..5f6a26e7 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/show-schedules-modal.tsx @@ -0,0 +1,28 @@ +import { useState } from "react"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; + +interface Props { + serverId: string; +} + +export const ShowSchedulesModal = ({ serverId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + e.preventDefault()} + > + Show Schedules + + + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 4771edd9..7ad9df94 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -43,6 +43,7 @@ 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 { ShowSchedulesModal } from "./show-schedules-modal"; export const ShowServers = () => { const { t } = useTranslation("settings"); @@ -332,6 +333,10 @@ export const ShowServers = () => { + + )} diff --git a/apps/dokploy/components/dashboard/settings/users/show-invitations.tsx b/apps/dokploy/components/dashboard/settings/users/show-invitations.tsx index 1bf7aa08..8c9813fc 100644 --- a/apps/dokploy/components/dashboard/settings/users/show-invitations.tsx +++ b/apps/dokploy/components/dashboard/settings/users/show-invitations.tsx @@ -36,6 +36,9 @@ export const ShowInvitations = () => { const { data, isLoading, refetch } = api.organization.allInvitations.useQuery(); + const { mutateAsync: removeInvitation } = + api.organization.removeInvitation.useMutation(); + return (
@@ -182,6 +185,22 @@ export const ShowInvitations = () => { Cancel Invitation )} + + { + await removeInvitation({ + invitationId: invitation.id, + }).then(() => { + refetch(); + toast.success( + "Invitation removed", + ); + }); + }} + > + Remove Invitation + )} diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 15d8728b..8d180967 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -10,6 +10,7 @@ import { ChevronRight, ChevronsUpDown, CircleHelp, + Clock, CreditCard, Database, Folder, @@ -158,6 +159,14 @@ const MENU: Menu = { // Only enabled in non-cloud environments isEnabled: ({ isCloud }) => !isCloud, }, + { + isSingle: true, + title: "Schedules", + url: "/dashboard/schedules", + icon: Clock, + // Only enabled in non-cloud environments + isEnabled: ({ isCloud, auth }) => !isCloud && auth?.role === "owner", + }, { isSingle: true, title: "Traefik File System", diff --git a/apps/dokploy/drizzle/0087_lively_risque.sql b/apps/dokploy/drizzle/0087_lively_risque.sql new file mode 100644 index 00000000..92b43cc0 --- /dev/null +++ b/apps/dokploy/drizzle/0087_lively_risque.sql @@ -0,0 +1,3 @@ +CREATE TYPE "public"."triggerType" AS ENUM('push', 'tag');--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "triggerType" "triggerType" DEFAULT 'push';--> statement-breakpoint +ALTER TABLE "compose" ADD COLUMN "triggerType" "triggerType" DEFAULT 'push'; \ No newline at end of file diff --git a/apps/dokploy/drizzle/0088_illegal_ma_gnuci.sql b/apps/dokploy/drizzle/0088_illegal_ma_gnuci.sql new file mode 100644 index 00000000..a1657072 --- /dev/null +++ b/apps/dokploy/drizzle/0088_illegal_ma_gnuci.sql @@ -0,0 +1,28 @@ +CREATE TYPE "public"."scheduleType" AS ENUM('application', 'compose', 'server', 'dokploy-server');--> statement-breakpoint +CREATE TYPE "public"."shellType" AS ENUM('bash', 'sh');--> statement-breakpoint +CREATE TABLE "schedule" ( + "scheduleId" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "cronExpression" text NOT NULL, + "appName" text NOT NULL, + "serviceName" text, + "shellType" "shellType" DEFAULT 'bash' NOT NULL, + "scheduleType" "scheduleType" DEFAULT 'application' NOT NULL, + "command" text NOT NULL, + "script" text, + "applicationId" text, + "composeId" text, + "serverId" text, + "userId" text, + "enabled" boolean DEFAULT true NOT NULL, + "createdAt" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "startedAt" text;--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "finishedAt" text;--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "scheduleId" text;--> statement-breakpoint +ALTER TABLE "schedule" ADD CONSTRAINT "schedule_applicationId_application_applicationId_fk" FOREIGN KEY ("applicationId") REFERENCES "public"."application"("applicationId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "schedule" ADD CONSTRAINT "schedule_composeId_compose_composeId_fk" FOREIGN KEY ("composeId") REFERENCES "public"."compose"("composeId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "schedule" ADD CONSTRAINT "schedule_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "schedule" ADD CONSTRAINT "schedule_userId_user_temp_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user_temp"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "deployment" ADD CONSTRAINT "deployment_scheduleId_schedule_scheduleId_fk" FOREIGN KEY ("scheduleId") REFERENCES "public"."schedule"("scheduleId") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/dokploy/drizzle/0089_noisy_sandman.sql b/apps/dokploy/drizzle/0089_noisy_sandman.sql new file mode 100644 index 00000000..ded9fecd --- /dev/null +++ b/apps/dokploy/drizzle/0089_noisy_sandman.sql @@ -0,0 +1,29 @@ +CREATE TYPE "public"."backupType" AS ENUM('database', 'compose');--> statement-breakpoint +ALTER TABLE "backup" ADD COLUMN "appName" text; + +UPDATE "backup" +SET "appName" = 'backup-' || + ( + ARRAY['optimize', 'parse', 'quantify', 'bypass', 'override', 'generate', + 'secure', 'hack', 'backup', 'connect', 'index', 'compress']::text[] + )[floor(random() * 12) + 1] || '-' || + ( + ARRAY['digital', 'virtual', 'mobile', 'neural', 'optical', 'auxiliary', + 'primary', 'backup', 'wireless', 'haptic', 'solid-state']::text[] + )[floor(random() * 11) + 1] || '-' || + ( + ARRAY['driver', 'protocol', 'array', 'matrix', 'system', 'bandwidth', + 'monitor', 'firewall', 'card', 'sensor', 'bus']::text[] + )[floor(random() * 11) + 1] || '-' || + substr(md5(random()::text), 1, 6); + + +ALTER TABLE "backup" ALTER COLUMN "appName" SET NOT NULL; +ALTER TABLE "backup" ADD COLUMN "serviceName" text;--> statement-breakpoint +ALTER TABLE "backup" ADD COLUMN "backupType" "backupType" DEFAULT 'database' NOT NULL;--> statement-breakpoint +ALTER TABLE "backup" ADD COLUMN "composeId" text;--> statement-breakpoint +ALTER TABLE "backup" ADD COLUMN "metadata" jsonb;--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "backupId" text;--> statement-breakpoint +ALTER TABLE "backup" ADD CONSTRAINT "backup_composeId_compose_composeId_fk" FOREIGN KEY ("composeId") REFERENCES "public"."compose"("composeId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "deployment" ADD CONSTRAINT "deployment_backupId_backup_backupId_fk" FOREIGN KEY ("backupId") REFERENCES "public"."backup"("backupId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "backup" ADD CONSTRAINT "backup_appName_unique" UNIQUE("appName"); \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0087_snapshot.json b/apps/dokploy/drizzle/meta/0087_snapshot.json new file mode 100644 index 00000000..9a1e8714 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0087_snapshot.json @@ -0,0 +1,5407 @@ +{ + "id": "7fb3716c-3cc6-4b18-b8d1-762844da26be", + "prevId": "4beeac91-91be-4f09-a2a2-b6efcfe66796", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "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": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "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", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_temp": { + "name": "user_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_temp_email_unique": { + "name": "user_temp_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_temp_id_fk": { + "name": "backup_userId_user_temp_id_fk", + "tableFrom": "backup", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_temp_id_fk": { + "name": "session_temp_user_id_user_temp_id_fk", + "tableFrom": "session_temp", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "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", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "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", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "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 + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "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 + }, + "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": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_temp_id_fk": { + "name": "account_user_id_user_temp_id_fk", + "tableFrom": "account", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_temp_id_fk": { + "name": "apikey_user_id_user_temp_id_fk", + "tableFrom": "apikey", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_temp_id_fk": { + "name": "invitation_inviter_id_user_temp_id_fk", + "tableFrom": "invitation", + "tableTo": "user_temp", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_temp_id_fk": { + "name": "member_user_id_user_temp_id_fk", + "tableFrom": "member", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_temp_id_fk": { + "name": "organization_owner_id_user_temp_id_fk", + "tableFrom": "organization", + "tableTo": "user_temp", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "gotify" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0088_snapshot.json b/apps/dokploy/drizzle/meta/0088_snapshot.json new file mode 100644 index 00000000..abeed6a3 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0088_snapshot.json @@ -0,0 +1,5617 @@ +{ + "id": "c7eae4ce-5acc-439b-962f-bb2ef8922187", + "prevId": "7fb3716c-3cc6-4b18-b8d1-762844da26be", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "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": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "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", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_temp": { + "name": "user_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_temp_email_unique": { + "name": "user_temp_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_temp_id_fk": { + "name": "backup_userId_user_temp_id_fk", + "tableFrom": "backup", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "startedAt": { + "name": "startedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_scheduleId_schedule_scheduleId_fk": { + "name": "deployment_scheduleId_schedule_scheduleId_fk", + "tableFrom": "deployment", + "tableTo": "schedule", + "columnsFrom": [ + "scheduleId" + ], + "columnsTo": [ + "scheduleId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_temp_id_fk": { + "name": "session_temp_user_id_user_temp_id_fk", + "tableFrom": "session_temp", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "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", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "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", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "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 + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "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 + }, + "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": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_temp_id_fk": { + "name": "account_user_id_user_temp_id_fk", + "tableFrom": "account", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_temp_id_fk": { + "name": "apikey_user_id_user_temp_id_fk", + "tableFrom": "apikey", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_temp_id_fk": { + "name": "invitation_inviter_id_user_temp_id_fk", + "tableFrom": "invitation", + "tableTo": "user_temp", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_temp_id_fk": { + "name": "member_user_id_user_temp_id_fk", + "tableFrom": "member", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_temp_id_fk": { + "name": "organization_owner_id_user_temp_id_fk", + "tableFrom": "organization", + "tableTo": "user_temp", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shellType": { + "name": "shellType", + "type": "shellType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "scheduleType": { + "name": "scheduleType", + "type": "scheduleType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "script": { + "name": "script", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_applicationId_application_applicationId_fk": { + "name": "schedule_applicationId_application_applicationId_fk", + "tableFrom": "schedule", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_composeId_compose_composeId_fk": { + "name": "schedule_composeId_compose_composeId_fk", + "tableFrom": "schedule", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_serverId_server_serverId_fk": { + "name": "schedule_serverId_server_serverId_fk", + "tableFrom": "schedule", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_userId_user_temp_id_fk": { + "name": "schedule_userId_user_temp_id_fk", + "tableFrom": "schedule", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "gotify" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0089_snapshot.json b/apps/dokploy/drizzle/meta/0089_snapshot.json new file mode 100644 index 00000000..adf2060c --- /dev/null +++ b/apps/dokploy/drizzle/meta/0089_snapshot.json @@ -0,0 +1,5697 @@ +{ + "id": "3ec09926-2da4-41c9-8eae-8ef6b023035e", + "prevId": "c7eae4ce-5acc-439b-962f-bb2ef8922187", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "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": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "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", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_temp": { + "name": "user_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_temp_email_unique": { + "name": "user_temp_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "backupType": { + "name": "backupType", + "type": "backupType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'database'" + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_composeId_compose_composeId_fk": { + "name": "backup_composeId_compose_composeId_fk", + "tableFrom": "backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_temp_id_fk": { + "name": "backup_userId_user_temp_id_fk", + "tableFrom": "backup", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "backup_appName_unique": { + "name": "backup_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "startedAt": { + "name": "startedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_scheduleId_schedule_scheduleId_fk": { + "name": "deployment_scheduleId_schedule_scheduleId_fk", + "tableFrom": "deployment", + "tableTo": "schedule", + "columnsFrom": [ + "scheduleId" + ], + "columnsTo": [ + "scheduleId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_backupId_backup_backupId_fk": { + "name": "deployment_backupId_backup_backupId_fk", + "tableFrom": "deployment", + "tableTo": "backup", + "columnsFrom": [ + "backupId" + ], + "columnsTo": [ + "backupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_temp_id_fk": { + "name": "session_temp_user_id_user_temp_id_fk", + "tableFrom": "session_temp", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "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", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "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", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "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 + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "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 + }, + "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": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_temp_id_fk": { + "name": "account_user_id_user_temp_id_fk", + "tableFrom": "account", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_temp_id_fk": { + "name": "apikey_user_id_user_temp_id_fk", + "tableFrom": "apikey", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_temp_id_fk": { + "name": "invitation_inviter_id_user_temp_id_fk", + "tableFrom": "invitation", + "tableTo": "user_temp", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_temp_id_fk": { + "name": "member_user_id_user_temp_id_fk", + "tableFrom": "member", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_temp_id_fk": { + "name": "organization_owner_id_user_temp_id_fk", + "tableFrom": "organization", + "tableTo": "user_temp", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shellType": { + "name": "shellType", + "type": "shellType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "scheduleType": { + "name": "scheduleType", + "type": "scheduleType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "script": { + "name": "script", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_applicationId_application_applicationId_fk": { + "name": "schedule_applicationId_application_applicationId_fk", + "tableFrom": "schedule", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_composeId_compose_composeId_fk": { + "name": "schedule_composeId_compose_composeId_fk", + "tableFrom": "schedule", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_serverId_server_serverId_fk": { + "name": "schedule_serverId_server_serverId_fk", + "tableFrom": "schedule", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_userId_user_temp_id_fk": { + "name": "schedule_userId_user_temp_id_fk", + "tableFrom": "schedule", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.backupType": { + "name": "backupType", + "schema": "public", + "values": [ + "database", + "compose" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "gotify" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index fe657a05..717d2c01 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -610,6 +610,27 @@ "when": 1745706676004, "tag": "0086_rainy_gertrude_yorkes", "breakpoints": true + }, + { + "idx": 87, + "version": "7", + "when": 1745723563822, + "tag": "0087_lively_risque", + "breakpoints": true + }, + { + "idx": 88, + "version": "7", + "when": 1746256928101, + "tag": "0088_illegal_ma_gnuci", + "breakpoints": true + }, + { + "idx": 89, + "version": "7", + "when": 1746392564463, + "tag": "0089_noisy_sandman", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/dokploy/lib/slug.ts b/apps/dokploy/lib/slug.ts index a4982a0e..858bd62c 100644 --- a/apps/dokploy/lib/slug.ts +++ b/apps/dokploy/lib/slug.ts @@ -5,7 +5,7 @@ export const slugify = (text: string | undefined) => { return ""; } - const cleanedText = text.trim().replace(/[^a-zA-Z0-9\s]/g, ""); + const cleanedText = text.trim().replace(/[^a-zA-Z0-9\s]/g, "") || "service"; return slug(cleanedText, { lower: true, diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 5f65f484..d66a3b56 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.21.8", + "version": "v0.22.0", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index b32f959e..b6224c15 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -89,6 +89,115 @@ export default async function handler( return; } + // Handle tag creation event + if ( + req.headers["x-github-event"] === "push" && + githubBody?.ref?.startsWith("refs/tags/") + ) { + try { + const tagName = githubBody?.ref.replace("refs/tags/", ""); + const repository = githubBody?.repository?.name; + const owner = githubBody?.repository?.owner?.name; + const deploymentTitle = `Tag created: ${tagName}`; + const deploymentHash = extractHash(req.headers, githubBody); + + // Find applications configured to deploy on tag + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.triggerType, "tag"), + eq(applications.repository, repository), + eq(applications.owner, owner), + eq(applications.githubId, githubResult.githubId), + ), + }); + + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + continue; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + + // Find compose apps configured to deploy on tag + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.triggerType, "tag"), + eq(compose.repository, repository), + eq(compose.owner, owner), + eq(compose.githubId, githubResult.githubId), + ), + }); + + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; + + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + continue; + } + + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + + const totalApps = apps.length + composeApps.length; + + if (totalApps === 0) { + res + .status(200) + .json({ message: "No apps configured to deploy on tag" }); + return; + } + + res.status(200).json({ + message: `Deployed ${totalApps} apps based on tag ${tagName}`, + }); + return; + } catch (error) { + console.error("Error deploying applications on tag:", error); + res + .status(400) + .json({ message: "Error deploying applications on tag", error }); + return; + } + } + if (req.headers["x-github-event"] === "push") { try { const branchName = githubBody?.ref?.replace("refs/heads/", ""); @@ -105,6 +214,7 @@ export default async function handler( where: and( eq(applications.sourceType, "github"), eq(applications.autoDeploy, true), + eq(applications.triggerType, "push"), eq(applications.branch, branchName), eq(applications.repository, repository), eq(applications.owner, owner), @@ -150,6 +260,7 @@ export default async function handler( where: and( eq(compose.sourceType, "github"), eq(compose.autoDeploy, true), + eq(compose.triggerType, "push"), eq(compose.branch, branchName), eq(compose.repository, repository), eq(compose.owner, owner), diff --git a/apps/dokploy/pages/dashboard/project/[projectId].tsx b/apps/dokploy/pages/dashboard/project/[projectId].tsx index 728d83d1..6bc39642 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId].tsx @@ -80,6 +80,7 @@ import { Loader2, PlusIcon, Search, + ServerIcon, Trash2, X, } from "lucide-react"; @@ -968,6 +969,11 @@ const Project = ( }} className="flex flex-col group relative cursor-pointer bg-transparent transition-colors hover:bg-border" > + {service.serverId && ( +
+ +
+ )}
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 91af2cfa..ceb7b408 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -12,6 +12,7 @@ import { ShowEnvironment } from "@/components/dashboard/application/environment/ import { ShowGeneralApplication } from "@/components/dashboard/application/general/show"; import { ShowDockerLogs } from "@/components/dashboard/application/logs/show"; import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments"; +import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; import { UpdateApplication } from "@/components/dashboard/application/update-application"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring"; @@ -232,6 +233,7 @@ const Service = ( Preview Deployments + Schedules Deployments Logs {((data?.serverId && isCloud) || !data?.server) && ( @@ -308,9 +310,22 @@ const Service = ( />
- -
- + +
+ +
+
+ +
+
@@ -320,7 +335,7 @@ const Service = (
- +
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 3bba9eb2..39f399e9 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -1,14 +1,16 @@ import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import"; import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes"; +import { ShowDeployments } from "@/components/dashboard/application/deployments/show-deployments"; +import { ShowDomains } from "@/components/dashboard/application/domains/show-domains"; import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment"; +import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; -import { ShowDeploymentsCompose } from "@/components/dashboard/compose/deployments/show-deployments-compose"; -import { ShowDomainsCompose } from "@/components/dashboard/compose/domains/show-domains"; import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show"; import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show"; import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack"; import { UpdateCompose } from "@/components/dashboard/compose/update-compose"; +import { ShowBackups } from "@/components/dashboard/database/backups/show-backups"; import { ComposeFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-compose-monitoring"; import { ComposePaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring"; import { ProjectLayout } from "@/components/layouts/project-layout"; @@ -217,16 +219,18 @@ const Service = ( className={cn( "lg:grid lg:w-fit max-md:overflow-y-scroll justify-start", isCloud && data?.serverId - ? "lg:grid-cols-7" + ? "lg:grid-cols-9" : data?.serverId - ? "lg:grid-cols-6" - : "lg:grid-cols-7", + ? "lg:grid-cols-8" + : "lg:grid-cols-9", )} > General Environment Domains Deployments + Backups + Schedules Logs {((data?.serverId && isCloud) || !data?.server) && ( Monitoring @@ -245,6 +249,17 @@ const Service = (
+ +
+ +
+
+ + +
+ +
+
@@ -316,15 +331,20 @@ const Service = (
- -
- + +
+
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx index 1f74adbe..09aa309f 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx @@ -271,7 +271,7 @@ const Mariadb = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx index 8a035579..fd4c75e0 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx @@ -272,7 +272,11 @@ const Mongo = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx index 6dbbd6a2..767e4211 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx @@ -252,7 +252,11 @@ const MySql = (
- +
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 2441bcb6..875e8d85 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -251,7 +251,11 @@ const Postgresql = (
- +
diff --git a/apps/dokploy/pages/dashboard/schedules.tsx b/apps/dokploy/pages/dashboard/schedules.tsx new file mode 100644 index 00000000..37c8d298 --- /dev/null +++ b/apps/dokploy/pages/dashboard/schedules.tsx @@ -0,0 +1,54 @@ +import { DashboardLayout } from "@/components/layouts/dashboard-layout"; +import type { ReactElement } from "react"; +import type { GetServerSidePropsContext } from "next"; +import { validateRequest } from "@dokploy/server/lib/auth"; +import { IS_CLOUD } from "@dokploy/server/constants"; +import { api } from "@/utils/api"; +import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; +import { Card } from "@/components/ui/card"; +function SchedulesPage() { + const { data: user } = api.user.get.useQuery(); + return ( +
+ +
+ +
+
+
+ ); +} +export default SchedulesPage; + +SchedulesPage.getLayout = (page: ReactElement) => { + return {page}; +}; + +export async function getServerSideProps( + ctx: GetServerSidePropsContext<{ serviceId: string }>, +) { + if (IS_CLOUD) { + return { + redirect: { + permanent: true, + destination: "/dashboard/projects", + }, + }; + } + const { user } = await validateRequest(ctx.req); + if (!user || user.role !== "owner") { + return { + redirect: { + permanent: true, + destination: "/", + }, + }; + } + + return { + props: {}, + }; +} diff --git a/apps/dokploy/pages/dashboard/settings/server.tsx b/apps/dokploy/pages/dashboard/settings/server.tsx index f3df395f..5e16ef42 100644 --- a/apps/dokploy/pages/dashboard/settings/server.tsx +++ b/apps/dokploy/pages/dashboard/settings/server.tsx @@ -20,7 +20,11 @@ const Page = () => {
- +
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 6ad4b433..95a00fec 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -35,6 +35,7 @@ import { sshRouter } from "./routers/ssh-key"; import { stripeRouter } from "./routers/stripe"; import { swarmRouter } from "./routers/swarm"; import { userRouter } from "./routers/user"; +import { scheduleRouter } from "./routers/schedule"; /** * This is the primary router for your server. * @@ -78,6 +79,7 @@ export const appRouter = createTRPCRouter({ swarm: swarmRouter, ai: aiRouter, organization: organizationRouter, + schedule: scheduleRouter, }); // export type definition of API diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index c64a7705..e023f936 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -163,7 +163,7 @@ export const aiRouter = createTRPCRouter({ deploy: protectedProcedure .input(deploySuggestionSchema) .mutation(async ({ ctx, input }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.session.activeOrganizationId, input.projectId, @@ -216,7 +216,7 @@ export const aiRouter = createTRPCRouter({ } } - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.session.activeOrganizationId, ctx.user.ownerId, diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 5da40650..0899903f 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -62,7 +62,7 @@ export const applicationRouter = createTRPCRouter({ .input(apiCreateApplication) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -87,7 +87,7 @@ export const applicationRouter = createTRPCRouter({ } const newApplication = await createApplication(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newApplication.applicationId, @@ -109,7 +109,7 @@ export const applicationRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneApplication) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.applicationId, @@ -168,7 +168,7 @@ export const applicationRouter = createTRPCRouter({ delete: protectedProcedure .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.applicationId, @@ -355,6 +355,7 @@ export const applicationRouter = createTRPCRouter({ applicationStatus: "idle", githubId: input.githubId, watchPaths: input.watchPaths, + triggerType: input.triggerType, enableSubmodules: input.enableSubmodules, }); diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts index 0aeebb02..af59d6c7 100644 --- a/apps/dokploy/server/api/routers/backup.ts +++ b/apps/dokploy/server/api/routers/backup.ts @@ -3,6 +3,7 @@ import { apiCreateBackup, apiFindOneBackup, apiRemoveBackup, + apiRestoreBackup, apiUpdateBackup, } from "@/server/db/schema"; import { removeJob, schedule, updateJob } from "@/server/utils/backup"; @@ -10,6 +11,8 @@ import { IS_CLOUD, createBackup, findBackupById, + findComposeByBackupId, + findComposeById, findMariadbByBackupId, findMariadbById, findMongoByBackupId, @@ -31,6 +34,7 @@ import { } from "@dokploy/server"; import { findDestinationById } from "@dokploy/server/services/destination"; +import { runComposeBackup } from "@dokploy/server/utils/backups/compose"; import { getS3Credentials, normalizeS3Path, @@ -40,6 +44,7 @@ import { execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; import { + restoreComposeBackup, restoreMariadbBackup, restoreMongoBackup, restoreMySqlBackup, @@ -82,6 +87,11 @@ export const backupRouter = createTRPCRouter({ serverId = backup.mongo.serverId; } else if (databaseType === "mariadb" && backup.mariadb?.serverId) { serverId = backup.mariadb.serverId; + } else if ( + backup.backupType === "compose" && + backup.compose?.serverId + ) { + serverId = backup.compose.serverId; } const server = await findServerById(serverId); @@ -232,6 +242,22 @@ export const backupRouter = createTRPCRouter({ }); } }), + manualBackupCompose: protectedProcedure + .input(apiFindOneBackup) + .mutation(async ({ input }) => { + try { + const backup = await findBackupById(input.backupId); + const compose = await findComposeByBackupId(backup.backupId); + await runComposeBackup(compose, backup); + return true; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error running manual Compose backup ", + cause: error, + }); + } + }), manualBackupMongo: protectedProcedure .input(apiFindOneBackup) .mutation(async ({ input }) => { @@ -341,88 +367,59 @@ export const backupRouter = createTRPCRouter({ override: true, }, }) - .input( - z.object({ - databaseId: z.string(), - databaseType: z.enum([ - "postgres", - "mysql", - "mariadb", - "mongo", - "web-server", - ]), - databaseName: z.string().min(1), - backupFile: z.string().min(1), - destinationId: z.string().min(1), - }), - ) + .input(apiRestoreBackup) .subscription(async ({ input }) => { const destination = await findDestinationById(input.destinationId); - if (input.databaseType === "postgres") { - const postgres = await findPostgresById(input.databaseId); + if (input.backupType === "database") { + if (input.databaseType === "postgres") { + const postgres = await findPostgresById(input.databaseId); - return observable((emit) => { - restorePostgresBackup( - postgres, - destination, - input.databaseName, - input.backupFile, - (log) => { + return observable((emit) => { + restorePostgresBackup(postgres, destination, input, (log) => { emit.next(log); - }, - ); - }); - } - if (input.databaseType === "mysql") { - const mysql = await findMySqlById(input.databaseId); - return observable((emit) => { - restoreMySqlBackup( - mysql, - destination, - input.databaseName, - input.backupFile, - (log) => { + }); + }); + } + if (input.databaseType === "mysql") { + const mysql = await findMySqlById(input.databaseId); + return observable((emit) => { + restoreMySqlBackup(mysql, destination, input, (log) => { emit.next(log); - }, - ); - }); - } - if (input.databaseType === "mariadb") { - const mariadb = await findMariadbById(input.databaseId); - return observable((emit) => { - restoreMariadbBackup( - mariadb, - destination, - input.databaseName, - input.backupFile, - (log) => { + }); + }); + } + if (input.databaseType === "mariadb") { + const mariadb = await findMariadbById(input.databaseId); + return observable((emit) => { + restoreMariadbBackup(mariadb, destination, input, (log) => { emit.next(log); - }, - ); - }); - } - if (input.databaseType === "mongo") { - const mongo = await findMongoById(input.databaseId); - return observable((emit) => { - restoreMongoBackup( - mongo, - destination, - input.databaseName, - input.backupFile, - (log) => { + }); + }); + } + if (input.databaseType === "mongo") { + const mongo = await findMongoById(input.databaseId); + return observable((emit) => { + restoreMongoBackup(mongo, destination, input, (log) => { emit.next(log); - }, - ); - }); + }); + }); + } + if (input.databaseType === "web-server") { + return observable((emit) => { + restoreWebServerBackup(destination, input.backupFile, (log) => { + emit.next(log); + }); + }); + } } - if (input.databaseType === "web-server") { + if (input.backupType === "compose") { + const compose = await findComposeById(input.databaseId); return observable((emit) => { - restoreWebServerBackup(destination, input.backupFile, (log) => { + restoreComposeBackup(compose, destination, input, (log) => { emit.next(log); }); }); } - return true; }), }); diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index c4f9b317..48c2bfa7 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -62,7 +62,7 @@ export const composeRouter = createTRPCRouter({ .input(apiCreateCompose) .mutation(async ({ ctx, input }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -86,7 +86,7 @@ export const composeRouter = createTRPCRouter({ } const newService = await createCompose(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newService.composeId, @@ -103,7 +103,7 @@ export const composeRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindCompose) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.composeId, @@ -137,7 +137,7 @@ export const composeRouter = createTRPCRouter({ delete: protectedProcedure .input(apiDeleteCompose) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.composeId, @@ -408,7 +408,7 @@ export const composeRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -455,7 +455,7 @@ export const composeRouter = createTRPCRouter({ isolatedDeployment: true, }); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, compose.composeId, diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts index 8d95c121..238b3317 100644 --- a/apps/dokploy/server/api/routers/deployment.ts +++ b/apps/dokploy/server/api/routers/deployment.ts @@ -2,6 +2,8 @@ import { apiFindAllByApplication, apiFindAllByCompose, apiFindAllByServer, + apiFindAllByType, + deployments, } from "@/server/db/schema"; import { findAllDeploymentsByApplicationId, @@ -12,7 +14,9 @@ import { findServerById, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; +import { desc, eq } from "drizzle-orm"; import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { db } from "@/server/db"; export const deploymentRouter = createTRPCRouter({ all: protectedProcedure @@ -54,4 +58,14 @@ export const deploymentRouter = createTRPCRouter({ } return await findAllDeploymentsByServerId(input.serverId); }), + + allByType: protectedProcedure + .input(apiFindAllByType) + .query(async ({ input }) => { + const deploymentsList = await db.query.deployments.findMany({ + where: eq(deployments[`${input.type}Id`], input.id), + orderBy: desc(deployments.createdAt), + }); + return deploymentsList; + }), }); diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts index 9e81bee1..a585a2fc 100644 --- a/apps/dokploy/server/api/routers/domain.ts +++ b/apps/dokploy/server/api/routers/domain.ts @@ -21,6 +21,7 @@ import { removeDomain, removeDomainById, updateDomainById, + validateDomain, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; @@ -56,7 +57,10 @@ export const domainRouter = createTRPCRouter({ } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", - message: "Error creating the domain", + message: + error instanceof Error + ? error.message + : "Error creating the domain", cause: error, }); } @@ -224,4 +228,15 @@ export const domainRouter = createTRPCRouter({ return result; }), + + validateDomain: protectedProcedure + .input( + z.object({ + domain: z.string(), + serverIp: z.string().optional(), + }), + ) + .mutation(async ({ input }) => { + return validateDomain(input.domain, input.serverIp); + }), }); diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 241274f1..dd3b1bf7 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -41,7 +41,7 @@ export const mariadbRouter = createTRPCRouter({ .input(apiCreateMariaDB) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -65,7 +65,7 @@ export const mariadbRouter = createTRPCRouter({ }); } const newMariadb = await createMariadb(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newMariadb.mariadbId, @@ -92,7 +92,7 @@ export const mariadbRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneMariaDB) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mariadbId, @@ -219,7 +219,7 @@ export const mariadbRouter = createTRPCRouter({ remove: protectedProcedure .input(apiFindOneMariaDB) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mariadbId, diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index eb51522a..4d815c0a 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -41,7 +41,7 @@ export const mongoRouter = createTRPCRouter({ .input(apiCreateMongo) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -65,7 +65,7 @@ export const mongoRouter = createTRPCRouter({ }); } const newMongo = await createMongo(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newMongo.mongoId, @@ -96,7 +96,7 @@ export const mongoRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneMongo) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mongoId, @@ -261,7 +261,7 @@ export const mongoRouter = createTRPCRouter({ remove: protectedProcedure .input(apiFindOneMongo) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mongoId, diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 91087601..ee1b80dc 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -44,7 +44,7 @@ export const mysqlRouter = createTRPCRouter({ .input(apiCreateMySql) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -69,7 +69,7 @@ export const mysqlRouter = createTRPCRouter({ } const newMysql = await createMysql(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newMysql.mysqlId, @@ -100,7 +100,7 @@ export const mysqlRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneMySql) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mysqlId, @@ -260,7 +260,7 @@ export const mysqlRouter = createTRPCRouter({ remove: protectedProcedure .input(apiFindOneMySql) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.mysqlId, diff --git a/apps/dokploy/server/api/routers/organization.ts b/apps/dokploy/server/api/routers/organization.ts index 3d7753de..55981e59 100644 --- a/apps/dokploy/server/api/routers/organization.ts +++ b/apps/dokploy/server/api/routers/organization.ts @@ -15,7 +15,7 @@ export const organizationRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - if (ctx.user.rol !== "owner" && !IS_CLOUD) { + if (ctx.user.role !== "owner" && !IS_CLOUD) { throw new TRPCError({ code: "FORBIDDEN", message: "Only the organization owner can create an organization", @@ -86,7 +86,7 @@ export const organizationRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - if (ctx.user.rol !== "owner" && !IS_CLOUD) { + if (ctx.user.role !== "owner" && !IS_CLOUD) { throw new TRPCError({ code: "FORBIDDEN", message: "Only the organization owner can update it", @@ -109,7 +109,7 @@ export const organizationRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - if (ctx.user.rol !== "owner" && !IS_CLOUD) { + if (ctx.user.role !== "owner" && !IS_CLOUD) { throw new TRPCError({ code: "FORBIDDEN", message: "Only the organization owner can delete it", @@ -157,4 +157,31 @@ export const organizationRouter = createTRPCRouter({ orderBy: [desc(invitation.status), desc(invitation.expiresAt)], }); }), + removeInvitation: adminProcedure + .input(z.object({ invitationId: z.string() })) + .mutation(async ({ ctx, input }) => { + const invitationResult = await db.query.invitation.findFirst({ + where: eq(invitation.id, input.invitationId), + }); + + if (!invitationResult) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Invitation not found", + }); + } + + if ( + invitationResult?.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not allowed to remove this invitation", + }); + } + + return await db + .delete(invitation) + .where(eq(invitation.id, input.invitationId)); + }), }); diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 28d5558a..9eef11f8 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -41,7 +41,7 @@ export const postgresRouter = createTRPCRouter({ .input(apiCreatePostgres) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -65,7 +65,7 @@ export const postgresRouter = createTRPCRouter({ }); } const newPostgres = await createPostgres(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newPostgres.postgresId, @@ -96,7 +96,7 @@ export const postgresRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOnePostgres) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.postgresId, @@ -244,7 +244,7 @@ export const postgresRouter = createTRPCRouter({ remove: protectedProcedure .input(apiFindOnePostgres) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.postgresId, diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index d686479b..05875450 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -57,7 +57,7 @@ export const projectRouter = createTRPCRouter({ .input(apiCreateProject) .mutation(async ({ ctx, input }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkProjectAccess( ctx.user.id, "create", @@ -78,7 +78,7 @@ export const projectRouter = createTRPCRouter({ input, ctx.session.activeOrganizationId, ); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewProject( ctx.user.id, project.projectId, @@ -99,7 +99,7 @@ export const projectRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneProject) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { const { accessedServices } = await findMemberById( ctx.user.id, ctx.session.activeOrganizationId, @@ -118,15 +118,15 @@ export const projectRouter = createTRPCRouter({ eq(projects.organizationId, ctx.session.activeOrganizationId), ), with: { - compose: { - where: buildServiceFilter(compose.composeId, accessedServices), - }, applications: { where: buildServiceFilter( applications.applicationId, accessedServices, ), }, + compose: { + where: buildServiceFilter(compose.composeId, accessedServices), + }, mariadb: { where: buildServiceFilter(mariadb.mariadbId, accessedServices), }, @@ -164,8 +164,7 @@ export const projectRouter = createTRPCRouter({ return project; }), all: protectedProcedure.query(async ({ ctx }) => { - // console.log(ctx.user); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { const { accessedProjects, accessedServices } = await findMemberById( ctx.user.id, ctx.session.activeOrganizationId, @@ -175,7 +174,7 @@ export const projectRouter = createTRPCRouter({ return []; } - const query = await db.query.projects.findMany({ + return await db.query.projects.findMany({ where: and( sql`${projects.projectId} IN (${sql.join( accessedProjects.map((projectId) => sql`${projectId}`), @@ -213,8 +212,6 @@ export const projectRouter = createTRPCRouter({ }, orderBy: desc(projects.createdAt), }); - - return query; } return await db.query.projects.findMany({ @@ -244,7 +241,7 @@ export const projectRouter = createTRPCRouter({ .input(apiRemoveProject) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkProjectAccess( ctx.user.id, "delete", @@ -316,7 +313,7 @@ export const projectRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkProjectAccess( ctx.user.id, "create", @@ -581,7 +578,7 @@ export const projectRouter = createTRPCRouter({ } } - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewProject( ctx.user.id, newProject.projectId, @@ -604,10 +601,10 @@ function buildServiceFilter( fieldName: AnyPgColumn, accessedServices: string[], ) { - return accessedServices.length > 0 - ? sql`${fieldName} IN (${sql.join( + return accessedServices.length === 0 + ? sql`false` + : sql`${fieldName} IN (${sql.join( accessedServices.map((serviceId) => sql`${serviceId}`), sql`, `, - )})` - : sql`1 = 0`; + )})`; } diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index 1de13fcb..0aafac8e 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -41,7 +41,7 @@ export const redisRouter = createTRPCRouter({ .input(apiCreateRedis) .mutation(async ({ input, ctx }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.projectId, @@ -65,7 +65,7 @@ export const redisRouter = createTRPCRouter({ }); } const newRedis = await createRedis(input); - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await addNewService( ctx.user.id, newRedis.redisId, @@ -89,7 +89,7 @@ export const redisRouter = createTRPCRouter({ one: protectedProcedure .input(apiFindOneRedis) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.redisId, @@ -251,7 +251,7 @@ export const redisRouter = createTRPCRouter({ remove: protectedProcedure .input(apiFindOneRedis) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { await checkServiceAccess( ctx.user.id, input.redisId, diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts new file mode 100644 index 00000000..2b204adb --- /dev/null +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -0,0 +1,142 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { + createScheduleSchema, + schedules, + updateScheduleSchema, +} from "@dokploy/server/db/schema/schedule"; +import { desc, eq } from "drizzle-orm"; +import { db } from "@dokploy/server/db"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { runCommand } from "@dokploy/server/index"; +import { deployments } from "@dokploy/server/db/schema/deployment"; +import { + deleteSchedule, + findScheduleById, + createSchedule, + updateSchedule, +} from "@dokploy/server/services/schedule"; +import { IS_CLOUD, scheduleJob } from "@dokploy/server"; +import { removeJob, schedule } from "@/server/utils/backup"; +import { removeScheduleJob } from "@dokploy/server"; +export const scheduleRouter = createTRPCRouter({ + create: protectedProcedure + .input(createScheduleSchema) + .mutation(async ({ input }) => { + const newSchedule = await createSchedule(input); + + if (newSchedule?.enabled) { + if (IS_CLOUD) { + schedule({ + scheduleId: newSchedule.scheduleId, + type: "schedule", + cronSchedule: newSchedule.cronExpression, + }); + } else { + scheduleJob(newSchedule); + } + } + return newSchedule; + }), + + update: protectedProcedure + .input(updateScheduleSchema) + .mutation(async ({ input }) => { + const updatedSchedule = await updateSchedule(input); + + if (IS_CLOUD) { + if (updatedSchedule?.enabled) { + schedule({ + scheduleId: updatedSchedule.scheduleId, + type: "schedule", + cronSchedule: updatedSchedule.cronExpression, + }); + } else { + await removeJob({ + cronSchedule: updatedSchedule.cronExpression, + scheduleId: updatedSchedule.scheduleId, + type: "schedule", + }); + } + } else { + if (updatedSchedule?.enabled) { + removeScheduleJob(updatedSchedule.scheduleId); + scheduleJob(updatedSchedule); + } else { + removeScheduleJob(updatedSchedule.scheduleId); + } + } + return updatedSchedule; + }), + + delete: protectedProcedure + .input(z.object({ scheduleId: z.string() })) + .mutation(async ({ input }) => { + const schedule = await findScheduleById(input.scheduleId); + await deleteSchedule(input.scheduleId); + + if (IS_CLOUD) { + await removeJob({ + cronSchedule: schedule.cronExpression, + scheduleId: schedule.scheduleId, + type: "schedule", + }); + } else { + removeScheduleJob(schedule.scheduleId); + } + return true; + }), + + list: protectedProcedure + .input( + z.object({ + id: z.string(), + scheduleType: z.enum([ + "application", + "compose", + "server", + "dokploy-server", + ]), + }), + ) + .query(async ({ input }) => { + const where = { + application: eq(schedules.applicationId, input.id), + compose: eq(schedules.composeId, input.id), + server: eq(schedules.serverId, input.id), + "dokploy-server": eq(schedules.userId, input.id), + }; + return db.query.schedules.findMany({ + where: where[input.scheduleType], + with: { + application: true, + server: true, + compose: true, + deployments: { + orderBy: [desc(deployments.createdAt)], + }, + }, + }); + }), + + one: protectedProcedure + .input(z.object({ scheduleId: z.string() })) + .query(async ({ input }) => { + return await findScheduleById(input.scheduleId); + }), + + runManually: protectedProcedure + .input(z.object({ scheduleId: z.string().min(1) })) + .mutation(async ({ input }) => { + try { + await runCommand(input.scheduleId); + return true; + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error ? error.message : "Error running schedule", + }); + } + }), +}); diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 5ca4c106..a4e667f1 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -407,7 +407,7 @@ export const settingsRouter = createTRPCRouter({ .input(apiServerSchema) .query(async ({ ctx, input }) => { try { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { const canAccess = await canAccessToTraefikFiles( ctx.user.id, ctx.session.activeOrganizationId, @@ -428,7 +428,7 @@ export const settingsRouter = createTRPCRouter({ updateTraefikFile: protectedProcedure .input(apiModifyTraefikConfig) .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { const canAccess = await canAccessToTraefikFiles( ctx.user.id, ctx.session.activeOrganizationId, @@ -449,7 +449,7 @@ export const settingsRouter = createTRPCRouter({ readTraefikFile: protectedProcedure .input(apiReadTraefikConfig) .query(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { + if (ctx.user.role === "member") { const canAccess = await canAccessToTraefikFiles( ctx.user.id, ctx.session.activeOrganizationId, diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 41af4c9a..92507ea0 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -103,6 +103,7 @@ export const userRouter = createTRPCRouter({ backups: { with: { destination: true, + deployments: true, }, }, apiKeys: true, diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index 4c88eb22..1b22ca34 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -30,7 +30,7 @@ import { ZodError } from "zod"; */ interface CreateContextOptions { - user: (User & { rol: "member" | "admin" | "owner"; ownerId: string }) | null; + user: (User & { role: "member" | "admin" | "owner"; ownerId: string }) | null; session: (Session & { activeOrganizationId: string }) | null; req: CreateNextContextOptions["req"]; res: CreateNextContextOptions["res"]; @@ -83,7 +83,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { ? { ...user, email: user.email, - rol: user.role as "owner" | "member" | "admin", + role: user.role as "owner" | "member" | "admin", id: user.id, ownerId: user.ownerId, } @@ -180,7 +180,7 @@ export const uploadProcedure = async (opts: any) => { }; export const cliProcedure = t.procedure.use(({ ctx, next }) => { - if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") { + if (!ctx.session || !ctx.user || ctx.user.role !== "owner") { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ @@ -194,7 +194,7 @@ export const cliProcedure = t.procedure.use(({ ctx, next }) => { }); export const adminProcedure = t.procedure.use(({ ctx, next }) => { - if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") { + if (!ctx.session || !ctx.user || ctx.user.role !== "owner") { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ diff --git a/apps/dokploy/server/server.ts b/apps/dokploy/server/server.ts index fd908245..148edda0 100644 --- a/apps/dokploy/server/server.ts +++ b/apps/dokploy/server/server.ts @@ -6,6 +6,7 @@ import { createDefaultServerTraefikConfig, createDefaultTraefikConfig, initCronJobs, + initSchedules, initializeNetwork, sendDokployRestartNotifications, setupDirectories, @@ -49,6 +50,7 @@ void app.prepare().then(async () => { createDefaultServerTraefikConfig(); await migration(); await initCronJobs(); + await initSchedules(); await sendDokployRestartNotifications(); } diff --git a/apps/dokploy/server/utils/backup.ts b/apps/dokploy/server/utils/backup.ts index 4fc9db93..cf0b6c22 100644 --- a/apps/dokploy/server/utils/backup.ts +++ b/apps/dokploy/server/utils/backup.ts @@ -14,6 +14,11 @@ type QueueJob = type: "server"; cronSchedule: string; serverId: string; + } + | { + type: "schedule"; + cronSchedule: string; + scheduleId: string; }; export const schedule = async (job: QueueJob) => { try { diff --git a/apps/schedules/src/index.ts b/apps/schedules/src/index.ts index 0ef8e930..7ab2b98c 100644 --- a/apps/schedules/src/index.ts +++ b/apps/schedules/src/index.ts @@ -34,8 +34,8 @@ app.use(async (c, next) => { app.post("/create-backup", zValidator("json", jobQueueSchema), async (c) => { const data = c.req.valid("json"); scheduleJob(data); - logger.info({ data }, "Backup created successfully"); - return c.json({ message: "Backup created successfully" }); + logger.info({ data }, `[${data.type}] created successfully`); + return c.json({ message: `[${data.type}] created successfully` }); }); app.post("/update-backup", zValidator("json", jobQueueSchema), async (c) => { @@ -55,6 +55,12 @@ app.post("/update-backup", zValidator("json", jobQueueSchema), async (c) => { type: "server", cronSchedule: job.pattern, }); + } else if (data.type === "schedule") { + result = await removeJob({ + scheduleId: data.scheduleId, + type: "schedule", + cronSchedule: job.pattern, + }); } logger.info({ result }, "Job removed"); } diff --git a/apps/schedules/src/queue.ts b/apps/schedules/src/queue.ts index e751fa6d..5a1efc05 100644 --- a/apps/schedules/src/queue.ts +++ b/apps/schedules/src/queue.ts @@ -36,6 +36,12 @@ export const scheduleJob = (job: QueueJob) => { pattern: job.cronSchedule, }, }); + } else if (job.type === "schedule") { + jobQueue.add(job.scheduleId, job, { + repeat: { + pattern: job.cronSchedule, + }, + }); } }; @@ -54,7 +60,13 @@ export const removeJob = async (data: QueueJob) => { }); return result; } - + if (data.type === "schedule") { + const { scheduleId, cronSchedule } = data; + const result = await jobQueue.removeRepeatable(scheduleId, { + pattern: cronSchedule, + }); + return result; + } return false; }; @@ -72,6 +84,11 @@ export const getJobRepeatable = async ( const job = repeatableJobs.find((j) => j.name === `${serverId}-cleanup`); return job ? job : null; } + if (data.type === "schedule") { + const { scheduleId } = data; + const job = repeatableJobs.find((j) => j.name === scheduleId); + return job ? job : null; + } return null; }; diff --git a/apps/schedules/src/schema.ts b/apps/schedules/src/schema.ts index feadb5a9..32b2536b 100644 --- a/apps/schedules/src/schema.ts +++ b/apps/schedules/src/schema.ts @@ -11,6 +11,11 @@ export const jobQueueSchema = z.discriminatedUnion("type", [ type: z.literal("server"), serverId: z.string(), }), + z.object({ + cronSchedule: z.string(), + type: z.literal("schedule"), + scheduleId: z.string(), + }), ]); export type QueueJob = z.infer; diff --git a/apps/schedules/src/utils.ts b/apps/schedules/src/utils.ts index b0d9b877..e3bd5a48 100644 --- a/apps/schedules/src/utils.ts +++ b/apps/schedules/src/utils.ts @@ -3,16 +3,19 @@ import { cleanUpSystemPrune, cleanUpUnusedImages, findBackupById, + findScheduleById, findServerById, keepLatestNBackups, + runCommand, runMariadbBackup, runMongoBackup, runMySqlBackup, runPostgresBackup, + runComposeBackup, } from "@dokploy/server"; import { db } from "@dokploy/server/dist/db"; -import { backups, server } from "@dokploy/server/dist/db/schema"; -import { eq } from "drizzle-orm"; +import { backups, schedules, server } from "@dokploy/server/dist/db/schema"; +import { and, eq } from "drizzle-orm"; import { logger } from "./logger.js"; import { scheduleJob } from "./queue.js"; import type { QueueJob } from "./schema.js"; @@ -22,43 +25,59 @@ export const runJobs = async (job: QueueJob) => { if (job.type === "backup") { const { backupId } = job; const backup = await findBackupById(backupId); - const { databaseType, postgres, mysql, mongo, mariadb } = backup; + const { + databaseType, + postgres, + mysql, + mongo, + mariadb, + compose, + backupType, + } = backup; - if (databaseType === "postgres" && postgres) { - const server = await findServerById(postgres.serverId as string); + if (backupType === "database") { + if (databaseType === "postgres" && postgres) { + const server = await findServerById(postgres.serverId as string); + if (server.serverStatus === "inactive") { + logger.info("Server is inactive"); + return; + } + await runPostgresBackup(postgres, backup); + await keepLatestNBackups(backup, server.serverId); + } else if (databaseType === "mysql" && mysql) { + const server = await findServerById(mysql.serverId as string); + if (server.serverStatus === "inactive") { + logger.info("Server is inactive"); + return; + } + await runMySqlBackup(mysql, backup); + await keepLatestNBackups(backup, server.serverId); + } else if (databaseType === "mongo" && mongo) { + const server = await findServerById(mongo.serverId as string); + if (server.serverStatus === "inactive") { + logger.info("Server is inactive"); + return; + } + await runMongoBackup(mongo, backup); + await keepLatestNBackups(backup, server.serverId); + } else if (databaseType === "mariadb" && mariadb) { + const server = await findServerById(mariadb.serverId as string); + if (server.serverStatus === "inactive") { + logger.info("Server is inactive"); + return; + } + await runMariadbBackup(mariadb, backup); + await keepLatestNBackups(backup, server.serverId); + } + } else if (backupType === "compose" && compose) { + const server = await findServerById(compose.serverId as string); if (server.serverStatus === "inactive") { logger.info("Server is inactive"); return; } - await runPostgresBackup(postgres, backup); - await keepLatestNBackups(backup, server.serverId); - } else if (databaseType === "mysql" && mysql) { - const server = await findServerById(mysql.serverId as string); - if (server.serverStatus === "inactive") { - logger.info("Server is inactive"); - return; - } - await runMySqlBackup(mysql, backup); - await keepLatestNBackups(backup, server.serverId); - } else if (databaseType === "mongo" && mongo) { - const server = await findServerById(mongo.serverId as string); - if (server.serverStatus === "inactive") { - logger.info("Server is inactive"); - return; - } - await runMongoBackup(mongo, backup); - await keepLatestNBackups(backup, server.serverId); - } else if (databaseType === "mariadb" && mariadb) { - const server = await findServerById(mariadb.serverId as string); - if (server.serverStatus === "inactive") { - logger.info("Server is inactive"); - return; - } - await runMariadbBackup(mariadb, backup); - await keepLatestNBackups(backup, server.serverId); + await runComposeBackup(compose, backup); } - } - if (job.type === "server") { + } else if (job.type === "server") { const { serverId } = job; const server = await findServerById(serverId); if (server.serverStatus === "inactive") { @@ -68,6 +87,12 @@ export const runJobs = async (job: QueueJob) => { await cleanUpUnusedImages(serverId); await cleanUpDockerBuilder(serverId); await cleanUpSystemPrune(serverId); + } else if (job.type === "schedule") { + const { scheduleId } = job; + const schedule = await findScheduleById(scheduleId); + if (schedule.enabled) { + await runCommand(schedule.scheduleId); + } } } catch (error) { logger.error(error); @@ -80,7 +105,10 @@ export const initializeJobs = async () => { logger.info("Setting up Jobs...."); const servers = await db.query.server.findMany({ - where: eq(server.enableDockerCleanup, true), + where: and( + eq(server.enableDockerCleanup, true), + eq(server.serverStatus, "active"), + ), }); for (const server of servers) { @@ -92,7 +120,7 @@ export const initializeJobs = async () => { }); } - logger.info({ Quantity: servers.length }, "Servers Initialized"); + logger.info({ Quantity: servers.length }, "Active Servers Initialized"); const backupsResult = await db.query.backups.findMany({ where: eq(backups.enabled, true), @@ -101,6 +129,7 @@ export const initializeJobs = async () => { mysql: true, postgres: true, mongo: true, + compose: true, }, }); @@ -112,4 +141,47 @@ export const initializeJobs = async () => { }); } logger.info({ Quantity: backupsResult.length }, "Backups Initialized"); + + const schedulesResult = await db.query.schedules.findMany({ + where: eq(schedules.enabled, true), + with: { + application: { + with: { + server: true, + }, + }, + compose: { + with: { + server: true, + }, + }, + server: true, + }, + }); + + const filteredSchedulesBasedOnServerStatus = schedulesResult.filter( + (schedule) => { + if (schedule.server) { + return schedule.server.serverStatus === "active"; + } + if (schedule.application) { + return schedule.application.server?.serverStatus === "active"; + } + if (schedule.compose) { + return schedule.compose.server?.serverStatus === "active"; + } + }, + ); + + for (const schedule of filteredSchedulesBasedOnServerStatus) { + scheduleJob({ + scheduleId: schedule.scheduleId, + type: "schedule", + cronSchedule: schedule.cronExpression, + }); + } + logger.info( + { Quantity: filteredSchedulesBasedOnServerStatus.length }, + "Schedules Initialized", + ); }; diff --git a/apps/schedules/src/workers.ts b/apps/schedules/src/workers.ts index b63f995a..1a6d2470 100644 --- a/apps/schedules/src/workers.ts +++ b/apps/schedules/src/workers.ts @@ -7,7 +7,7 @@ import { runJobs } from "./utils.js"; export const firstWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Job received"); + logger.info({ data: job.data }, "Running job"); await runJobs(job.data); }, { @@ -18,7 +18,7 @@ export const firstWorker = new Worker( export const secondWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Job received"); + logger.info({ data: job.data }, "Running job"); await runJobs(job.data); }, { diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index b6dfd217..461d40f5 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -23,5 +23,6 @@ export const paths = (isServer = false) => { CERTIFICATES_PATH: `${DYNAMIC_TRAEFIK_PATH}/certificates`, MONITORING_PATH: `${BASE_PATH}/monitoring`, REGISTRY_PATH: `${BASE_PATH}/registry`, + SCHEDULES_PATH: `${BASE_PATH}/schedules`, }; }; diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 636bc534..2741b413 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -24,7 +24,7 @@ import { redirects } from "./redirects"; import { registry } from "./registry"; import { security } from "./security"; import { server } from "./server"; -import { applicationStatus, certificateType } from "./shared"; +import { applicationStatus, certificateType, triggerType } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; @@ -149,6 +149,7 @@ export const applications = pgTable("application", { owner: text("owner"), branch: text("branch"), buildPath: text("buildPath").default("/"), + triggerType: triggerType("triggerType").default("push"), autoDeploy: boolean("autoDeploy").$defaultFn(() => true), // Gitlab gitlabProjectId: integer("gitlabProjectId"), @@ -473,7 +474,10 @@ export const apiSaveGithubProvider = createSchema watchPaths: true, enableSubmodules: true, }) - .required(); + .required() + .extend({ + triggerType: z.enum(["push", "tag"]).default("push"), + }); export const apiSaveGitlabProvider = createSchema .pick({ diff --git a/packages/server/src/db/schema/backups.ts b/packages/server/src/db/schema/backups.ts index 951652d4..328ed512 100644 --- a/packages/server/src/db/schema/backups.ts +++ b/packages/server/src/db/schema/backups.ts @@ -3,6 +3,7 @@ import { type AnyPgColumn, boolean, integer, + jsonb, pgEnum, pgTable, text, @@ -16,6 +17,9 @@ import { mongo } from "./mongo"; import { mysql } from "./mysql"; import { postgres } from "./postgres"; import { users_temp } from "./user"; +import { compose } from "./compose"; +import { deployments } from "./deployment"; +import { generateAppName } from "."; export const databaseType = pgEnum("databaseType", [ "postgres", "mariadb", @@ -24,23 +28,34 @@ export const databaseType = pgEnum("databaseType", [ "web-server", ]); +export const backupType = pgEnum("backupType", ["database", "compose"]); + export const backups = pgTable("backup", { backupId: text("backupId") .notNull() .primaryKey() .$defaultFn(() => nanoid()), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("backup")) + .unique(), schedule: text("schedule").notNull(), enabled: boolean("enabled"), database: text("database").notNull(), prefix: text("prefix").notNull(), - + serviceName: text("serviceName"), destinationId: text("destinationId") .notNull() .references(() => destinations.destinationId, { onDelete: "cascade" }), - keepLatestCount: integer("keepLatestCount"), - + backupType: backupType("backupType").notNull().default("database"), databaseType: databaseType("databaseType").notNull(), + composeId: text("composeId").references( + (): AnyPgColumn => compose.composeId, + { + onDelete: "cascade", + }, + ), postgresId: text("postgresId").references( (): AnyPgColumn => postgres.postgresId, { @@ -60,9 +75,29 @@ export const backups = pgTable("backup", { onDelete: "cascade", }), userId: text("userId").references(() => users_temp.id), + // Only for compose backups + metadata: jsonb("metadata").$type< + | { + postgres?: { + databaseUser: string; + }; + mariadb?: { + databaseUser: string; + databasePassword: string; + }; + mongo?: { + databaseUser: string; + databasePassword: string; + }; + mysql?: { + databaseRootPassword: string; + }; + } + | undefined + >(), }); -export const backupsRelations = relations(backups, ({ one }) => ({ +export const backupsRelations = relations(backups, ({ one, many }) => ({ destination: one(destinations, { fields: [backups.destinationId], references: [destinations.destinationId], @@ -87,6 +122,11 @@ export const backupsRelations = relations(backups, ({ one }) => ({ fields: [backups.userId], references: [users_temp.id], }), + compose: one(compose, { + fields: [backups.composeId], + references: [compose.composeId], + }), + deployments: many(deployments), })); const createSchema = createInsertSchema(backups, { @@ -103,6 +143,7 @@ const createSchema = createInsertSchema(backups, { mysqlId: z.string().optional(), mongoId: z.string().optional(), userId: z.string().optional(), + metadata: z.any().optional(), }); export const apiCreateBackup = createSchema.pick({ @@ -118,6 +159,10 @@ export const apiCreateBackup = createSchema.pick({ mongoId: true, databaseType: true, userId: true, + backupType: true, + composeId: true, + serviceName: true, + metadata: true, }); export const apiFindOneBackup = createSchema @@ -141,5 +186,44 @@ export const apiUpdateBackup = createSchema destinationId: true, database: true, keepLatestCount: true, + serviceName: true, + metadata: true, + databaseType: true, }) .required(); + +export const apiRestoreBackup = z.object({ + databaseId: z.string(), + databaseType: z.enum(["postgres", "mysql", "mariadb", "mongo", "web-server"]), + backupType: z.enum(["database", "compose"]), + databaseName: z.string().min(1), + backupFile: z.string().min(1), + destinationId: z.string().min(1), + metadata: z + .object({ + serviceName: z.string().optional(), + postgres: z + .object({ + databaseUser: z.string(), + }) + .optional(), + mariadb: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mongo: z + .object({ + databaseUser: z.string(), + databasePassword: z.string(), + }) + .optional(), + mysql: z + .object({ + databaseRootPassword: z.string(), + }) + .optional(), + }) + .optional(), +}); diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index aff497d3..f67521e5 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -12,10 +12,12 @@ import { gitlab } from "./gitlab"; import { mounts } from "./mount"; import { projects } from "./project"; import { server } from "./server"; -import { applicationStatus } from "./shared"; +import { applicationStatus, triggerType } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; +import { backups } from "./backups"; +import { schedules } from "./schedule"; export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ "git", "github", @@ -77,6 +79,7 @@ export const compose = pgTable("compose", { suffix: text("suffix").notNull().default(""), randomize: boolean("randomize").notNull().default(false), isolatedDeployment: boolean("isolatedDeployment").notNull().default(false), + triggerType: triggerType("triggerType").default("push"), composeStatus: applicationStatus("composeStatus").notNull().default("idle"), projectId: text("projectId") .notNull() @@ -134,6 +137,8 @@ export const composeRelations = relations(compose, ({ one, many }) => ({ fields: [compose.serverId], references: [server.serverId], }), + backups: many(backups), + schedules: many(schedules), })); const createSchema = createInsertSchema(compose, { diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts index 4dfed76b..8e13bf6c 100644 --- a/packages/server/src/db/schema/deployment.ts +++ b/packages/server/src/db/schema/deployment.ts @@ -13,7 +13,8 @@ import { applications } from "./application"; import { compose } from "./compose"; import { previewDeployments } from "./preview-deployments"; import { server } from "./server"; - +import { schedules } from "./schedule"; +import { backups } from "./backups"; export const deploymentStatus = pgEnum("deploymentStatus", [ "running", "done", @@ -47,7 +48,16 @@ export const deployments = pgTable("deployment", { createdAt: text("createdAt") .notNull() .$defaultFn(() => new Date().toISOString()), + startedAt: text("startedAt"), + finishedAt: text("finishedAt"), errorMessage: text("errorMessage"), + scheduleId: text("scheduleId").references( + (): AnyPgColumn => schedules.scheduleId, + { onDelete: "cascade" }, + ), + backupId: text("backupId").references((): AnyPgColumn => backups.backupId, { + onDelete: "cascade", + }), }); export const deploymentsRelations = relations(deployments, ({ one }) => ({ @@ -67,6 +77,14 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({ fields: [deployments.previewDeploymentId], references: [previewDeployments.previewDeploymentId], }), + schedule: one(schedules, { + fields: [deployments.scheduleId], + references: [schedules.scheduleId], + }), + backup: one(backups, { + fields: [deployments.backupId], + references: [backups.backupId], + }), })); const schema = createInsertSchema(deployments, { @@ -116,6 +134,18 @@ export const apiCreateDeploymentCompose = schema composeId: z.string().min(1), }); +export const apiCreateDeploymentBackup = schema + .pick({ + title: true, + status: true, + logPath: true, + backupId: true, + description: true, + }) + .extend({ + backupId: z.string().min(1), + }); + export const apiCreateDeploymentServer = schema .pick({ title: true, @@ -128,6 +158,17 @@ export const apiCreateDeploymentServer = schema serverId: z.string().min(1), }); +export const apiCreateDeploymentSchedule = schema + .pick({ + title: true, + status: true, + logPath: true, + description: true, + }) + .extend({ + scheduleId: z.string().min(1), + }); + export const apiFindAllByApplication = schema .pick({ applicationId: true, @@ -154,3 +195,17 @@ export const apiFindAllByServer = schema serverId: z.string().min(1), }) .required(); + +export const apiFindAllByType = z + .object({ + id: z.string().min(1), + type: z.enum([ + "application", + "compose", + "server", + "schedule", + "previewDeployment", + "backup", + ]), + }) + .required(); diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts index 7d8c184f..e5c346cf 100644 --- a/packages/server/src/db/schema/index.ts +++ b/packages/server/src/db/schema/index.ts @@ -31,3 +31,4 @@ export * from "./utils"; export * from "./preview-deployments"; export * from "./ai"; export * from "./account"; +export * from "./schedule"; diff --git a/packages/server/src/db/schema/schedule.ts b/packages/server/src/db/schema/schedule.ts new file mode 100644 index 00000000..3c83247f --- /dev/null +++ b/packages/server/src/db/schema/schedule.ts @@ -0,0 +1,83 @@ +import { relations } from "drizzle-orm"; +import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { applications } from "./application"; +import { deployments } from "./deployment"; +import { generateAppName } from "./utils"; +import { compose } from "./compose"; +import { server } from "./server"; +import { users_temp } from "./user"; +export const shellTypes = pgEnum("shellType", ["bash", "sh"]); + +export const scheduleType = pgEnum("scheduleType", [ + "application", + "compose", + "server", + "dokploy-server", +]); + +export const schedules = pgTable("schedule", { + scheduleId: text("scheduleId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + cronExpression: text("cronExpression").notNull(), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("schedule")), + serviceName: text("serviceName"), + shellType: shellTypes("shellType").notNull().default("bash"), + scheduleType: scheduleType("scheduleType").notNull().default("application"), + command: text("command").notNull(), + script: text("script"), + applicationId: text("applicationId").references( + () => applications.applicationId, + { + onDelete: "cascade", + }, + ), + composeId: text("composeId").references(() => compose.composeId, { + onDelete: "cascade", + }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "cascade", + }), + userId: text("userId").references(() => users_temp.id, { + onDelete: "cascade", + }), + enabled: boolean("enabled").notNull().default(true), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), +}); + +export type Schedule = typeof schedules.$inferSelect; + +export const schedulesRelations = relations(schedules, ({ one, many }) => ({ + application: one(applications, { + fields: [schedules.applicationId], + references: [applications.applicationId], + }), + compose: one(compose, { + fields: [schedules.composeId], + references: [compose.composeId], + }), + server: one(server, { + fields: [schedules.serverId], + references: [server.serverId], + }), + user: one(users_temp, { + fields: [schedules.userId], + references: [users_temp.id], + }), + deployments: many(deployments), +})); + +export const createScheduleSchema = createInsertSchema(schedules); + +export const updateScheduleSchema = createScheduleSchema.extend({ + scheduleId: z.string().min(1), +}); diff --git a/packages/server/src/db/schema/server.ts b/packages/server/src/db/schema/server.ts index 26bb4632..31434122 100644 --- a/packages/server/src/db/schema/server.ts +++ b/packages/server/src/db/schema/server.ts @@ -22,7 +22,7 @@ import { postgres } from "./postgres"; import { redis } from "./redis"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; - +import { schedules } from "./schedule"; export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]); export const server = pgTable("server", { @@ -114,6 +114,7 @@ export const serverRelations = relations(server, ({ one, many }) => ({ fields: [server.organizationId], references: [organization.id], }), + schedules: many(schedules), })); const createSchema = createInsertSchema(server, { diff --git a/packages/server/src/db/schema/shared.ts b/packages/server/src/db/schema/shared.ts index 2fdc53e2..268ef987 100644 --- a/packages/server/src/db/schema/shared.ts +++ b/packages/server/src/db/schema/shared.ts @@ -12,3 +12,5 @@ export const certificateType = pgEnum("certificateType", [ "none", "custom", ]); + +export const triggerType = pgEnum("triggerType", ["push", "tag"]); diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index 9f4a5482..18bcb359 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -14,6 +14,7 @@ import { account, apikey, organization } from "./account"; import { projects } from "./project"; import { certificateType } from "./shared"; import { backups } from "./backups"; +import { schedules } from "./schedule"; /** * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same * database instance for multiple projects. @@ -127,6 +128,7 @@ export const usersRelations = relations(users_temp, ({ one, many }) => ({ projects: many(projects), apiKeys: many(apikey), backups: many(backups), + schedules: many(schedules), })); const createSchema = createInsertSchema(users_temp, { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 711570d8..b321f9ce 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -30,6 +30,7 @@ export * from "./services/github"; export * from "./services/gitlab"; export * from "./services/gitea"; export * from "./services/server"; +export * from "./services/schedule"; export * from "./services/application"; export * from "./utils/databases/rebuild"; export * from "./setup/config-paths"; @@ -49,6 +50,7 @@ export * from "./utils/backups/mysql"; export * from "./utils/backups/postgres"; export * from "./utils/backups/utils"; export * from "./utils/backups/web-server"; +export * from "./utils/backups/compose"; export * from "./templates/processors"; export * from "./utils/notifications/build-error"; @@ -126,3 +128,6 @@ export { stopLogCleanup, getLogCleanupStatus, } from "./utils/access-log/handler"; + +export * from "./utils/schedules/utils"; +export * from "./utils/schedules/index"; diff --git a/packages/server/src/services/backup.ts b/packages/server/src/services/backup.ts index 32705786..40faf6d9 100644 --- a/packages/server/src/services/backup.ts +++ b/packages/server/src/services/backup.ts @@ -35,6 +35,7 @@ export const findBackupById = async (backupId: string) => { mariadb: true, mongo: true, destination: true, + compose: true, }, }); if (!backup) { diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index d855ff9e..8b3bd3b0 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -131,6 +131,12 @@ export const findComposeById = async (composeId: string) => { bitbucket: true, gitea: true, server: true, + backups: { + with: { + destination: true, + deployments: true, + }, + }, }, }); if (!result) { @@ -472,7 +478,9 @@ export const removeCompose = async ( const projectPath = join(COMPOSE_PATH, compose.appName); if (compose.composeType === "stack") { - const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; + const command = ` + docker network disconnect ${compose.appName} dokploy-traefik; + cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`; if (compose.serverId) { await execAsyncRemote(compose.serverId, command); @@ -483,12 +491,11 @@ export const removeCompose = async ( cwd: projectPath, }); } else { - let command: string; - if (deleteVolumes) { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`; - } else { - command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; - } + const command = ` + docker network disconnect ${compose.appName} dokploy-traefik; + cd ${projectPath} && docker compose -p ${compose.appName} down ${ + deleteVolumes ? "--volumes" : "" + } && rm -rf ${projectPath}`; if (compose.serverId) { await execAsyncRemote(compose.serverId, command); diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 88086615..74f73f0e 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -4,8 +4,10 @@ import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; import { type apiCreateDeployment, + type apiCreateDeploymentBackup, type apiCreateDeploymentCompose, type apiCreateDeploymentPreview, + type apiCreateDeploymentSchedule, type apiCreateDeploymentServer, deployments, } from "@dokploy/server/db/schema"; @@ -27,6 +29,8 @@ import { findPreviewDeploymentById, updatePreviewDeployment, } from "./preview-deployment"; +import { findScheduleById } from "./schedule"; +import { findBackupById } from "./backup"; export type Deployment = typeof deployments.$inferSelect; @@ -57,6 +61,7 @@ export const createDeployment = async ( try { await removeLastTenDeployments( deployment.applicationId, + "application", application.serverId, ); const { LOGS_PATH } = paths(!!application.serverId); @@ -88,6 +93,7 @@ export const createDeployment = async ( status: "running", logPath: logFilePath, description: deployment.description || "", + startedAt: new Date().toISOString(), }) .returning(); if (deploymentCreate.length === 0 || !deploymentCreate[0]) { @@ -107,6 +113,8 @@ export const createDeployment = async ( logPath: "", description: deployment.description || "", errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), }) .returning(); await updateApplicationStatus(application.applicationId, "error"); @@ -128,8 +136,9 @@ export const createDeploymentPreview = async ( deployment.previewDeploymentId, ); try { - await removeLastTenPreviewDeploymenById( + await removeLastTenDeployments( deployment.previewDeploymentId, + "previewDeployment", previewDeployment?.application?.serverId, ); @@ -165,6 +174,7 @@ export const createDeploymentPreview = async ( logPath: logFilePath, description: deployment.description || "", previewDeploymentId: deployment.previewDeploymentId, + startedAt: new Date().toISOString(), }) .returning(); if (deploymentCreate.length === 0 || !deploymentCreate[0]) { @@ -184,6 +194,8 @@ export const createDeploymentPreview = async ( logPath: "", description: deployment.description || "", errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), }) .returning(); await updatePreviewDeployment(deployment.previewDeploymentId, { @@ -205,8 +217,9 @@ export const createDeploymentCompose = async ( ) => { const compose = await findComposeById(deployment.composeId); try { - await removeLastTenComposeDeployments( + await removeLastTenDeployments( deployment.composeId, + "compose", compose.serverId, ); const { LOGS_PATH } = paths(!!compose.serverId); @@ -238,6 +251,7 @@ echo "Initializing deployment" >> ${logFilePath}; description: deployment.description || "", status: "running", logPath: logFilePath, + startedAt: new Date().toISOString(), }) .returning(); if (deploymentCreate.length === 0 || !deploymentCreate[0]) { @@ -257,6 +271,8 @@ echo "Initializing deployment" >> ${logFilePath}; logPath: "", description: deployment.description || "", errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), }) .returning(); await updateCompose(compose.composeId, { @@ -270,6 +286,162 @@ echo "Initializing deployment" >> ${logFilePath}; } }; +export const createDeploymentBackup = async ( + deployment: Omit< + typeof apiCreateDeploymentBackup._type, + "deploymentId" | "createdAt" | "status" | "logPath" + >, +) => { + const backup = await findBackupById(deployment.backupId); + + let serverId: string | null | undefined; + if (backup.backupType === "database") { + serverId = + backup.postgres?.serverId || + backup.mariadb?.serverId || + backup.mysql?.serverId || + backup.mongo?.serverId; + } else if (backup.backupType === "compose") { + serverId = backup.compose?.serverId; + } + try { + await removeLastTenDeployments(deployment.backupId, "backup", serverId); + const { LOGS_PATH } = paths(!!serverId); + const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); + const fileName = `${backup.appName}-${formattedDateTime}.log`; + const logFilePath = path.join(LOGS_PATH, backup.appName, fileName); + + if (serverId) { + const server = await findServerById(serverId); + + const command = ` +mkdir -p ${LOGS_PATH}/${backup.appName}; +echo "Initializing backup\n" >> ${logFilePath}; +`; + + await execAsyncRemote(server.serverId, command); + } else { + await fsPromises.mkdir(path.join(LOGS_PATH, backup.appName), { + recursive: true, + }); + await fsPromises.writeFile(logFilePath, "Initializing backup\n"); + } + + const deploymentCreate = await db + .insert(deployments) + .values({ + backupId: deployment.backupId, + title: deployment.title || "Backup", + description: deployment.description || "", + status: "running", + logPath: logFilePath, + startedAt: new Date().toISOString(), + }) + .returning(); + if (deploymentCreate.length === 0 || !deploymentCreate[0]) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the backup", + }); + } + return deploymentCreate[0]; + } catch (error) { + await db + .insert(deployments) + .values({ + backupId: deployment.backupId, + title: deployment.title || "Backup", + status: "error", + logPath: "", + description: deployment.description || "", + errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), + }) + .returning(); + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the backup", + }); + } +}; + +export const createDeploymentSchedule = async ( + deployment: Omit< + typeof apiCreateDeploymentSchedule._type, + "deploymentId" | "createdAt" | "status" | "logPath" + >, +) => { + const schedule = await findScheduleById(deployment.scheduleId); + + try { + const serverId = + schedule.application?.serverId || + schedule.compose?.serverId || + schedule.server?.serverId; + await removeLastTenDeployments(deployment.scheduleId, "schedule", serverId); + const { SCHEDULES_PATH } = paths(!!serverId); + const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); + const fileName = `${schedule.appName}-${formattedDateTime}.log`; + const logFilePath = path.join(SCHEDULES_PATH, schedule.appName, fileName); + + if (serverId) { + const server = await findServerById(serverId); + + const command = ` + mkdir -p ${SCHEDULES_PATH}/${schedule.appName}; + echo "Initializing schedule" >> ${logFilePath}; + `; + + await execAsyncRemote(server.serverId, command); + } else { + await fsPromises.mkdir(path.join(SCHEDULES_PATH, schedule.appName), { + recursive: true, + }); + await fsPromises.writeFile(logFilePath, "Initializing schedule\n"); + } + + const deploymentCreate = await db + .insert(deployments) + .values({ + scheduleId: deployment.scheduleId, + title: deployment.title || "Deployment", + status: "running", + logPath: logFilePath, + description: deployment.description || "", + startedAt: new Date().toISOString(), + }) + .returning(); + if (deploymentCreate.length === 0 || !deploymentCreate[0]) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the deployment", + }); + } + return deploymentCreate[0]; + } catch (error) { + console.log(error); + await db + .insert(deployments) + .values({ + scheduleId: deployment.scheduleId, + title: deployment.title || "Deployment", + status: "error", + logPath: "", + description: deployment.description || "", + errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), + }) + .returning(); + + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error creating the deployment", + }); + } +}; + export const removeDeployment = async (deploymentId: string) => { try { const deployment = await db @@ -296,109 +468,21 @@ export const removeDeploymentsByApplicationId = async ( .returning(); }; -const removeLastTenDeployments = async ( - applicationId: string, - serverId: string | null, +const getDeploymentsByType = async ( + id: string, + type: + | "application" + | "compose" + | "server" + | "schedule" + | "previewDeployment" + | "backup", ) => { const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.applicationId, applicationId), + where: eq(deployments[`${type}Id`], id), orderBy: desc(deployments.createdAt), }); - - if (deploymentList.length > 10) { - const deploymentsToDelete = deploymentList.slice(9); - if (serverId) { - let command = ""; - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - - command += ` - rm -rf ${logPath}; - `; - await removeDeployment(oldDeployment.deploymentId); - } - - await execAsyncRemote(serverId, command); - } else { - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } - } -}; - -const removeLastTenComposeDeployments = async ( - composeId: string, - serverId: string | null, -) => { - const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.composeId, composeId), - orderBy: desc(deployments.createdAt), - }); - if (deploymentList.length > 10) { - if (serverId) { - let command = ""; - const deploymentsToDelete = deploymentList.slice(9); - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - - command += ` - rm -rf ${logPath}; - `; - await removeDeployment(oldDeployment.deploymentId); - } - - await execAsyncRemote(serverId, command); - } else { - const deploymentsToDelete = deploymentList.slice(9); - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } - } -}; - -export const removeLastTenPreviewDeploymenById = async ( - previewDeploymentId: string, - serverId: string | null, -) => { - const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.previewDeploymentId, previewDeploymentId), - orderBy: desc(deployments.createdAt), - }); - - if (deploymentList.length > 10) { - const deploymentsToDelete = deploymentList.slice(9); - if (serverId) { - let command = ""; - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - - command += ` - rm -rf ${logPath}; - `; - await removeDeployment(oldDeployment.deploymentId); - } - - await execAsyncRemote(serverId, command); - } else { - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } - } + return deploymentList; }; export const removeDeployments = async (application: Application) => { @@ -413,6 +497,44 @@ export const removeDeployments = async (application: Application) => { await removeDeploymentsByApplicationId(applicationId); }; +const removeLastTenDeployments = async ( + id: string, + type: + | "application" + | "compose" + | "server" + | "schedule" + | "previewDeployment" + | "backup", + serverId?: string | null, +) => { + const deploymentList = await getDeploymentsByType(id, type); + if (deploymentList.length > 10) { + const deploymentsToDelete = deploymentList.slice(10); + if (serverId) { + let command = ""; + for (const oldDeployment of deploymentsToDelete) { + const logPath = path.join(oldDeployment.logPath); + + command += ` + rm -rf ${logPath}; + `; + await removeDeployment(oldDeployment.deploymentId); + } + + await execAsyncRemote(serverId, command); + } else { + for (const oldDeployment of deploymentsToDelete) { + const logPath = path.join(oldDeployment.logPath); + if (existsSync(logPath)) { + await fsPromises.unlink(logPath); + } + await removeDeployment(oldDeployment.deploymentId); + } + } + } +}; + export const removeDeploymentsByPreviewDeploymentId = async ( previewDeployment: PreviewDeployment, serverId: string | null, @@ -494,6 +616,10 @@ export const updateDeploymentStatus = async ( .update(deployments) .set({ status: deploymentStatus, + finishedAt: + deploymentStatus === "done" || deploymentStatus === "error" + ? new Date().toISOString() + : null, }) .where(eq(deployments.deploymentId, deploymentId)) .returning(); diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts index da2d6bc4..1ce8f199 100644 --- a/packages/server/src/services/domain.ts +++ b/packages/server/src/services/domain.ts @@ -7,6 +7,8 @@ import { type apiCreateDomain, domains } from "../db/schema"; import { findUserById } from "./admin"; import { findApplicationById } from "./application"; import { findServerById } from "./server"; +import dns from "node:dns"; +import { promisify } from "node:util"; export type Domain = typeof domains.$inferSelect; @@ -137,3 +139,84 @@ export const removeDomainById = async (domainId: string) => { export const getDomainHost = (domain: Domain) => { return `${domain.https ? "https" : "http"}://${domain.host}`; }; + +const resolveDns = promisify(dns.resolve4); + +// Cloudflare IP ranges (simplified - these are some common ones) +const CLOUDFLARE_IPS = [ + "172.67.", + "104.21.", + "104.16.", + "104.17.", + "104.18.", + "104.19.", + "104.20.", + "104.22.", + "104.23.", + "104.24.", + "104.25.", + "104.26.", + "104.27.", + "104.28.", +]; + +const isCloudflareIp = (ip: string) => { + return CLOUDFLARE_IPS.some((range) => ip.startsWith(range)); +}; + +export const validateDomain = async ( + domain: string, + expectedIp?: string, +): Promise<{ + isValid: boolean; + resolvedIp?: string; + error?: string; + isCloudflare?: boolean; +}> => { + try { + // Remove protocol and path if present + const cleanDomain = domain.replace(/^https?:\/\//, "").split("/")[0]; + + // Resolve the domain to get its IP + const ips = await resolveDns(cleanDomain || ""); + + const resolvedIps = ips.map((ip) => ip.toString()); + + // Check if it's a Cloudflare IP + const behindCloudflare = ips.some((ip) => isCloudflareIp(ip)); + + // If behind Cloudflare, we consider it valid but inform the user + if (behindCloudflare) { + return { + isValid: true, + resolvedIp: resolvedIps.join(", "), + isCloudflare: true, + error: + "Domain is behind Cloudflare - actual IP is masked by Cloudflare proxy", + }; + } + + // If we have an expected IP, validate against it + if (expectedIp) { + return { + isValid: resolvedIps.includes(expectedIp), + resolvedIp: resolvedIps.join(", "), + error: !resolvedIps.includes(expectedIp) + ? `Domain resolves to ${resolvedIps.join(", ")} but should point to ${expectedIp}` + : undefined, + }; + } + + // If no expected IP, just return the resolved IP + return { + isValid: true, + resolvedIp: resolvedIps.join(", "), + }; + } catch (error) { + return { + isValid: false, + error: + error instanceof Error ? error.message : "Failed to resolve domain", + }; + } +}; diff --git a/packages/server/src/services/mariadb.ts b/packages/server/src/services/mariadb.ts index 6a7f3b92..4ef48b27 100644 --- a/packages/server/src/services/mariadb.ts +++ b/packages/server/src/services/mariadb.ts @@ -63,6 +63,7 @@ export const findMariadbById = async (mariadbId: string) => { backups: { with: { destination: true, + deployments: true, }, }, }, diff --git a/packages/server/src/services/mongo.ts b/packages/server/src/services/mongo.ts index 0eb3c2c9..188f63c8 100644 --- a/packages/server/src/services/mongo.ts +++ b/packages/server/src/services/mongo.ts @@ -1,5 +1,10 @@ import { db } from "@dokploy/server/db"; -import { type apiCreateMongo, backups, mongo } from "@dokploy/server/db/schema"; +import { + type apiCreateMongo, + backups, + compose, + mongo, +} from "@dokploy/server/db/schema"; import { buildAppName } from "@dokploy/server/db/schema"; import { generatePassword } from "@dokploy/server/templates"; import { buildMongo } from "@dokploy/server/utils/databases/mongo"; @@ -55,6 +60,7 @@ export const findMongoById = async (mongoId: string) => { backups: { with: { destination: true, + deployments: true, }, }, }, @@ -103,6 +109,25 @@ export const findMongoByBackupId = async (backupId: string) => { return result[0]; }; +export const findComposeByBackupId = async (backupId: string) => { + const result = await db + .select({ + ...getTableColumns(compose), + }) + .from(compose) + .innerJoin(backups, eq(compose.composeId, backups.composeId)) + .where(eq(backups.backupId, backupId)) + .limit(1); + + if (!result || !result[0]) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Compose not found", + }); + } + return result[0]; +}; + export const removeMongoById = async (mongoId: string) => { const result = await db .delete(mongo) diff --git a/packages/server/src/services/mysql.ts b/packages/server/src/services/mysql.ts index 4599e20b..d078035b 100644 --- a/packages/server/src/services/mysql.ts +++ b/packages/server/src/services/mysql.ts @@ -59,6 +59,7 @@ export const findMySqlById = async (mysqlId: string) => { backups: { with: { destination: true, + deployments: true, }, }, }, diff --git a/packages/server/src/services/postgres.ts b/packages/server/src/services/postgres.ts index 5c76dabe..9021f596 100644 --- a/packages/server/src/services/postgres.ts +++ b/packages/server/src/services/postgres.ts @@ -58,6 +58,7 @@ export const findPostgresById = async (postgresId: string) => { backups: { with: { destination: true, + deployments: true, }, }, }, diff --git a/packages/server/src/services/schedule.ts b/packages/server/src/services/schedule.ts new file mode 100644 index 00000000..b9a6cd52 --- /dev/null +++ b/packages/server/src/services/schedule.ts @@ -0,0 +1,126 @@ +import { type Schedule, schedules } from "../db/schema/schedule"; +import { db } from "../db"; +import { eq } from "drizzle-orm"; +import { TRPCError } from "@trpc/server"; +import type { z } from "zod"; +import type { + createScheduleSchema, + updateScheduleSchema, +} from "../db/schema/schedule"; +import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; +import { paths } from "../constants"; +import path from "node:path"; +import { encodeBase64 } from "../utils/docker/utils"; + +export type ScheduleExtended = Awaited>; + +export const createSchedule = async ( + input: z.infer, +) => { + const { scheduleId, ...rest } = input; + const [newSchedule] = await db.insert(schedules).values(rest).returning(); + + if ( + newSchedule && + (newSchedule.scheduleType === "dokploy-server" || + newSchedule.scheduleType === "server") + ) { + await handleScript(newSchedule); + } + + return newSchedule; +}; + +export const findScheduleById = async (scheduleId: string) => { + const schedule = await db.query.schedules.findFirst({ + where: eq(schedules.scheduleId, scheduleId), + with: { + application: true, + compose: true, + server: true, + }, + }); + + if (!schedule) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Schedule not found", + }); + } + return schedule; +}; + +export const deleteSchedule = async (scheduleId: string) => { + const schedule = await findScheduleById(scheduleId); + const serverId = + schedule?.serverId || + schedule?.application?.serverId || + schedule?.compose?.serverId; + const { SCHEDULES_PATH } = paths(!!serverId); + + const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); + const command = `rm -rf ${fullPath}`; + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } + + const scheduleResult = await db + .delete(schedules) + .where(eq(schedules.scheduleId, scheduleId)); + if (!scheduleResult) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Schedule not found", + }); + } + + return true; +}; + +export const updateSchedule = async ( + input: z.infer, +) => { + const { scheduleId, ...rest } = input; + const [updatedSchedule] = await db + .update(schedules) + .set(rest) + .where(eq(schedules.scheduleId, scheduleId)) + .returning(); + + if (!updatedSchedule) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Schedule not found", + }); + } + + if ( + updatedSchedule?.scheduleType === "dokploy-server" || + updatedSchedule?.scheduleType === "server" + ) { + await handleScript(updatedSchedule); + } + + return updatedSchedule; +}; + +const handleScript = async (schedule: Schedule) => { + const { SCHEDULES_PATH } = paths(!!schedule?.serverId); + const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); + const encodedContent = encodeBase64(schedule?.script || ""); + const script = ` + mkdir -p ${fullPath} + rm -f ${fullPath}/script.sh + touch ${fullPath}/script.sh + chmod +x ${fullPath}/script.sh + echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh + `; + + if (schedule?.scheduleType === "dokploy-server") { + await execAsync(script); + } else if (schedule?.scheduleType === "server") { + await execAsyncRemote(schedule?.serverId || "", script); + } +}; diff --git a/packages/server/src/setup/config-paths.ts b/packages/server/src/setup/config-paths.ts index 190e438b..95f2d30d 100644 --- a/packages/server/src/setup/config-paths.ts +++ b/packages/server/src/setup/config-paths.ts @@ -18,6 +18,7 @@ export const setupDirectories = () => { MAIN_TRAEFIK_PATH, MONITORING_PATH, SSH_PATH, + SCHEDULES_PATH, } = paths(); const directories = [ BASE_PATH, @@ -28,6 +29,7 @@ export const setupDirectories = () => { SSH_PATH, CERTIFICATES_PATH, MONITORING_PATH, + SCHEDULES_PATH, ]; for (const dir of directories) { diff --git a/packages/server/src/utils/backups/compose.ts b/packages/server/src/utils/backups/compose.ts new file mode 100644 index 00000000..79cb69e8 --- /dev/null +++ b/packages/server/src/utils/backups/compose.ts @@ -0,0 +1,68 @@ +import type { BackupSchedule } from "@dokploy/server/services/backup"; +import type { Compose } from "@dokploy/server/services/compose"; +import { findProjectById } from "@dokploy/server/services/project"; +import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; +import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getS3Credentials, normalizeS3Path, getBackupCommand } from "./utils"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; + +export const runComposeBackup = async ( + compose: Compose, + backup: BackupSchedule, +) => { + const { projectId, name } = compose; + const project = await findProjectById(projectId); + const { prefix } = backup; + const destination = backup.destination; + const backupFileName = `${new Date().toISOString()}.dump.gz`; + const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "Compose Backup", + description: "Compose Backup", + }); + + try { + const rcloneFlags = getS3Credentials(destination); + const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; + const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; + + const backupCommand = getBackupCommand( + backup, + rcloneCommand, + deployment.logPath, + ); + if (compose.serverId) { + await execAsyncRemote(compose.serverId, backupCommand); + } else { + await execAsync(backupCommand); + } + + await sendDatabaseBackupNotifications({ + applicationName: name, + projectName: project.name, + databaseType: "mongodb", + type: "success", + organizationId: project.organizationId, + }); + + await updateDeploymentStatus(deployment.deploymentId, "done"); + } catch (error) { + console.log(error); + await sendDatabaseBackupNotifications({ + applicationName: name, + projectName: project.name, + databaseType: "mongodb", + type: "error", + // @ts-ignore + errorMessage: error?.message || "Error message not provided", + organizationId: project.organizationId, + }); + + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; + } +}; diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 6c940406..38a0b446 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -70,6 +70,7 @@ export const initCronJobs = async () => { mysql: true, mongo: true, user: true, + compose: true, }, }); @@ -77,10 +78,10 @@ export const initCronJobs = async () => { try { if (backup.enabled) { scheduleBackup(backup); + console.log( + `[Backup] ${backup.databaseType} Enabled with cron: [${backup.schedule}]`, + ); } - console.log( - `[Backup] ${backup.databaseType} Enabled with cron: [${backup.schedule}]`, - ); } catch (error) { console.error(`[Backup] ${backup.databaseType} Error`, error); } diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts index 776c5ff4..6f71939e 100644 --- a/packages/server/src/utils/backups/mariadb.ts +++ b/packages/server/src/utils/backups/mariadb.ts @@ -1,46 +1,43 @@ import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { Mariadb } from "@dokploy/server/services/mariadb"; import { findProjectById } from "@dokploy/server/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials, normalizeS3Path } from "./utils"; +import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; export const runMariadbBackup = async ( mariadb: Mariadb, backup: BackupSchedule, ) => { - const { appName, databasePassword, databaseUser, projectId, name } = mariadb; + const { projectId, name } = mariadb; const project = await findProjectById(projectId); - const { prefix, database } = backup; + const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; - + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "MariaDB Backup", + description: "MariaDB Backup", + }); try { const rcloneFlags = getS3Credentials(destination); const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; + + const backupCommand = getBackupCommand( + backup, + rcloneCommand, + deployment.logPath, + ); if (mariadb.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mariadb.serverId, - appName, - ); - const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; - - await execAsyncRemote( - mariadb.serverId, - `${mariadbDumpCommand} | ${rcloneCommand}`, - ); + await execAsyncRemote(mariadb.serverId, backupCommand); } else { - const { Id: containerId } = await getServiceContainer(appName); - const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; - - await execAsync(`${mariadbDumpCommand} | ${rcloneCommand}`); + await execAsync(backupCommand); } await sendDatabaseBackupNotifications({ @@ -50,6 +47,7 @@ export const runMariadbBackup = async ( type: "success", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { console.log(error); await sendDatabaseBackupNotifications({ @@ -61,6 +59,7 @@ export const runMariadbBackup = async ( errorMessage: error?.message || "Error message not provided", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "error"); throw error; } }; diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts index a043a5a7..815439d5 100644 --- a/packages/server/src/utils/backups/mongo.ts +++ b/packages/server/src/utils/backups/mongo.ts @@ -1,43 +1,41 @@ import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { Mongo } from "@dokploy/server/services/mongo"; import { findProjectById } from "@dokploy/server/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials, normalizeS3Path } from "./utils"; +import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; -// mongodb://mongo:Bqh7AQl-PRbnBu@localhost:27017/?tls=false&directConnection=true export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { - const { appName, databasePassword, databaseUser, projectId, name } = mongo; + const { projectId, name } = mongo; const project = await findProjectById(projectId); - const { prefix, database } = backup; + const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.dump.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; - + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "MongoDB Backup", + description: "MongoDB Backup", + }); try { const rcloneFlags = getS3Credentials(destination); const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; - if (mongo.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mongo.serverId, - appName, - ); - const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase=admin --gzip"`; - await execAsyncRemote( - mongo.serverId, - `${mongoDumpCommand} | ${rcloneCommand}`, - ); + const backupCommand = getBackupCommand( + backup, + rcloneCommand, + deployment.logPath, + ); + + if (mongo.serverId) { + await execAsyncRemote(mongo.serverId, backupCommand); } else { - const { Id: containerId } = await getServiceContainer(appName); - const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase=admin --gzip"`; - await execAsync(`${mongoDumpCommand} | ${rcloneCommand}`); + await execAsync(backupCommand); } await sendDatabaseBackupNotifications({ @@ -47,6 +45,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { type: "success", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { console.log(error); await sendDatabaseBackupNotifications({ @@ -58,7 +57,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { errorMessage: error?.message || "Error message not provided", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "error"); throw error; } }; -// mongorestore -d monguito -u mongo -p Bqh7AQl-PRbnBu --authenticationDatabase admin --gzip --archive=2024-04-13T05:03:58.937Z.dump.gz diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts index d98a8ecc..80a24827 100644 --- a/packages/server/src/utils/backups/mysql.ts +++ b/packages/server/src/utils/backups/mysql.ts @@ -1,43 +1,43 @@ import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { MySql } from "@dokploy/server/services/mysql"; import { findProjectById } from "@dokploy/server/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials, normalizeS3Path } from "./utils"; +import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { - const { appName, databaseRootPassword, projectId, name } = mysql; + const { projectId, name } = mysql; const project = await findProjectById(projectId); - const { prefix, database } = backup; + const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "MySQL Backup", + description: "MySQL Backup", + }); try { const rcloneFlags = getS3Credentials(destination); const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; + + const backupCommand = getBackupCommand( + backup, + rcloneCommand, + deployment.logPath, + ); + if (mysql.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mysql.serverId, - appName, - ); - const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`; - - await execAsyncRemote( - mysql.serverId, - `${mysqlDumpCommand} | ${rcloneCommand}`, - ); + await execAsyncRemote(mysql.serverId, backupCommand); } else { - const { Id: containerId } = await getServiceContainer(appName); - const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`; - - await execAsync(`${mysqlDumpCommand} | ${rcloneCommand}`); + await execAsync(backupCommand); } await sendDatabaseBackupNotifications({ applicationName: name, @@ -46,6 +46,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { type: "success", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { console.log(error); await sendDatabaseBackupNotifications({ @@ -57,6 +58,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { errorMessage: error?.message || "Error message not provided", organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "error"); throw error; } }; diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts index cac582f7..29974106 100644 --- a/packages/server/src/utils/backups/postgres.ts +++ b/packages/server/src/utils/backups/postgres.ts @@ -1,22 +1,27 @@ import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { Postgres } from "@dokploy/server/services/postgres"; import { findProjectById } from "@dokploy/server/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials, normalizeS3Path } from "./utils"; +import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; export const runPostgresBackup = async ( postgres: Postgres, backup: BackupSchedule, ) => { - const { appName, databaseUser, name, projectId } = postgres; + const { name, projectId } = postgres; const project = await findProjectById(projectId); - const { prefix, database } = backup; + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "Initializing Backup", + description: "Initializing Backup", + }); + const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; @@ -25,22 +30,16 @@ export const runPostgresBackup = async ( const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; + + const backupCommand = getBackupCommand( + backup, + rcloneCommand, + deployment.logPath, + ); if (postgres.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - postgres.serverId, - appName, - ); - const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`; - - await execAsyncRemote( - postgres.serverId, - `${pgDumpCommand} | ${rcloneCommand}`, - ); + await execAsyncRemote(postgres.serverId, backupCommand); } else { - const { Id: containerId } = await getServiceContainer(appName); - - const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`; - await execAsync(`${pgDumpCommand} | ${rcloneCommand}`); + await execAsync(backupCommand); } await sendDatabaseBackupNotifications({ @@ -50,6 +49,8 @@ export const runPostgresBackup = async ( type: "success", organizationId: project.organizationId, }); + + await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { await sendDatabaseBackupNotifications({ applicationName: name, @@ -61,10 +62,9 @@ export const runPostgresBackup = async ( organizationId: project.organizationId, }); + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; } finally { } }; - -// Restore -// /Applications/pgAdmin 4.app/Contents/SharedSupport/pg_restore --host "localhost" --port "5432" --username "mauricio" --no-password --dbname "postgres" --verbose "/Users/mauricio/Downloads/_databases_2024-04-12T07_02_05.234Z.sql" diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index df3c8339..0d4471f6 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -7,26 +7,40 @@ import { runMongoBackup } from "./mongo"; import { runMySqlBackup } from "./mysql"; import { runPostgresBackup } from "./postgres"; import { runWebServerBackup } from "./web-server"; +import { runComposeBackup } from "./compose"; export const scheduleBackup = (backup: BackupSchedule) => { - const { schedule, backupId, databaseType, postgres, mysql, mongo, mariadb } = - backup; + const { + schedule, + backupId, + databaseType, + postgres, + mysql, + mongo, + mariadb, + compose, + } = backup; scheduleJob(backupId, schedule, async () => { - if (databaseType === "postgres" && postgres) { - await runPostgresBackup(postgres, backup); - await keepLatestNBackups(backup, postgres.serverId); - } else if (databaseType === "mysql" && mysql) { - await runMySqlBackup(mysql, backup); - await keepLatestNBackups(backup, mysql.serverId); - } else if (databaseType === "mongo" && mongo) { - await runMongoBackup(mongo, backup); - await keepLatestNBackups(backup, mongo.serverId); - } else if (databaseType === "mariadb" && mariadb) { - await runMariadbBackup(mariadb, backup); - await keepLatestNBackups(backup, mariadb.serverId); - } else if (databaseType === "web-server") { - await runWebServerBackup(backup); - await keepLatestNBackups(backup); + if (backup.backupType === "database") { + if (databaseType === "postgres" && postgres) { + await runPostgresBackup(postgres, backup); + await keepLatestNBackups(backup, postgres.serverId); + } else if (databaseType === "mysql" && mysql) { + await runMySqlBackup(mysql, backup); + await keepLatestNBackups(backup, mysql.serverId); + } else if (databaseType === "mongo" && mongo) { + await runMongoBackup(mongo, backup); + await keepLatestNBackups(backup, mongo.serverId); + } else if (databaseType === "mariadb" && mariadb) { + await runMariadbBackup(mariadb, backup); + await keepLatestNBackups(backup, mariadb.serverId); + } else if (databaseType === "web-server") { + await runWebServerBackup(backup); + await keepLatestNBackups(backup); + } + } else if (backup.backupType === "compose" && compose) { + await runComposeBackup(compose, backup); + await keepLatestNBackups(backup, compose.serverId); } }); }; @@ -61,3 +75,181 @@ export const getS3Credentials = (destination: Destination) => { return rcloneFlags; }; + +export const getPostgresBackupCommand = ( + database: string, + databaseUser: string, +) => { + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`; +}; + +export const getMariadbBackupCommand = ( + database: string, + databaseUser: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; +}; + +export const getMysqlBackupCommand = ( + database: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databasePassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`; +}; + +export const getMongoBackupCommand = ( + database: string, + databaseUser: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase admin --gzip"`; +}; + +export const getServiceContainerCommand = (appName: string) => { + return `docker ps -q --filter "status=running" --filter "label=com.docker.swarm.service.name=${appName}" | head -n 1`; +}; + +export const getComposeContainerCommand = ( + appName: string, + serviceName: string, + composeType: "stack" | "docker-compose" | undefined, +) => { + if (composeType === "stack") { + return `docker ps -q --filter "status=running" --filter "label=com.docker.stack.namespace=${appName}" --filter "label=com.docker.swarm.service.name=${serviceName}" | head -n 1`; + } + return `docker ps -q --filter "status=running" --filter "label=com.docker.compose.project=${appName}" --filter "label=com.docker.compose.service=${serviceName}" | head -n 1`; +}; + +const getContainerSearchCommand = (backup: BackupSchedule) => { + const { backupType, postgres, mysql, mariadb, mongo, compose, serviceName } = + backup; + + if (backupType === "database") { + const appName = + postgres?.appName || mysql?.appName || mariadb?.appName || mongo?.appName; + return getServiceContainerCommand(appName || ""); + } + if (backupType === "compose") { + const { appName, composeType } = compose || {}; + return getComposeContainerCommand( + appName || "", + serviceName || "", + composeType, + ); + } +}; + +export const generateBackupCommand = (backup: BackupSchedule) => { + const { backupType, databaseType } = backup; + switch (databaseType) { + case "postgres": { + const postgres = backup.postgres; + if (backupType === "database" && postgres) { + return getPostgresBackupCommand(backup.database, postgres.databaseUser); + } + if (backupType === "compose" && backup.metadata?.postgres) { + return getPostgresBackupCommand( + backup.database, + backup.metadata.postgres.databaseUser, + ); + } + break; + } + case "mysql": { + const mysql = backup.mysql; + if (backupType === "database" && mysql) { + return getMysqlBackupCommand(backup.database, mysql.databasePassword); + } + if (backupType === "compose" && backup.metadata?.mysql) { + return getMysqlBackupCommand( + backup.database, + backup.metadata?.mysql?.databaseRootPassword || "", + ); + } + break; + } + case "mariadb": { + const mariadb = backup.mariadb; + if (backupType === "database" && mariadb) { + return getMariadbBackupCommand( + backup.database, + mariadb.databaseUser, + mariadb.databasePassword, + ); + } + if (backupType === "compose" && backup.metadata?.mariadb) { + return getMariadbBackupCommand( + backup.database, + backup.metadata.mariadb.databaseUser, + backup.metadata.mariadb.databasePassword, + ); + } + break; + } + case "mongo": { + const mongo = backup.mongo; + if (backupType === "database" && mongo) { + return getMongoBackupCommand( + backup.database, + mongo.databaseUser, + mongo.databasePassword, + ); + } + if (backupType === "compose" && backup.metadata?.mongo) { + return getMongoBackupCommand( + backup.database, + backup.metadata.mongo.databaseUser, + backup.metadata.mongo.databasePassword, + ); + } + break; + } + default: + throw new Error(`Database type not supported: ${databaseType}`); + } + + return null; +}; + +export const getBackupCommand = ( + backup: BackupSchedule, + rcloneCommand: string, + logPath: string, +) => { + const containerSearch = getContainerSearchCommand(backup); + const backupCommand = generateBackupCommand(backup); + return ` + set -eo pipefail; + echo "[$(date)] Starting backup process..." >> ${logPath}; + echo "[$(date)] Executing backup command..." >> ${logPath}; + CONTAINER_ID=$(${containerSearch}) + + if [ -z "$CONTAINER_ID" ]; then + echo "[$(date)] ❌ Error: Container not found" >> ${logPath}; + exit 1; + fi + + echo "[$(date)] Container Up: $CONTAINER_ID" >> ${logPath}; + + # Run the backup command and capture the exit status + BACKUP_OUTPUT=$(${backupCommand} 2>&1 >/dev/null) || { + echo "[$(date)] ❌ Error: Backup failed" >> ${logPath}; + echo "Error: $BACKUP_OUTPUT" >> ${logPath}; + exit 1; + } + + echo "[$(date)] ✅ backup completed successfully" >> ${logPath}; + echo "[$(date)] Starting upload to S3..." >> ${logPath}; + + # Run the upload command and capture the exit status + UPLOAD_OUTPUT=$(${backupCommand} | ${rcloneCommand} 2>&1 >/dev/null) || { + echo "[$(date)] ❌ Error: Upload to S3 failed" >> ${logPath}; + echo "Error: $UPLOAD_OUTPUT" >> ${logPath}; + exit 1; + } + + echo "[$(date)] ✅ Upload to S3 completed successfully" >> ${logPath}; + echo "Backup done ✅" >> ${logPath}; + `; +}; diff --git a/packages/server/src/utils/backups/web-server.ts b/packages/server/src/utils/backups/web-server.ts index 2dea3d81..71df47ba 100644 --- a/packages/server/src/utils/backups/web-server.ts +++ b/packages/server/src/utils/backups/web-server.ts @@ -6,12 +6,25 @@ import { IS_CLOUD, paths } from "@dokploy/server/constants"; import { mkdtemp } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; +import { + createDeploymentBackup, + updateDeploymentStatus, +} from "@dokploy/server/services/deployment"; +import { createWriteStream } from "node:fs"; export const runWebServerBackup = async (backup: BackupSchedule) => { + if (IS_CLOUD) { + return; + } + + const deployment = await createDeploymentBackup({ + backupId: backup.backupId, + title: "Web Server Backup", + description: "Web Server Backup", + }); + const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); + try { - if (IS_CLOUD) { - return; - } const destination = await findDestinationById(backup.destinationId); const rcloneFlags = getS3Credentials(destination); const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); @@ -29,29 +42,51 @@ export const runWebServerBackup = async (backup: BackupSchedule) => { ); if (!containerId) { - throw new Error("PostgreSQL container not found"); + writeStream.write("Dokploy postgres container not found❌\n"); + writeStream.end(); + throw new Error("Dokploy postgres container not found"); } + writeStream.write(`Dokploy postgres container ID: ${containerId}\n`); + const postgresContainerId = containerId.trim(); const postgresCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy > '${tempDir}/database.sql'`; + + writeStream.write(`Running command: ${postgresCommand}\n`); await execAsync(postgresCommand); - await execAsync(`cp -r ${BASE_PATH}/* ${tempDir}/filesystem/`); + await execAsync( + `rsync -av --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`, + ); + + writeStream.write("Copied filesystem to temp directory\n"); await execAsync( // Zip all .sql files since we created more than one `cd ${tempDir} && zip -r ${backupFileName} *.sql filesystem/ > /dev/null 2>&1`, ); + writeStream.write("Zipped database and filesystem\n"); + const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`; + writeStream.write(`Running command: ${uploadCommand}\n`); await execAsync(uploadCommand); + writeStream.write("Uploaded backup to S3 ✅\n"); + writeStream.end(); + await updateDeploymentStatus(deployment.deploymentId, "done"); return true; } finally { await execAsync(`rm -rf ${tempDir}`); } } catch (error) { console.error("Backup error:", error); + writeStream.write("Backup error❌\n"); + writeStream.write( + error instanceof Error ? error.message : "Unknown error\n", + ); + writeStream.end(); + await updateDeploymentStatus(deployment.deploymentId, "error"); throw error; } }; diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 71b7e4aa..be497242 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -13,6 +13,7 @@ import type { RedisNested } from "../databases/redis"; import { execAsync, execAsyncRemote } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; import { getRemoteDocker } from "../servers/remote-docker"; +import type { Compose } from "@dokploy/server/services/compose"; interface RegistryAuth { username: string; @@ -493,32 +494,9 @@ export const getCreateFileCommand = ( `; }; -export const getServiceContainer = async (appName: string) => { - try { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; - - const containers = await docker.listContainers({ - filters: JSON.stringify(filter), - }); - - if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); - } - - const container = containers[0]; - - return container; - } catch (error) { - throw error; - } -}; - -export const getRemoteServiceContainer = async ( - serverId: string, +export const getServiceContainer = async ( appName: string, + serverId?: string | null, ) => { try { const filter = { @@ -531,7 +509,7 @@ export const getRemoteServiceContainer = async ( }); if (containers.length === 0 || !containers[0]) { - throw new Error(`No container found with name: ${appName}`); + return null; } const container = containers[0]; @@ -541,3 +519,42 @@ export const getRemoteServiceContainer = async ( throw error; } }; + +export const getComposeContainer = async ( + compose: Compose, + serviceName: string, +) => { + try { + const { appName, composeType, serverId } = compose; + // 1. Determine the correct labels based on composeType + const labels: string[] = []; + if (composeType === "stack") { + // Labels for Docker Swarm stack services + labels.push(`com.docker.stack.namespace=${appName}`); + labels.push(`com.docker.swarm.service.name=${appName}_${serviceName}`); + } else { + // Labels for Docker Compose projects (default) + labels.push(`com.docker.compose.project=${appName}`); + labels.push(`com.docker.compose.service=${serviceName}`); + } + const filter = { + status: ["running"], + label: labels, + }; + + const remoteDocker = await getRemoteDocker(serverId); + const containers = await remoteDocker.listContainers({ + filters: JSON.stringify(filter), + limit: 1, + }); + + if (containers.length === 0 || !containers[0]) { + return null; + } + + const container = containers[0]; + return container; + } catch (error) { + throw error; + } +}; diff --git a/packages/server/src/utils/process/execAsync.ts b/packages/server/src/utils/process/execAsync.ts index c3e40907..84f0701d 100644 --- a/packages/server/src/utils/process/execAsync.ts +++ b/packages/server/src/utils/process/execAsync.ts @@ -5,6 +5,51 @@ import { Client } from "ssh2"; export const execAsync = util.promisify(exec); +interface ExecOptions { + cwd?: string; + env?: NodeJS.ProcessEnv; +} + +export const execAsyncStream = ( + command: string, + onData?: (data: string) => void, + options: ExecOptions = {}, +): Promise<{ stdout: string; stderr: string }> => { + return new Promise((resolve, reject) => { + let stdoutComplete = ""; + let stderrComplete = ""; + + const childProcess = exec(command, options, (error) => { + if (error) { + reject(error); + return; + } + resolve({ stdout: stdoutComplete, stderr: stderrComplete }); + }); + + childProcess.stdout?.on("data", (data: Buffer | string) => { + const stringData = data.toString(); + stdoutComplete += stringData; + if (onData) { + onData(stringData); + } + }); + + childProcess.stderr?.on("data", (data: Buffer | string) => { + const stringData = data.toString(); + stderrComplete += stringData; + if (onData) { + onData(stringData); + } + }); + + childProcess.on("error", (error) => { + console.log(error); + reject(error); + }); + }); +}; + export const execFileAsync = async ( command: string, args: string[], diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts index fc8e05a4..88144eb5 100644 --- a/packages/server/src/utils/providers/gitea.ts +++ b/packages/server/src/utils/providers/gitea.ts @@ -362,27 +362,48 @@ export const testGiteaConnection = async (input: { giteaId: string }) => { } const baseUrl = provider.giteaUrl.replace(/\/+$/, ""); - const url = `${baseUrl}/api/v1/user/repos`; + const limit = 30; + let allRepos = 0; + let nextUrl = `${baseUrl}/api/v1/repos/search?limit=${limit}`; - const response = await fetch(url, { - headers: { - Accept: "application/json", - Authorization: `token ${provider.accessToken}`, - }, - }); + while (nextUrl) { + const response = await fetch(nextUrl, { + headers: { + Accept: "application/json", + Authorization: `token ${provider.accessToken}`, + }, + }); - if (!response.ok) { - throw new Error( - `Failed to connect to Gitea API: ${response.status} ${response.statusText}`, - ); + if (!response.ok) { + throw new Error( + `Failed to connect to Gitea API: ${response.status} ${response.statusText}`, + ); + } + + const repos = await response.json(); + allRepos += repos.data.length; + + const linkHeader = response.headers.get("link"); + nextUrl = ""; + + if (linkHeader) { + const nextLink = linkHeader + .split(",") + .find((link) => link.includes('rel="next"')); + if (nextLink) { + const matches = nextLink.match(/<([^>]+)>/); + if (matches?.[1]) { + nextUrl = matches[1]; + } + } + } } - const repos = await response.json(); await updateGitea(giteaId, { lastAuthenticatedAt: Math.floor(Date.now() / 1000), }); - return repos.length; + return allRepos; } catch (error) { throw error; } @@ -394,38 +415,57 @@ export const getGiteaRepositories = async (giteaId?: string) => { } await refreshGiteaToken(giteaId); - const giteaProvider = await findGiteaById(giteaId); const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ""); - const url = `${baseUrl}/api/v1/user/repos`; + const limit = 30; + let allRepositories: any[] = []; + let nextUrl = `${baseUrl}/api/v1/repos/search?limit=${limit}`; - 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}`, + while (nextUrl) { + const response = await fetch(nextUrl, { + headers: { + Accept: "application/json", + Authorization: `token ${giteaProvider.accessToken}`, + }, }); + + if (!response.ok) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Failed to fetch repositories: ${response.statusText}`, + }); + } + + const result = await response.json(); + allRepositories = [...allRepositories, ...result.data]; + + const linkHeader = response.headers.get("link"); + nextUrl = ""; + + if (linkHeader) { + const nextLink = linkHeader + .split(",") + .find((link) => link.includes('rel="next"')); + if (nextLink) { + const matches = nextLink.match(/<([^>]+)>/); + if (matches?.[1]) { + nextUrl = matches[1]; + } + } + } } - const repositories = await response.json(); - - const mappedRepositories = repositories.map((repo: any) => ({ - id: repo.id, - name: repo.name, - url: repo.full_name, - owner: { - username: repo.owner.login, - }, - })); - - return mappedRepositories; + return ( + allRepositories?.map((repo: any) => ({ + id: repo.id, + name: repo.name, + url: repo.full_name, + owner: { + username: repo.owner.login, + }, + })) || [] + ); }; export const getGiteaBranches = async (input: { @@ -457,7 +497,10 @@ export const getGiteaBranches = async (input: { const branches = await response.json(); - return branches.map((branch: any) => ({ + if (!branches) { + return []; + } + return branches?.map((branch: any) => ({ id: branch.name, name: branch.name, commit: { diff --git a/packages/server/src/utils/restore/compose.ts b/packages/server/src/utils/restore/compose.ts new file mode 100644 index 00000000..219cd6dd --- /dev/null +++ b/packages/server/src/utils/restore/compose.ts @@ -0,0 +1,97 @@ +import type { Destination } from "@dokploy/server/services/destination"; +import type { Compose } from "@dokploy/server/services/compose"; +import { getS3Credentials } from "../backups/utils"; +import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRestoreCommand } from "./utils"; +import type { apiRestoreBackup } from "@dokploy/server/db/schema"; +import type { z } from "zod"; + +interface DatabaseCredentials { + databaseUser?: string; + databasePassword?: string; +} + +export const restoreComposeBackup = async ( + compose: Compose, + destination: Destination, + backupInput: z.infer, + emit: (log: string) => void, +) => { + try { + if (backupInput.databaseType === "web-server") { + return; + } + const { serverId, appName, composeType } = compose; + + const rcloneFlags = getS3Credentials(destination); + const bucketPath = `:s3:${destination.bucket}`; + const backupPath = `${bucketPath}/${backupInput.backupFile}`; + let rcloneCommand = `rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip`; + + if (backupInput.metadata?.mongo) { + rcloneCommand = `rclone copy ${rcloneFlags.join(" ")} "${backupPath}"`; + } + + let credentials: DatabaseCredentials; + + switch (backupInput.databaseType) { + case "postgres": + credentials = { + databaseUser: backupInput.metadata?.postgres?.databaseUser, + }; + break; + case "mariadb": + credentials = { + databaseUser: backupInput.metadata?.mariadb?.databaseUser, + databasePassword: backupInput.metadata?.mariadb?.databasePassword, + }; + break; + case "mysql": + credentials = { + databasePassword: backupInput.metadata?.mysql?.databaseRootPassword, + }; + break; + case "mongo": + credentials = { + databaseUser: backupInput.metadata?.mongo?.databaseUser, + databasePassword: backupInput.metadata?.mongo?.databasePassword, + }; + break; + } + + const restoreCommand = getRestoreCommand({ + appName: appName, + serviceName: backupInput.metadata?.serviceName, + type: backupInput.databaseType, + credentials: { + database: backupInput.databaseName, + ...credentials, + }, + restoreType: composeType, + rcloneCommand, + }); + + emit("Starting restore..."); + emit(`Backup path: ${backupPath}`); + + emit(`Executing command: ${restoreCommand}`); + + if (serverId) { + await execAsyncRemote(serverId, restoreCommand); + } else { + await execAsync(restoreCommand); + } + + emit("Restore completed successfully!"); + } catch (error) { + console.error(error); + emit( + `Error: ${ + error instanceof Error ? error.message : "Error restoring mongo backup" + }`, + ); + throw new Error( + error instanceof Error ? error.message : "Error restoring mongo backup", + ); + } +}; diff --git a/packages/server/src/utils/restore/index.ts b/packages/server/src/utils/restore/index.ts index 972e1d1d..615e53d3 100644 --- a/packages/server/src/utils/restore/index.ts +++ b/packages/server/src/utils/restore/index.ts @@ -3,3 +3,4 @@ export { restoreMySqlBackup } from "./mysql"; export { restoreMariadbBackup } from "./mariadb"; export { restoreMongoBackup } from "./mongo"; export { restoreWebServerBackup } from "./web-server"; +export { restoreComposeBackup } from "./compose"; diff --git a/packages/server/src/utils/restore/mariadb.ts b/packages/server/src/utils/restore/mariadb.ts index 00080a2c..afc4a22b 100644 --- a/packages/server/src/utils/restore/mariadb.ts +++ b/packages/server/src/utils/restore/mariadb.ts @@ -1,42 +1,46 @@ import type { Destination } from "@dokploy/server/services/destination"; import type { Mariadb } from "@dokploy/server/services/mariadb"; import { getS3Credentials } from "../backups/utils"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRestoreCommand } from "./utils"; +import type { apiRestoreBackup } from "@dokploy/server/db/schema"; +import type { z } from "zod"; export const restoreMariadbBackup = async ( mariadb: Mariadb, destination: Destination, - database: string, - backupFile: string, + backupInput: z.infer, emit: (log: string) => void, ) => { try { - const { appName, databasePassword, databaseUser, serverId } = mariadb; + const { appName, serverId, databaseUser, databasePassword } = mariadb; const rcloneFlags = getS3Credentials(destination); const bucketPath = `:s3:${destination.bucket}`; - const backupPath = `${bucketPath}/${backupFile}`; + const backupPath = `${bucketPath}/${backupInput.backupFile}`; - const { Id: containerName } = serverId - ? await getRemoteServiceContainer(serverId, appName) - : await getServiceContainer(appName); + const rcloneCommand = `rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip`; - const restoreCommand = ` - rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mariadb -u ${databaseUser} -p${databasePassword} ${database} - `; + const command = getRestoreCommand({ + appName, + credentials: { + database: backupInput.databaseName, + databaseUser, + databasePassword, + }, + type: "mariadb", + rcloneCommand, + restoreType: "database", + }); emit("Starting restore..."); - emit(`Executing command: ${restoreCommand}`); + emit(`Executing command: ${command}`); if (serverId) { - await execAsyncRemote(serverId, restoreCommand); + await execAsyncRemote(serverId, command); } else { - await execAsync(restoreCommand); + await execAsync(command); } emit("Restore completed successfully!"); diff --git a/packages/server/src/utils/restore/mongo.ts b/packages/server/src/utils/restore/mongo.ts index 27471808..cb9e3583 100644 --- a/packages/server/src/utils/restore/mongo.ts +++ b/packages/server/src/utils/restore/mongo.ts @@ -1,17 +1,15 @@ import type { Destination } from "@dokploy/server/services/destination"; import type { Mongo } from "@dokploy/server/services/mongo"; import { getS3Credentials } from "../backups/utils"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRestoreCommand } from "./utils"; +import type { apiRestoreBackup } from "@dokploy/server/db/schema"; +import type { z } from "zod"; export const restoreMongoBackup = async ( mongo: Mongo, destination: Destination, - database: string, - backupFile: string, + backupInput: z.infer, emit: (log: string) => void, ) => { try { @@ -19,34 +17,30 @@ export const restoreMongoBackup = async ( const rcloneFlags = getS3Credentials(destination); const bucketPath = `:s3:${destination.bucket}`; - const backupPath = `${bucketPath}/${backupFile}`; + const backupPath = `${bucketPath}/${backupInput.backupFile}`; + const rcloneCommand = `rclone copy ${rcloneFlags.join(" ")} "${backupPath}"`; - const { Id: containerName } = serverId - ? await getRemoteServiceContainer(serverId, appName) - : await getServiceContainer(appName); - - // For MongoDB, we need to first download the backup file since mongorestore expects a directory - const tempDir = "/tmp/dokploy-restore"; - const fileName = backupFile.split("/").pop() || "backup.dump.gz"; - const decompressedName = fileName.replace(".gz", ""); - - const downloadCommand = `\ -rm -rf ${tempDir} && \ -mkdir -p ${tempDir} && \ -rclone copy ${rcloneFlags.join(" ")} "${backupPath}" ${tempDir} && \ -cd ${tempDir} && \ -gunzip -f "${fileName}" && \ -docker exec -i ${containerName} mongorestore --username ${databaseUser} --password ${databasePassword} --authenticationDatabase admin --db ${database} --archive < "${decompressedName}" && \ -rm -rf ${tempDir}`; + const command = getRestoreCommand({ + appName, + type: "mongo", + credentials: { + database: backupInput.databaseName, + databaseUser, + databasePassword, + }, + restoreType: "database", + rcloneCommand, + backupFile: backupInput.backupFile, + }); emit("Starting restore..."); - emit(`Executing command: ${downloadCommand}`); + emit(`Executing command: ${command}`); if (serverId) { - await execAsyncRemote(serverId, downloadCommand); + await execAsyncRemote(serverId, command); } else { - await execAsync(downloadCommand); + await execAsync(command); } emit("Restore completed successfully!"); diff --git a/packages/server/src/utils/restore/mysql.ts b/packages/server/src/utils/restore/mysql.ts index 8478ac96..103536bc 100644 --- a/packages/server/src/utils/restore/mysql.ts +++ b/packages/server/src/utils/restore/mysql.ts @@ -1,17 +1,15 @@ import type { Destination } from "@dokploy/server/services/destination"; import type { MySql } from "@dokploy/server/services/mysql"; import { getS3Credentials } from "../backups/utils"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRestoreCommand } from "./utils"; +import type { apiRestoreBackup } from "@dokploy/server/db/schema"; +import type { z } from "zod"; export const restoreMySqlBackup = async ( mysql: MySql, destination: Destination, - database: string, - backupFile: string, + backupInput: z.infer, emit: (log: string) => void, ) => { try { @@ -19,24 +17,29 @@ export const restoreMySqlBackup = async ( const rcloneFlags = getS3Credentials(destination); const bucketPath = `:s3:${destination.bucket}`; - const backupPath = `${bucketPath}/${backupFile}`; + const backupPath = `${bucketPath}/${backupInput.backupFile}`; - const { Id: containerName } = serverId - ? await getRemoteServiceContainer(serverId, appName) - : await getServiceContainer(appName); + const rcloneCommand = `rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip`; - const restoreCommand = ` - rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mysql -u root -p${databaseRootPassword} ${database} - `; + const command = getRestoreCommand({ + appName, + type: "mysql", + credentials: { + database: backupInput.databaseName, + databasePassword: databaseRootPassword, + }, + restoreType: "database", + rcloneCommand, + }); emit("Starting restore..."); - emit(`Executing command: ${restoreCommand}`); + emit(`Executing command: ${command}`); if (serverId) { - await execAsyncRemote(serverId, restoreCommand); + await execAsyncRemote(serverId, command); } else { - await execAsync(restoreCommand); + await execAsync(command); } emit("Restore completed successfully!"); diff --git a/packages/server/src/utils/restore/postgres.ts b/packages/server/src/utils/restore/postgres.ts index 5ab7c74e..68ced8a5 100644 --- a/packages/server/src/utils/restore/postgres.ts +++ b/packages/server/src/utils/restore/postgres.ts @@ -1,17 +1,15 @@ import type { Destination } from "@dokploy/server/services/destination"; import type { Postgres } from "@dokploy/server/services/postgres"; import { getS3Credentials } from "../backups/utils"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRestoreCommand } from "./utils"; +import type { apiRestoreBackup } from "@dokploy/server/db/schema"; +import type { z } from "zod"; export const restorePostgresBackup = async ( postgres: Postgres, destination: Destination, - database: string, - backupFile: string, + backupInput: z.infer, emit: (log: string) => void, ) => { try { @@ -20,30 +18,30 @@ export const restorePostgresBackup = async ( const rcloneFlags = getS3Credentials(destination); const bucketPath = `:s3:${destination.bucket}`; - const backupPath = `${bucketPath}/${backupFile}`; + const backupPath = `${bucketPath}/${backupInput.backupFile}`; - const { Id: containerName } = serverId - ? await getRemoteServiceContainer(serverId, appName) - : await getServiceContainer(appName); + const rcloneCommand = `rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip`; emit("Starting restore..."); emit(`Backup path: ${backupPath}`); - const command = `\ -rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} pg_restore -U ${databaseUser} -d ${database} --clean --if-exists`; + const command = getRestoreCommand({ + appName, + credentials: { + database: backupInput.databaseName, + databaseUser, + }, + type: "postgres", + rcloneCommand, + restoreType: "database", + }); emit(`Executing command: ${command}`); if (serverId) { - const { stdout, stderr } = await execAsyncRemote(serverId, command); - emit(stdout); - emit(stderr); + await execAsyncRemote(serverId, command); } else { - const { stdout, stderr } = await execAsync(command); - console.log("stdout", stdout); - console.log("stderr", stderr); - emit(stdout); - emit(stderr); + await execAsync(command); } emit("Restore completed successfully!"); diff --git a/packages/server/src/utils/restore/utils.ts b/packages/server/src/utils/restore/utils.ts new file mode 100644 index 00000000..822993f4 --- /dev/null +++ b/packages/server/src/utils/restore/utils.ts @@ -0,0 +1,131 @@ +import { + getComposeContainerCommand, + getServiceContainerCommand, +} from "../backups/utils"; + +export const getPostgresRestoreCommand = ( + database: string, + databaseUser: string, +) => { + return `docker exec -i $CONTAINER_ID sh -c "pg_restore -U ${databaseUser} -d ${database} --clean --if-exists"`; +}; + +export const getMariadbRestoreCommand = ( + database: string, + databaseUser: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID sh -c "mariadb -u ${databaseUser} -p${databasePassword} ${database}"`; +}; + +export const getMysqlRestoreCommand = ( + database: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID sh -c "mysql -u root -p${databasePassword} ${database}"`; +}; + +export const getMongoRestoreCommand = ( + database: string, + databaseUser: string, + databasePassword: string, +) => { + return `docker exec -i $CONTAINER_ID sh -c "mongorestore --username ${databaseUser} --password ${databasePassword} --authenticationDatabase admin --db ${database} --archive"`; +}; + +export const getComposeSearchCommand = ( + appName: string, + type: "stack" | "docker-compose" | "database", + serviceName?: string, +) => { + if (type === "database") { + return getServiceContainerCommand(appName || ""); + } + return getComposeContainerCommand(appName || "", serviceName || "", type); +}; + +interface DatabaseCredentials { + database: string; + databaseUser?: string; + databasePassword?: string; +} + +const generateRestoreCommand = ( + type: "postgres" | "mariadb" | "mysql" | "mongo", + credentials: DatabaseCredentials, +) => { + const { database, databaseUser, databasePassword } = credentials; + switch (type) { + case "postgres": + return getPostgresRestoreCommand(database, databaseUser || ""); + case "mariadb": + return getMariadbRestoreCommand( + database, + databaseUser || "", + databasePassword || "", + ); + case "mysql": + return getMysqlRestoreCommand(database, databasePassword || ""); + case "mongo": + return getMongoRestoreCommand( + database, + databaseUser || "", + databasePassword || "", + ); + } +}; + +const getMongoSpecificCommand = ( + rcloneCommand: string, + restoreCommand: string, + backupFile: string, +): string => { + const tempDir = "/tmp/dokploy-restore"; + const fileName = backupFile.split("/").pop() || "backup.dump.gz"; + const decompressedName = fileName.replace(".gz", ""); + return ` +rm -rf ${tempDir} && \ +mkdir -p ${tempDir} && \ +${rcloneCommand} ${tempDir} && \ +cd ${tempDir} && \ +gunzip -f "${fileName}" && \ +${restoreCommand} < "${decompressedName}" && \ +rm -rf ${tempDir} + `; +}; + +interface RestoreOptions { + appName: string; + type: "postgres" | "mariadb" | "mysql" | "mongo"; + restoreType: "stack" | "docker-compose" | "database"; + credentials: DatabaseCredentials; + serviceName?: string; + rcloneCommand: string; + backupFile?: string; +} + +export const getRestoreCommand = ({ + appName, + type, + restoreType, + credentials, + serviceName, + rcloneCommand, + backupFile, +}: RestoreOptions) => { + const containerSearch = getComposeSearchCommand( + appName, + restoreType, + serviceName, + ); + const restoreCommand = generateRestoreCommand(type, credentials); + let cmd = `CONTAINER_ID=$(${containerSearch})`; + + if (type !== "mongo") { + cmd += ` && ${rcloneCommand} | ${restoreCommand}`; + } else { + cmd += ` && ${getMongoSpecificCommand(rcloneCommand, restoreCommand, backupFile || "")}`; + } + + return cmd; +}; diff --git a/packages/server/src/utils/schedules/index.ts b/packages/server/src/utils/schedules/index.ts new file mode 100644 index 00000000..0f9b167e --- /dev/null +++ b/packages/server/src/utils/schedules/index.ts @@ -0,0 +1,28 @@ +import { db } from "../../db/index"; +import { schedules } from "@dokploy/server/db/schema"; +import { eq } from "drizzle-orm"; +import { scheduleJob } from "./utils"; + +export const initSchedules = async () => { + try { + const schedulesResult = await db.query.schedules.findMany({ + where: eq(schedules.enabled, true), + with: { + server: true, + application: true, + compose: true, + user: true, + }, + }); + + console.log(`Initializing ${schedulesResult.length} schedules`); + for (const schedule of schedulesResult) { + scheduleJob(schedule); + console.log( + `Initialized schedule: ${schedule.name} ${schedule.scheduleType} ✅`, + ); + } + } catch (error) { + console.log(`Error initializing schedules: ${error}`); + } +}; diff --git a/packages/server/src/utils/schedules/utils.ts b/packages/server/src/utils/schedules/utils.ts new file mode 100644 index 00000000..d1f5ef78 --- /dev/null +++ b/packages/server/src/utils/schedules/utils.ts @@ -0,0 +1,149 @@ +import type { Schedule } from "@dokploy/server/db/schema/schedule"; +import { findScheduleById } from "@dokploy/server/services/schedule"; +import { scheduledJobs, scheduleJob as scheduleJobNode } from "node-schedule"; +import { getComposeContainer, getServiceContainer } from "../docker/utils"; +import { execAsyncRemote } from "../process/execAsync"; +import { spawnAsync } from "../process/spawnAsync"; +import { createDeploymentSchedule } from "@dokploy/server/services/deployment"; +import { createWriteStream } from "node:fs"; +import { updateDeploymentStatus } from "@dokploy/server/services/deployment"; +import { paths } from "@dokploy/server/constants"; +import path from "node:path"; + +export const scheduleJob = (schedule: Schedule) => { + const { cronExpression, scheduleId } = schedule; + + scheduleJobNode(scheduleId, cronExpression, async () => { + await runCommand(scheduleId); + }); +}; + +export const removeScheduleJob = (scheduleId: string) => { + const currentJob = scheduledJobs[scheduleId]; + currentJob?.cancel(); +}; + +export const runCommand = async (scheduleId: string) => { + const { + application, + command, + shellType, + scheduleType, + compose, + serviceName, + appName, + serverId, + } = await findScheduleById(scheduleId); + + const deployment = await createDeploymentSchedule({ + scheduleId, + title: "Schedule", + description: "Schedule", + }); + + if (scheduleType === "application" || scheduleType === "compose") { + let containerId = ""; + let serverId = ""; + if (scheduleType === "application" && application) { + const container = await getServiceContainer( + application.appName, + application.serverId, + ); + containerId = container?.Id || ""; + serverId = application.serverId || ""; + } + if (scheduleType === "compose" && compose) { + const container = await getComposeContainer(compose, serviceName || ""); + containerId = container?.Id || ""; + serverId = compose.serverId || ""; + } + + if (serverId) { + try { + await execAsyncRemote( + serverId, + ` + set -e + echo "Running command: docker exec ${containerId} ${shellType} -c '${command}'" >> ${deployment.logPath}; + docker exec ${containerId} ${shellType} -c '${command}' >> ${deployment.logPath} 2>> ${deployment.logPath} || { + echo "❌ Command failed" >> ${deployment.logPath}; + exit 1; + } + echo "✅ Command executed successfully" >> ${deployment.logPath}; + `, + ); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; + } + } else { + const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); + + try { + writeStream.write( + `docker exec ${containerId} ${shellType} -c ${command}\n`, + ); + await spawnAsync( + "docker", + ["exec", containerId, shellType, "-c", command], + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + ); + + writeStream.write("✅ Command executed successfully\n"); + } catch (error) { + writeStream.write("❌ Command failed\n"); + writeStream.write( + error instanceof Error ? error.message : "Unknown error", + ); + writeStream.end(); + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; + } + } + } else if (scheduleType === "dokploy-server") { + try { + const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); + const { SCHEDULES_PATH } = paths(); + const fullPath = path.join(SCHEDULES_PATH, appName || ""); + + await spawnAsync( + "bash", + ["-c", "./script.sh"], + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + { + cwd: fullPath, + }, + ); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; + } + } else if (scheduleType === "server") { + try { + const { SCHEDULES_PATH } = paths(true); + const fullPath = path.join(SCHEDULES_PATH, appName || ""); + const command = ` + set -e + echo "Running script" >> ${deployment.logPath}; + bash -c ${fullPath}/script.sh >> ${deployment.logPath} 2>> ${deployment.logPath} || { + echo "❌ Command failed" >> ${deployment.logPath}; + exit 1; + } + echo "✅ Command executed successfully" >> ${deployment.logPath}; + `; + await execAsyncRemote(serverId, command); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + throw error; + } + } + await updateDeploymentStatus(deployment.deploymentId, "done"); +};