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; }; /**