From ce9ba60902d5d9e8ec279c3f2928e97b83be434c Mon Sep 17 00:00:00 2001 From: user Date: Fri, 30 Jan 2026 20:23:32 +0300 Subject: [PATCH 01/27] impl --- .../__test__/deploy/application.real.test.ts | 126 + .../patches/patch.integration.test.ts | 106 + .../dashboard/application/patches/index.ts | 2 + .../application/patches/patch-editor.tsx | 235 + .../application/patches/show-patches.tsx | 205 + .../servers/actions/show-storage-actions.tsx | 26 +- .../services/application/[applicationId].tsx | 9 + .../services/compose/[composeId].tsx | 10 + apps/dokploy/server/api/root.ts | 2 + apps/dokploy/server/api/routers/patch.ts | 502 + openapi.json | 42831 ++++++++-------- packages/server/src/constants/index.ts | 1 + packages/server/src/db/schema/application.ts | 2 + packages/server/src/db/schema/compose.ts | 2 + packages/server/src/db/schema/index.ts | 1 + packages/server/src/db/schema/patch.ts | 95 + packages/server/src/index.ts | 2 + packages/server/src/services/application.ts | 18 + packages/server/src/services/compose.ts | 24 + packages/server/src/services/patch-repo.ts | 308 + packages/server/src/services/patch.ts | 295 + 21 files changed, 24797 insertions(+), 20005 deletions(-) create mode 100644 apps/dokploy/__test__/patches/patch.integration.test.ts create mode 100644 apps/dokploy/components/dashboard/application/patches/index.ts create mode 100644 apps/dokploy/components/dashboard/application/patches/patch-editor.tsx create mode 100644 apps/dokploy/components/dashboard/application/patches/show-patches.tsx create mode 100644 apps/dokploy/server/api/routers/patch.ts create mode 100644 packages/server/src/db/schema/patch.ts create mode 100644 packages/server/src/services/patch-repo.ts create mode 100644 packages/server/src/services/patch.ts diff --git a/apps/dokploy/__test__/deploy/application.real.test.ts b/apps/dokploy/__test__/deploy/application.real.test.ts index 43ff07836..3831212d7 100644 --- a/apps/dokploy/__test__/deploy/application.real.test.ts +++ b/apps/dokploy/__test__/deploy/application.real.test.ts @@ -1,3 +1,4 @@ + import { existsSync } from "node:fs"; import path from "node:path"; import type { ApplicationNested } from "@dokploy/server"; @@ -8,6 +9,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const REAL_TEST_TIMEOUT = 180000; // 3 minutes +// Mock constants to avoid load error +vi.mock("@dokploy/server/constants", () => ({ + paths: () => ({ + LOGS_PATH: "/tmp/dokploy-test-real/logs", + APPLICATIONS_PATH: "/tmp/dokploy-test-real/applications", + PATCH_REPOS_PATH: "/tmp/dokploy-test-real/patch-repos", + }), + IS_CLOUD: false, + docker: {}, +})); + // Mock ONLY database and notifications vi.mock("@dokploy/server/db", () => { const createChainableMock = (): any => { @@ -67,6 +79,16 @@ vi.mock("@dokploy/server/services/rollbacks", () => ({ createRollback: vi.fn(), })); +vi.mock("@dokploy/server/services/patch", async (importOriginal) => { + const actual = await importOriginal< + typeof import("@dokploy/server/services/patch") + >(); + return { + ...actual, + findPatchesByApplicationId: vi.fn().mockResolvedValue([]), + }; +}); + // NOT mocked (executed for real): // - execAsync // - cloneGitRepository @@ -78,6 +100,11 @@ import * as adminService from "@dokploy/server/services/admin"; import * as applicationService from "@dokploy/server/services/application"; import { deployApplication } from "@dokploy/server/services/application"; import * as deploymentService from "@dokploy/server/services/deployment"; +import * as patchService from "@dokploy/server/services/patch"; +import { generatePatch } from "@dokploy/server/services/patch"; +import { mkdtemp, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; const createMockApplication = ( overrides: Partial = {}, @@ -474,6 +501,105 @@ describe( }, REAL_TEST_TIMEOUT, ); + it( + "should REALLY apply patches from database during deployment", + async () => { + // 1. Setup local temporary git repo + const tempRepo = await mkdtemp(join(tmpdir(), "real-patch-repo-")); + // Helper for local git commands + const execLocal = async (cmd: string) => execAsync(cmd, { cwd: tempRepo }); + + await execLocal("git init"); + await execLocal("git config user.email 'test@dokploy.com'"); + await execLocal("git config user.name 'Dokploy Test'"); + + // Create a simple Dockerfile and server script + // We use a simple python server to verify output + await writeFile(join(tempRepo, "app.py"), "print('Original App')\n"); + await writeFile( + join(tempRepo, "Dockerfile"), + "FROM python:3.9-slim\nCOPY app.py .\nCMD [\"python\", \"app.py\"]\n", + ); + + await execLocal("git add ."); + await execLocal("git commit -m 'Initial commit'"); + // Ensure master/main branch exists (git init might create master or main depending on config) + // We force create a branch named 'main' to be consistent + await execLocal("git checkout -b main || git checkout main"); + + // 2. Mock Application to use this local repo + const patchAppName = `real-patch-app-${Date.now()}`; + const patchApp = createMockApplication({ + appName: patchAppName, + buildType: "dockerfile", + customGitUrl: `file://${tempRepo}`, + customGitBranch: "main", + dockerfile: "Dockerfile", + }); + currentAppName = patchAppName; + allTestAppNames.push(patchAppName); + + // Setup standard mocks + vi.mocked(db.query.applications.findFirst).mockResolvedValue( + patchApp as any, + ); + vi.mocked(applicationService.findApplicationById).mockResolvedValue( + patchApp as any, + ); + + // 3. Generate a patch + // We modify the file, generate patch, and then reset. + const newContent = "print('Patched App')\n"; + const patchContent = await generatePatch({ + codePath: tempRepo, + filePath: "app.py", + newContent, + serverId: null, + }); + + // 4. Mock patch service to return this patch + vi.mocked(patchService.findPatchesByApplicationId).mockResolvedValue([ + { + patchId: "test-patch-1", + applicationId: "test-app-id", + composeId: null, + filePath: "app.py", + content: patchContent, + enabled: true, + createdAt: new Date().toISOString(), + } as any, + ]); + + console.log(`\nšŸš€ Testing deployment with patch: ${currentAppName}`); + + // 5. Deploy + const result = await deployApplication({ + applicationId: "test-app-id", + titleLog: "Real Patch Test", + descriptionLog: "Testing patch application", + }); + + expect(result).toBe(true); + + // 6. Verify Log contains "Applying patch" + const { stdout: logContent } = await execAsync( + `cat ${currentDeployment.logPath}`, + ); + // The implementation logs "Applying patch: ..." + expect(logContent).toContain("Applying patch"); + expect(logContent).toContain("app.py"); + console.log("āœ… Verified patch execution logs"); + + // 7. Verify the deployed image contains the patched code + // We run the image and check output + const { stdout: runOutput } = await execAsync( + `docker run --rm ${patchAppName}`, + ); + expect(runOutput.trim()).toBe("Patched App"); + console.log("āœ… Verified patched output:", runOutput.trim()); + }, + REAL_TEST_TIMEOUT, + ); }, REAL_TEST_TIMEOUT, ); diff --git a/apps/dokploy/__test__/patches/patch.integration.test.ts b/apps/dokploy/__test__/patches/patch.integration.test.ts new file mode 100644 index 000000000..2e370022e --- /dev/null +++ b/apps/dokploy/__test__/patches/patch.integration.test.ts @@ -0,0 +1,106 @@ + +import { generatePatch } from "@dokploy/server/services/patch"; +import { describe, expect, it, afterEach } from "vitest"; +import { mkdtemp, rm, writeFile, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { exec } from "node:child_process"; +import { promisify } from "node:util"; + +const execAsyncLocal = promisify(exec); + +describe("Patch System Integration", () => { + let tempDir: string; + + afterEach(async () => { + if (tempDir) { + await rm(tempDir, { recursive: true, force: true }); + } + }); + + it("should generate a patch that can be successfully applied via git", async () => { + // Setup repo + tempDir = await mkdtemp(join(tmpdir(), "dokploy-patch-test-")); + const fileName = "test.txt"; + const filePath = join(tempDir, fileName); + + await execAsyncLocal("git init", { cwd: tempDir }); + await execAsyncLocal("git config user.email 'test@test.com'", { cwd: tempDir }); + await execAsyncLocal("git config user.name 'Test'", { cwd: tempDir }); + + // Original content + await writeFile(filePath, "line1\nline2\n"); + await execAsyncLocal(`git add ${fileName}`, { cwd: tempDir }); + await execAsyncLocal("git commit -m 'init'", { cwd: tempDir }); + + // Generate patch (modify content) + const newContent = "line1\nline2\nline3\n"; + const patchContent = await generatePatch({ + codePath: tempDir, + filePath: fileName, + newContent, + serverId: null, + }); + + // Verify patch format + expect(patchContent.endsWith("\n")).toBe(true); + + // Reset file (generatePatch does reset, but ensure it) + await execAsyncLocal("git checkout .", { cwd: tempDir }); + const savedContent = await readFile(filePath, "utf-8"); + expect(savedContent).toBe("line1\nline2\n"); + + // Apply patch verification + // We simulate what Deployment Service does: write patch to file and run git apply + const patchFile = join(tempDir, "changes.patch"); + await writeFile(patchFile, patchContent); + + try { + await execAsyncLocal(`git apply --whitespace=fix ${patchFile}`, { cwd: tempDir }); + } catch (e: any) { + console.error("Git apply failed:", e.message); + console.log("Patch content:", JSON.stringify(patchContent)); + throw e; + } + + const appliedContent = await readFile(filePath, "utf-8"); + expect(appliedContent).toBe(newContent); + }); + + it("should handle files created without trailing newline", async () => { + // Setup repo + tempDir = await mkdtemp(join(tmpdir(), "dokploy-patch-test-noline-")); + const fileName = "noline.txt"; + const filePath = join(tempDir, fileName); + + await execAsyncLocal("git init", { cwd: tempDir }); + await execAsyncLocal("git config user.email 'test@test.com'", { cwd: tempDir }); + await execAsyncLocal("git config user.name 'Test'", { cwd: tempDir }); + + // Original content WITHOUT newline + await writeFile(filePath, "line1"); + await execAsyncLocal(`git add ${fileName}`, { cwd: tempDir }); + await execAsyncLocal("git commit -m 'init'", { cwd: tempDir }); + + // Generate patch + const newContent = "line1\nline2"; + const patchContent = await generatePatch({ + codePath: tempDir, + filePath: fileName, + newContent, + serverId: null, + }); + + // Verify patch format + expect(patchContent.endsWith("\n")).toBe(true); + + // Apply patch + const patchFile = join(tempDir, "changes.patch"); + await writeFile(patchFile, patchContent); + + await execAsyncLocal(`git apply --whitespace=fix ${patchFile}`, { cwd: tempDir }); + + const appliedContent = await readFile(filePath, "utf-8"); + expect(appliedContent).toBe(newContent); + }); +}); diff --git a/apps/dokploy/components/dashboard/application/patches/index.ts b/apps/dokploy/components/dashboard/application/patches/index.ts new file mode 100644 index 000000000..1854bd3e5 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/patches/index.ts @@ -0,0 +1,2 @@ +export * from "./show-patches"; +export * from "./patch-editor"; diff --git a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx new file mode 100644 index 000000000..bd7e6e83a --- /dev/null +++ b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx @@ -0,0 +1,235 @@ +import { ArrowLeft, ChevronRight, File, Folder, Loader2, Save } from "lucide-react"; +import { useCallback, useState } from "react"; +import { toast } from "sonner"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { api } from "@/utils/api"; +import type { RouterOutputs } from "@/utils/api"; + +interface Props { + applicationId?: string; + composeId?: string; + repoPath: string; + onClose: () => void; +} + +type DirectoryEntry = { + name: string; + path: string; + type: "file" | "directory"; + children?: DirectoryEntry[]; +}; + +export const PatchEditor = ({ + applicationId, + composeId, + repoPath, + onClose, +}: Props) => { + const [selectedFile, setSelectedFile] = useState(null); + const [fileContent, setFileContent] = useState(""); + const [originalContent, setOriginalContent] = useState(""); + const [expandedFolders, setExpandedFolders] = useState>(new Set()); + const [isSaving, setIsSaving] = useState(false); + + // Fetch directory tree + const { data: directories, isLoading: isDirLoading } = + api.patch.readRepoDirectories.useQuery( + { applicationId, composeId, repoPath }, + { enabled: !!repoPath }, + ); + + // Save mutation + const saveAsPatch = api.patch.saveFileAsPatch.useMutation({ + onSuccess: (result) => { + setIsSaving(false); + if (result.deleted) { + toast.success("No changes - patch removed"); + } else { + toast.success("Patch saved"); + } + setOriginalContent(fileContent); + }, + onError: () => { + setIsSaving(false); + toast.error("Failed to save patch"); + }, + }); + + // Read file content when selected + const { data: fileData, isFetching: isFileLoading } = + api.patch.readRepoFile.useQuery( + { + applicationId, + composeId, + repoPath, + filePath: selectedFile || "", + }, + { + enabled: !!selectedFile, + onSuccess: (data) => { + setFileContent(data.content); + setOriginalContent(data.content); + if (data.patchError) { + toast.error(data.patchErrorMessage || "Failed to apply patch"); + } + }, + }, + ); + + const handleFileSelect = (filePath: string) => { + setSelectedFile(filePath); + }; + + const toggleFolder = (path: string) => { + setExpandedFolders((prev) => { + const next = new Set(prev); + if (next.has(path)) { + next.delete(path); + } else { + next.add(path); + } + return next; + }); + }; + + const handleSave = () => { + if (!selectedFile) return; + setIsSaving(true); + saveAsPatch.mutate({ + applicationId, + composeId, + repoPath, + filePath: selectedFile, + content: fileContent, + }); + }; + + const hasChanges = fileContent !== originalContent; + + const renderTree = useCallback( + (entries: DirectoryEntry[], depth = 0) => { + return entries + .sort((a, b) => { + // Directories first, then alphabetically + if (a.type !== b.type) { + return a.type === "directory" ? -1 : 1; + } + return a.name.localeCompare(b.name); + }) + .map((entry) => { + const isExpanded = expandedFolders.has(entry.path); + const isSelected = selectedFile === entry.path; + + if (entry.type === "directory") { + return ( +
+ + {isExpanded && entry.children && ( +
{renderTree(entry.children, depth + 1)}
+ )} +
+ ); + } + + return ( + + ); + }); + }, + [expandedFolders, selectedFile], + ); + + return ( + + +
+ +
+ Edit File + + {selectedFile + ? `Editing: ${selectedFile}` + : "Select a file from the tree to edit"} + +
+
+ {selectedFile && ( + + )} +
+ +
+ {/* File Tree */} +
+ +
+ {isDirLoading ? ( +
+ +
+ ) : directories ? ( + renderTree(directories) + ) : ( +
+ No files found +
+ )} +
+
+
+ {/* Editor */} +
+ {isFileLoading ? ( +
+ +
+ ) : selectedFile ? ( + setFileContent(value || "")} + className="h-full w-full" + wrapperClassName="h-full" + lineWrapping + /> + ) : ( +
+ Select a file to edit +
+ )} +
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx new file mode 100644 index 000000000..e42269886 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx @@ -0,0 +1,205 @@ +import { AlertCircle, ChevronRight, File, Folder, Loader2, Power, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Switch } from "@/components/ui/switch"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { api } from "@/utils/api"; +import type { RouterOutputs } from "@/utils/api"; +import { PatchEditor } from "./patch-editor"; + +interface Props { + applicationId?: string; + composeId?: string; +} + +type Patch = RouterOutputs["patch"]["byApplicationId"][number]; + +export const ShowPatches = ({ applicationId, composeId }: Props) => { + const [selectedFile, setSelectedFile] = useState(null); + const [repoPath, setRepoPath] = useState(null); + const [isLoadingRepo, setIsLoadingRepo] = useState(false); + + const utils = api.useUtils(); + + // Fetch patches + // Fetch patches + const { data: appPatches, isLoading: isAppPatchesLoading } = + api.patch.byApplicationId.useQuery( + { applicationId: applicationId! }, + { enabled: !!applicationId }, + ); + + const { data: composePatches, isLoading: isComposePatchesLoading } = + api.patch.byComposeId.useQuery( + { composeId: composeId! }, + { enabled: !!composeId }, + ); + + const patches = applicationId ? appPatches : composePatches; + const isPatchesLoading = applicationId + ? isAppPatchesLoading + : isComposePatchesLoading; + + // Mutations + const deletePatch = api.patch.delete.useMutation({ + onSuccess: () => { + toast.success("Patch deleted"); + if (applicationId) { + utils.patch.byApplicationId.invalidate({ applicationId }); + } else if (composeId) { + utils.patch.byComposeId.invalidate({ composeId }); + } + }, + onError: () => { + toast.error("Failed to delete patch"); + }, + }); + + const togglePatch = api.patch.toggleEnabled.useMutation({ + onSuccess: () => { + toast.success("Patch updated"); + if (applicationId) { + utils.patch.byApplicationId.invalidate({ applicationId }); + } else if (composeId) { + utils.patch.byComposeId.invalidate({ composeId }); + } + }, + onError: () => { + toast.error("Failed to update patch"); + }, + }); + + const ensureRepo = api.patch.ensureRepo.useMutation(); + + const handleOpenEditor = async () => { + setIsLoadingRepo(true); + const toastId = toast.loading("Syncing repository..."); + ensureRepo.mutate( + { applicationId, composeId }, + { + onSuccess: (path) => { + setRepoPath(path); + setIsLoadingRepo(false); + toast.dismiss(toastId); + }, + onError: () => { + setIsLoadingRepo(false); + toast.dismiss(toastId); + toast.error("Failed to load repository"); + }, + }, + ); + }; + + const handleDeletePatch = (patchId: string) => { + deletePatch.mutate({ patchId }); + }; + + const handleTogglePatch = (patchId: string, enabled: boolean) => { + togglePatch.mutate({ patchId, enabled }); + }; + + const handleCloseEditor = () => { + setSelectedFile(null); + setRepoPath(null); + if (applicationId) { + utils.patch.byApplicationId.invalidate({ applicationId }); + } else if (composeId) { + utils.patch.byComposeId.invalidate({ composeId }); + } + }; + + if (repoPath) { + return ( + + ); + } + + return ( + + +
+ Patches + + Apply code patches to your repository during build. Patches are applied after + cloning the repository and before building. + +
+ +
+ + {isPatchesLoading ? ( +
+ +
+ ) : !patches || patches.length === 0 ? ( + + + No patches + + No patches have been created for this application yet. Click "Create Patch" + to add modifications to your code during build. + + + ) : ( + + + + File Path + Enabled + Actions + + + + {patches.map((patch: Patch) => ( + + +
+ + {patch.filePath} +
+
+ + + handleTogglePatch(patch.patchId, checked) + } + /> + + + + +
+ ))} +
+
+ )} +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx index c80648142..5bf3b8fc6 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx @@ -42,6 +42,9 @@ export const ShowStorageActions = ({ serverId }: Props) => { isLoading: cleanStoppedContainersIsLoading, } = api.settings.cleanStoppedContainers.useMutation(); + const { mutateAsync: cleanPatchRepos, isLoading: cleanPatchReposIsLoading } = + api.patch.cleanPatchRepos.useMutation(); + return ( { cleanDockerBuilderIsLoading || cleanUnusedImagesIsLoading || cleanUnusedVolumesIsLoading || - cleanStoppedContainersIsLoading + cleanStoppedContainersIsLoading || + cleanPatchReposIsLoading } > + {patches && patches?.length > 0 && ( + + )} {isPatchesLoading ? (
- ) : !patches || patches.length === 0 ? ( - - - No patches - - No patches have been created for this application yet. Click - "Create Patch" to add modifications to your code during build. - - + ) : patches?.length === 0 ? ( +
+
+ +
+
+

No patches yet

+

+ Add file patches to modify your repo before each build—configs, + env, or code. Create your first patch to get started. +

+
+ +
) : ( @@ -183,7 +147,7 @@ export const ShowPatches = ({ applicationId, composeId }: Props) => { - {patches.map((patch: Patch) => ( + {patches?.map((patch) => (
@@ -194,16 +158,49 @@ export const ShowPatches = ({ applicationId, composeId }: Props) => { - handleTogglePatch(patch.patchId, checked) - } + onCheckedChange={(checked) => { + togglePatch + .mutateAsync({ + patchId: patch.patchId, + enabled: checked, + }) + .then(() => { + toast.success("Patch updated"); + utils.patch.byApplicationId.invalidate({ + applicationId: id, + }); + utils.patch.byComposeId.invalidate({ + composeId: id, + }); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(() => { + setIsLoadingRepo(false); + }); + }} /> diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx index c075bfa89..c17f92052 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx @@ -26,11 +26,11 @@ import { ShowDomains } from "@/components/dashboard/application/domains/show-dom import { ShowEnvironment } from "@/components/dashboard/application/environment/show"; import { ShowGeneralApplication } from "@/components/dashboard/application/general/show"; import { ShowDockerLogs } from "@/components/dashboard/application/logs/show"; +import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; 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 { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups"; -import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring"; import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring"; @@ -365,7 +365,7 @@ const Service = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index 5423bb86c..3b731ee08 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -17,9 +17,9 @@ import { ShowVolumes } from "@/components/dashboard/application/advanced/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 { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules"; import { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups"; -import { ShowPatches } from "@/components/dashboard/application/patches/show-patches"; import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; import { IsolatedDeploymentTab } from "@/components/dashboard/compose/advanced/add-isolation"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; @@ -367,7 +367,7 @@ const Service = (
- +
diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index f5c10946a..9deae22e4 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -6,10 +6,10 @@ import { ensurePatchRepo, findApplicationById, findComposeById, + findPatchByFilePath, findPatchById, findPatchesByApplicationId, findPatchesByComposeId, - findPatchByFilePath, generatePatch, readPatchRepoDirectory, readPatchRepoFile, @@ -218,13 +218,13 @@ export const patchRouter = createTRPCRouter({ ensureRepo: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), }), ) .mutation(async ({ input, ctx }) => { - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -253,8 +253,8 @@ export const patchRouter = createTRPCRouter({ }); } - if (input.composeId) { - const compose = await findComposeById(input.composeId); + if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -285,21 +285,21 @@ export const patchRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); }), readRepoDirectories: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), }), ) .query(async ({ input, ctx }) => { - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -312,8 +312,8 @@ export const patchRouter = createTRPCRouter({ return await readPatchRepoDirectory(input.repoPath, app.serverId); } - if (input.composeId) { - const compose = await findComposeById(input.composeId); + if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -328,15 +328,15 @@ export const patchRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); }), readRepoFile: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), filePath: z.string(), }), @@ -345,8 +345,8 @@ export const patchRouter = createTRPCRouter({ let serverId: string | null = null; let patchContent: string | undefined; - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -361,14 +361,14 @@ export const patchRouter = createTRPCRouter({ // Check if patch exists for this file const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, + input.id, undefined, ); if (existingPatch?.enabled) { patchContent = existingPatch.content; } - } else if (input.composeId) { - const compose = await findComposeById(input.composeId); + } else if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -384,7 +384,7 @@ export const patchRouter = createTRPCRouter({ const existingPatch = await findPatchByFilePath( input.filePath, undefined, - input.composeId, + input.id, ); if (existingPatch?.enabled) { patchContent = existingPatch.content; @@ -407,8 +407,8 @@ export const patchRouter = createTRPCRouter({ saveFileAsPatch: protectedProcedure .input( z.object({ - applicationId: z.string().optional(), - composeId: z.string().optional(), + id: z.string(), + type: z.enum(["application", "compose"]), repoPath: z.string(), filePath: z.string(), content: z.string(), @@ -417,8 +417,8 @@ export const patchRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { let serverId: string | null = null; - if (input.applicationId) { - const app = await findApplicationById(input.applicationId); + if (input.type === "application") { + const app = await findApplicationById(input.id); if ( app.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -429,8 +429,8 @@ export const patchRouter = createTRPCRouter({ }); } serverId = app.serverId; - } else if (input.composeId) { - const compose = await findComposeById(input.composeId); + } else if (input.type === "compose") { + const compose = await findComposeById(input.id); if ( compose.environment.project.organizationId !== ctx.session.activeOrganizationId @@ -444,7 +444,7 @@ export const patchRouter = createTRPCRouter({ } else { throw new TRPCError({ code: "BAD_REQUEST", - message: "Either applicationId or composeId must be provided", + message: "Either application or compose must be provided", }); } @@ -460,8 +460,8 @@ export const patchRouter = createTRPCRouter({ // No changes - remove existing patch if any const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, - input.composeId, + input.id, + input.id, ); if (existingPatch) { await deletePatch(existingPatch.patchId); @@ -472,8 +472,8 @@ export const patchRouter = createTRPCRouter({ // Check if patch exists const existingPatch = await findPatchByFilePath( input.filePath, - input.applicationId, - input.composeId, + input.id, + input.id, ); if (existingPatch) { @@ -487,8 +487,8 @@ export const patchRouter = createTRPCRouter({ filePath: input.filePath, content: patchContent, enabled: true, - applicationId: input.applicationId, - composeId: input.composeId, + applicationId: input.id, + composeId: input.id, }); return { deleted: false, patchId: newPatch.patchId }; diff --git a/packages/server/src/services/patch-repo.ts b/packages/server/src/services/patch-repo.ts index c4aee3b68..98ac201a0 100644 --- a/packages/server/src/services/patch-repo.ts +++ b/packages/server/src/services/patch-repo.ts @@ -132,6 +132,8 @@ export const readPatchRepoDirectory = async ( // Use git ls-tree to get tracked files only const command = `cd "${repoPath}" && git ls-tree -r --name-only HEAD`; + console.log("command", command); + let stdout: string; try { if (serverId) { From 20320639ce91ad39d19db81ded0c131839cb812a Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 00:41:46 -0600 Subject: [PATCH 07/27] refactor(patch-editor): streamline patch saving and loading logic - Removed the local state for saving status and integrated loading state directly from the mutation hook. - Updated the saveAsPatch function to handle success and error notifications more cleanly. - Adjusted the file content loading to ensure it retrieves the latest data, improving user experience and consistency in the PatchEditor component. - Refactored API calls in the patch router to unify patch retrieval logic for applications and composes, enhancing maintainability. --- .../application/patches/patch-editor.tsx | 42 ++++++----------- apps/dokploy/server/api/routers/patch.ts | 46 ++++++------------- packages/server/src/services/patch.ts | 24 ++++------ 3 files changed, 36 insertions(+), 76 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx index 6092b9b85..6688896fe 100644 --- a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx +++ b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx @@ -41,7 +41,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { const [expandedFolders, setExpandedFolders] = useState>( new Set(), ); - const [isSaving, setIsSaving] = useState(false); const { data: directories, isLoading: isDirLoading } = api.patch.readRepoDirectories.useQuery( @@ -49,27 +48,13 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { { enabled: !!repoPath }, ); - const saveAsPatch = api.patch.saveFileAsPatch.useMutation({ - onSuccess: (result) => { - setIsSaving(false); - if (result.deleted) { - toast.success("No changes - patch removed"); - } else { - toast.success("Patch saved"); - } - setOriginalContent(fileContent); - }, - onError: () => { - setIsSaving(false); - toast.error("Failed to save patch"); - }, - }); + const { mutateAsync: saveAsPatch, isLoading: isSavingPatch } = + api.patch.saveFileAsPatch.useMutation(); - // Read file content when selected const { data: fileData, isFetching: isFileLoading } = api.patch.readRepoFile.useQuery( { - id: id || "", + id, type, repoPath, filePath: selectedFile || "", @@ -77,8 +62,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { { enabled: !!selectedFile, onSuccess: (data) => { - setFileContent(data.content); - setOriginalContent(data.content); if (data.patchError) { toast.error(data.patchErrorMessage || "Failed to apply patch"); } @@ -104,14 +87,19 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { const handleSave = () => { if (!selectedFile) return; - setIsSaving(true); - saveAsPatch.mutate({ + saveAsPatch({ id, type, repoPath, filePath: selectedFile, content: fileContent, - }); + }) + .then(() => { + toast.success("Patch saved"); + }) + .catch(() => { + toast.error("Failed to save patch"); + }); }; const hasChanges = fileContent !== originalContent; @@ -192,8 +180,8 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
{selectedFile && ( - @@ -201,7 +189,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
- {/* File Tree */}
@@ -219,7 +206,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
- {/* Editor */}
{isFileLoading ? (
@@ -227,7 +213,7 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
) : selectedFile ? ( setFileContent(value || "")} className="h-full w-full" wrapperClassName="h-full" diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 9deae22e4..43486038e 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -51,8 +51,8 @@ const getApplicationGitConfig = ( }; case "gitea": return { - gitUrl: app.gitea?.gitUrl - ? `${app.gitea.gitUrl}/${app.giteaOwner}/${app.giteaRepository}.git` + gitUrl: app.gitea?.giteaUrl + ? `${app.gitea.giteaUrl}/${app.giteaOwner}/${app.giteaRepository}.git` : "", gitBranch: app.giteaBranch || "main", sshKeyId: null, @@ -93,8 +93,8 @@ const getComposeGitConfig = ( }; case "gitea": return { - gitUrl: compose.gitea?.gitUrl - ? `${compose.gitea.gitUrl}/${compose.giteaOwner}/${compose.giteaRepository}.git` + gitUrl: compose.gitea?.giteaUrl + ? `${compose.gitea.giteaUrl}/${compose.giteaOwner}/${compose.giteaRepository}.git` : "", gitBranch: compose.giteaBranch || "main", sshKeyId: null, @@ -343,7 +343,6 @@ export const patchRouter = createTRPCRouter({ ) .query(async ({ input, ctx }) => { let serverId: string | null = null; - let patchContent: string | undefined; if (input.type === "application") { const app = await findApplicationById(input.id); @@ -357,16 +356,6 @@ export const patchRouter = createTRPCRouter({ }); } serverId = app.serverId; - - // Check if patch exists for this file - const existingPatch = await findPatchByFilePath( - input.filePath, - input.id, - undefined, - ); - if (existingPatch?.enabled) { - patchContent = existingPatch.content; - } } else if (input.type === "compose") { const compose = await findComposeById(input.id); if ( @@ -379,27 +368,22 @@ export const patchRouter = createTRPCRouter({ }); } serverId = compose.serverId; - - // Check if patch exists for this file - const existingPatch = await findPatchByFilePath( - input.filePath, - undefined, - input.id, - ); - if (existingPatch?.enabled) { - patchContent = existingPatch.content; - } } else { throw new TRPCError({ code: "BAD_REQUEST", message: "Either applicationId or composeId must be provided", }); } + const existingPatch = await findPatchByFilePath( + input.filePath, + input.id, + input.type, + ); return await readPatchRepoFile( input.repoPath, input.filePath, - patchContent, + existingPatch?.enabled ? existingPatch?.content : undefined, serverId, ); }), @@ -461,7 +445,7 @@ export const patchRouter = createTRPCRouter({ const existingPatch = await findPatchByFilePath( input.filePath, input.id, - input.id, + input.type, ); if (existingPatch) { await deletePatch(existingPatch.patchId); @@ -473,22 +457,20 @@ export const patchRouter = createTRPCRouter({ const existingPatch = await findPatchByFilePath( input.filePath, input.id, - input.id, + input.type, ); if (existingPatch) { - // Update existing patch await updatePatch(existingPatch.patchId, { content: patchContent }); return { deleted: false, patchId: existingPatch.patchId }; } - // Create new patch const newPatch = await createPatch({ filePath: input.filePath, content: patchContent, enabled: true, - applicationId: input.id, - composeId: input.id, + applicationId: input.type === "application" ? input.id : undefined, + composeId: input.type === "compose" ? input.id : undefined, }); return { deleted: false, patchId: newPatch.patchId }; diff --git a/packages/server/src/services/patch.ts b/packages/server/src/services/patch.ts index 22d0bc8c0..095e9a9d1 100644 --- a/packages/server/src/services/patch.ts +++ b/packages/server/src/services/patch.ts @@ -76,23 +76,15 @@ export const findPatchesByComposeId = async (composeId: string) => { export const findPatchByFilePath = async ( filePath: string, - applicationId?: string, - composeId?: string, + id: string, + type: "application" | "compose", ) => { - if (applicationId) { - return await db.query.patch.findFirst({ - where: and( - eq(patch.filePath, filePath), - eq(patch.applicationId, applicationId), - ), - }); - } - if (composeId) { - return await db.query.patch.findFirst({ - where: and(eq(patch.filePath, filePath), eq(patch.composeId, composeId)), - }); - } - return null; + return await db.query.patch.findFirst({ + where: + type === "application" + ? and(eq(patch.filePath, filePath), eq(patch.applicationId, id)) + : and(eq(patch.filePath, filePath), eq(patch.composeId, id)), + }); }; export const updatePatch = async (patchId: string, data: Partial) => { From 9818e3c3ba78968182b26076b3a1ffe4df04cb61 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 01:11:23 -0600 Subject: [PATCH 08/27] refactor(patch): simplify patch repository management and enhance git configuration handling - Removed redundant helper functions for retrieving git configurations for applications and composes, streamlining the codebase. - Updated the `ensurePatchRepo` function to directly handle repository cloning based on the application or compose type, improving clarity and maintainability. - Refactored patch creation logic to eliminate unnecessary checks and streamline the process of creating patches. - Enhanced the handling of output paths in repository cloning functions across different git providers, ensuring consistent behavior. --- apps/dokploy/server/api/routers/patch.ts | 190 +----------------- packages/server/src/services/patch-repo.ts | 143 +++++-------- packages/server/src/services/patch.ts | 99 +-------- .../server/src/utils/providers/bitbucket.ts | 4 +- packages/server/src/utils/providers/git.ts | 4 +- packages/server/src/utils/providers/gitea.ts | 4 +- packages/server/src/utils/providers/github.ts | 4 +- packages/server/src/utils/providers/gitlab.ts | 4 +- 8 files changed, 71 insertions(+), 381 deletions(-) diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 43486038e..4c8dfefe5 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -32,90 +32,6 @@ import { apiUpdatePatch, } from "@/server/db/schema"; -// Helper to get git config from application -const getApplicationGitConfig = ( - app: Awaited>, -) => { - switch (app.sourceType) { - case "github": - return { - gitUrl: `https://github.com/${app.owner}/${app.repository}.git`, - gitBranch: app.branch || "main", - sshKeyId: null, - }; - case "gitlab": - return { - gitUrl: `https://gitlab.com/${app.gitlabOwner}/${app.gitlabRepository}.git`, - gitBranch: app.gitlabBranch || "main", - sshKeyId: null, - }; - case "gitea": - return { - gitUrl: app.gitea?.giteaUrl - ? `${app.gitea.giteaUrl}/${app.giteaOwner}/${app.giteaRepository}.git` - : "", - gitBranch: app.giteaBranch || "main", - sshKeyId: null, - }; - case "bitbucket": - return { - gitUrl: `https://bitbucket.org/${app.bitbucketOwner}/${app.bitbucketRepository}.git`, - gitBranch: app.bitbucketBranch || "main", - sshKeyId: null, - }; - case "git": - return { - gitUrl: app.customGitUrl || "", - gitBranch: app.customGitBranch || "main", - sshKeyId: app.customGitSSHKeyId, - }; - default: - return null; - } -}; - -// Helper to get git config from compose -const getComposeGitConfig = ( - compose: Awaited>, -) => { - switch (compose.sourceType) { - case "github": - return { - gitUrl: `https://github.com/${compose.owner}/${compose.repository}.git`, - gitBranch: compose.branch || "main", - sshKeyId: null, - }; - case "gitlab": - return { - gitUrl: `https://gitlab.com/${compose.gitlabOwner}/${compose.gitlabRepository}.git`, - gitBranch: compose.gitlabBranch || "main", - sshKeyId: null, - }; - case "gitea": - return { - gitUrl: compose.gitea?.giteaUrl - ? `${compose.gitea.giteaUrl}/${compose.giteaOwner}/${compose.giteaRepository}.git` - : "", - gitBranch: compose.giteaBranch || "main", - sshKeyId: null, - }; - case "bitbucket": - return { - gitUrl: `https://bitbucket.org/${compose.bitbucketOwner}/${compose.bitbucketRepository}.git`, - gitBranch: compose.bitbucketBranch || "main", - sshKeyId: null, - }; - case "git": - return { - gitUrl: compose.customGitUrl || "", - gitBranch: compose.customGitBranch || "main", - sshKeyId: compose.customGitSSHKeyId, - }; - default: - return null; - } -}; - export const patchRouter = createTRPCRouter({ // CRUD Operations create: protectedProcedure @@ -222,70 +138,10 @@ export const patchRouter = createTRPCRouter({ type: z.enum(["application", "compose"]), }), ) - .mutation(async ({ input, ctx }) => { - if (input.type === "application") { - const app = await findApplicationById(input.id); - if ( - app.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - - const gitConfig = getApplicationGitConfig(app); - if (!gitConfig || !gitConfig.gitUrl) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Application does not have a git source configured", - }); - } - - return await ensurePatchRepo({ - appName: app.appName, - type: "application", - gitUrl: gitConfig.gitUrl, - gitBranch: gitConfig.gitBranch, - sshKeyId: gitConfig.sshKeyId, - serverId: app.serverId, - }); - } - - if (input.type === "compose") { - const compose = await findComposeById(input.id); - if ( - compose.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - - const gitConfig = getComposeGitConfig(compose); - if (!gitConfig || !gitConfig.gitUrl) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Compose does not have a git source configured", - }); - } - - return await ensurePatchRepo({ - appName: compose.appName, - type: "compose", - gitUrl: gitConfig.gitUrl, - gitBranch: gitConfig.gitBranch, - sshKeyId: gitConfig.sshKeyId, - serverId: compose.serverId, - }); - } - - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Either application or compose must be provided", + .mutation(async ({ input }) => { + return await ensurePatchRepo({ + type: input.type, + id: input.id, }); }), @@ -432,48 +288,14 @@ export const patchRouter = createTRPCRouter({ }); } - // Generate patch diff - const patchContent = await generatePatch({ - codePath: input.repoPath, - filePath: input.filePath, - newContent: input.content, - serverId, - }); - - if (!patchContent.trim()) { - // No changes - remove existing patch if any - const existingPatch = await findPatchByFilePath( - input.filePath, - input.id, - input.type, - ); - if (existingPatch) { - await deletePatch(existingPatch.patchId); - } - return { deleted: true, patchId: null }; - } - - // Check if patch exists - const existingPatch = await findPatchByFilePath( - input.filePath, - input.id, - input.type, - ); - - if (existingPatch) { - await updatePatch(existingPatch.patchId, { content: patchContent }); - return { deleted: false, patchId: existingPatch.patchId }; - } - const newPatch = await createPatch({ filePath: input.filePath, - content: patchContent, - enabled: true, + content: input.content, applicationId: input.type === "application" ? input.id : undefined, composeId: input.type === "compose" ? input.id : undefined, }); - return { deleted: false, patchId: newPatch.patchId }; + return newPatch; }), // Cleanup diff --git a/packages/server/src/services/patch-repo.ts b/packages/server/src/services/patch-repo.ts index 98ac201a0..f2af7ce25 100644 --- a/packages/server/src/services/patch-repo.ts +++ b/packages/server/src/services/patch-repo.ts @@ -1,16 +1,18 @@ -import path, { join } from "node:path"; +import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; -import { findSSHKeyById } from "@dokploy/server/services/ssh-key"; import { TRPCError } from "@trpc/server"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; +import { cloneBitbucketRepository } from "../utils/providers/bitbucket"; +import { cloneGitRepository } from "../utils/providers/git"; +import { cloneGiteaRepository } from "../utils/providers/gitea"; +import { cloneGithubRepository } from "../utils/providers/github"; +import { cloneGitlabRepository } from "../utils/providers/gitlab"; +import { findApplicationById } from "./application"; +import { findComposeById } from "./compose"; interface PatchRepoConfig { - appName: string; type: "application" | "compose"; - gitUrl: string; - gitBranch: string; - sshKeyId?: string | null; - serverId?: string | null; + id: string; } /** @@ -18,98 +20,51 @@ interface PatchRepoConfig { * Returns path to the repo */ export const ensurePatchRepo = async ({ - appName, type, - gitUrl, - gitBranch, - sshKeyId, - serverId, + id, }: PatchRepoConfig): Promise => { - const { PATCH_REPOS_PATH, SSH_PATH } = paths(!!serverId); - const repoPath = join(PATCH_REPOS_PATH, type, appName); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); + let serverId: string | null = null; - // Check if repo exists - const checkCommand = `test -d "${repoPath}/.git" && echo "exists" || echo "not_exists"`; + if (type === "application") { + const application = await findApplicationById(id); + serverId = application.buildServerId || application.serverId; + } else { + const compose = await findComposeById(id); + serverId = compose.serverId; + } + + const application = + type === "application" + ? await findApplicationById(id) + : await findComposeById(id); + + const { PATCH_REPOS_PATH } = paths(!!serverId); + const repoPath = join(PATCH_REPOS_PATH, type, application.appName); + + const applicationEntity = { + ...application, + type, + serverId: serverId, + outputPathOverride: repoPath, + }; + + let command = "set -e;"; + if (application.sourceType === "github") { + command += await cloneGithubRepository(applicationEntity); + } else if (application.sourceType === "gitlab") { + command += await cloneGitlabRepository(applicationEntity); + } else if (application.sourceType === "gitea") { + command += await cloneGiteaRepository(applicationEntity); + } else if (application.sourceType === "bitbucket") { + command += await cloneBitbucketRepository(applicationEntity); + } else if (application.sourceType === "git") { + command += await cloneGitRepository(applicationEntity); + } - let exists = false; if (serverId) { - const result = await execAsyncRemote(serverId, checkCommand); - exists = result.stdout.trim() === "exists"; + await execAsyncRemote(serverId, command); } else { - const result = await execAsync(checkCommand); - exists = result.stdout.trim() === "exists"; - } - - // Setup SSH if needed - let sshSetup = ""; - if (sshKeyId) { - const sshKey = await findSSHKeyById(sshKeyId); - const temporalKeyPath = "/tmp/patch_repo_id_rsa"; - sshSetup = ` -echo "${sshKey.privateKey}" > ${temporalKeyPath}; -chmod 600 ${temporalKeyPath}; -export GIT_SSH_COMMAND="ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath} -o StrictHostKeyChecking=accept-new"; -`; - } - - if (!exists) { - // Clone the repo - const cloneCommand = ` -set -e; -${sshSetup} -mkdir -p "${repoPath}"; -git clone --branch ${gitBranch} --progress "${gitUrl}" "${repoPath}"; -echo "Repository cloned successfully"; -`; - - try { - if (serverId) { - await execAsyncRemote(serverId, cloneCommand); - } else { - await execAsync(cloneCommand); - } - } catch (error) { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Failed to clone repository: ${error}`, - }); - } - } else { - // Repo exists - check if on correct branch and update - const updateCommand = ` -set -e; -cd "${repoPath}"; -${sshSetup} - -# Fetch all updates including tags -git fetch origin --tags --force - -# Checkout the target (branch or tag) - this handles switching branches/tags -git checkout --force "${gitBranch}" - -# If it's a branch that corresponds to a remote branch, hard reset to match remote -# This ensures we pull the latest changes for that branch. -# If it's a tag, we are already at the correct commit after checkout. -if git rev-parse --verify "origin/${gitBranch}" >/dev/null 2>&1; then - git reset --hard "origin/${gitBranch}" -fi - -echo "Updated repository to ${gitBranch}" -`; - - try { - if (serverId) { - await execAsyncRemote(serverId, updateCommand); - } else { - await execAsync(updateCommand); - } - } catch (error) { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Failed to update repository: ${error}`, - }); - } + await execAsync(command); } return repoPath; @@ -132,8 +87,6 @@ export const readPatchRepoDirectory = async ( // Use git ls-tree to get tracked files only const command = `cd "${repoPath}" && git ls-tree -r --name-only HEAD`; - console.log("command", command); - let stdout: string; try { if (serverId) { diff --git a/packages/server/src/services/patch.ts b/packages/server/src/services/patch.ts index 095e9a9d1..0be127a58 100644 --- a/packages/server/src/services/patch.ts +++ b/packages/server/src/services/patch.ts @@ -11,8 +11,6 @@ import { and, eq, isNotNull } from "drizzle-orm"; export type Patch = typeof patch.$inferSelect; -// CRUD Operations - export const createPatch = async (input: typeof apiCreatePatch._type) => { if (!input.applicationId && !input.composeId) { throw new TRPCError({ @@ -25,9 +23,8 @@ export const createPatch = async (input: typeof apiCreatePatch._type) => { .insert(patch) .values({ ...input, - content: input.content.endsWith("\n") - ? input.content - : `${input.content}\n`, + content: input.content, + enabled: true, }) .returning() .then((value) => value[0]); @@ -187,95 +184,3 @@ export const applyPatches = async ({ await execAsync(command); } }; - -interface GeneratePatchOptions { - codePath: string; - filePath: string; - newContent: string; - serverId?: string | null; -} - -/** - * Generate a patch from modified file content using git diff - */ -export const generatePatch = async ({ - codePath, - filePath, - newContent, - serverId, -}: GeneratePatchOptions): Promise => { - const fullPath = join(codePath, filePath); - - // Write new content to the file - const encodedContent = Buffer.from(newContent).toString("base64"); - const writeCommand = `echo "${encodedContent}" | base64 -d > "${fullPath}"`; - - if (serverId) { - await execAsyncRemote(serverId, writeCommand); - } else { - await execAsync(writeCommand); - } - - // Generate diff - const diffCommand = `cd "${codePath}" && git diff -- "${filePath}"`; - - let diffResult: string; - if (serverId) { - const result = await execAsyncRemote(serverId, diffCommand); - diffResult = result.stdout; - } else { - const result = await execAsync(diffCommand); - diffResult = result.stdout; - } - - // Reset the file to original state - const resetCommand = `cd "${codePath}" && git checkout -- "${filePath}"`; - if (serverId) { - await execAsyncRemote(serverId, resetCommand); - } else { - await execAsync(resetCommand); - } - - return diffResult; -}; - -interface ApplyPatchToContentOptions { - originalContent: string; - patchContent: string; -} - -/** - * Apply a patch to content in memory (for preview purposes) - * Returns the patched content or throws an error if patch fails - */ -export const applyPatchToContent = async ({ - originalContent, - patchContent, -}: ApplyPatchToContentOptions): Promise => { - // Create temp files and apply patch - const tempDir = "/tmp/patch_preview_" + Date.now(); - const tempFile = `${tempDir}/file`; - const patchFile = `${tempDir}/patch.diff`; - - const encodedOriginal = Buffer.from(originalContent).toString("base64"); - const encodedPatch = Buffer.from(patchContent).toString("base64"); - - const command = ` -mkdir -p "${tempDir}"; -echo "${encodedOriginal}" | base64 -d > "${tempFile}"; -echo "${encodedPatch}" | base64 -d > "${patchFile}"; -cd "${tempDir}" && patch -p0 < "${patchFile}" 2>/dev/null; -cat "${tempFile}"; -rm -rf "${tempDir}"; -`; - - try { - const result = await execAsync(command); - return result.stdout; - } catch { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Failed to apply patch to content", - }); - } -}; diff --git a/packages/server/src/utils/providers/bitbucket.ts b/packages/server/src/utils/providers/bitbucket.ts index 57d6de3bc..5a7072198 100644 --- a/packages/server/src/utils/providers/bitbucket.ts +++ b/packages/server/src/utils/providers/bitbucket.ts @@ -86,6 +86,7 @@ interface CloneBitbucketRepository { enableSubmodules: boolean; serverId: string | null; type?: "application" | "compose"; + outputPathOverride?: string; } export const cloneBitbucketRepository = async ({ @@ -101,6 +102,7 @@ export const cloneBitbucketRepository = async ({ bitbucketId, enableSubmodules, serverId, + outputPathOverride, } = entity; const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId); @@ -115,7 +117,7 @@ export const cloneBitbucketRepository = async ({ return command; } const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const outputPath = outputPathOverride ?? join(basePath, appName, "code"); command += `rm -rf ${outputPath};`; command += `mkdir -p ${outputPath};`; const repoToUse = entity.bitbucketRepositorySlug || bitbucketRepository; diff --git a/packages/server/src/utils/providers/git.ts b/packages/server/src/utils/providers/git.ts index 8e640892d..4c0610921 100644 --- a/packages/server/src/utils/providers/git.ts +++ b/packages/server/src/utils/providers/git.ts @@ -14,6 +14,7 @@ interface CloneGitRepository { enableSubmodules?: boolean; serverId: string | null; type?: "application" | "compose"; + outputPathOverride?: string; } export const cloneGitRepository = async ({ @@ -28,6 +29,7 @@ export const cloneGitRepository = async ({ customGitSSHKeyId, enableSubmodules, serverId, + outputPathOverride, } = entity; const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId); @@ -47,7 +49,7 @@ export const cloneGitRepository = async ({ `; } const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const outputPath = outputPathOverride ?? join(basePath, appName, "code"); const knownHostsPath = path.join(SSH_PATH, "known_hosts"); if (!isHttpOrHttps(customGitUrl)) { diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts index 1555e7713..4d26a9212 100644 --- a/packages/server/src/utils/providers/gitea.ts +++ b/packages/server/src/utils/providers/gitea.ts @@ -130,6 +130,7 @@ interface CloneGiteaRepository { enableSubmodules: boolean; serverId: string | null; type?: "application" | "compose"; + outputPathOverride?: string; } export const cloneGiteaRepository = async ({ @@ -145,6 +146,7 @@ export const cloneGiteaRepository = async ({ giteaRepository, enableSubmodules, serverId, + outputPathOverride, } = entity; const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(!!serverId); @@ -162,7 +164,7 @@ export const cloneGiteaRepository = async ({ } const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const outputPath = outputPathOverride ?? join(basePath, appName, "code"); command += `rm -rf ${outputPath};`; command += `mkdir -p ${outputPath};`; diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts index 5b7763df7..e7907cb47 100644 --- a/packages/server/src/utils/providers/github.ts +++ b/packages/server/src/utils/providers/github.ts @@ -121,6 +121,7 @@ interface CloneGithubRepository { type?: "application" | "compose"; enableSubmodules: boolean; serverId: string | null; + outputPathOverride?: string; } export const cloneGithubRepository = async ({ type = "application", @@ -136,6 +137,7 @@ export const cloneGithubRepository = async ({ githubId, enableSubmodules, serverId, + outputPathOverride, } = entity; const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(!!serverId); @@ -155,7 +157,7 @@ export const cloneGithubRepository = async ({ const githubProvider = await findGithubById(githubId); const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const outputPath = outputPathOverride ?? join(basePath, appName, "code"); const octokit = authGithub(githubProvider); const token = await getGithubToken(octokit); const repoclone = `github.com/${owner}/${repository}.git`; diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts index 22e5df3ae..1ab1ddabd 100644 --- a/packages/server/src/utils/providers/gitlab.ts +++ b/packages/server/src/utils/providers/gitlab.ts @@ -107,6 +107,7 @@ interface CloneGitlabRepository { enableSubmodules: boolean; serverId: string | null; type?: "application" | "compose"; + outputPathOverride?: string; } export const cloneGitlabRepository = async ({ @@ -121,6 +122,7 @@ export const cloneGitlabRepository = async ({ gitlabPathNamespace, enableSubmodules, serverId, + outputPathOverride, } = entity; const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId); @@ -141,7 +143,7 @@ export const cloneGitlabRepository = async ({ } const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); + const outputPath = outputPathOverride ?? join(basePath, appName, "code"); command += `rm -rf ${outputPath};`; command += `mkdir -p ${outputPath};`; const repoClone = getGitlabRepoClone(gitlab, gitlabPathNamespace); From 46ac272f3fb2526517901b6b1a7899b652109549 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 01:35:08 -0600 Subject: [PATCH 09/27] refactor(patch): unify patch retrieval and application logic - Consolidated patch retrieval for applications and composes into a single query method, improving code clarity and reducing redundancy. - Updated the ShowPatches component to utilize the new unified query, simplifying data fetching logic. - Refactored patch application commands to streamline the process for both application and compose types, enhancing maintainability and consistency across the codebase. --- .../application/patches/show-patches.tsx | 34 ++---- apps/dokploy/server/api/routers/patch.ts | 63 +++++----- packages/server/src/services/application.ts | 23 +--- packages/server/src/services/compose.ts | 24 +--- packages/server/src/services/patch.ts | 110 ++++++------------ 5 files changed, 86 insertions(+), 168 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx index ade0edb87..2a090e39f 100644 --- a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx +++ b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx @@ -33,22 +33,8 @@ export const ShowPatches = ({ id, type }: Props) => { const utils = api.useUtils(); - const queryMap = { - application: () => - api.patch.byApplicationId.useQuery( - { applicationId: id }, - { enabled: !!id }, - ), - compose: () => - api.patch.byComposeId.useQuery({ composeId: id }, { enabled: !!id }), - }; - - const { data: patches, isLoading: isPatchesLoading } = queryMap[type] - ? queryMap[type]() - : api.patch.byApplicationId.useQuery( - { applicationId: id }, - { enabled: !!id }, - ); + const { data: patches, isLoading: isPatchesLoading } = + api.patch.byEntityId.useQuery({ id, type }, { enabled: !!id }); const mutationMap = { application: () => api.patch.delete.useMutation(), @@ -166,11 +152,9 @@ export const ShowPatches = ({ id, type }: Props) => { }) .then(() => { toast.success("Patch updated"); - utils.patch.byApplicationId.invalidate({ - applicationId: id, - }); - utils.patch.byComposeId.invalidate({ - composeId: id, + utils.patch.byEntityId.invalidate({ + id, + type, }); }) .catch((err) => { @@ -190,11 +174,9 @@ export const ShowPatches = ({ id, type }: Props) => { mutateAsync({ patchId: patch.patchId }) .then(() => { toast.success("Patch deleted"); - utils.patch.byApplicationId.invalidate({ - applicationId: id, - }); - utils.patch.byComposeId.invalidate({ - composeId: id, + utils.patch.byEntityId.invalidate({ + id, + type, }); }) .catch((err) => { diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 4c8dfefe5..1c9eb4fe3 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -8,9 +8,7 @@ import { findComposeById, findPatchByFilePath, findPatchById, - findPatchesByApplicationId, - findPatchesByComposeId, - generatePatch, + findPatchesByEntityId, readPatchRepoDirectory, readPatchRepoFile, updatePatch, @@ -26,8 +24,6 @@ import { apiCreatePatch, apiDeletePatch, apiFindPatch, - apiFindPatchesByApplicationId, - apiFindPatchesByComposeId, apiTogglePatchEnabled, apiUpdatePatch, } from "@/server/db/schema"; @@ -77,38 +73,37 @@ export const patchRouter = createTRPCRouter({ return await findPatchById(input.patchId); }), - byApplicationId: protectedProcedure - .input(apiFindPatchesByApplicationId) + byEntityId: protectedProcedure + .input( + z.object({ id: z.string(), type: z.enum(["application", "compose"]) }), + ) .query(async ({ input, ctx }) => { - const app = await findApplicationById(input.applicationId); - if ( - app.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); + if (input.type === "application") { + const app = await findApplicationById(input.id); + if ( + app.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + } else if (input.type === "compose") { + const compose = await findComposeById(input.id); + if ( + compose.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this compose", + }); + } } + const result = await findPatchesByEntityId(input.id, input.type); - return await findPatchesByApplicationId(input.applicationId); - }), - - byComposeId: protectedProcedure - .input(apiFindPatchesByComposeId) - .query(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if ( - compose.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - - return await findPatchesByComposeId(input.composeId); + return result; }), update: protectedProcedure diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index a1517514b..458ab34f5 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -44,10 +44,7 @@ import { issueCommentExists, updateIssueComment, } from "./github"; -import { - findPatchesByApplicationId, - generateApplyPatchesCommand, -} from "./patch"; +import { generateApplyPatchesCommand } from "./patch"; import { findPreviewDeploymentById, updatePreviewDeployment, @@ -206,20 +203,12 @@ export const deployApplication = async ({ command += await buildRemoteDocker(application); } - // Apply patches after cloning (for non-docker sources only) if (application.sourceType !== "docker") { - const patches = await findPatchesByApplicationId( - application.applicationId, - ); - const enabledPatches = patches.filter((p) => p.enabled); - if (enabledPatches.length > 0) { - command += generateApplyPatchesCommand({ - appName: application.appName, - type: "application", - serverId, - patches: enabledPatches, - }); - } + command += await generateApplyPatchesCommand({ + id: application.applicationId, + type: "application", + serverId, + }); } command += await getBuildCommand(application); diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 7ab7c1745..e6aab7672 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -40,7 +40,7 @@ import { updateDeployment, updateDeploymentStatus, } from "./deployment"; -import { findPatchesByComposeId, generateApplyPatchesCommand } from "./patch"; +import { generateApplyPatchesCommand } from "./patch"; import { validUniqueServerAppName } from "./project"; export type Compose = typeof compose.$inferSelect; @@ -249,24 +249,12 @@ export const deployCompose = async ({ await execAsync(commandWithLog); } - // Apply patches after cloning (for non-raw sources only) if (compose.sourceType !== "raw") { - const patches = await findPatchesByComposeId(compose.composeId); - const enabledPatches = patches.filter((p) => p.enabled); - if (enabledPatches.length > 0) { - const patchCommand = generateApplyPatchesCommand({ - appName: compose.appName, - type: "compose", - serverId: compose.serverId, - patches: enabledPatches, - }); - const patchCommandWithLog = `(${patchCommand}) >> ${deployment.logPath} 2>&1`; - if (compose.serverId) { - await execAsyncRemote(compose.serverId, patchCommandWithLog); - } else { - await execAsync(patchCommandWithLog); - } - } + command += await generateApplyPatchesCommand({ + id: compose.composeId, + type: "compose", + serverId: compose.serverId, + }); } command = "set -e;"; diff --git a/packages/server/src/services/patch.ts b/packages/server/src/services/patch.ts index 0be127a58..f14a6b082 100644 --- a/packages/server/src/services/patch.ts +++ b/packages/server/src/services/patch.ts @@ -2,12 +2,11 @@ import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; import { type apiCreatePatch, patch } from "@dokploy/server/db/schema"; -import { - execAsync, - execAsyncRemote, -} from "@dokploy/server/utils/process/execAsync"; import { TRPCError } from "@trpc/server"; -import { and, eq, isNotNull } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; +import { encodeBase64 } from "../utils/docker/utils"; +import { findApplicationById } from "./application"; +import { findComposeById } from "./compose"; export type Patch = typeof patch.$inferSelect; @@ -54,33 +53,29 @@ export const findPatchById = async (patchId: string) => { return result; }; -export const findPatchesByApplicationId = async (applicationId: string) => { +export const findPatchesByEntityId = async ( + id: string, + type: "application" | "compose", +) => { return await db.query.patch.findMany({ - where: and( - eq(patch.applicationId, applicationId), - isNotNull(patch.applicationId), + where: eq( + type === "application" ? patch.applicationId : patch.composeId, + id, ), orderBy: (patch, { asc }) => [asc(patch.filePath)], }); }; -export const findPatchesByComposeId = async (composeId: string) => { - return await db.query.patch.findMany({ - where: and(eq(patch.composeId, composeId), isNotNull(patch.composeId)), - orderBy: (patch, { asc }) => [asc(patch.filePath)], - }); -}; - export const findPatchByFilePath = async ( filePath: string, id: string, type: "application" | "compose", ) => { return await db.query.patch.findFirst({ - where: - type === "application" - ? and(eq(patch.filePath, filePath), eq(patch.applicationId, id)) - : and(eq(patch.filePath, filePath), eq(patch.composeId, id)), + where: and( + eq(patch.filePath, filePath), + eq(type === "application" ? patch.applicationId : patch.composeId, id), + ), }); }; @@ -111,76 +106,45 @@ export const deletePatch = async (patchId: string) => { return result[0]; }; -// Patch Application Functions - interface ApplyPatchesOptions { - appName: string; + id: string; type: "application" | "compose"; serverId: string | null; - patches: Patch[]; } -/** - * Generate shell commands to apply patches to cloned repository - * Uses git apply to apply unified diff patches - */ -export const generateApplyPatchesCommand = ({ - appName, +export const generateApplyPatchesCommand = async ({ + id, type, - patches, serverId, -}: ApplyPatchesOptions): string => { +}: ApplyPatchesOptions) => { + const entity = + type === "application" + ? await findApplicationById(id) + : await findComposeById(id); + const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId); + const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; + const codePath = join(basePath, entity.appName, "code"); + + const patches = (await findPatchesByEntityId(id, type)).filter( + (p) => p.enabled, + ); + if (patches.length === 0) { return ""; } - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId); - const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; - const codePath = join(basePath, appName, "code"); - let command = `echo "Applying ${patches.length} patch(es)...";`; for (const p of patches) { - // Create a temporary patch file and apply it - const patchFileName = `/tmp/patch_${p.patchId}.patch`; - // Escape content for shell - use base64 encoding - const encodedContent = Buffer.from(p.content).toString("base64"); - + if (!p.enabled) { + continue; + } + const filePath = join(codePath, p.filePath); command += ` -echo "${encodedContent}" | base64 -d > ${patchFileName}; -cd ${codePath} && git apply --whitespace=fix ${patchFileName} && echo "āœ… Applied patch for: ${p.filePath}" || echo "āš ļø Warning: Failed to apply patch for: ${p.filePath}"; -rm -f ${patchFileName}; +rm -f ${filePath}; +echo "${encodeBase64(p.content)}" | base64 -d > ${filePath}; `; } return command; }; - -/** - * Apply patches during build process - */ -export const applyPatches = async ({ - appName, - type, - serverId, - patches, -}: ApplyPatchesOptions): Promise => { - const enabledPatches = patches.filter((p) => p.enabled); - - if (enabledPatches.length === 0) { - return; - } - - const command = generateApplyPatchesCommand({ - appName, - type, - serverId, - patches: enabledPatches, - }); - - if (serverId) { - await execAsyncRemote(serverId, command); - } else { - await execAsync(command); - } -}; From 1c25ab4303001570f159dc45d6a3d4611a8ce999 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 01:47:27 -0600 Subject: [PATCH 10/27] feat(patch-dialog): add EditPatchDialog component for editing patches - Introduced the EditPatchDialog component to facilitate patch editing within the dashboard. - Integrated the dialog into the ShowPatches component, allowing users to edit patches directly from the list view. - Enhanced user experience with loading indicators and success/error notifications during patch updates. - Updated the UI to ensure consistent styling and behavior across patch management features. --- .../application/patches/edit-patch-dialog.tsx | 102 ++++++++++++++++++ .../application/patches/show-patches.tsx | 49 +++++---- apps/dokploy/server/api/routers/patch.ts | 2 - 3 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 apps/dokploy/components/dashboard/application/patches/edit-patch-dialog.tsx diff --git a/apps/dokploy/components/dashboard/application/patches/edit-patch-dialog.tsx b/apps/dokploy/components/dashboard/application/patches/edit-patch-dialog.tsx new file mode 100644 index 000000000..284b62d10 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/patches/edit-patch-dialog.tsx @@ -0,0 +1,102 @@ +import { Loader2, Pencil } from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { api } from "@/utils/api"; + +interface Props { + patchId: string; + entityId: string; + type: "application" | "compose"; + onSuccess?: () => void; +} + +export const EditPatchDialog = ({ + patchId, + entityId, + type, + onSuccess, +}: Props) => { + const { data: patch, isLoading: isPatchLoading } = api.patch.one.useQuery( + { patchId }, + { enabled: !!patchId }, + ); + const [content, setContent] = useState(""); + + useEffect(() => { + if (patch) { + setContent(patch.content); + } + }, [patch]); + + const utils = api.useUtils(); + const updatePatch = api.patch.update.useMutation(); + + const handleSave = () => { + updatePatch + .mutateAsync({ patchId, content }) + .then(() => { + toast.success("Patch saved"); + utils.patch.byEntityId.invalidate({ id: entityId, type }); + onSuccess?.(); + }) + .catch((err) => { + toast.error(err.message); + }); + }; + + return ( + + + + + + + Edit Patch + + {patch ? `Editing: ${patch.filePath}` : "Loading patch..."} + + + {isPatchLoading ? ( +
+ +
+ ) : ( +
+ setContent(value ?? "")} + className="h-[400px] w-full" + wrapperClassName="h-[400px]" + lineWrapping + /> +
+ )} + + + + + + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx index 2a090e39f..eace7d820 100644 --- a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx +++ b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx @@ -19,6 +19,7 @@ import { TableRow, } from "@/components/ui/table"; import { api } from "@/utils/api"; +import { EditPatchDialog } from "./edit-patch-dialog"; import { PatchEditor } from "./patch-editor"; interface Props { @@ -82,7 +83,7 @@ export const ShowPatches = ({ id, type }: Props) => { return ( - +
Patches @@ -129,7 +130,7 @@ export const ShowPatches = ({ id, type }: Props) => { File Path Enabled - Actions + Actions @@ -167,25 +168,33 @@ export const ShowPatches = ({ id, type }: Props) => { /> - + }} + title="Delete patch" + > + + +
))} diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 1c9eb4fe3..0279c8916 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -29,11 +29,9 @@ import { } from "@/server/db/schema"; export const patchRouter = createTRPCRouter({ - // CRUD Operations create: protectedProcedure .input(apiCreatePatch) .mutation(async ({ input, ctx }) => { - // Verify access if (input.applicationId) { const app = await findApplicationById(input.applicationId); if ( From c89f2e302b352ea9c895959d1f62a1a89ac72573 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 01:57:57 -0600 Subject: [PATCH 11/27] refactor(patch-editor): remove repoPath dependency and streamline patch handling - Eliminated the repoPath parameter from the PatchEditor component and related API calls, simplifying the patch management logic. - Updated the patch retrieval and saving processes to focus on filePath and content, enhancing clarity and maintainability. - Adjusted the handling of file content in the CodeEditor to ensure it retrieves the correct data, improving user experience. --- .../application/patches/patch-editor.tsx | 9 +- apps/dokploy/server/api/routers/patch.ts | 40 +++--- packages/server/src/services/patch-repo.ts | 120 ++++-------------- 3 files changed, 48 insertions(+), 121 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx index 6688896fe..9f0608260 100644 --- a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx +++ b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx @@ -56,16 +56,10 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { { id, type, - repoPath, filePath: selectedFile || "", }, { enabled: !!selectedFile, - onSuccess: (data) => { - if (data.patchError) { - toast.error(data.patchErrorMessage || "Failed to apply patch"); - } - }, }, ); @@ -90,7 +84,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { saveAsPatch({ id, type, - repoPath, filePath: selectedFile, content: fileContent, }) @@ -213,7 +206,7 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
) : selectedFile ? ( setFileContent(value || "")} className="h-full w-full" wrapperClassName="h-full" diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 0279c8916..9ad51c214 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -186,7 +186,6 @@ export const patchRouter = createTRPCRouter({ z.object({ id: z.string(), type: z.enum(["application", "compose"]), - repoPath: z.string(), filePath: z.string(), }), ) @@ -229,12 +228,10 @@ export const patchRouter = createTRPCRouter({ input.type, ); - return await readPatchRepoFile( - input.repoPath, - input.filePath, - existingPatch?.enabled ? existingPatch?.content : undefined, - serverId, - ); + if (existingPatch) { + return existingPatch.content; + } + return await readPatchRepoFile(input.id, input.type, input.filePath); }), saveFileAsPatch: protectedProcedure @@ -242,14 +239,11 @@ export const patchRouter = createTRPCRouter({ z.object({ id: z.string(), type: z.enum(["application", "compose"]), - repoPath: z.string(), filePath: z.string(), content: z.string(), }), ) .mutation(async ({ input, ctx }) => { - let serverId: string | null = null; - if (input.type === "application") { const app = await findApplicationById(input.id); if ( @@ -261,7 +255,6 @@ export const patchRouter = createTRPCRouter({ message: "You are not authorized to access this application", }); } - serverId = app.serverId; } else if (input.type === "compose") { const compose = await findComposeById(input.id); if ( @@ -273,7 +266,6 @@ export const patchRouter = createTRPCRouter({ message: "You are not authorized to access this compose", }); } - serverId = compose.serverId; } else { throw new TRPCError({ code: "BAD_REQUEST", @@ -281,14 +273,24 @@ export const patchRouter = createTRPCRouter({ }); } - const newPatch = await createPatch({ - filePath: input.filePath, - content: input.content, - applicationId: input.type === "application" ? input.id : undefined, - composeId: input.type === "compose" ? input.id : undefined, - }); + const existingPatch = await findPatchByFilePath( + input.filePath, + input.id, + input.type, + ); - return newPatch; + if (!existingPatch) { + const newPatch = await createPatch({ + filePath: input.filePath, + content: input.content, + applicationId: input.type === "application" ? input.id : undefined, + composeId: input.type === "compose" ? input.id : undefined, + }); + } else { + return await updatePatch(existingPatch.patchId, { + content: input.content, + }); + } }), // Cleanup diff --git a/packages/server/src/services/patch-repo.ts b/packages/server/src/services/patch-repo.ts index f2af7ce25..35e734533 100644 --- a/packages/server/src/services/patch-repo.ts +++ b/packages/server/src/services/patch-repo.ts @@ -144,107 +144,39 @@ export const readPatchRepoDirectory = async ( return root; }; -interface ReadFileResult { - content: string; - patchError?: boolean; - patchErrorMessage?: string; -} - -/** - * Read file content from patch repo, optionally with patch applied - */ export const readPatchRepoFile = async ( - repoPath: string, + id: string, + type: "application" | "compose", filePath: string, - patchContent?: string, - serverId?: string | null, -): Promise => { +) => { + let serverId: string | null = null; + + if (type === "application") { + const application = await findApplicationById(id); + serverId = application.buildServerId || application.serverId; + } else { + const compose = await findComposeById(id); + serverId = compose.serverId; + } + const { PATCH_REPOS_PATH } = paths(!!serverId); + + const application = + type === "application" + ? await findApplicationById(id) + : await findComposeById(id); + + const repoPath = join(PATCH_REPOS_PATH, type, application.appName); const fullPath = join(repoPath, filePath); - // Read original file - const command = `cat "${fullPath}" 2>/dev/null || echo "__FILE_NOT_FOUND__"`; + const command = `cat "${fullPath}"`; - let content: string; - try { - if (serverId) { - const result = await execAsyncRemote(serverId, command); - content = result.stdout; - } else { - const result = await execAsync(command); - content = result.stdout; - } - } catch (error) { - throw new TRPCError({ - code: "NOT_FOUND", - message: `File not found: ${filePath}`, - }); + if (serverId) { + const result = await execAsyncRemote(serverId, command); + return result.stdout; } - if (content.trim() === "__FILE_NOT_FOUND__") { - throw new TRPCError({ - code: "NOT_FOUND", - message: `File not found: ${filePath}`, - }); - } - - // If no patch, return original content - if (!patchContent) { - return { content }; - } - - // Try to apply patch - const tempDir = `/tmp/patch_apply_${Date.now()}`; - const encodedContent = Buffer.from(content).toString("base64"); - const encodedPatch = Buffer.from(patchContent).toString("base64"); - - // We need to recreate the file structure for git apply to work - // git diff usually uses paths relative to repo root - const applyCommand = ` -set -e; -mkdir -p "${tempDir}"; -cd "${tempDir}"; -git init -q; -# Create file with correct path -mkdir -p "$(dirname "${filePath}")"; -echo "${encodedContent}" | base64 -d > "${filePath}"; -# Save patch -echo "${encodedPatch}" | base64 -d > "patch.diff"; -# Apply patch -git apply --ignore-space-change --ignore-whitespace patch.diff; -# Read result -cat "${filePath}"; -rm -rf "${tempDir}"; -`; - - try { - let patchedContent: string; - if (serverId) { - const result = await execAsyncRemote(serverId, applyCommand); - patchedContent = result.stdout; - } else { - const result = await execAsync(applyCommand); - patchedContent = result.stdout; - } - return { content: patchedContent }; - } catch (error) { - // Patch failed - return original content with error - const cleanupCommand = `rm -rf "${tempDir}" 2>/dev/null || true`; - try { - if (serverId) { - await execAsyncRemote(serverId, cleanupCommand); - } else { - await execAsync(cleanupCommand); - } - } catch { - // Ignore cleanup errors - } - - return { - content, - patchError: true, - patchErrorMessage: `Failed to apply patch: ${error}`, - }; - } + const result = await execAsync(command); + return result.stdout; }; /** From 2db4c448d4938dc9dd4a0c30bba142bc6d9fb463 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 02:05:54 -0600 Subject: [PATCH 12/27] refactor(patch): optimize patch application command generation - Streamlined the command generation for applying patches by removing unnecessary checks for enabled status within the loop. - Consolidated patch retrieval and filtering logic to enhance clarity and maintainability. - Improved directory handling for file paths to ensure proper creation before applying patches, enhancing robustness. --- packages/server/src/services/patch.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/server/src/services/patch.ts b/packages/server/src/services/patch.ts index f14a6b082..fd6b29a2d 100644 --- a/packages/server/src/services/patch.ts +++ b/packages/server/src/services/patch.ts @@ -125,24 +125,19 @@ export const generateApplyPatchesCommand = async ({ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH; const codePath = join(basePath, entity.appName, "code"); - const patches = (await findPatchesByEntityId(id, type)).filter( - (p) => p.enabled, - ); + const resultPatches = await findPatchesByEntityId(id, type); - if (patches.length === 0) { - return ""; - } + const patches = resultPatches.filter((p) => p.enabled); let command = `echo "Applying ${patches.length} patch(es)...";`; for (const p of patches) { - if (!p.enabled) { - continue; - } const filePath = join(codePath, p.filePath); command += ` -rm -f ${filePath}; -echo "${encodeBase64(p.content)}" | base64 -d > ${filePath}; +file="${filePath}" +dir="$(dirname "$file")" +mkdir -p "$dir" +echo "${encodeBase64(p.content)}" | base64 -d > "$file" `; } From 9eeac50642952335f3566ee0ab01bbb3e69dda99 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 02:09:00 -0600 Subject: [PATCH 13/27] feat(patch): add patchType enum and update patch schema - Introduced a new ENUM type "patchType" with values 'create', 'update', and 'delete' to categorize patch operations. - Updated the "patch" table schema to include a new "type" column, defaulting to 'update', ensuring better management of patch types. - Added a new snapshot file for version 7 to reflect the updated database schema. --- .../drizzle/0146_clammy_titanium_man.sql | 2 + apps/dokploy/drizzle/meta/0146_snapshot.json | 7459 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + packages/server/src/db/schema/patch.ts | 5 +- 4 files changed, 7472 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/drizzle/0146_clammy_titanium_man.sql create mode 100644 apps/dokploy/drizzle/meta/0146_snapshot.json diff --git a/apps/dokploy/drizzle/0146_clammy_titanium_man.sql b/apps/dokploy/drizzle/0146_clammy_titanium_man.sql new file mode 100644 index 000000000..b91f17ae5 --- /dev/null +++ b/apps/dokploy/drizzle/0146_clammy_titanium_man.sql @@ -0,0 +1,2 @@ +CREATE TYPE "public"."patchType" AS ENUM('create', 'update', 'delete');--> statement-breakpoint +ALTER TABLE "patch" ADD COLUMN "type" "patchType" DEFAULT 'update' NOT NULL; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0146_snapshot.json b/apps/dokploy/drizzle/meta/0146_snapshot.json new file mode 100644 index 000000000..539e3453c --- /dev/null +++ b/apps/dokploy/drizzle/meta/0146_snapshot.json @@ -0,0 +1,7459 @@ +{ + "id": "60feaf85-7230-485c-9234-c7f4e8c6f8b0", + "prevId": "5ba7ed04-440d-49a1-a7b6-8d4f4b147fc8", + "version": "7", + "dialect": "postgresql", + "tables": { + "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_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "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_id_fk": { + "name": "apikey_user_id_user_id_fk", + "tableFrom": "apikey", + "tableTo": "user", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "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_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "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 + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "canDeleteEnvironments": { + "name": "canDeleteEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateEnvironments": { + "name": "canCreateEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accessedEnvironments": { + "name": "accessedEnvironments", + "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_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "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_id_fk": { + "name": "organization_owner_id_user_id_fk", + "tableFrom": "organization", + "tableTo": "user", + "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_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "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.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.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 + }, + "previewBuildSecrets": { + "name": "previewBuildSecrets", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLabels": { + "name": "previewLabels", + "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 + }, + "previewRequireCollaboratorPermissions": { + "name": "previewRequireCollaboratorPermissions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rollbackActive": { + "name": "rollbackActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildSecrets": { + "name": "buildSecrets", + "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 + }, + "args": { + "name": "args", + "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 + }, + "bitbucketRepositorySlug": { + "name": "bitbucketRepositorySlug", + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "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'" + }, + "railpackVersion": { + "name": "railpackVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0.15.4'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isStaticSpa": { + "name": "isStaticSpa", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "createEnvFile": { + "name": "createEnvFile", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollbackRegistryId": { + "name": "rollbackRegistryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environmentId": { + "name": "environmentId", + "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 + }, + "buildServerId": { + "name": "buildServerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildRegistryId": { + "name": "buildRegistryId", + "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_rollbackRegistryId_registry_registryId_fk": { + "name": "application_rollbackRegistryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "rollbackRegistryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_environmentId_environment_environmentId_fk": { + "name": "application_environmentId_environment_environmentId_fk", + "tableFrom": "application", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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" + }, + "application_buildServerId_server_serverId_fk": { + "name": "application_buildServerId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "buildServerId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_buildRegistryId_registry_registryId_fk": { + "name": "application_buildRegistryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "buildRegistryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_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_id_fk": { + "name": "backup_userId_user_id_fk", + "tableFrom": "backup", + "tableTo": "user", + "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.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketEmail": { + "name": "bitbucketEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apiToken": { + "name": "apiToken", + "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.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.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 + }, + "bitbucketRepositorySlug": { + "name": "bitbucketRepositorySlug", + "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 + }, + "isolatedDeploymentsVolume": { + "name": "isolatedDeploymentsVolume", + "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'" + }, + "environmentId": { + "name": "environmentId", + "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_environmentId_environment_environmentId_fk": { + "name": "compose_environmentId_environment_environmentId_fk", + "tableFrom": "compose", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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.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 + }, + "pid": { + "name": "pid", + "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 + }, + "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 + }, + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildServerId": { + "name": "buildServerId", + "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" + }, + "deployment_rollbackId_rollback_rollbackId_fk": { + "name": "deployment_rollbackId_rollback_rollbackId_fk", + "tableFrom": "deployment", + "tableTo": "rollback", + "columnsFrom": [ + "rollbackId" + ], + "columnsTo": [ + "rollbackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": { + "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk", + "tableFrom": "deployment", + "tableTo": "volume_backup", + "columnsFrom": [ + "volumeBackupId" + ], + "columnsTo": [ + "volumeBackupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_buildServerId_server_serverId_fk": { + "name": "deployment_buildServerId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "buildServerId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "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.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'" + }, + "internalPath": { + "name": "internalPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "stripPath": { + "name": "stripPath", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "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.environment": { + "name": "environment", + "schema": "", + "columns": { + "environmentId": { + "name": "environmentId", + "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 + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isDefault": { + "name": "isDefault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "environment_projectId_project_projectId_fk": { + "name": "environment_projectId_project_projectId_fk", + "tableFrom": "environment", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "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 + }, + "userId": { + "name": "userId", + "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" + }, + "git_provider_userId_user_id_fk": { + "name": "git_provider_userId_user_id_fk", + "tableFrom": "git_provider", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "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'" + }, + "giteaInternalUrl": { + "name": "giteaInternalUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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.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'" + }, + "gitlabInternalUrl": { + "name": "gitlabInternalUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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.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 + }, + "args": { + "name": "args", + "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'" + }, + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_environmentId_environment_environmentId_fk": { + "name": "mariadb_environmentId_environment_environmentId_fk", + "tableFrom": "mariadb", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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 + }, + "args": { + "name": "args", + "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'" + }, + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "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_environmentId_environment_environmentId_fk": { + "name": "mongo_environmentId_environment_environmentId_fk", + "tableFrom": "mongo", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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.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.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 + }, + "args": { + "name": "args", + "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'" + }, + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_environmentId_environment_environmentId_fk": { + "name": "mysql_environmentId_environment_environmentId_fk", + "tableFrom": "mysql", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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.custom": { + "name": "custom", + "schema": "", + "columns": { + "customId": { + "name": "customId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "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.lark": { + "name": "lark", + "schema": "", + "columns": { + "larkId": { + "name": "larkId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "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 + }, + "volumeBackup": { + "name": "volumeBackup", + "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 + }, + "resendId": { + "name": "resendId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customId": { + "name": "customId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "larkId": { + "name": "larkId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pushoverId": { + "name": "pushoverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "teamsId": { + "name": "teamsId", + "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_resendId_resend_resendId_fk": { + "name": "notification_resendId_resend_resendId_fk", + "tableFrom": "notification", + "tableTo": "resend", + "columnsFrom": [ + "resendId" + ], + "columnsTo": [ + "resendId" + ], + "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_ntfyId_ntfy_ntfyId_fk": { + "name": "notification_ntfyId_ntfy_ntfyId_fk", + "tableFrom": "notification", + "tableTo": "ntfy", + "columnsFrom": [ + "ntfyId" + ], + "columnsTo": [ + "ntfyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_customId_custom_customId_fk": { + "name": "notification_customId_custom_customId_fk", + "tableFrom": "notification", + "tableTo": "custom", + "columnsFrom": [ + "customId" + ], + "columnsTo": [ + "customId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_larkId_lark_larkId_fk": { + "name": "notification_larkId_lark_larkId_fk", + "tableFrom": "notification", + "tableTo": "lark", + "columnsFrom": [ + "larkId" + ], + "columnsTo": [ + "larkId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_pushoverId_pushover_pushoverId_fk": { + "name": "notification_pushoverId_pushover_pushoverId_fk", + "tableFrom": "notification", + "tableTo": "pushover", + "columnsFrom": [ + "pushoverId" + ], + "columnsTo": [ + "pushoverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_teamsId_teams_teamsId_fk": { + "name": "notification_teamsId_teams_teamsId_fk", + "tableFrom": "notification", + "tableTo": "teams", + "columnsFrom": [ + "teamsId" + ], + "columnsTo": [ + "teamsId" + ], + "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.ntfy": { + "name": "ntfy", + "schema": "", + "columns": { + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pushover": { + "name": "pushover", + "schema": "", + "columns": { + "pushoverId": { + "name": "pushoverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userKey": { + "name": "userKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiToken": { + "name": "apiToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry": { + "name": "retry", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expire": { + "name": "expire", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resend": { + "name": "resend", + "schema": "", + "columns": { + "resendId": { + "name": "resendId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "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.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.teams": { + "name": "teams", + "schema": "", + "columns": { + "teamsId": { + "name": "teamsId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "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.patch": { + "name": "patch", + "schema": "", + "columns": { + "patchId": { + "name": "patchId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "patchType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'update'" + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "patch_applicationId_application_applicationId_fk": { + "name": "patch_applicationId_application_applicationId_fk", + "tableFrom": "patch", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "patch_composeId_compose_composeId_fk": { + "name": "patch_composeId_compose_composeId_fk", + "tableFrom": "patch", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "patch_filepath_application_unique": { + "name": "patch_filepath_application_unique", + "nullsNotDistinct": false, + "columns": [ + "filePath", + "applicationId" + ] + }, + "patch_filepath_compose_unique": { + "name": "patch_filepath_compose_unique", + "nullsNotDistinct": false, + "columns": [ + "filePath", + "composeId" + ] + } + }, + "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 + }, + "publishMode": { + "name": "publishMode", + "type": "publishModeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'host'" + }, + "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.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 + }, + "args": { + "name": "args", + "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'" + }, + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_environmentId_environment_environmentId_fk": { + "name": "postgres_environmentId_environment_environmentId_fk", + "tableFrom": "postgres", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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.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.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.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.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 + }, + "args": { + "name": "args", + "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'" + }, + "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 + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_environmentId_environment_environmentId_fk": { + "name": "redis_environmentId_environment_environmentId_fk", + "tableFrom": "redis", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "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.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.rollback": { + "name": "rollback", + "schema": "", + "columns": { + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullContext": { + "name": "fullContext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "rollback_deploymentId_deployment_deploymentId_fk": { + "name": "rollback_deploymentId_deployment_deploymentId_fk", + "tableFrom": "rollback", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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 + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_id_fk": { + "name": "schedule_userId_user_id_fk", + "tableFrom": "schedule", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "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.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'" + }, + "serverType": { + "name": "serverType", + "type": "serverType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'deploy'" + }, + "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.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_id_fk": { + "name": "session_temp_user_id_user_id_fk", + "tableFrom": "session_temp", + "tableTo": "user", + "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.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.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "firstName": { + "name": "firstName", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "lastName": { + "name": "lastName", + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowImpersonation": { + "name": "allowImpersonation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableEnterpriseFeatures": { + "name": "enableEnterpriseFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "licenseKey": { + "name": "licenseKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isValidEnterpriseLicense": { + "name": "isValidEnterpriseLicense", + "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 + }, + "trustedOrigins": { + "name": "trustedOrigins", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volume_backup": { + "name": "volume_backup", + "schema": "", + "columns": { + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "turnOff": { + "name": "turnOff", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volume_backup_applicationId_application_applicationId_fk": { + "name": "volume_backup_applicationId_application_applicationId_fk", + "tableFrom": "volume_backup", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_postgresId_postgres_postgresId_fk": { + "name": "volume_backup_postgresId_postgres_postgresId_fk", + "tableFrom": "volume_backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mariadbId_mariadb_mariadbId_fk": { + "name": "volume_backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "volume_backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mongoId_mongo_mongoId_fk": { + "name": "volume_backup_mongoId_mongo_mongoId_fk", + "tableFrom": "volume_backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mysqlId_mysql_mysqlId_fk": { + "name": "volume_backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "volume_backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_redisId_redis_redisId_fk": { + "name": "volume_backup_redisId_redis_redisId_fk", + "tableFrom": "volume_backup", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_composeId_compose_composeId_fk": { + "name": "volume_backup_composeId_compose_composeId_fk", + "tableFrom": "volume_backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_destinationId_destination_destinationId_fk": { + "name": "volume_backup_destinationId_destination_destinationId_fk", + "tableFrom": "volume_backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webServerSettings": { + "name": "webServerSettings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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": true + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0 0 * * *'" + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "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.backupType": { + "name": "backupType", + "schema": "public", + "values": [ + "database", + "compose" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error", + "cancelled" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "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.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "resend", + "gotify", + "ntfy", + "pushover", + "custom", + "lark", + "teams" + ] + }, + "public.patchType": { + "name": "patchType", + "schema": "public", + "values": [ + "create", + "update", + "delete" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.publishModeType": { + "name": "publishModeType", + "schema": "public", + "values": [ + "ingress", + "host" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.serverType": { + "name": "serverType", + "schema": "public", + "values": [ + "deploy", + "build" + ] + }, + "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" + ] + } + }, + "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 5575c9b0e..ac55d65cb 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -1023,6 +1023,13 @@ "when": 1771307553867, "tag": "0145_bitter_vivisector", "breakpoints": true + }, + { + "idx": 146, + "version": "7", + "when": 1771315685460, + "tag": "0146_clammy_titanium_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/db/schema/patch.ts b/packages/server/src/db/schema/patch.ts index 3ba28cbc4..5e8dc1056 100644 --- a/packages/server/src/db/schema/patch.ts +++ b/packages/server/src/db/schema/patch.ts @@ -1,11 +1,13 @@ import { relations } from "drizzle-orm"; -import { boolean, pgTable, text, unique } from "drizzle-orm/pg-core"; +import { boolean, pgEnum, pgTable, text, unique } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; import { applications } from "./application"; import { compose } from "./compose"; +export const patchType = pgEnum("patchType", ["create", "update", "delete"]); + export const patch = pgTable( "patch", { @@ -13,6 +15,7 @@ export const patch = pgTable( .notNull() .primaryKey() .$defaultFn(() => nanoid()), + type: patchType("type").notNull().default("update"), filePath: text("filePath").notNull(), enabled: boolean("enabled").notNull().default(true), content: text("content").notNull(), From 8aba7b08cf41200ac120cdc30608a0ff76a20130 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 02:28:20 -0600 Subject: [PATCH 14/27] feat(patch): implement CreateFileDialog and integrate file creation in PatchEditor - Added CreateFileDialog component for creating new files within the dashboard. - Integrated file creation functionality into the PatchEditor, allowing users to create files directly from the directory structure. - Enhanced user experience with form validation and success/error notifications during file creation. - Updated ShowPatches to display file types with badges for better clarity on patch operations. --- .../patches/create-file-dialog.tsx | 107 ++++++++++ .../application/patches/patch-editor.tsx | 197 +++++++++++++++--- .../application/patches/show-patches.tsx | 31 ++- apps/dokploy/server/api/routers/patch.ts | 68 +++++- packages/server/src/db/schema/patch.ts | 2 + packages/server/src/services/patch.ts | 30 ++- 6 files changed, 395 insertions(+), 40 deletions(-) create mode 100644 apps/dokploy/components/dashboard/application/patches/create-file-dialog.tsx diff --git a/apps/dokploy/components/dashboard/application/patches/create-file-dialog.tsx b/apps/dokploy/components/dashboard/application/patches/create-file-dialog.tsx new file mode 100644 index 000000000..5f6f88e36 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/patches/create-file-dialog.tsx @@ -0,0 +1,107 @@ +import { FilePlus } from "lucide-react"; +import { useState } from "react"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +interface Props { + folderPath: string; + onCreate: (filename: string, content: string) => void; + onOpenChange: (open: boolean) => void; + alwaysVisible?: boolean; +} + +export const CreateFileDialog = ({ + folderPath, + onCreate, + onOpenChange, + alwaysVisible = false, +}: Props) => { + const [filename, setFilename] = useState(""); + const [content, setContent] = useState(""); + + const handleCreate = () => { + if (!filename.trim()) return; + onCreate(filename.trim(), content); + setFilename(""); + setContent(""); + onOpenChange(false); + }; + + return ( + + + + + +
{ + e.preventDefault(); + handleCreate(); + }} + > + + Create file + + {folderPath ? `New file in ${folderPath}/` : "New file in root"} + + +
+
+ + setFilename(e.target.value)} + /> +
+
+ +
+ setContent(v ?? "")} + className="h-full" + wrapperClassName="h-[200px]" + lineWrapping + /> +
+
+
+ + + + + + + + + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx index 9f0608260..ba38e29a6 100644 --- a/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx +++ b/apps/dokploy/components/dashboard/application/patches/patch-editor.tsx @@ -5,8 +5,9 @@ import { Folder, Loader2, Save, + Trash2, } from "lucide-react"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; @@ -19,6 +20,7 @@ import { } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { api } from "@/utils/api"; +import { CreateFileDialog } from "./create-file-dialog"; interface Props { id: string; @@ -37,20 +39,31 @@ type DirectoryEntry = { export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { const [selectedFile, setSelectedFile] = useState(null); const [fileContent, setFileContent] = useState(""); - const [originalContent, setOriginalContent] = useState(""); + const [createFolderPath, setCreateFolderPath] = useState(null); const [expandedFolders, setExpandedFolders] = useState>( new Set(), ); + const utils = api.useUtils(); const { data: directories, isLoading: isDirLoading } = api.patch.readRepoDirectories.useQuery( { id: id, type, repoPath }, { enabled: !!repoPath }, ); + const { data: patches } = api.patch.byEntityId.useQuery( + { id, type }, + { enabled: !!id }, + ); + const { mutateAsync: saveAsPatch, isLoading: isSavingPatch } = api.patch.saveFileAsPatch.useMutation(); + const { mutateAsync: markForDeletion, isLoading: isMarkingDeletion } = + api.patch.markFileForDeletion.useMutation(); + + const updatePatch = api.patch.update.useMutation(); + const { data: fileData, isFetching: isFileLoading } = api.patch.readRepoFile.useQuery( { @@ -63,6 +76,12 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { }, ); + useEffect(() => { + if (fileData !== undefined) { + setFileContent(fileData); + } + }, [fileData]); + const handleFileSelect = (filePath: string) => { setSelectedFile(filePath); }; @@ -86,16 +105,72 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { type, filePath: selectedFile, content: fileContent, + patchType: "update", }) .then(() => { toast.success("Patch saved"); + utils.patch.byEntityId.invalidate({ id, type }); }) .catch(() => { toast.error("Failed to save patch"); }); }; - const hasChanges = fileContent !== originalContent; + const handleMarkForDeletion = () => { + if (!selectedFile) return; + markForDeletion({ id, type, filePath: selectedFile }) + .then(() => { + toast.success("File marked for deletion"); + utils.patch.byEntityId.invalidate({ id, type }); + }) + .catch(() => { + toast.error("Failed to mark file for deletion"); + }); + }; + + const handleCreateFile = useCallback( + (folderPath: string, filename: string, content: string) => { + const filePath = folderPath ? `${folderPath}/${filename}` : filename; + saveAsPatch({ + id, + type, + filePath, + content, + patchType: "create", + }) + .then(() => { + toast.success("File created"); + utils.patch.byEntityId.invalidate({ id, type }); + }) + .catch(() => { + toast.error("Failed to create file"); + }); + }, + [id, type, saveAsPatch, utils], + ); + + const selectedFilePatch = patches?.find( + (p) => p.filePath === selectedFile && p.type === "delete", + ); + + const handleUnmarkDeletion = () => { + if (!selectedFilePatch) return; + updatePatch + .mutateAsync({ + patchId: selectedFilePatch.patchId, + type: "update", + content: fileData || "", + }) + .then(() => { + toast.success("Deletion unmarked"); + utils.patch.byEntityId.invalidate({ id, type }); + }) + .catch(() => { + toast.error("Failed to unmark deletion"); + }); + }; + + const hasChanges = fileData !== undefined && fileContent !== fileData; const renderTree = useCallback( (entries: DirectoryEntry[], depth = 0) => { @@ -114,22 +189,33 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { if (entry.type === "directory") { return (
- + + handleCreateFile(entry.path, filename, content) + } + onOpenChange={(open) => + setCreateFolderPath(open ? entry.path : null) + } /> - - {entry.name} - +
{isExpanded && entry.children && (
{renderTree(entry.children, depth + 1)}
)} @@ -137,6 +223,10 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => { ); } + const isMarkedForDeletion = patches?.some( + (p) => p.filePath === entry.path && p.type === "delete", + ); + return ( ); }); }, - [expandedFolders, selectedFile], + [expandedFolders, selectedFile, patches, handleCreateFile], ); return ( @@ -173,18 +266,68 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
{selectedFile && ( - +
+ {selectedFilePatch ? ( + + ) : ( + <> + + + + )} +
)}
-
+
+
+ + handleCreateFile("", filename, content) + } + onOpenChange={(open) => + setCreateFolderPath(open ? "" : null) + } + /> + + New file in root + +
{isDirLoading ? (
diff --git a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx index eace7d820..1f417ecdf 100644 --- a/apps/dokploy/components/dashboard/application/patches/show-patches.tsx +++ b/apps/dokploy/components/dashboard/application/patches/show-patches.tsx @@ -9,6 +9,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; import { Switch } from "@/components/ui/switch"; import { Table, @@ -129,6 +130,7 @@ export const ShowPatches = ({ id, type }: Props) => { File Path + Type Enabled Actions @@ -138,10 +140,24 @@ export const ShowPatches = ({ id, type }: Props) => {
- + {patch.filePath}
+ + + {patch.type} + + {
- + {(patch.type === "update" || + patch.type === "create") && ( + + )}