diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts index f833e9f9..f1841ae5 100644 --- a/apps/dokploy/server/api/routers/preview-deployment.ts +++ b/apps/dokploy/server/api/routers/preview-deployment.ts @@ -1,13 +1,23 @@ -import { apiFindAllByApplication } from "@/server/db/schema"; +import { db } from "@/server/db"; +import { apiFindAllByApplication, applications } from "@/server/db/schema"; import { + createPreviewDeployment, findApplicationById, + findPreviewDeploymentByApplicationId, findPreviewDeploymentById, findPreviewDeploymentsByApplicationId, + findPreviewDeploymentsByPullRequestId, + IS_CLOUD, removePreviewDeployment, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { eq } from "drizzle-orm"; +import { and } from "drizzle-orm"; +import { myQueue } from "@/server/queues/queueSetup"; +import { deploy } from "@/server/utils/deploy"; +import type { DeploymentJob } from "@/server/queues/queue-types"; export const previewDeploymentRouter = createTRPCRouter({ all: protectedProcedure @@ -59,4 +69,142 @@ export const previewDeploymentRouter = createTRPCRouter({ } return previewDeployment; }), + create: protectedProcedure + .input( + z.object({ + action: z.enum(["opened", "synchronize", "reopened", "closed"]), + pullRequestId: z.string(), + repository: z.string(), + owner: z.string(), + branch: z.string(), + deploymentHash: z.string(), + prBranch: z.string(), + prNumber: z.any(), + prTitle: z.string(), + prURL: z.string(), + }), + ) + .mutation(async ({ input, ctx }) => { + const organizationId = ctx.session.activeOrganizationId; + const action = input.action; + const prId = input.pullRequestId; + + if (action === "closed") { + const previewDeploymentResult = + await findPreviewDeploymentsByPullRequestId(prId); + + const filteredPreviewDeploymentResult = previewDeploymentResult.filter( + (previewDeployment) => + previewDeployment.application.project.organizationId === + organizationId, + ); + + if (filteredPreviewDeploymentResult.length > 0) { + for (const previewDeployment of filteredPreviewDeploymentResult) { + try { + await removePreviewDeployment( + previewDeployment.previewDeploymentId, + ); + } catch (error) { + console.log(error); + } + } + } + + return { + message: "Preview Deployments Closed", + }; + } + + if ( + action === "opened" || + action === "synchronize" || + action === "reopened" + ) { + const deploymentHash = input.deploymentHash; + + const prBranch = input.prBranch; + const prNumber = input.prNumber; + const prTitle = input.prTitle; + const prURL = input.prURL; + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.repository, input.repository), + eq(applications.branch, input.branch), + eq(applications.isPreviewDeploymentsActive, true), + eq(applications.owner, input.owner), + ), + with: { + previewDeployments: true, + project: true, + }, + }); + + const filteredApps = apps.filter( + (app) => app.project.organizationId === organizationId, + ); + + console.log(filteredApps); + + for (const app of filteredApps) { + const previewLimit = app?.previewLimit || 0; + if (app?.previewDeployments?.length > previewLimit) { + continue; + } + const previewDeploymentResult = + await findPreviewDeploymentByApplicationId(app.applicationId, prId); + + let previewDeploymentId = + previewDeploymentResult?.previewDeploymentId || ""; + + if (!previewDeploymentResult) { + try { + const previewDeployment = await createPreviewDeployment({ + applicationId: app.applicationId as string, + branch: prBranch, + pullRequestId: prId, + pullRequestNumber: prNumber, + pullRequestTitle: prTitle, + pullRequestURL: prURL, + }); + + console.log(previewDeployment); + previewDeploymentId = previewDeployment.previewDeploymentId; + } catch (error) { + console.log(error); + } + } + + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: "Preview Deployment", + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application-preview", + server: !!app.serverId, + previewDeploymentId, + isExternal: true, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + continue; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + } + + return { + message: "Preview Deployments Created", + }; + }), }); diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 24007599..a480fed1 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -596,25 +596,17 @@ export const settingsRouter = createTRPCRouter({ }), readStats: adminProcedure .input( - z - .object({ - dateRange: z - .object({ - start: z.string().optional(), - end: z.string().optional(), - }) - .optional(), - }) - .optional(), + z.object({ + start: z.string().optional(), + end: z.string().optional(), + }), ) .query(({ input }) => { if (IS_CLOUD) { return []; } - const rawConfig = readMonitoringConfig( - !!input?.dateRange?.start || !!input?.dateRange?.end, - ); - const processedLogs = processLogs(rawConfig as string, input?.dateRange); + const rawConfig = readMonitoringConfig(!!input?.start || !!input?.end); + const processedLogs = processLogs(rawConfig as string, input); return processedLogs || []; }), haveActivateRequests: adminProcedure.query(async () => { diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts index b8dfb8cd..c6ec6a73 100644 --- a/apps/dokploy/server/queues/deployments-queue.ts +++ b/apps/dokploy/server/queues/deployments-queue.ts @@ -98,6 +98,7 @@ export const deploymentWorker = new Worker( titleLog: job.data.titleLog, descriptionLog: job.data.descriptionLog, previewDeploymentId: job.data.previewDeploymentId, + isExternal: job.data.isExternal, }); } } else { @@ -107,6 +108,7 @@ export const deploymentWorker = new Worker( titleLog: job.data.titleLog, descriptionLog: job.data.descriptionLog, previewDeploymentId: job.data.previewDeploymentId, + isExternal: job.data.isExternal, }); } } diff --git a/apps/dokploy/server/queues/queue-types.ts b/apps/dokploy/server/queues/queue-types.ts index ef8df694..8940378c 100644 --- a/apps/dokploy/server/queues/queue-types.ts +++ b/apps/dokploy/server/queues/queue-types.ts @@ -26,6 +26,7 @@ type DeployJob = applicationType: "application-preview"; previewDeploymentId: string; serverId?: string; + isExternal?: boolean; }; export type DeploymentJob = DeployJob; diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index 425a6adb..af7e0a0e 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -387,11 +387,13 @@ export const deployPreviewApplication = async ({ titleLog = "Preview Deployment", descriptionLog = "", previewDeploymentId, + isExternal = false, }: { applicationId: string; titleLog: string; descriptionLog: string; previewDeploymentId: string; + isExternal?: boolean; }) => { const application = await findApplicationById(applicationId); @@ -417,46 +419,43 @@ export const deployPreviewApplication = async ({ githubId: application?.githubId || "", }; try { - const commentExists = await issueCommentExists({ - ...issueParams, - }); - if (!commentExists) { - const result = await createPreviewDeploymentComment({ + if (!isExternal) { + const commentExists = await issueCommentExists({ ...issueParams, - previewDomain, - appName: previewDeployment.appName, - githubId: application?.githubId || "", - previewDeploymentId, }); - - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Pull request comment not found", + if (!commentExists) { + const result = await createPreviewDeploymentComment({ + ...issueParams, + previewDomain, + appName: previewDeployment.appName, + githubId: application?.githubId || "", + previewDeploymentId, }); - } - issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId); + if (!result) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Pull request comment not found", + }); + } + + issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId); + } + const buildingComment = getIssueComment( + application.name, + "running", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${buildingComment}`, + }); } - const buildingComment = getIssueComment( - application.name, - "running", - previewDomain, - ); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${buildingComment}`, - }); + application.appName = previewDeployment.appName; application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`; application.buildArgs = application.previewBuildArgs; - // const admin = await findUserById(application.project.userId); - - // if (admin.cleanupCacheOnPreviews) { - // await cleanupFullDocker(application?.serverId); - // } - if (application.sourceType === "github") { await cloneGithubRepository({ ...application, @@ -466,25 +465,31 @@ export const deployPreviewApplication = async ({ }); await buildApplication(application, deployment.logPath); } - const successComment = getIssueComment( - application.name, - "success", - previewDomain, - ); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${successComment}`, - }); + + if (!isExternal) { + const successComment = getIssueComment( + application.name, + "success", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${successComment}`, + }); + } + await updateDeploymentStatus(deployment.deploymentId, "done"); await updatePreviewDeployment(previewDeploymentId, { previewStatus: "done", }); } catch (error) { - const comment = getIssueComment(application.name, "error", previewDomain); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${comment}`, - }); + if (!isExternal) { + const comment = getIssueComment(application.name, "error", previewDomain); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${comment}`, + }); + } await updateDeploymentStatus(deployment.deploymentId, "error"); await updatePreviewDeployment(previewDeploymentId, { previewStatus: "error", @@ -500,11 +505,13 @@ export const deployRemotePreviewApplication = async ({ titleLog = "Preview Deployment", descriptionLog = "", previewDeploymentId, + isExternal = false, }: { applicationId: string; titleLog: string; descriptionLog: string; previewDeploymentId: string; + isExternal?: boolean; }) => { const application = await findApplicationById(applicationId); @@ -530,36 +537,39 @@ export const deployRemotePreviewApplication = async ({ githubId: application?.githubId || "", }; try { - const commentExists = await issueCommentExists({ - ...issueParams, - }); - if (!commentExists) { - const result = await createPreviewDeploymentComment({ + if (!isExternal) { + const commentExists = await issueCommentExists({ ...issueParams, - previewDomain, - appName: previewDeployment.appName, - githubId: application?.githubId || "", - previewDeploymentId, }); - - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Pull request comment not found", + if (!commentExists) { + const result = await createPreviewDeploymentComment({ + ...issueParams, + previewDomain, + appName: previewDeployment.appName, + githubId: application?.githubId || "", + previewDeploymentId, }); - } - issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId); + if (!result) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Pull request comment not found", + }); + } + + issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId); + } + const buildingComment = getIssueComment( + application.name, + "running", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${buildingComment}`, + }); } - const buildingComment = getIssueComment( - application.name, - "running", - previewDomain, - ); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${buildingComment}`, - }); + application.appName = previewDeployment.appName; application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`; application.buildArgs = application.previewBuildArgs; @@ -586,25 +596,29 @@ export const deployRemotePreviewApplication = async ({ await mechanizeDockerContainer(application); } - const successComment = getIssueComment( - application.name, - "success", - previewDomain, - ); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${successComment}`, - }); + if (!isExternal) { + const successComment = getIssueComment( + application.name, + "success", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${successComment}`, + }); + } await updateDeploymentStatus(deployment.deploymentId, "done"); await updatePreviewDeployment(previewDeploymentId, { previewStatus: "done", }); } catch (error) { - const comment = getIssueComment(application.name, "error", previewDomain); - await updateIssueComment({ - ...issueParams, - body: `### Dokploy Preview Deployment\n\n${comment}`, - }); + if (!isExternal) { + const comment = getIssueComment(application.name, "error", previewDomain); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${comment}`, + }); + } await updateDeploymentStatus(deployment.deploymentId, "error"); await updatePreviewDeployment(previewDeploymentId, { previewStatus: "error", diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts index a1ffca4b..b141716c 100644 --- a/packages/server/src/services/preview-deployment.ts +++ b/packages/server/src/services/preview-deployment.ts @@ -151,6 +151,7 @@ export const findPreviewDeploymentsByApplicationId = async ( export const createPreviewDeployment = async ( schema: typeof apiCreatePreviewDeployment._type, + isExternal = false, ) => { const application = await findApplicationById(schema.applicationId); const appName = `preview-${application.appName}-${generatePassword(6)}`; @@ -165,27 +166,32 @@ export const createPreviewDeployment = async ( org?.ownerId || "", ); - const octokit = authGithub(application?.github as Github); + let issueId = ""; + if (!isExternal) { + const octokit = authGithub(application?.github as Github); - const runningComment = getIssueComment( - application.name, - "initializing", - generateDomain, - ); + const runningComment = getIssueComment( + application.name, + "initializing", + generateDomain, + ); - const issue = await octokit.rest.issues.createComment({ - owner: application?.owner || "", - repo: application?.repository || "", - issue_number: Number.parseInt(schema.pullRequestNumber), - body: `### Dokploy Preview Deployment\n\n${runningComment}`, - }); + const issue = await octokit.rest.issues.createComment({ + owner: application?.owner || "", + repo: application?.repository || "", + issue_number: Number.parseInt(schema.pullRequestNumber), + body: `### Dokploy Preview Deployment\n\n${runningComment}`, + }); + + issueId = `${issue.data.id}`; + } const previewDeployment = await db .insert(previewDeployments) .values({ ...schema, appName: appName, - pullRequestCommentId: `${issue.data.id}`, + pullRequestCommentId: issueId, }) .returning() .then((value) => value[0]); @@ -231,6 +237,13 @@ export const findPreviewDeploymentsByPullRequestId = async ( ) => { const previewDeploymentResult = await db.query.previewDeployments.findMany({ where: eq(previewDeployments.pullRequestId, pullRequestId), + with: { + application: { + with: { + project: true, + }, + }, + }, }); return previewDeploymentResult;