From 90d98803016c8dae44d6cc37eec84d7b26b1db15 Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Wed, 3 Sep 2025 09:05:33 +0530 Subject: [PATCH] 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(),