From 90d98803016c8dae44d6cc37eec84d7b26b1db15 Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Wed, 3 Sep 2025 09:05:33 +0530 Subject: [PATCH 1/3] feat: add custom title/description support for API/CLI deployments - Add optional title and description fields to deployment schemas - Update TRPC endpoints to accept custom deployment titles/descriptions - Update external API to support custom deployment metadata - Maintain backward compatibility with existing deployments - Resolves issue #1485: Allow setting title/description for deployments via API/CLI --- apps/api/src/index.ts | 13 ++++++-- apps/api/src/schema.ts | 31 +++++++++++++++++++ .../dokploy/server/api/routers/application.ts | 14 +++++---- apps/dokploy/server/api/routers/compose.ts | 14 +++++---- packages/server/src/db/schema/application.ts | 20 ++++++++++++ packages/server/src/db/schema/compose.ts | 12 +++++++ 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index aa7358335..a76b60032 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import { Inngest } from "inngest"; import { serve as serveInngest } from "inngest/hono"; import { logger } from "./logger.js"; -import { type DeployJob, deployJobSchema } from "./schema.js"; +import { type DeployJob, deployJobSchema, deployRequestSchema } from "./schema.js"; import { deploy } from "./utils.js"; const app = new Hono(); @@ -84,15 +84,22 @@ app.use(async (c, next) => { return next(); }); -app.post("/deploy", zValidator("json", deployJobSchema), async (c) => { +app.post("/deploy", zValidator("json", deployRequestSchema), async (c) => { const data = c.req.valid("json"); logger.info("Received deployment request", data); try { + // Transform the request to the internal job format + const jobData: DeployJob = { + ...data, + titleLog: data.title || "Manual deployment", + descriptionLog: data.description || "", + }; + // Send event to Inngest instead of adding to Redis queue await inngest.send({ name: "deployment/requested", - data, + data: jobData, }); logger.info("Deployment event sent to Inngest", { diff --git a/apps/api/src/schema.ts b/apps/api/src/schema.ts index 609289bf7..8097ae1fb 100644 --- a/apps/api/src/schema.ts +++ b/apps/api/src/schema.ts @@ -31,4 +31,35 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ }), ]); +export const deployRequestSchema = z.discriminatedUnion("applicationType", [ + z.object({ + applicationId: z.string(), + title: z.string().optional(), + description: z.string().optional(), + server: z.boolean().optional(), + type: z.enum(["deploy", "redeploy"]), + applicationType: z.literal("application"), + serverId: z.string().min(1), + }), + z.object({ + composeId: z.string(), + title: z.string().optional(), + description: z.string().optional(), + server: z.boolean().optional(), + type: z.enum(["deploy", "redeploy"]), + applicationType: z.literal("compose"), + serverId: z.string().min(1), + }), + z.object({ + applicationId: z.string(), + previewDeploymentId: z.string(), + title: z.string().optional(), + description: z.string().optional(), + server: z.boolean().optional(), + type: z.enum(["deploy"]), + applicationType: z.literal("application-preview"), + serverId: z.string().min(1), + }), +]); + export type DeployJob = z.infer; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 5299ba6c2..c131d9b75 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -39,8 +39,10 @@ import { import { db } from "@/server/db"; import { apiCreateApplication, + apiDeployApplication, apiFindMonitoringStats, apiFindOneApplication, + apiRedeployApplication, apiReloadApplication, apiSaveBitbucketProvider, apiSaveBuildType, @@ -292,7 +294,7 @@ export const applicationRouter = createTRPCRouter({ }), redeploy: protectedProcedure - .input(apiFindOneApplication) + .input(apiRedeployApplication) .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); if ( @@ -305,8 +307,8 @@ export const applicationRouter = createTRPCRouter({ } const jobData: DeploymentJob = { applicationId: input.applicationId, - titleLog: "Rebuild deployment", - descriptionLog: "", + titleLog: input.title || "Rebuild deployment", + descriptionLog: input.description || "", type: "redeploy", applicationType: "application", server: !!application.serverId, @@ -643,7 +645,7 @@ export const applicationRouter = createTRPCRouter({ return true; }), deploy: protectedProcedure - .input(apiFindOneApplication) + .input(apiDeployApplication) .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); if ( @@ -656,8 +658,8 @@ export const applicationRouter = createTRPCRouter({ } const jobData: DeploymentJob = { applicationId: input.applicationId, - titleLog: "Manual deployment", - descriptionLog: "", + titleLog: input.title || "Manual deployment", + descriptionLog: input.description || "", type: "deploy", applicationType: "application", server: !!application.serverId, diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 6d6b8b55d..f80dc98f0 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -47,9 +47,11 @@ import { db } from "@/server/db"; import { apiCreateCompose, apiDeleteCompose, + apiDeployCompose, apiFetchServices, apiFindCompose, apiRandomizeCompose, + apiRedeployCompose, apiUpdateCompose, compose as composeTable, } from "@/server/db/schema"; @@ -337,7 +339,7 @@ export const composeRouter = createTRPCRouter({ }), deploy: protectedProcedure - .input(apiFindCompose) + .input(apiDeployCompose) .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); @@ -349,10 +351,10 @@ export const composeRouter = createTRPCRouter({ } const jobData: DeploymentJob = { composeId: input.composeId, - titleLog: "Manual deployment", + titleLog: input.title || "Manual deployment", type: "deploy", applicationType: "compose", - descriptionLog: "", + descriptionLog: input.description || "", server: !!compose.serverId, }; @@ -371,7 +373,7 @@ export const composeRouter = createTRPCRouter({ ); }), redeploy: protectedProcedure - .input(apiFindCompose) + .input(apiRedeployCompose) .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); if (compose.project.organizationId !== ctx.session.activeOrganizationId) { @@ -382,10 +384,10 @@ export const composeRouter = createTRPCRouter({ } const jobData: DeploymentJob = { composeId: input.composeId, - titleLog: "Rebuild deployment", + titleLog: input.title || "Rebuild deployment", type: "redeploy", applicationType: "compose", - descriptionLog: "", + descriptionLog: input.description || "", server: !!compose.serverId, }; if (IS_CLOUD && compose.serverId) { diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 198611a77..fca242dc4 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -327,6 +327,26 @@ export const apiFindOneApplication = createSchema }) .required(); +export const apiDeployApplication = createSchema + .pick({ + applicationId: true, + }) + .extend({ + applicationId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), + }); + +export const apiRedeployApplication = createSchema + .pick({ + applicationId: true, + }) + .extend({ + applicationId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), + }); + export const apiReloadApplication = createSchema .pick({ appName: true, diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index 57d8d9f1e..b1d28c874 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -180,6 +180,18 @@ export const apiFindCompose = z.object({ composeId: z.string().min(1), }); +export const apiDeployCompose = z.object({ + composeId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), +}); + +export const apiRedeployCompose = z.object({ + composeId: z.string().min(1), + title: z.string().optional(), + description: z.string().optional(), +}); + export const apiDeleteCompose = z.object({ composeId: z.string().min(1), deleteVolumes: z.boolean(), From 32cbc5b4b756353ec028dfcc469579e8bed32a91 Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Thu, 4 Sep 2025 19:12:29 +0530 Subject: [PATCH 2/3] feat: Add custom title/description for deployments via API/CLI - Add optional title/description fields to deployment schemas - Update TRPC and external API endpoints - Replace generic "Manual deployment" with custom titles - Maintain backward compatibility with default values Fixes #1485 --- apps/api/src/index.ts | 15 ++++----------- apps/api/src/schema.ts | 42 ++++++------------------------------------ apps/api/src/utils.ts | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 57 deletions(-) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index a76b60032..fcac429ca 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import { Inngest } from "inngest"; import { serve as serveInngest } from "inngest/hono"; import { logger } from "./logger.js"; -import { type DeployJob, deployJobSchema, deployRequestSchema } from "./schema.js"; +import { type DeployJob, deployJobSchema } from "./schema.js"; import { deploy } from "./utils.js"; const app = new Hono(); @@ -84,22 +84,15 @@ app.use(async (c, next) => { return next(); }); -app.post("/deploy", zValidator("json", deployRequestSchema), async (c) => { +app.post("/deploy", zValidator("json", deployJobSchema), async (c) => { const data = c.req.valid("json"); logger.info("Received deployment request", data); try { - // Transform the request to the internal job format - const jobData: DeployJob = { - ...data, - titleLog: data.title || "Manual deployment", - descriptionLog: data.description || "", - }; - // Send event to Inngest instead of adding to Redis queue await inngest.send({ name: "deployment/requested", - data: jobData, + data, }); logger.info("Deployment event sent to Inngest", { @@ -142,4 +135,4 @@ app.on( const port = Number.parseInt(process.env.PORT || "3000"); logger.info("Starting Deployments Server with Inngest ✅", port); -serve({ fetch: app.fetch, port }); +serve({ fetch: app.fetch, port }); \ No newline at end of file diff --git a/apps/api/src/schema.ts b/apps/api/src/schema.ts index 8097ae1fb..f29b7c6e0 100644 --- a/apps/api/src/schema.ts +++ b/apps/api/src/schema.ts @@ -3,8 +3,8 @@ import { z } from "zod"; export const deployJobSchema = z.discriminatedUnion("applicationType", [ z.object({ applicationId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy", "redeploy"]), applicationType: z.literal("application"), @@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ }), z.object({ composeId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy", "redeploy"]), applicationType: z.literal("compose"), @@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ z.object({ applicationId: z.string(), previewDeploymentId: z.string(), - titleLog: z.string(), - descriptionLog: z.string(), + titleLog: z.string().optional(), + descriptionLog: z.string().optional(), server: z.boolean().optional(), type: z.enum(["deploy"]), applicationType: z.literal("application-preview"), @@ -31,35 +31,5 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ }), ]); -export const deployRequestSchema = z.discriminatedUnion("applicationType", [ - z.object({ - applicationId: z.string(), - title: z.string().optional(), - description: z.string().optional(), - server: z.boolean().optional(), - type: z.enum(["deploy", "redeploy"]), - applicationType: z.literal("application"), - serverId: z.string().min(1), - }), - z.object({ - composeId: z.string(), - title: z.string().optional(), - description: z.string().optional(), - server: z.boolean().optional(), - type: z.enum(["deploy", "redeploy"]), - applicationType: z.literal("compose"), - serverId: z.string().min(1), - }), - z.object({ - applicationId: z.string(), - previewDeploymentId: z.string(), - title: z.string().optional(), - description: z.string().optional(), - server: z.boolean().optional(), - type: z.enum(["deploy"]), - applicationType: z.literal("application-preview"), - serverId: z.string().min(1), - }), -]); export type DeployJob = z.infer; diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts index ee3943d34..ee2ac3e50 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/utils.ts @@ -18,14 +18,14 @@ export const deploy = async (job: DeployJob) => { if (job.type === "redeploy") { await rebuildRemoteApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Rebuild deployment", + descriptionLog: job.descriptionLog || "", }); } else if (job.type === "deploy") { await deployRemoteApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Manual deployment", + descriptionLog: job.descriptionLog || "", }); } } @@ -38,14 +38,14 @@ export const deploy = async (job: DeployJob) => { if (job.type === "redeploy") { await rebuildRemoteCompose({ composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Rebuild deployment", + descriptionLog: job.descriptionLog || "", }); } else if (job.type === "deploy") { await deployRemoteCompose({ composeId: job.composeId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Manual deployment", + descriptionLog: job.descriptionLog || "", }); } } @@ -57,8 +57,8 @@ export const deploy = async (job: DeployJob) => { if (job.type === "deploy") { await deployRemotePreviewApplication({ applicationId: job.applicationId, - titleLog: job.titleLog, - descriptionLog: job.descriptionLog, + titleLog: job.titleLog || "Preview Deployment", + descriptionLog: job.descriptionLog || "", previewDeploymentId: job.previewDeploymentId, }); } From fc2bd4498363e6a4b312995468f607d79aa3ea03 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:49:09 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes --- apps/api/src/index.ts | 2 +- apps/api/src/schema.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index fcac429ca..aa7358335 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -135,4 +135,4 @@ app.on( const port = Number.parseInt(process.env.PORT || "3000"); logger.info("Starting Deployments Server with Inngest ✅", port); -serve({ fetch: app.fetch, port }); \ No newline at end of file +serve({ fetch: app.fetch, port }); diff --git a/apps/api/src/schema.ts b/apps/api/src/schema.ts index f29b7c6e0..f87d0ee32 100644 --- a/apps/api/src/schema.ts +++ b/apps/api/src/schema.ts @@ -31,5 +31,4 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [ }), ]); - export type DeployJob = z.infer;