mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-29 11:05:33 +02:00
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.
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;";
|
||||
|
||||
@@ -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<void> => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user