diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index c68b35a3f..148cacf29 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -58,7 +58,7 @@ "@codemirror/search": "^6.6.0", "@codemirror/view": "^6.39.15", "@dokploy/server": "workspace:*", - "@dokploy/trpc-openapi": "0.0.18", + "@dokploy/trpc-openapi": "0.0.19", "@faker-js/faker": "^8.4.1", "@hookform/resolvers": "^5.2.2", "@octokit/auth-app": "^6.1.3", diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index 4323d9e47..ac35e820a 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -10,6 +10,12 @@ import { adminProcedure, createTRPCRouter } from "../trpc"; export const adminRouter = createTRPCRouter({ setupMonitoring: adminProcedure + .meta({ + openapi: { + summary: "Update web server monitoring settings", + description: "Update the monitoring configuration for the web server including refresh rates, thresholds, and container services. Restarts the monitoring system and returns the updated settings. Disabled on cloud.", + }, + }) .input(apiUpdateWebServerMonitoring) .mutation(async ({ input }) => { try { diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index 3a299235a..7d37f536c 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -41,12 +41,24 @@ import { generatePassword } from "@/templates/utils"; export const aiRouter = createTRPCRouter({ one: adminProcedure + .meta({ + openapi: { + summary: "Get AI settings by ID", + description: "Returns a single AI provider configuration by its ID.", + }, + }) .input(z.object({ aiId: z.string() })) .query(async ({ input }) => { return await getAiSettingById(input.aiId); }), getModels: protectedProcedure + .meta({ + openapi: { + summary: "List available AI models", + description: "Fetches the list of models from the given AI provider URL. Supports OpenAI-compatible, Ollama, Gemini, Perplexity, ZAI, and MiniMax providers.", + }, + }) .input(z.object({ apiUrl: z.string().min(1), apiKey: z.string() })) .query(async ({ input }) => { try { @@ -174,33 +186,75 @@ export const aiRouter = createTRPCRouter({ }); } }), - create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => { + create: adminProcedure + .meta({ + openapi: { + summary: "Create AI provider", + description: "Saves a new AI provider configuration (API URL, key, model) for the current organization.", + }, + }) + .input(apiCreateAi) + .mutation(async ({ ctx, input }) => { return await saveAiSettings(ctx.session.activeOrganizationId, input); }), - update: adminProcedure.input(apiUpdateAi).mutation(async ({ ctx, input }) => { + update: adminProcedure + .meta({ + openapi: { + summary: "Update AI provider", + description: "Updates an existing AI provider configuration for the current organization.", + }, + }) + .input(apiUpdateAi) + .mutation(async ({ ctx, input }) => { return await saveAiSettings(ctx.session.activeOrganizationId, input); }), - getAll: adminProcedure.query(async ({ ctx }) => { + getAll: adminProcedure + .meta({ + openapi: { + summary: "List all AI providers", + description: "Returns all AI provider configurations for the current organization.", + }, + }) + .query(async ({ ctx }) => { return await getAiSettingsByOrganizationId( ctx.session.activeOrganizationId, ); }), get: adminProcedure + .meta({ + openapi: { + summary: "Get AI provider", + description: "Returns a single AI provider configuration by its ID.", + }, + }) .input(z.object({ aiId: z.string() })) .query(async ({ input }) => { return await getAiSettingById(input.aiId); }), delete: adminProcedure + .meta({ + openapi: { + summary: "Delete AI provider", + description: "Removes an AI provider configuration by its ID.", + }, + }) .input(z.object({ aiId: z.string() })) .mutation(async ({ input }) => { return await deleteAiSettings(input.aiId); }), - getEnabledProviders: protectedProcedure.query(async ({ ctx }) => { + getEnabledProviders: protectedProcedure + .meta({ + openapi: { + summary: "List enabled AI providers", + description: "Returns a lightweight list of enabled AI providers (ID, name, model) for the current organization, suitable for dropdown selectors.", + }, + }) + .query(async ({ ctx }) => { const settings = await getAiSettingsByOrganizationId( ctx.session.activeOrganizationId, ); @@ -210,6 +264,12 @@ export const aiRouter = createTRPCRouter({ }), analyzeLogs: protectedProcedure + .meta({ + openapi: { + summary: "Analyze logs with AI", + description: "Sends build or runtime logs to the specified AI provider for analysis. Returns a summary of issues found, root causes, and suggested fixes.", + }, + }) .input( z.object({ aiId: z.string().min(1), @@ -268,6 +328,12 @@ ${input.logs}`, }), testConnection: protectedProcedure + .meta({ + openapi: { + summary: "Test AI provider connection", + description: "Sends a minimal prompt to the specified AI provider and model to verify the API URL, key, and model are valid and reachable.", + }, + }) .input( z.object({ apiUrl: z.string().min(1), @@ -302,6 +368,12 @@ ${input.logs}`, }), suggest: protectedProcedure + .meta({ + openapi: { + summary: "Suggest deployment variants", + description: "Uses AI to generate deployment configuration suggestions (docker-compose variants) based on the user's input prompt.", + }, + }) .input( z.object({ aiId: z.string(), @@ -323,6 +395,12 @@ ${input.logs}`, } }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy AI suggestion", + description: "Deploys an AI-generated suggestion by creating a compose service with its docker-compose file, environment variables, domains, and config file mounts.", + }, + }) .input(deploySuggestionSchema) .mutation(async ({ ctx, input }) => { const environment = await findEnvironmentById(input.environmentId); diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 228c98656..4d093bd57 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -79,6 +79,12 @@ import { cancelDeployment, deploy } from "@/server/utils/deploy"; export const applicationRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create an application", + description: "Creates a new application in the specified project environment. Supports GitHub, GitLab, Bitbucket, Git, Docker image, and drop sources.", + }, + }) .input(apiCreateApplication) .mutation(async ({ input, ctx }) => { try { @@ -134,6 +140,12 @@ export const applicationRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get an application", + description: "Retrieves detailed information about an application by its ID, including git provider access status and deployment configuration.", + }, + }) .input(apiFindOneApplication) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.applicationId, "read"); @@ -189,6 +201,12 @@ export const applicationRouter = createTRPCRouter({ }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload an application", + description: "Restarts the Docker container for the application by mechanizing it. Resets the application status to idle, then to done on success or error on failure.", + }, + }) .input(apiReloadApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -218,6 +236,12 @@ export const applicationRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete an application", + description: "Permanently deletes an application and cleans up all associated resources including Docker services, Traefik configuration, deployments, middlewares, and source code.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.applicationId, "delete"); @@ -279,6 +303,12 @@ export const applicationRouter = createTRPCRouter({ }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop an application", + description: "Stops the running Docker service for the application and sets its status to idle.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -301,6 +331,12 @@ export const applicationRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start an application", + description: "Starts the Docker service for the application and sets its status to done.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -323,6 +359,12 @@ export const applicationRouter = createTRPCRouter({ }), redeploy: protectedProcedure + .meta({ + openapi: { + summary: "Redeploy an application", + description: "Triggers a rebuild and redeployment of the application. Queues a deployment job or executes it directly for cloud servers.", + }, + }) .input(apiRedeployApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -367,6 +409,12 @@ export const applicationRouter = createTRPCRouter({ }); }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables", + description: "Updates the environment variables, build arguments, and build secrets for an application.", + }, + }) .input(apiSaveEnvironmentVariables) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -388,6 +436,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveBuildType: protectedProcedure + .meta({ + openapi: { + summary: "Save build type configuration", + description: "Updates the build type and related settings for an application, including Dockerfile path, build context, publish directory, and build stage.", + }, + }) .input(apiSaveBuildType) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -413,6 +467,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveGithubProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save GitHub provider", + description: "Configures the application to use a GitHub repository as its source, setting the repository, branch, owner, and build path.", + }, + }) .input(apiSaveGithubProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -440,6 +500,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveGitlabProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save GitLab provider", + description: "Configures the application to use a GitLab repository as its source, setting the repository, branch, owner, build path, and project ID.", + }, + }) .input(apiSaveGitlabProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -468,6 +534,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveBitbucketProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save Bitbucket provider", + description: "Configures the application to use a Bitbucket repository as its source, setting the repository, branch, owner, and build path.", + }, + }) .input(apiSaveBitbucketProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -495,6 +567,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveGiteaProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save Gitea provider", + description: "Configures the application to use a Gitea repository as its source, setting the repository, branch, owner, and build path.", + }, + }) .input(apiSaveGiteaProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -521,6 +599,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveDockerProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save Docker provider", + description: "Configures the application to use a Docker image as its source, setting the image name, registry URL, and optional credentials.", + }, + }) .input(apiSaveDockerProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -544,6 +628,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), saveGitProvider: protectedProcedure + .meta({ + openapi: { + summary: "Save Git provider", + description: "Configures the application to use a custom Git repository URL as its source, with optional SSH key authentication.", + }, + }) .input(apiSaveGitProvider) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -569,6 +659,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), disconnectGitProvider: protectedProcedure + .meta({ + openapi: { + summary: "Disconnect git provider", + description: "Removes all git provider configuration from the application, resetting source type to default and clearing repository, branch, and owner fields for all providers.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -622,6 +718,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), markRunning: protectedProcedure + .meta({ + openapi: { + summary: "Mark application as running", + description: "Sets the application status to running. Used to indicate that a deployment is in progress.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -637,6 +739,12 @@ export const applicationRouter = createTRPCRouter({ }); }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update an application", + description: "Updates the general configuration of an application such as name, description, memory limits, CPU limits, and other settings.", + }, + }) .input(apiUpdateApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -673,6 +781,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), refreshToken: protectedProcedure + .meta({ + openapi: { + summary: "Refresh deploy token", + description: "Regenerates the webhook refresh token for the application, invalidating the previous token used for triggering deployments.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -691,6 +805,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy an application", + description: "Triggers a new deployment for the application. Queues a deployment job or executes it directly for cloud servers.", + }, + }) .input(apiDeployApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -735,6 +855,12 @@ export const applicationRouter = createTRPCRouter({ }), cleanQueues: protectedProcedure + .meta({ + openapi: { + summary: "Clean deployment queues", + description: "Removes all pending deployment jobs from the queue for the specified application.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -743,6 +869,12 @@ export const applicationRouter = createTRPCRouter({ await cleanQueuesByApplication(input.applicationId); }), clearDeployments: protectedProcedure + .meta({ + openapi: { + summary: "Clear old deployments", + description: "Removes old deployment logs and artifacts for the application to free up disk space.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -759,6 +891,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), killBuild: protectedProcedure + .meta({ + openapi: { + summary: "Kill active build", + description: "Forcefully terminates the currently running Docker build process for the application.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -774,6 +912,12 @@ export const applicationRouter = createTRPCRouter({ }); }), readTraefikConfig: protectedProcedure + .meta({ + openapi: { + summary: "Read Traefik configuration", + description: "Reads the current Traefik reverse proxy configuration file for the application. Supports both local and remote server configurations.", + }, + }) .input(apiFindOneApplication) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -793,6 +937,12 @@ export const applicationRouter = createTRPCRouter({ }), dropDeployment: protectedProcedure + .meta({ + openapi: { + summary: "Deploy from zip upload", + description: "Deploys an application from an uploaded zip file. Unzips the file into the application directory and triggers a deployment.", + }, + }) .input( zfd.formData({ applicationId: z.string(), @@ -849,6 +999,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), updateTraefikConfig: protectedProcedure + .meta({ + openapi: { + summary: "Update Traefik configuration", + description: "Writes a new Traefik reverse proxy configuration for the application. Supports both local and remote server configurations.", + }, + }) .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -873,6 +1029,12 @@ export const applicationRouter = createTRPCRouter({ return true; }), readAppMonitoring: withPermission("monitoring", "read") + .meta({ + openapi: { + summary: "Read application monitoring stats", + description: "Retrieves CPU and memory monitoring statistics for the application. Only available in self-hosted mode.", + }, + }) .input(apiFindMonitoringStats) .query(async ({ input }) => { if (IS_CLOUD) { @@ -886,6 +1048,12 @@ export const applicationRouter = createTRPCRouter({ return stats; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move application to another environment", + description: "Moves an application to a different environment within the same project or to another project's environment.", + }, + }) .input( z.object({ applicationId: z.string(), @@ -922,6 +1090,12 @@ export const applicationRouter = createTRPCRouter({ }), cancelDeployment: protectedProcedure + .meta({ + openapi: { + summary: "Cancel a deployment", + description: "Cancels an in-progress deployment for the application and resets its status to idle. Only available in cloud version.", + }, + }) .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -972,6 +1146,12 @@ export const applicationRouter = createTRPCRouter({ }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search applications", + description: "Searches applications by name, appName, description, repository, owner, or Docker image with pagination. Respects service-level access control.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -1104,6 +1284,12 @@ export const applicationRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read application logs", + description: "Retrieves Docker container logs for the application with configurable tail length, time range, and optional text search filtering.", + }, + }) .input( apiFindOneApplication.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts index c3633b135..1d1090113 100644 --- a/apps/dokploy/server/api/routers/backup.ts +++ b/apps/dokploy/server/api/routers/backup.ts @@ -78,6 +78,12 @@ interface RcloneFile { export const backupRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a backup", + description: "Creates a new backup configuration for a database or compose service. If enabled, automatically schedules the backup according to the provided cron expression.", + }, + }) .input(apiCreateBackup) .mutation(async ({ input, ctx }) => { try { @@ -152,6 +158,12 @@ export const backupRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a backup", + description: "Returns the details of a specific backup configuration by its ID.", + }, + }) .input(apiFindOneBackup) .query(async ({ input, ctx }) => { const backup = await findBackupById(input.backupId); @@ -172,6 +184,12 @@ export const backupRouter = createTRPCRouter({ return backup; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a backup", + description: "Updates an existing backup configuration. Reschedules or removes the backup job depending on the enabled state.", + }, + }) .input(apiUpdateBackup) .mutation(async ({ input, ctx }) => { try { @@ -229,6 +247,12 @@ export const backupRouter = createTRPCRouter({ } }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a backup", + description: "Permanently removes a backup configuration and unschedules any associated backup job.", + }, + }) .input(apiRemoveBackup) .mutation(async ({ input, ctx }) => { try { @@ -272,6 +296,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupPostgres: protectedProcedure + .meta({ + openapi: { + summary: "Run a PostgreSQL backup manually", + description: "Immediately executes a PostgreSQL backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -303,6 +333,12 @@ export const backupRouter = createTRPCRouter({ }), manualBackupMySql: protectedProcedure + .meta({ + openapi: { + summary: "Run a MySQL backup manually", + description: "Immediately executes a MySQL backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -330,6 +366,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupMariadb: protectedProcedure + .meta({ + openapi: { + summary: "Run a MariaDB backup manually", + description: "Immediately executes a MariaDB backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -357,6 +399,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupCompose: protectedProcedure + .meta({ + openapi: { + summary: "Run a Compose backup manually", + description: "Immediately executes a Compose service backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -384,6 +432,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupMongo: protectedProcedure + .meta({ + openapi: { + summary: "Run a MongoDB backup manually", + description: "Immediately executes a MongoDB backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -411,6 +465,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupLibsql: protectedProcedure + .meta({ + openapi: { + summary: "Run a LibSQL backup manually", + description: "Immediately executes a LibSQL backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { try { @@ -438,6 +498,12 @@ export const backupRouter = createTRPCRouter({ } }), manualBackupWebServer: withPermission("backup", "create") + .meta({ + openapi: { + summary: "Run a web server backup manually", + description: "Immediately executes a web server backup using the specified backup configuration. Cleans up old backups according to retention settings.", + }, + }) .input(apiFindOneBackup) .mutation(async ({ input, ctx }) => { const backup = await findBackupById(input.backupId); @@ -451,6 +517,12 @@ export const backupRouter = createTRPCRouter({ return true; }), listBackupFiles: withPermission("backup", "read") + .meta({ + openapi: { + summary: "List backup files in S3", + description: "Lists backup files stored in the S3 destination bucket. Supports searching by path prefix and returns up to 100 results.", + }, + }) .input( z.object({ destinationId: z.string(), diff --git a/apps/dokploy/server/api/routers/bitbucket.ts b/apps/dokploy/server/api/routers/bitbucket.ts index 7830b6b4c..513661ac3 100644 --- a/apps/dokploy/server/api/routers/bitbucket.ts +++ b/apps/dokploy/server/api/routers/bitbucket.ts @@ -25,6 +25,12 @@ import { export const bitbucketRouter = createTRPCRouter({ create: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Create Bitbucket provider", + description: "Creates a new Bitbucket provider configuration linked to the active organization. Requires gitProviders create permission.", + }, + }) .input(apiCreateBitbucket) .mutation(async ({ input, ctx }) => { try { @@ -50,11 +56,24 @@ export const bitbucketRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get Bitbucket provider", + description: "Returns a single Bitbucket provider configuration by its ID.", + }, + }) .input(apiFindOneBitbucket) .query(async ({ input }) => { return await findBitbucketById(input.bitbucketId); }), - bitbucketProviders: protectedProcedure.query(async ({ ctx }) => { + bitbucketProviders: protectedProcedure + .meta({ + openapi: { + summary: "List Bitbucket providers", + description: "Returns all Bitbucket providers accessible to the current user within the active organization.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleGitProviderIds(ctx.session); let result = await db.query.bitbucket.findMany({ @@ -77,16 +96,34 @@ export const bitbucketRouter = createTRPCRouter({ }), getBitbucketRepositories: protectedProcedure + .meta({ + openapi: { + summary: "List Bitbucket repositories", + description: "Fetches the list of repositories accessible by the Bitbucket provider. Calls the Bitbucket API using the provider's credentials.", + }, + }) .input(apiFindOneBitbucket) .query(async ({ input }) => { return await getBitbucketRepositories(input.bitbucketId); }), getBitbucketBranches: protectedProcedure + .meta({ + openapi: { + summary: "List Bitbucket branches", + description: "Fetches the list of branches for a specific Bitbucket repository. Calls the Bitbucket API using the provider's credentials.", + }, + }) .input(apiFindBitbucketBranches) .query(async ({ input }) => { return await getBitbucketBranches(input); }), testConnection: protectedProcedure + .meta({ + openapi: { + summary: "Test Bitbucket connection", + description: "Tests the connection to a Bitbucket provider by attempting to fetch its repositories. Returns the number of repositories found or throws an error on failure.", + }, + }) .input(apiBitbucketTestConnection) .mutation(async ({ input }) => { try { @@ -101,6 +138,12 @@ export const bitbucketRouter = createTRPCRouter({ } }), update: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Update Bitbucket provider", + description: "Updates a Bitbucket provider configuration. Requires gitProviders create permission.", + }, + }) .input(apiUpdateBitbucket) .mutation(async ({ input, ctx }) => { const result = await updateBitbucket(input.bitbucketId, { diff --git a/apps/dokploy/server/api/routers/certificate.ts b/apps/dokploy/server/api/routers/certificate.ts index b0692ebad..bc3538964 100644 --- a/apps/dokploy/server/api/routers/certificate.ts +++ b/apps/dokploy/server/api/routers/certificate.ts @@ -19,6 +19,12 @@ import { export const certificateRouter = createTRPCRouter({ create: withPermission("certificate", "create") + .meta({ + openapi: { + summary: "Create a certificate", + description: "Creates a new SSL/TLS certificate. In cloud mode, a server must be specified. Logs an audit entry upon creation.", + }, + }) .input(apiCreateCertificate) .mutation(async ({ input, ctx }) => { if (IS_CLOUD && !input.serverId) { @@ -41,6 +47,12 @@ export const certificateRouter = createTRPCRouter({ }), one: withPermission("certificate", "read") + .meta({ + openapi: { + summary: "Get a certificate", + description: "Returns a single certificate by its ID. Verifies that the certificate belongs to the current organization.", + }, + }) .input(apiFindCertificate) .query(async ({ input, ctx }) => { const certificates = await findCertificateById(input.certificateId); @@ -53,6 +65,12 @@ export const certificateRouter = createTRPCRouter({ return certificates; }), remove: withPermission("certificate", "delete") + .meta({ + openapi: { + summary: "Delete a certificate", + description: "Deletes a certificate by its ID after verifying organization ownership. Logs an audit entry before removal.", + }, + }) .input(apiFindCertificate) .mutation(async ({ input, ctx }) => { const certificates = await findCertificateById(input.certificateId); @@ -71,7 +89,14 @@ export const certificateRouter = createTRPCRouter({ await removeCertificateById(input.certificateId); return true; }), - all: withPermission("certificate", "read").query(async ({ ctx }) => { + all: withPermission("certificate", "read") + .meta({ + openapi: { + summary: "List all certificates", + description: "Returns all certificates belonging to the current organization, including their associated server information.", + }, + }) + .query(async ({ ctx }) => { return await db.query.certificates.findMany({ where: eq(certificates.organizationId, ctx.session.activeOrganizationId), with: { @@ -80,6 +105,12 @@ export const certificateRouter = createTRPCRouter({ }); }), update: withPermission("certificate", "update") + .meta({ + openapi: { + summary: "Update a certificate", + description: "Updates the name, certificate data, and private key of an existing certificate. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateCertificate) .mutation(async ({ input, ctx }) => { const certificate = await findCertificateById(input.certificateId); diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts index afd8a0e92..5913a10e8 100644 --- a/apps/dokploy/server/api/routers/cluster.ts +++ b/apps/dokploy/server/api/routers/cluster.ts @@ -13,6 +13,12 @@ import { createTRPCRouter, withPermission } from "../trpc"; export const clusterRouter = createTRPCRouter({ getNodes: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get cluster nodes", + description: "Retrieves all nodes in the Docker Swarm cluster. Optionally targets a remote server.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -25,6 +31,12 @@ export const clusterRouter = createTRPCRouter({ }), removeWorker: withPermission("server", "delete") + .meta({ + openapi: { + summary: "Remove a worker node", + description: "Drains and forcefully removes a worker node from the Docker Swarm cluster. An audit log entry is created for the removal.", + }, + }) .input( z.object({ nodeId: z.string(), @@ -60,6 +72,12 @@ export const clusterRouter = createTRPCRouter({ }), addWorker: withPermission("server", "create") + .meta({ + openapi: { + summary: "Get worker join command", + description: "Returns the Docker Swarm join command and token for adding a new worker node to the cluster, along with the Docker version.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -83,6 +101,12 @@ export const clusterRouter = createTRPCRouter({ }), addManager: withPermission("server", "create") + .meta({ + openapi: { + summary: "Get manager join command", + description: "Returns the Docker Swarm join command and token for adding a new manager node to the cluster, along with the Docker version.", + }, + }) .input( z.object({ serverId: z.string().optional(), diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index d395bdffc..67445ab4e 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -83,6 +83,12 @@ import { audit } from "../utils/audit"; export const composeRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a compose service", + description: "Creates a new Docker Compose service in the specified project environment with the given configuration.", + }, + }) .input(apiCreateCompose) .mutation(async ({ ctx, input }) => { try { @@ -133,6 +139,12 @@ export const composeRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a compose service", + description: "Retrieves detailed information about a compose service by its ID, including git provider access status and deployment configuration.", + }, + }) .input(apiFindCompose) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.composeId, "read"); @@ -189,6 +201,12 @@ export const composeRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a compose service", + description: "Updates the configuration of a compose service such as name, description, compose file content, and other settings.", + }, + }) .input(apiUpdateCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -204,6 +222,12 @@ export const composeRouter = createTRPCRouter({ return updated; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save compose environment variables", + description: "Updates the environment variables for a compose service.", + }, + }) .input(apiSaveEnvironmentVariablesCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -229,6 +253,12 @@ export const composeRouter = createTRPCRouter({ return true; }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a compose service", + description: "Permanently deletes a compose service and cleans up associated Docker resources, deployments, and directories. Optionally deletes associated volumes.", + }, + }) .input(apiDeleteCompose) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.composeId, "delete"); @@ -279,6 +309,12 @@ export const composeRouter = createTRPCRouter({ return composeResult; }), cleanQueues: protectedProcedure + .meta({ + openapi: { + summary: "Clean deployment queues", + description: "Removes all pending deployment jobs from the queue for the specified compose service.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -288,6 +324,12 @@ export const composeRouter = createTRPCRouter({ return { success: true, message: "Queues cleaned successfully" }; }), clearDeployments: protectedProcedure + .meta({ + openapi: { + summary: "Clear old deployments", + description: "Removes old deployment logs and artifacts for the compose service to free up disk space.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -304,6 +346,12 @@ export const composeRouter = createTRPCRouter({ return true; }), killBuild: protectedProcedure + .meta({ + openapi: { + summary: "Kill active build", + description: "Forcefully terminates the currently running Docker build process for the compose service.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -314,6 +362,12 @@ export const composeRouter = createTRPCRouter({ }), loadServices: protectedProcedure + .meta({ + openapi: { + summary: "Load compose services", + description: "Parses the compose file and returns the list of services defined in it, with their current container status.", + }, + }) .input(apiFetchServices) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -322,6 +376,12 @@ export const composeRouter = createTRPCRouter({ return await loadServices(input.composeId, input.type); }), loadMountsByService: protectedProcedure + .meta({ + openapi: { + summary: "Load mounts by service", + description: "Retrieves the Docker volume mounts for a specific service within a compose stack by inspecting the running container.", + }, + }) .input( z.object({ composeId: z.string().min(1), @@ -340,6 +400,12 @@ export const composeRouter = createTRPCRouter({ return mounts; }), fetchSourceType: protectedProcedure + .meta({ + openapi: { + summary: "Fetch and clone source", + description: "Clones the compose repository from the configured git provider and returns the source type. Executes the clone command locally or on a remote server.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { try { @@ -365,6 +431,12 @@ export const composeRouter = createTRPCRouter({ }), randomizeCompose: protectedProcedure + .meta({ + openapi: { + summary: "Randomize compose file", + description: "Adds a random suffix to service names and volumes in the compose file to avoid naming conflicts between deployments.", + }, + }) .input(apiRandomizeCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -381,6 +453,12 @@ export const composeRouter = createTRPCRouter({ return result; }), isolatedDeployment: protectedProcedure + .meta({ + openapi: { + summary: "Randomize for isolated deployment", + description: "Randomizes the compose file for isolated deployment mode, ensuring unique service and volume names to support parallel deployments.", + }, + }) .input(apiRandomizeCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -400,6 +478,12 @@ export const composeRouter = createTRPCRouter({ return result; }), getConvertedCompose: protectedProcedure + .meta({ + openapi: { + summary: "Get converted compose file", + description: "Returns the compose file with domains injected as Traefik labels, converted to YAML format ready for deployment.", + }, + }) .input(apiFindCompose) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -414,6 +498,12 @@ export const composeRouter = createTRPCRouter({ }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a compose service", + description: "Triggers a new deployment for the compose service. Queues a deployment job or executes it directly for cloud servers.", + }, + }) .input(apiDeployCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -464,6 +554,12 @@ export const composeRouter = createTRPCRouter({ }; }), redeploy: protectedProcedure + .meta({ + openapi: { + summary: "Redeploy a compose service", + description: "Triggers a rebuild and redeployment of the compose service. Queues a deployment job or executes it directly for cloud servers.", + }, + }) .input(apiRedeployCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -512,6 +608,12 @@ export const composeRouter = createTRPCRouter({ }; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a compose service", + description: "Stops all running containers for the compose service using docker compose stop.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -528,6 +630,12 @@ export const composeRouter = createTRPCRouter({ return true; }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a compose service", + description: "Starts all containers for the compose service using docker compose start.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -544,6 +652,12 @@ export const composeRouter = createTRPCRouter({ return true; }), getDefaultCommand: protectedProcedure + .meta({ + openapi: { + summary: "Get default compose command", + description: "Generates and returns the default docker compose command that would be used to deploy the service.", + }, + }) .input(apiFindCompose) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -554,6 +668,12 @@ export const composeRouter = createTRPCRouter({ return `docker ${command}`; }), refreshToken: protectedProcedure + .meta({ + openapi: { + summary: "Refresh deploy token", + description: "Regenerates the webhook refresh token for the compose service, invalidating the previous token used for triggering deployments.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -572,6 +692,12 @@ export const composeRouter = createTRPCRouter({ return true; }), deployTemplate: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a template", + description: "Creates a new compose service from a template by fetching its files, processing variables, creating mounts and domains, and setting up the compose configuration.", + }, + }) .input( z.object({ environmentId: z.string(), @@ -680,6 +806,12 @@ export const composeRouter = createTRPCRouter({ }), templates: protectedProcedure + .meta({ + openapi: { + summary: "List available templates", + description: "Fetches the list of available compose templates from the GitHub templates repository.", + }, + }) .input(z.object({ baseUrl: z.string().optional() })) .query(async ({ input }) => { try { @@ -698,6 +830,12 @@ export const composeRouter = createTRPCRouter({ }), getTags: protectedProcedure + .meta({ + openapi: { + summary: "Get template tags", + description: "Fetches all unique tags from the available compose templates for filtering purposes.", + }, + }) .input(z.object({ baseUrl: z.string().optional() })) .query(async ({ input }) => { const githubTemplates = await fetchTemplatesList(input.baseUrl); @@ -707,6 +845,12 @@ export const composeRouter = createTRPCRouter({ return uniqueTags; }), disconnectGitProvider: protectedProcedure + .meta({ + openapi: { + summary: "Disconnect git provider", + description: "Removes all git provider configuration from the compose service, resetting source type to default and clearing repository, branch, and owner fields for all providers.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -759,6 +903,12 @@ export const composeRouter = createTRPCRouter({ }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move compose to another environment", + description: "Moves a compose service to a different environment within the same project or to another project's environment.", + }, + }) .input( z.object({ composeId: z.string(), @@ -796,6 +946,12 @@ export const composeRouter = createTRPCRouter({ }), processTemplate: protectedProcedure + .meta({ + openapi: { + summary: "Process a template", + description: "Processes a base64-encoded template configuration, resolving variables and generating the compose file and environment settings without applying them.", + }, + }) .input( z.object({ base64: z.string(), @@ -860,6 +1016,12 @@ export const composeRouter = createTRPCRouter({ }), import: protectedProcedure + .meta({ + openapi: { + summary: "Import a template", + description: "Imports a base64-encoded template into an existing compose service, replacing its compose file, environment variables, mounts, and domains with the template's configuration.", + }, + }) .input( z.object({ base64: z.string(), @@ -972,6 +1134,12 @@ export const composeRouter = createTRPCRouter({ }), cancelDeployment: protectedProcedure + .meta({ + openapi: { + summary: "Cancel a deployment", + description: "Cancels an in-progress deployment for the compose service and resets its status to idle. Only available in cloud version.", + }, + }) .input(apiFindCompose) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -1025,6 +1193,12 @@ export const composeRouter = createTRPCRouter({ }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search compose services", + description: "Searches compose services by name, appName, or description with pagination. Respects service-level access control.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -1133,6 +1307,12 @@ export const composeRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read compose container logs", + description: "Retrieves Docker container logs for a specific container within the compose service with configurable tail length, time range, and optional text search filtering.", + }, + }) .input( apiFindCompose.extend({ containerId: z diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts index 03cd3c935..d57a251e1 100644 --- a/apps/dokploy/server/api/routers/deployment.ts +++ b/apps/dokploy/server/api/routers/deployment.ts @@ -34,6 +34,12 @@ import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc"; export const deploymentRouter = createTRPCRouter({ all: protectedProcedure + .meta({ + openapi: { + summary: "List deployments by application", + description: "Returns all deployments associated with the given application, ordered by creation date.", + }, + }) .input(apiFindAllByApplication) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -43,6 +49,12 @@ export const deploymentRouter = createTRPCRouter({ }), allByCompose: protectedProcedure + .meta({ + openapi: { + summary: "List deployments by compose", + description: "Returns all deployments associated with the given compose service.", + }, + }) .input(apiFindAllByCompose) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -51,11 +63,24 @@ export const deploymentRouter = createTRPCRouter({ return await findAllDeploymentsByComposeId(input.composeId); }), allByServer: withPermission("deployment", "read") + .meta({ + openapi: { + summary: "List deployments by server", + description: "Returns all deployments associated with the given server.", + }, + }) .input(apiFindAllByServer) .query(async ({ input }) => { return await findAllDeploymentsByServerId(input.serverId); }), - allCentralized: withPermission("deployment", "read").query( + allCentralized: withPermission("deployment", "read") + .meta({ + openapi: { + summary: "List all deployments centralized", + description: "Returns all deployments across all services in the organization. Non-admin users only see deployments for their accessible services.", + }, + }) + .query( async ({ ctx }) => { const orgId = ctx.session.activeOrganizationId; const accessedServices = @@ -69,7 +94,14 @@ export const deploymentRouter = createTRPCRouter({ }, ), - queueList: withPermission("deployment", "read").query(async ({ ctx }) => { + queueList: withPermission("deployment", "read") + .meta({ + openapi: { + summary: "List deployment queue jobs", + description: "Returns all jobs in the deployment queue with their current state, timestamps, and resolved service paths.", + }, + }) + .query(async ({ ctx }) => { const orgId = ctx.session.activeOrganizationId; let rows: QueueJobRow[]; @@ -116,6 +148,12 @@ export const deploymentRouter = createTRPCRouter({ }), allByType: protectedProcedure + .meta({ + openapi: { + summary: "List deployments by service type", + description: "Returns all deployments for a given service ID and type (application, compose, etc.), including associated rollback information.", + }, + }) .input(apiFindAllByType) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.id, { @@ -131,6 +169,12 @@ export const deploymentRouter = createTRPCRouter({ return deploymentsList; }), killProcess: protectedProcedure + .meta({ + openapi: { + summary: "Cancel a running deployment", + description: "Kills the running process of a deployment by sending SIGKILL to its PID. Updates the deployment status to error.", + }, + }) .input( z.object({ deploymentId: z.string().min(1), @@ -168,6 +212,12 @@ export const deploymentRouter = createTRPCRouter({ }), removeDeployment: protectedProcedure + .meta({ + openapi: { + summary: "Delete a deployment", + description: "Permanently removes a deployment record and its associated data.", + }, + }) .input( z.object({ deploymentId: z.string().min(1), diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts index cf7395a3f..3d365b63b 100644 --- a/apps/dokploy/server/api/routers/destination.ts +++ b/apps/dokploy/server/api/routers/destination.ts @@ -22,6 +22,12 @@ import { export const destinationRouter = createTRPCRouter({ create: withPermission("destination", "create") + .meta({ + openapi: { + summary: "Create backup destination", + description: "Creates a new S3-compatible backup destination for the current organization and logs an audit event.", + }, + }) .input(apiCreateDestination) .mutation(async ({ input, ctx }) => { try { @@ -45,6 +51,12 @@ export const destinationRouter = createTRPCRouter({ } }), testConnection: withPermission("destination", "create") + .meta({ + openapi: { + summary: "Test backup destination connection", + description: "Tests connectivity to an S3-compatible bucket using rclone. Runs locally or on a remote server depending on configuration.", + }, + }) .input(apiCreateDestination) .mutation(async ({ input }) => { const { @@ -102,6 +114,12 @@ export const destinationRouter = createTRPCRouter({ } }), one: withPermission("destination", "read") + .meta({ + openapi: { + summary: "Get backup destination", + description: "Returns a single backup destination by ID. Verifies the caller belongs to the same organization.", + }, + }) .input(apiFindOneDestination) .query(async ({ input, ctx }) => { const destination = await findDestinationById(input.destinationId); @@ -113,13 +131,26 @@ export const destinationRouter = createTRPCRouter({ } return destination; }), - all: withPermission("destination", "read").query(async ({ ctx }) => { + all: withPermission("destination", "read") + .meta({ + openapi: { + summary: "List all backup destinations", + description: "Returns all S3-compatible backup destinations for the current organization, ordered by creation date descending.", + }, + }) + .query(async ({ ctx }) => { return await db.query.destinations.findMany({ where: eq(destinations.organizationId, ctx.session.activeOrganizationId), orderBy: [desc(destinations.createdAt)], }); }), remove: withPermission("destination", "delete") + .meta({ + openapi: { + summary: "Delete backup destination", + description: "Removes a backup destination by ID. Verifies organization ownership and logs an audit event before deletion.", + }, + }) .input(apiRemoveDestination) .mutation(async ({ input, ctx }) => { try { @@ -147,6 +178,12 @@ export const destinationRouter = createTRPCRouter({ } }), update: withPermission("destination", "create") + .meta({ + openapi: { + summary: "Update backup destination", + description: "Updates an existing backup destination. Verifies organization ownership before applying changes and logs an audit event.", + }, + }) .input(apiUpdateDestination) .mutation(async ({ input, ctx }) => { try { diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index dd322e6c3..86003afb6 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -20,6 +20,12 @@ export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/; export const dockerRouter = createTRPCRouter({ getContainers: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Get Docker containers", + description: "Retrieves a list of all Docker containers. Optionally targets a specific remote server by ID.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -36,6 +42,12 @@ export const dockerRouter = createTRPCRouter({ }), restartContainer: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Restart a Docker container", + description: "Restarts a Docker container by its ID. An audit log entry is created for the action.", + }, + }) .input( z.object({ containerId: z @@ -56,6 +68,12 @@ export const dockerRouter = createTRPCRouter({ }), removeContainer: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Remove a Docker container", + description: "Removes a Docker container by its ID. Optionally targets a remote server. An audit log entry is created for the deletion.", + }, + }) .input( z.object({ containerId: z @@ -82,6 +100,12 @@ export const dockerRouter = createTRPCRouter({ }), getConfig: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Get Docker container configuration", + description: "Retrieves the configuration (inspect data) for a specific Docker container. Optionally targets a remote server.", + }, + }) .input( z.object({ containerId: z @@ -102,6 +126,12 @@ export const dockerRouter = createTRPCRouter({ }), getContainersByAppNameMatch: withPermission("service", "read") + .meta({ + openapi: { + summary: "Get containers by app name match", + description: "Retrieves containers whose names match the given application name. Supports filtering by app type (stack or docker-compose) and optionally targets a remote server.", + }, + }) .input( z.object({ appType: z.enum(["stack", "docker-compose"]).optional(), @@ -124,6 +154,12 @@ export const dockerRouter = createTRPCRouter({ }), getContainersByAppLabel: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Get containers by app label", + description: "Retrieves containers filtered by application label. Supports standalone and swarm deployment types, and optionally targets a remote server.", + }, + }) .input( z.object({ appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."), @@ -146,6 +182,12 @@ export const dockerRouter = createTRPCRouter({ }), getStackContainersByAppName: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Get stack containers by app name", + description: "Retrieves all containers belonging to a Docker stack by application name. Optionally targets a remote server.", + }, + }) .input( z.object({ appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."), @@ -163,6 +205,12 @@ export const dockerRouter = createTRPCRouter({ }), getServiceContainersByAppName: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Get service containers by app name", + description: "Retrieves all containers belonging to a Docker Swarm service by application name. Optionally targets a remote server.", + }, + }) .input( z.object({ appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."), @@ -180,6 +228,12 @@ export const dockerRouter = createTRPCRouter({ }), uploadFileToContainer: withPermission("docker", "read") + .meta({ + openapi: { + summary: "Upload a file to a Docker container", + description: "Uploads a file to a specified path inside a Docker container. The file is converted to a buffer and transferred to the container's filesystem.", + }, + }) .input(uploadFileToContainerSchema) .mutation(async ({ input, ctx }) => { if (input.serverId) { diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts index 8210fcf8a..c2c4276b7 100644 --- a/apps/dokploy/server/api/routers/domain.ts +++ b/apps/dokploy/server/api/routers/domain.ts @@ -33,6 +33,12 @@ import { export const domainRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a domain", + description: "Creates a new domain for an application or compose service. Validates permissions and logs an audit entry.", + }, + }) .input(apiCreateDomain) .mutation(async ({ input, ctx }) => { try { @@ -65,6 +71,12 @@ export const domainRouter = createTRPCRouter({ } }), byApplicationId: protectedProcedure + .meta({ + openapi: { + summary: "List domains by application", + description: "Returns all domains associated with a given application ID.", + }, + }) .input(apiFindOneApplication) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -73,6 +85,12 @@ export const domainRouter = createTRPCRouter({ return await findDomainsByApplicationId(input.applicationId); }), byComposeId: protectedProcedure + .meta({ + openapi: { + summary: "List domains by compose service", + description: "Returns all domains associated with a given compose service ID.", + }, + }) .input(apiFindCompose) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.composeId, { @@ -81,6 +99,12 @@ export const domainRouter = createTRPCRouter({ return await findDomainsByComposeId(input.composeId); }), generateDomain: withPermission("domain", "create") + .meta({ + openapi: { + summary: "Generate a traefik.me domain", + description: "Generates a free traefik.me domain for an application, using the server IP to create a wildcard subdomain.", + }, + }) .input(z.object({ appName: z.string(), serverId: z.string().optional() })) .mutation(async ({ input, ctx }) => { return generateTraefikMeDomain( @@ -90,6 +114,12 @@ export const domainRouter = createTRPCRouter({ ); }), canGenerateTraefikMeDomains: withPermission("domain", "read") + .meta({ + openapi: { + summary: "Check traefik.me domain availability", + description: "Checks whether traefik.me domains can be generated by returning the server IP address. Returns the IP from the server record or web server settings.", + }, + }) .input(z.object({ serverId: z.string() })) .query(async ({ input }) => { if (input.serverId) { @@ -101,6 +131,12 @@ export const domainRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a domain", + description: "Updates a domain's configuration and refreshes the Traefik routing rules for the associated application or preview deployment.", + }, + }) .input(apiUpdateDomain) .mutation(async ({ input, ctx }) => { const currentDomain = await findDomainById(input.domainId); @@ -141,7 +177,15 @@ export const domainRouter = createTRPCRouter({ } return result; }), - one: protectedProcedure.input(apiFindDomain).query(async ({ input, ctx }) => { + one: protectedProcedure + .meta({ + openapi: { + summary: "Get a domain", + description: "Returns a single domain by its ID. Validates read permissions against the associated service or preview deployment.", + }, + }) + .input(apiFindDomain) + .query(async ({ input, ctx }) => { const domain = await findDomainById(input.domainId); const serviceId = domain.applicationId || domain.composeId; if (serviceId) { @@ -159,6 +203,12 @@ export const domainRouter = createTRPCRouter({ return domain; }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a domain", + description: "Deletes a domain by its ID and removes the associated Traefik routing configuration for the application.", + }, + }) .input(apiFindDomain) .mutation(async ({ input, ctx }) => { const domain = await findDomainById(input.domainId); @@ -193,6 +243,12 @@ export const domainRouter = createTRPCRouter({ }), validateDomain: withPermission("domain", "read") + .meta({ + openapi: { + summary: "Validate a domain", + description: "Checks whether a domain's DNS records are correctly configured, optionally verifying against a specific server IP.", + }, + }) .input( z.object({ domain: z.string(), diff --git a/apps/dokploy/server/api/routers/environment.ts b/apps/dokploy/server/api/routers/environment.ts index 78a60362e..52584b1a8 100644 --- a/apps/dokploy/server/api/routers/environment.ts +++ b/apps/dokploy/server/api/routers/environment.ts @@ -63,6 +63,12 @@ const filterEnvironmentServices = ( export const environmentRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create environment", + description: "Creates a new environment within a project. The name 'production' is reserved and cannot be used. Checks creation permissions and logs an audit event.", + }, + }) .input(apiCreateEnvironment) .mutation(async ({ input, ctx }) => { try { @@ -99,6 +105,12 @@ export const environmentRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get environment", + description: "Returns a single environment by ID with all its services. Non-admin users only see services they have been granted access to.", + }, + }) .input(apiFindOneEnvironment) .query(async ({ input, ctx }) => { const environment = await findEnvironmentById(input.environmentId); @@ -137,6 +149,12 @@ export const environmentRouter = createTRPCRouter({ }), byProjectId: protectedProcedure + .meta({ + openapi: { + summary: "List environments by project", + description: "Returns all environments for a given project. Non-admin users only see environments and services they have been granted access to.", + }, + }) .input(z.object({ projectId: z.string() })) .query(async ({ input, ctx }) => { try { @@ -183,6 +201,12 @@ export const environmentRouter = createTRPCRouter({ }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete environment", + description: "Deletes an environment by ID. The default environment cannot be deleted. Checks deletion permissions and environment access before removing.", + }, + }) .input(apiRemoveEnvironment) .mutation(async ({ input, ctx }) => { try { @@ -229,6 +253,12 @@ export const environmentRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update environment", + description: "Updates an environment's name, description, or env variables. The default environment cannot be renamed. Checks environment access and env-var write permissions.", + }, + }) .input(apiUpdateEnvironment) .mutation(async ({ input, ctx }) => { try { @@ -296,6 +326,12 @@ export const environmentRouter = createTRPCRouter({ }), duplicate: protectedProcedure + .meta({ + openapi: { + summary: "Duplicate environment", + description: "Creates a copy of an existing environment including its services. Checks environment access and organization ownership before duplicating.", + }, + }) .input(apiDuplicateEnvironment) .mutation(async ({ input, ctx }) => { try { @@ -343,6 +379,12 @@ export const environmentRouter = createTRPCRouter({ }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search environments", + description: "Searches environments by name, description, or project with pagination. Non-admin users only see environments they have been granted access to.", + }, + }) .input( z.object({ q: z.string().optional(), diff --git a/apps/dokploy/server/api/routers/git-provider.ts b/apps/dokploy/server/api/routers/git-provider.ts index 5f48ff422..0ed29d3c8 100644 --- a/apps/dokploy/server/api/routers/git-provider.ts +++ b/apps/dokploy/server/api/routers/git-provider.ts @@ -21,7 +21,14 @@ import { } from "@/server/db/schema"; export const gitProviderRouter = createTRPCRouter({ - getAll: protectedProcedure.query(async ({ ctx }) => { + getAll: protectedProcedure + .meta({ + openapi: { + summary: "List all git providers", + description: "Returns all git providers (GitHub, GitLab, Bitbucket, Gitea) accessible to the current user within the active organization, ordered by creation date.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleGitProviderIds(ctx.session); if (accessibleIds.size === 0) { @@ -46,6 +53,12 @@ export const gitProviderRouter = createTRPCRouter({ }), toggleShare: protectedProcedure + .meta({ + openapi: { + summary: "Toggle git provider sharing", + description: "Toggles whether a git provider is shared with the entire organization. Only the owner of the provider can change this setting.", + }, + }) .input(apiToggleShareGitProvider) .mutation(async ({ input, ctx }) => { const provider = await findGitProviderById(input.gitProviderId); @@ -73,6 +86,12 @@ export const gitProviderRouter = createTRPCRouter({ }), allForPermissions: withPermission("member", "update") + .meta({ + openapi: { + summary: "List git providers for permissions", + description: "Returns a minimal list of all git providers in the organization for use in permission assignment UIs. Requires a valid enterprise license and member update permission.", + }, + }) .use(async ({ ctx, next }) => { const licensed = await hasValidLicense(ctx.session.activeOrganizationId); if (!licensed) { @@ -96,6 +115,12 @@ export const gitProviderRouter = createTRPCRouter({ }), remove: withPermission("gitProviders", "delete") + .meta({ + openapi: { + summary: "Remove git provider", + description: "Deletes a git provider from the organization. Requires gitProviders delete permission and the provider must belong to the active organization.", + }, + }) .input(apiRemoveGitProvider) .mutation(async ({ input, ctx }) => { try { diff --git a/apps/dokploy/server/api/routers/gitea.ts b/apps/dokploy/server/api/routers/gitea.ts index b16a351c1..1505fffa0 100644 --- a/apps/dokploy/server/api/routers/gitea.ts +++ b/apps/dokploy/server/api/routers/gitea.ts @@ -27,6 +27,12 @@ import { export const giteaRouter = createTRPCRouter({ create: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Create Gitea provider", + description: "Creates a new Gitea provider configuration linked to the active organization. Requires gitProviders create permission.", + }, + }) .input(apiCreateGitea) .mutation(async ({ input, ctx }) => { try { @@ -53,11 +59,26 @@ export const giteaRouter = createTRPCRouter({ } }), - one: protectedProcedure.input(apiFindOneGitea).query(async ({ input }) => { - return await findGiteaById(input.giteaId); - }), + one: protectedProcedure + .meta({ + openapi: { + summary: "Get Gitea provider", + description: "Returns a single Gitea provider configuration by its ID.", + }, + }) + .input(apiFindOneGitea) + .query(async ({ input }) => { + return await findGiteaById(input.giteaId); + }), - giteaProviders: protectedProcedure.query(async ({ ctx }) => { + giteaProviders: protectedProcedure + .meta({ + openapi: { + summary: "List Gitea providers", + description: "Returns all Gitea providers accessible to the current user within the active organization, filtered to only those with valid credentials.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleGitProviderIds(ctx.session); let result = await db.query.gitea.findMany({ @@ -88,6 +109,12 @@ export const giteaRouter = createTRPCRouter({ }), getGiteaRepositories: protectedProcedure + .meta({ + openapi: { + summary: "List Gitea repositories", + description: "Fetches the list of repositories accessible by the Gitea provider. Calls the Gitea API using the provider's credentials.", + }, + }) .input(apiFindOneGitea) .query(async ({ input }) => { const { giteaId } = input; @@ -112,6 +139,12 @@ export const giteaRouter = createTRPCRouter({ }), getGiteaBranches: protectedProcedure + .meta({ + openapi: { + summary: "List Gitea branches", + description: "Fetches the list of branches for a specific Gitea repository. Calls the Gitea API using the provider's credentials.", + }, + }) .input(apiFindGiteaBranches) .query(async ({ input }) => { const { giteaId, owner, repositoryName } = input; @@ -140,6 +173,12 @@ export const giteaRouter = createTRPCRouter({ }), testConnection: protectedProcedure + .meta({ + openapi: { + summary: "Test Gitea connection", + description: "Tests the connection to a Gitea provider by attempting to fetch its repositories. Returns the number of repositories found or throws an error on failure.", + }, + }) .input(apiGiteaTestConnection) .mutation(async ({ input }) => { const giteaId = input.giteaId ?? ""; @@ -160,6 +199,12 @@ export const giteaRouter = createTRPCRouter({ }), update: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Update Gitea provider", + description: "Updates a Gitea provider configuration and its associated git provider record. Requires gitProviders create permission.", + }, + }) .input(apiUpdateGitea) .mutation(async ({ input, ctx }) => { if (input.name) { @@ -188,6 +233,12 @@ export const giteaRouter = createTRPCRouter({ }), getGiteaUrl: protectedProcedure + .meta({ + openapi: { + summary: "Get Gitea instance URL", + description: "Returns the base URL of the Gitea instance associated with the given provider ID.", + }, + }) .input(apiFindOneGitea) .query(async ({ input }) => { const { giteaId } = input; diff --git a/apps/dokploy/server/api/routers/github.ts b/apps/dokploy/server/api/routers/github.ts index f337a05fc..67e418e9e 100644 --- a/apps/dokploy/server/api/routers/github.ts +++ b/apps/dokploy/server/api/routers/github.ts @@ -22,20 +22,47 @@ import { } from "@/server/db/schema"; export const githubRouter = createTRPCRouter({ - one: protectedProcedure.input(apiFindOneGithub).query(async ({ input }) => { - return await findGithubById(input.githubId); - }), + one: protectedProcedure + .meta({ + openapi: { + summary: "Get GitHub provider", + description: "Returns a single GitHub provider configuration by its ID.", + }, + }) + .input(apiFindOneGithub) + .query(async ({ input }) => { + return await findGithubById(input.githubId); + }), getGithubRepositories: protectedProcedure + .meta({ + openapi: { + summary: "List GitHub repositories", + description: "Fetches the list of repositories accessible by the GitHub provider. Calls the GitHub API using the provider's credentials.", + }, + }) .input(apiFindOneGithub) .query(async ({ input }) => { return await getGithubRepositories(input.githubId); }), getGithubBranches: protectedProcedure + .meta({ + openapi: { + summary: "List GitHub branches", + description: "Fetches the list of branches for a specific GitHub repository. Calls the GitHub API using the provider's credentials.", + }, + }) .input(apiFindGithubBranches) .query(async ({ input }) => { return await getGithubBranches(input); }), - githubProviders: protectedProcedure.query(async ({ ctx }) => { + githubProviders: protectedProcedure + .meta({ + openapi: { + summary: "List GitHub providers", + description: "Returns all GitHub providers accessible to the current user within the active organization, filtered to only those with valid credentials.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleGitProviderIds(ctx.session); let result = await db.query.github.findMany({ @@ -66,6 +93,12 @@ export const githubRouter = createTRPCRouter({ }), testConnection: protectedProcedure + .meta({ + openapi: { + summary: "Test GitHub connection", + description: "Tests the connection to a GitHub provider by attempting to fetch its repositories. Returns the number of repositories found or throws an error on failure.", + }, + }) .input(apiFindOneGithub) .mutation(async ({ input }) => { try { @@ -79,6 +112,12 @@ export const githubRouter = createTRPCRouter({ } }), update: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Update GitHub provider", + description: "Updates a GitHub provider configuration and its associated git provider record. Requires gitProviders create permission.", + }, + }) .input(apiUpdateGithub) .mutation(async ({ input, ctx }) => { await updateGitProvider(input.gitProviderId, { diff --git a/apps/dokploy/server/api/routers/gitlab.ts b/apps/dokploy/server/api/routers/gitlab.ts index ff42a8bbe..0c6c883f4 100644 --- a/apps/dokploy/server/api/routers/gitlab.ts +++ b/apps/dokploy/server/api/routers/gitlab.ts @@ -27,6 +27,12 @@ import { export const gitlabRouter = createTRPCRouter({ create: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Create GitLab provider", + description: "Creates a new GitLab provider configuration linked to the active organization. Requires gitProviders create permission.", + }, + }) .input(apiCreateGitlab) .mutation(async ({ input, ctx }) => { try { @@ -51,10 +57,25 @@ export const gitlabRouter = createTRPCRouter({ }); } }), - one: protectedProcedure.input(apiFindOneGitlab).query(async ({ input }) => { - return await findGitlabById(input.gitlabId); - }), - gitlabProviders: protectedProcedure.query(async ({ ctx }) => { + one: protectedProcedure + .meta({ + openapi: { + summary: "Get GitLab provider", + description: "Returns a single GitLab provider configuration by its ID.", + }, + }) + .input(apiFindOneGitlab) + .query(async ({ input }) => { + return await findGitlabById(input.gitlabId); + }), + gitlabProviders: protectedProcedure + .meta({ + openapi: { + summary: "List GitLab providers", + description: "Returns all GitLab providers accessible to the current user within the active organization, filtered to only those with valid credentials.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleGitProviderIds(ctx.session); let result = await db.query.gitlab.findMany({ @@ -85,17 +106,35 @@ export const gitlabRouter = createTRPCRouter({ return filtered; }), getGitlabRepositories: protectedProcedure + .meta({ + openapi: { + summary: "List GitLab repositories", + description: "Fetches the list of repositories accessible by the GitLab provider. Calls the GitLab API using the provider's credentials.", + }, + }) .input(apiFindOneGitlab) .query(async ({ input }) => { return await getGitlabRepositories(input.gitlabId); }), getGitlabBranches: protectedProcedure + .meta({ + openapi: { + summary: "List GitLab branches", + description: "Fetches the list of branches for a specific GitLab repository. Calls the GitLab API using the provider's credentials.", + }, + }) .input(apiFindGitlabBranches) .query(async ({ input }) => { return await getGitlabBranches(input); }), testConnection: protectedProcedure + .meta({ + openapi: { + summary: "Test GitLab connection", + description: "Tests the connection to a GitLab provider by attempting to fetch its repositories. Returns the number of repositories found or throws an error on failure.", + }, + }) .input(apiGitlabTestConnection) .mutation(async ({ input }) => { try { @@ -110,6 +149,12 @@ export const gitlabRouter = createTRPCRouter({ } }), update: withPermission("gitProviders", "create") + .meta({ + openapi: { + summary: "Update GitLab provider", + description: "Updates a GitLab provider configuration and its associated git provider record. Requires gitProviders create permission.", + }, + }) .input(apiUpdateGitlab) .mutation(async ({ input, ctx }) => { if (input.name) { diff --git a/apps/dokploy/server/api/routers/libsql.ts b/apps/dokploy/server/api/routers/libsql.ts index 77fff4e59..5fd368d5f 100644 --- a/apps/dokploy/server/api/routers/libsql.ts +++ b/apps/dokploy/server/api/routers/libsql.ts @@ -43,6 +43,12 @@ import { } from "@/server/db/schema"; export const libsqlRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a LibSQL database", + description: "Creates a new LibSQL database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreateLibsql) .mutation(async ({ input, ctx }) => { try { @@ -100,6 +106,12 @@ export const libsqlRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a LibSQL database by ID", + description: "Returns the full details of a LibSQL database service, including its environment and project configuration.", + }, + }) .input(apiFindOneLibsql) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.libsqlId, "read"); @@ -118,6 +130,12 @@ export const libsqlRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a LibSQL database", + description: "Starts the Docker container for the specified LibSQL database and sets its status to done.", + }, + }) .input(apiFindOneLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -143,6 +161,12 @@ export const libsqlRouter = createTRPCRouter({ return libsql; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a LibSQL database", + description: "Stops the Docker container for the specified LibSQL database and sets its status to idle.", + }, + }) .input(apiFindOneLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -168,6 +192,12 @@ export const libsqlRouter = createTRPCRouter({ return libsql; }), saveExternalPorts: protectedProcedure + .meta({ + openapi: { + summary: "Save the external ports for a LibSQL database", + description: "Updates the external port mappings (HTTP, gRPC, admin) for the LibSQL database and triggers a redeployment. Validates that ports are not already in use.", + }, + }) .input(apiSaveExternalPortsLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -230,6 +260,12 @@ export const libsqlRouter = createTRPCRouter({ return libsql; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a LibSQL database", + description: "Triggers a deployment for the specified LibSQL database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -282,6 +318,12 @@ export const libsqlRouter = createTRPCRouter({ } }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change LibSQL database status", + description: "Updates the application status of a LibSQL database without starting or stopping the container.", + }, + }) .input(apiChangeLibsqlStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -300,6 +342,12 @@ export const libsqlRouter = createTRPCRouter({ return libsql; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a LibSQL database", + description: "Removes the LibSQL database service, its Docker container, and deletes the database record.", + }, + }) .input(apiFindOneLibsql) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.libsqlId, "delete"); @@ -335,6 +383,12 @@ export const libsqlRouter = createTRPCRouter({ return libsql; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a LibSQL database", + description: "Updates the environment variables for the specified LibSQL database service.", + }, + }) .input(apiSaveEnvironmentVariablesLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -359,6 +413,12 @@ export const libsqlRouter = createTRPCRouter({ return true; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a LibSQL database", + description: "Restarts the LibSQL database by stopping and then starting its Docker container.", + }, + }) .input(apiResetLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -391,6 +451,12 @@ export const libsqlRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a LibSQL database", + description: "Updates the configuration of an existing LibSQL database service.", + }, + }) .input(apiUpdateLibsql) .mutation(async ({ input, ctx }) => { const { libsqlId, ...rest } = input; @@ -417,6 +483,12 @@ export const libsqlRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a LibSQL database to another environment", + description: "Moves the LibSQL database to a different environment within the same project.", + }, + }) .input( z.object({ libsqlId: z.string(), @@ -453,6 +525,12 @@ export const libsqlRouter = createTRPCRouter({ return updatedLibsql; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a LibSQL database", + description: "Rebuilds the LibSQL database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildLibsql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.libsqlId, { @@ -469,6 +547,12 @@ export const libsqlRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read LibSQL container logs", + description: "Retrieves the Docker container logs for the specified LibSQL database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOneLibsql.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index a58a33a9c..09c6a5595 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -54,6 +54,12 @@ import { import { cancelJobs } from "@/server/utils/backup"; export const mariadbRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a MariaDB database", + description: "Creates a new MariaDB database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreateMariaDB) .mutation(async ({ input, ctx }) => { try { @@ -114,6 +120,12 @@ export const mariadbRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a MariaDB database by ID", + description: "Returns the full details of a MariaDB database service, including its environment and project configuration.", + }, + }) .input(apiFindOneMariaDB) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mariadbId, "read"); @@ -131,6 +143,12 @@ export const mariadbRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a MariaDB database", + description: "Starts the Docker container for the specified MariaDB database and sets its status to done.", + }, + }) .input(apiFindOneMariaDB) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -155,6 +173,12 @@ export const mariadbRouter = createTRPCRouter({ return service; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a MariaDB database", + description: "Stops the Docker container for the specified MariaDB database and sets its status to idle.", + }, + }) .input(apiFindOneMariaDB) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -180,6 +204,12 @@ export const mariadbRouter = createTRPCRouter({ return mariadb; }), saveExternalPort: protectedProcedure + .meta({ + openapi: { + summary: "Save the external port for a MariaDB database", + description: "Updates the external port mapping for the MariaDB database and triggers a redeployment. Validates that the port is not already in use.", + }, + }) .input(apiSaveExternalPortMariaDB) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -213,6 +243,12 @@ export const mariadbRouter = createTRPCRouter({ return mariadb; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a MariaDB database", + description: "Triggers a deployment for the specified MariaDB database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployMariaDB) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -250,6 +286,12 @@ export const mariadbRouter = createTRPCRouter({ }); }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change MariaDB database status", + description: "Updates the application status of a MariaDB database without starting or stopping the container.", + }, + }) .input(apiChangeMariaDBStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -268,6 +310,12 @@ export const mariadbRouter = createTRPCRouter({ return mongo; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a MariaDB database", + description: "Removes the MariaDB database service, its Docker container, cancels associated backup jobs, and deletes the database record.", + }, + }) .input(apiFindOneMariaDB) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mariadbId, "delete"); @@ -305,6 +353,12 @@ export const mariadbRouter = createTRPCRouter({ return mongo; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a MariaDB database", + description: "Updates the environment variables for the specified MariaDB database service.", + }, + }) .input(apiSaveEnvironmentVariablesMariaDB) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -329,6 +383,12 @@ export const mariadbRouter = createTRPCRouter({ return true; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a MariaDB database", + description: "Restarts the MariaDB database by stopping and then starting its Docker container.", + }, + }) .input(apiResetMariadb) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -361,6 +421,12 @@ export const mariadbRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a MariaDB database", + description: "Updates the configuration of an existing MariaDB database service.", + }, + }) .input(apiUpdateMariaDB) .mutation(async ({ input, ctx }) => { const { mariadbId, ...rest } = input; @@ -387,6 +453,12 @@ export const mariadbRouter = createTRPCRouter({ return true; }), changePassword: protectedProcedure + .meta({ + openapi: { + summary: "Change MariaDB database password", + description: "Changes the password for a MariaDB user or root account by executing ALTER USER inside the running container and updating the stored password.", + }, + }) .input( z.object({ mariadbId: z.string().min(1), @@ -444,6 +516,12 @@ export const mariadbRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a MariaDB database to another environment", + description: "Moves the MariaDB database to a different environment within the same project.", + }, + }) .input( z.object({ mariadbId: z.string(), @@ -480,6 +558,12 @@ export const mariadbRouter = createTRPCRouter({ return updatedMariadb; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a MariaDB database", + description: "Rebuilds the MariaDB database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildMariadb) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mariadbId, { @@ -495,6 +579,12 @@ export const mariadbRouter = createTRPCRouter({ return true; }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search MariaDB databases", + description: "Returns a paginated list of MariaDB databases matching the given filters. Supports searching by name, appName, description, project, and environment.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -593,6 +683,12 @@ export const mariadbRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read MariaDB container logs", + description: "Retrieves the Docker container logs for the specified MariaDB database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOneMariaDB.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 222917b9f..1f97edc86 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -53,6 +53,12 @@ import { import { cancelJobs } from "@/server/utils/backup"; export const mongoRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a MongoDB database", + description: "Creates a new MongoDB database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreateMongo) .mutation(async ({ input, ctx }) => { try { @@ -117,6 +123,12 @@ export const mongoRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a MongoDB database by ID", + description: "Returns the full details of a MongoDB database service, including its environment and project configuration.", + }, + }) .input(apiFindOneMongo) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mongoId, "read"); @@ -135,6 +147,12 @@ export const mongoRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a MongoDB database", + description: "Starts the Docker container for the specified MongoDB database and sets its status to done.", + }, + }) .input(apiFindOneMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -160,6 +178,12 @@ export const mongoRouter = createTRPCRouter({ return service; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a MongoDB database", + description: "Stops the Docker container for the specified MongoDB database and sets its status to idle.", + }, + }) .input(apiFindOneMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -185,6 +209,12 @@ export const mongoRouter = createTRPCRouter({ return mongo; }), saveExternalPort: protectedProcedure + .meta({ + openapi: { + summary: "Save the external port for a MongoDB database", + description: "Updates the external port mapping for the MongoDB database and triggers a redeployment. Validates that the port is not already in use.", + }, + }) .input(apiSaveExternalPortMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -218,6 +248,12 @@ export const mongoRouter = createTRPCRouter({ return mongo; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a MongoDB database", + description: "Triggers a deployment for the specified MongoDB database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -271,6 +307,12 @@ export const mongoRouter = createTRPCRouter({ }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change MongoDB database status", + description: "Updates the application status of a MongoDB database without starting or stopping the container.", + }, + }) .input(apiChangeMongoStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -289,6 +331,12 @@ export const mongoRouter = createTRPCRouter({ return mongo; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a MongoDB database", + description: "Restarts the MongoDB database by stopping and then starting its Docker container.", + }, + }) .input(apiResetMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -321,6 +369,12 @@ export const mongoRouter = createTRPCRouter({ return true; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a MongoDB database", + description: "Removes the MongoDB database service, its Docker container, cancels associated backup jobs, and deletes the database record.", + }, + }) .input(apiFindOneMongo) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mongoId, "delete"); @@ -359,6 +413,12 @@ export const mongoRouter = createTRPCRouter({ return mongo; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a MongoDB database", + description: "Updates the environment variables for the specified MongoDB database service.", + }, + }) .input(apiSaveEnvironmentVariablesMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -383,6 +443,12 @@ export const mongoRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a MongoDB database", + description: "Updates the configuration of an existing MongoDB database service.", + }, + }) .input(apiUpdateMongo) .mutation(async ({ input, ctx }) => { const { mongoId, ...rest } = input; @@ -409,6 +475,12 @@ export const mongoRouter = createTRPCRouter({ return true; }), changePassword: protectedProcedure + .meta({ + openapi: { + summary: "Change MongoDB database password", + description: "Changes the password for the MongoDB database user by executing changeUserPassword via mongosh inside the running container and updating the stored password.", + }, + }) .input( z.object({ mongoId: z.string().min(1), @@ -459,6 +531,12 @@ export const mongoRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a MongoDB database to another environment", + description: "Moves the MongoDB database to a different environment within the same project.", + }, + }) .input( z.object({ mongoId: z.string(), @@ -495,6 +573,12 @@ export const mongoRouter = createTRPCRouter({ return updatedMongo; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a MongoDB database", + description: "Rebuilds the MongoDB database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildMongo) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mongoId, { @@ -511,6 +595,12 @@ export const mongoRouter = createTRPCRouter({ return true; }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search MongoDB databases", + description: "Returns a paginated list of MongoDB databases matching the given filters. Supports searching by name, appName, description, project, and environment.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -604,6 +694,12 @@ export const mongoRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read MongoDB container logs", + description: "Retrieves the Docker container logs for the specified MongoDB database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOneMongo.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/mount.ts b/apps/dokploy/server/api/routers/mount.ts index 9d9e0a06e..d0fbe639b 100644 --- a/apps/dokploy/server/api/routers/mount.ts +++ b/apps/dokploy/server/api/routers/mount.ts @@ -75,6 +75,12 @@ async function getServiceOrganizationId( export const mountRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create mount", + description: "Creates a new volume, bind, or file mount for a service. Checks service-level volume permissions and logs an audit event.", + }, + }) .input(apiCreateMount) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.serviceId, { @@ -90,6 +96,12 @@ export const mountRouter = createTRPCRouter({ return mount; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete mount", + description: "Removes a mount by ID. Resolves the owning service to check volume delete permissions and logs an audit event.", + }, + }) .input(apiRemoveMount) .mutation(async ({ input, ctx }) => { const mount = await findMountById(input.mountId); @@ -116,6 +128,12 @@ export const mountRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get mount", + description: "Returns a single mount by ID. Resolves the owning service to check volume read permissions.", + }, + }) .input(apiFindOneMount) .query(async ({ input, ctx }) => { const mount = await findMountById(input.mountId); @@ -136,6 +154,12 @@ export const mountRouter = createTRPCRouter({ return mount; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update mount", + description: "Updates an existing mount. Resolves the owning service to check volume create permissions and logs an audit event.", + }, + }) .input(apiUpdateMount) .mutation(async ({ input, ctx }) => { const mount = await findMountById(input.mountId); @@ -162,6 +186,12 @@ export const mountRouter = createTRPCRouter({ return await updateMount(input.mountId, input); }), allNamedByApplicationId: protectedProcedure + .meta({ + openapi: { + summary: "List named volumes by application", + description: "Returns Docker named volumes attached to the running container of a given application. Inspects the live container to retrieve mount information.", + }, + }) .input(z.object({ applicationId: z.string().min(1) })) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -175,6 +205,12 @@ export const mountRouter = createTRPCRouter({ return mounts; }), listByServiceId: protectedProcedure + .meta({ + openapi: { + summary: "List mounts by service", + description: "Returns all configured mounts for a given service (application, compose, or database). Verifies service access and organization ownership.", + }, + }) .input(apiFindMountByApplicationId) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.serviceId, "read"); diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index 263fa53f0..b331cfca8 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -54,6 +54,12 @@ import { cancelJobs } from "@/server/utils/backup"; export const mysqlRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a MySQL database", + description: "Creates a new MySQL database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreateMySql) .mutation(async ({ input, ctx }) => { try { @@ -118,6 +124,12 @@ export const mysqlRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a MySQL database by ID", + description: "Returns the full details of a MySQL database service, including its environment and project configuration.", + }, + }) .input(apiFindOneMySql) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mysqlId, "read"); @@ -135,6 +147,12 @@ export const mysqlRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a MySQL database", + description: "Starts the Docker container for the specified MySQL database and sets its status to done.", + }, + }) .input(apiFindOneMySql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -160,6 +178,12 @@ export const mysqlRouter = createTRPCRouter({ return service; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a MySQL database", + description: "Stops the Docker container for the specified MySQL database and sets its status to idle.", + }, + }) .input(apiFindOneMySql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -184,6 +208,12 @@ export const mysqlRouter = createTRPCRouter({ return mongo; }), saveExternalPort: protectedProcedure + .meta({ + openapi: { + summary: "Save the external port for a MySQL database", + description: "Updates the external port mapping for the MySQL database and triggers a redeployment. Validates that the port is not already in use.", + }, + }) .input(apiSaveExternalPortMySql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -217,6 +247,12 @@ export const mysqlRouter = createTRPCRouter({ return mysql; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a MySQL database", + description: "Triggers a deployment for the specified MySQL database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployMySql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -270,6 +306,12 @@ export const mysqlRouter = createTRPCRouter({ } }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change MySQL database status", + description: "Updates the application status of a MySQL database without starting or stopping the container.", + }, + }) .input(apiChangeMySqlStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -288,6 +330,12 @@ export const mysqlRouter = createTRPCRouter({ return mongo; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a MySQL database", + description: "Restarts the MySQL database by stopping and then starting its Docker container.", + }, + }) .input(apiResetMysql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -319,6 +367,12 @@ export const mysqlRouter = createTRPCRouter({ return true; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a MySQL database", + description: "Removes the MySQL database service, its Docker container, cancels associated backup jobs, and deletes the database record.", + }, + }) .input(apiFindOneMySql) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.mysqlId, "delete"); @@ -355,6 +409,12 @@ export const mysqlRouter = createTRPCRouter({ return mongo; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a MySQL database", + description: "Updates the environment variables for the specified MySQL database service.", + }, + }) .input(apiSaveEnvironmentVariablesMySql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -379,6 +439,12 @@ export const mysqlRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a MySQL database", + description: "Updates the configuration of an existing MySQL database service.", + }, + }) .input(apiUpdateMySql) .mutation(async ({ input, ctx }) => { const { mysqlId, ...rest } = input; @@ -405,6 +471,12 @@ export const mysqlRouter = createTRPCRouter({ return true; }), changePassword: protectedProcedure + .meta({ + openapi: { + summary: "Change MySQL database password", + description: "Changes the password for a MySQL user or root account by executing ALTER USER inside the running container and updating the stored password.", + }, + }) .input( z.object({ mysqlId: z.string().min(1), @@ -462,6 +534,12 @@ export const mysqlRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a MySQL database to another environment", + description: "Moves the MySQL database to a different environment within the same project.", + }, + }) .input( z.object({ mysqlId: z.string(), @@ -498,6 +576,12 @@ export const mysqlRouter = createTRPCRouter({ return updatedMysql; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a MySQL database", + description: "Rebuilds the MySQL database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildMysql) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.mysqlId, { @@ -514,6 +598,12 @@ export const mysqlRouter = createTRPCRouter({ return true; }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search MySQL databases", + description: "Returns a paginated list of MySQL databases matching the given filters. Supports searching by name, appName, description, project, and environment.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -607,6 +697,12 @@ export const mysqlRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read MySQL container logs", + description: "Retrieves the Docker container logs for the specified MySQL database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOneMySql.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index dd96bc302..e24d4be51 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -95,6 +95,12 @@ import { export const notificationRouter = createTRPCRouter({ createSlack: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Slack notification", + description: "Creates a new Slack notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateSlack) .mutation(async ({ input, ctx }) => { try { @@ -114,6 +120,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateSlack: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Slack notification", + description: "Updates an existing Slack notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateSlack) .mutation(async ({ input, ctx }) => { try { @@ -140,6 +152,12 @@ export const notificationRouter = createTRPCRouter({ } }), testSlackConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Slack connection", + description: "Sends a test message to the configured Slack channel to verify the webhook connection works.", + }, + }) .input(apiTestSlackConnection) .mutation(async ({ input }) => { try { @@ -157,6 +175,12 @@ export const notificationRouter = createTRPCRouter({ } }), createTelegram: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Telegram notification", + description: "Creates a new Telegram notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateTelegram) .mutation(async ({ input, ctx }) => { try { @@ -179,6 +203,12 @@ export const notificationRouter = createTRPCRouter({ }), updateTelegram: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Telegram notification", + description: "Updates an existing Telegram notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateTelegram) .mutation(async ({ input, ctx }) => { try { @@ -209,6 +239,12 @@ export const notificationRouter = createTRPCRouter({ } }), testTelegramConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Telegram connection", + description: "Sends a test message to the configured Telegram chat to verify the bot token and chat ID work.", + }, + }) .input(apiTestTelegramConnection) .mutation(async ({ input }) => { try { @@ -223,6 +259,12 @@ export const notificationRouter = createTRPCRouter({ } }), createDiscord: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Discord notification", + description: "Creates a new Discord notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateDiscord) .mutation(async ({ input, ctx }) => { try { @@ -245,6 +287,12 @@ export const notificationRouter = createTRPCRouter({ }), updateDiscord: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Discord notification", + description: "Updates an existing Discord notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateDiscord) .mutation(async ({ input, ctx }) => { try { @@ -276,6 +324,12 @@ export const notificationRouter = createTRPCRouter({ }), testDiscordConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Discord connection", + description: "Sends a test embed message to the configured Discord webhook to verify the connection works.", + }, + }) .input(apiTestDiscordConnection) .mutation(async ({ input }) => { try { @@ -298,6 +352,12 @@ export const notificationRouter = createTRPCRouter({ } }), createEmail: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Email notification", + description: "Creates a new SMTP email notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateEmail) .mutation(async ({ input, ctx }) => { try { @@ -316,6 +376,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateEmail: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Email notification", + description: "Updates an existing SMTP email notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateEmail) .mutation(async ({ input, ctx }) => { try { @@ -346,6 +412,12 @@ export const notificationRouter = createTRPCRouter({ } }), testEmailConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Email connection", + description: "Sends a test email via the configured SMTP settings to verify the connection works.", + }, + }) .input(apiTestEmailConnection) .mutation(async ({ input }) => { try { @@ -364,6 +436,12 @@ export const notificationRouter = createTRPCRouter({ } }), createResend: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Resend notification", + description: "Creates a new Resend email notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateResend) .mutation(async ({ input, ctx }) => { try { @@ -382,6 +460,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateResend: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Resend notification", + description: "Updates an existing Resend email notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateResend) .mutation(async ({ input, ctx }) => { try { @@ -412,6 +496,12 @@ export const notificationRouter = createTRPCRouter({ } }), testResendConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Resend connection", + description: "Sends a test email via Resend to verify the API key and configuration work.", + }, + }) .input(apiTestResendConnection) .mutation(async ({ input }) => { try { @@ -430,6 +520,12 @@ export const notificationRouter = createTRPCRouter({ } }), remove: withPermission("notification", "delete") + .meta({ + openapi: { + summary: "Delete notification", + description: "Removes a notification provider by ID. Verifies organization ownership and logs an audit event before deletion.", + }, + }) .input(apiFindOneNotification) .mutation(async ({ input, ctx }) => { try { @@ -458,6 +554,12 @@ export const notificationRouter = createTRPCRouter({ } }), one: withPermission("notification", "read") + .meta({ + openapi: { + summary: "Get notification", + description: "Returns a single notification provider by ID. Verifies the caller belongs to the same organization.", + }, + }) .input(apiFindOneNotification) .query(async ({ input, ctx }) => { const notification = await findNotificationById(input.notificationId); @@ -469,7 +571,14 @@ export const notificationRouter = createTRPCRouter({ } return notification; }), - all: withPermission("notification", "read").query(async ({ ctx }) => { + all: withPermission("notification", "read") + .meta({ + openapi: { + summary: "List all notifications", + description: "Returns all notification providers for the current organization, including all provider-specific details (Slack, Telegram, Discord, etc.).", + }, + }) + .query(async ({ ctx }) => { return await db.query.notifications.findMany({ with: { slack: true, @@ -490,6 +599,12 @@ export const notificationRouter = createTRPCRouter({ }); }), receiveNotification: publicProcedure + .meta({ + openapi: { + summary: "Receive server threshold notification", + description: "Public endpoint that receives CPU/memory threshold alerts from Dokploy or remote servers. Validates the token and dispatches notifications to all configured providers.", + }, + }) .input( z.object({ ServerType: z.enum(["Dokploy", "Remote"]).default("Dokploy"), @@ -551,6 +666,12 @@ export const notificationRouter = createTRPCRouter({ } }), createGotify: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Gotify notification", + description: "Creates a new Gotify notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateGotify) .mutation(async ({ input, ctx }) => { try { @@ -569,6 +690,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateGotify: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Gotify notification", + description: "Updates an existing Gotify notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateGotify) .mutation(async ({ input, ctx }) => { try { @@ -598,6 +725,12 @@ export const notificationRouter = createTRPCRouter({ } }), testGotifyConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Gotify connection", + description: "Sends a test notification to the configured Gotify server to verify the connection works.", + }, + }) .input(apiTestGotifyConnection) .mutation(async ({ input }) => { try { @@ -616,6 +749,12 @@ export const notificationRouter = createTRPCRouter({ } }), createNtfy: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create ntfy notification", + description: "Creates a new ntfy notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateNtfy) .mutation(async ({ input, ctx }) => { try { @@ -634,6 +773,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateNtfy: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update ntfy notification", + description: "Updates an existing ntfy notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateNtfy) .mutation(async ({ input, ctx }) => { try { @@ -663,6 +808,12 @@ export const notificationRouter = createTRPCRouter({ } }), testNtfyConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test ntfy connection", + description: "Sends a test notification to the configured ntfy topic to verify the connection works.", + }, + }) .input(apiTestNtfyConnection) .mutation(async ({ input }) => { try { @@ -686,6 +837,12 @@ export const notificationRouter = createTRPCRouter({ } }), createMattermost: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Mattermost notification", + description: "Creates a new Mattermost notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateMattermost) .mutation(async ({ input, ctx }) => { try { @@ -707,6 +864,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateMattermost: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Mattermost notification", + description: "Updates an existing Mattermost notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateMattermost) .mutation(async ({ input, ctx }) => { try { @@ -736,6 +899,12 @@ export const notificationRouter = createTRPCRouter({ } }), testMattermostConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Mattermost connection", + description: "Sends a test message to the configured Mattermost webhook to verify the connection works.", + }, + }) .input(apiTestMattermostConnection) .mutation(async ({ input }) => { try { @@ -754,6 +923,12 @@ export const notificationRouter = createTRPCRouter({ } }), createCustom: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create custom webhook notification", + description: "Creates a new custom webhook notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateCustom) .mutation(async ({ input, ctx }) => { try { @@ -772,6 +947,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateCustom: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update custom webhook notification", + description: "Updates an existing custom webhook notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateCustom) .mutation(async ({ input, ctx }) => { try { @@ -798,6 +979,12 @@ export const notificationRouter = createTRPCRouter({ } }), testCustomConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test custom webhook connection", + description: "Sends a test payload to the configured custom webhook URL to verify the connection works.", + }, + }) .input(apiTestCustomConnection) .mutation(async ({ input }) => { try { @@ -816,6 +1003,12 @@ export const notificationRouter = createTRPCRouter({ } }), createLark: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Lark notification", + description: "Creates a new Lark notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateLark) .mutation(async ({ input, ctx }) => { try { @@ -834,6 +1027,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateLark: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Lark notification", + description: "Updates an existing Lark notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateLark) .mutation(async ({ input, ctx }) => { try { @@ -863,6 +1062,12 @@ export const notificationRouter = createTRPCRouter({ } }), testLarkConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Lark connection", + description: "Sends a test message to the configured Lark webhook to verify the connection works.", + }, + }) .input(apiTestLarkConnection) .mutation(async ({ input }) => { try { @@ -882,6 +1087,12 @@ export const notificationRouter = createTRPCRouter({ } }), createTeams: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Teams notification", + description: "Creates a new Microsoft Teams notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreateTeams) .mutation(async ({ input, ctx }) => { try { @@ -900,6 +1111,12 @@ export const notificationRouter = createTRPCRouter({ } }), updateTeams: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Teams notification", + description: "Updates an existing Microsoft Teams notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdateTeams) .mutation(async ({ input, ctx }) => { try { @@ -929,6 +1146,12 @@ export const notificationRouter = createTRPCRouter({ } }), testTeamsConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Teams connection", + description: "Sends a test message to the configured Microsoft Teams webhook to verify the connection works.", + }, + }) .input(apiTestTeamsConnection) .mutation(async ({ input }) => { try { @@ -946,6 +1169,12 @@ export const notificationRouter = createTRPCRouter({ } }), createPushover: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Create Pushover notification", + description: "Creates a new Pushover notification provider for the current organization and logs an audit event.", + }, + }) .input(apiCreatePushover) .mutation(async ({ input, ctx }) => { try { @@ -967,6 +1196,12 @@ export const notificationRouter = createTRPCRouter({ } }), updatePushover: withPermission("notification", "update") + .meta({ + openapi: { + summary: "Update Pushover notification", + description: "Updates an existing Pushover notification provider. Verifies organization ownership before applying changes.", + }, + }) .input(apiUpdatePushover) .mutation(async ({ input, ctx }) => { try { @@ -996,6 +1231,12 @@ export const notificationRouter = createTRPCRouter({ } }), testPushoverConnection: withPermission("notification", "create") + .meta({ + openapi: { + summary: "Test Pushover connection", + description: "Sends a test notification to the configured Pushover account to verify the connection works.", + }, + }) .input(apiTestPushoverConnection) .mutation(async ({ input }) => { try { @@ -1013,7 +1254,14 @@ export const notificationRouter = createTRPCRouter({ }); } }), - getEmailProviders: withPermission("notification", "read").query( + getEmailProviders: withPermission("notification", "read") + .meta({ + openapi: { + summary: "List email notification providers", + description: "Returns all notification providers that support email (SMTP and Resend) for the current organization.", + }, + }) + .query( async ({ ctx }) => { return await db.query.notifications.findMany({ where: eq( diff --git a/apps/dokploy/server/api/routers/organization.ts b/apps/dokploy/server/api/routers/organization.ts index 2f9da6d71..662673fa1 100644 --- a/apps/dokploy/server/api/routers/organization.ts +++ b/apps/dokploy/server/api/routers/organization.ts @@ -15,6 +15,12 @@ import { import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc"; export const organizationRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create an organization", + description: "Create a new organization and add the current user as the owner. Only owners and admins can create organizations in self-hosted mode.", + }, + }) .input( z.object({ name: z.string(), @@ -65,7 +71,14 @@ export const organizationRouter = createTRPCRouter({ }); return result; }), - all: protectedProcedure.query(async ({ ctx }) => { + all: protectedProcedure + .meta({ + openapi: { + summary: "List all organizations", + description: "Retrieve all organizations the current user is a member of, including their membership details.", + }, + }) + .query(async ({ ctx }) => { const memberResult = await db.query.organization.findMany({ where: (organization) => exists( @@ -88,6 +101,12 @@ export const organizationRouter = createTRPCRouter({ return memberResult; }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get an organization by ID", + description: "Retrieve a single organization by its ID. The current user must be a member of the organization.", + }, + }) .input( z.object({ organizationId: z.string(), @@ -114,6 +133,12 @@ export const organizationRouter = createTRPCRouter({ }); }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update an organization", + description: "Update the name and logo of an organization. Only the organization owner can perform this action.", + }, + }) .input( z.object({ organizationId: z.string(), @@ -178,6 +203,12 @@ export const organizationRouter = createTRPCRouter({ return result[0]; }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete an organization", + description: "Delete an organization by ID. Only the owner can delete it, and they must retain at least one organization.", + }, + }) .input( z.object({ organizationId: z.string(), @@ -248,6 +279,12 @@ export const organizationRouter = createTRPCRouter({ return result; }), inviteMember: withPermission("member", "create") + .meta({ + openapi: { + summary: "Invite a member to organization", + description: "Create a pending invitation for a user by email to join the active organization with the specified role. Checks for existing membership and pending invitations. Supports custom roles.", + }, + }) .input( z.object({ email: z.string().email(), @@ -335,13 +372,26 @@ export const organizationRouter = createTRPCRouter({ return created; }), - allInvitations: withPermission("member", "create").query(async ({ ctx }) => { + allInvitations: withPermission("member", "create") + .meta({ + openapi: { + summary: "List all organization invitations", + description: "Retrieve all invitations for the active organization, ordered by status and expiration date.", + }, + }) + .query(async ({ ctx }) => { return await db.query.invitation.findMany({ where: eq(invitation.organizationId, ctx.session.activeOrganizationId), orderBy: [desc(invitation.status), desc(invitation.expiresAt)], }); }), removeInvitation: withPermission("member", "create") + .meta({ + openapi: { + summary: "Remove an invitation", + description: "Delete a pending invitation by ID. Only invitations belonging to the active organization can be removed.", + }, + }) .input(z.object({ invitationId: z.string() })) .mutation(async ({ ctx, input }) => { const invitationResult = await db.query.invitation.findFirst({ @@ -377,6 +427,12 @@ export const organizationRouter = createTRPCRouter({ return result; }), updateMemberRole: withPermission("member", "update") + .meta({ + openapi: { + summary: "Update member role", + description: "Change the role of a member in the active organization. Users cannot change their own role, and the owner role is nontransferable. Only owners can change admin roles. Supports custom roles.", + }, + }) .input( z.object({ memberId: z.string(), @@ -463,6 +519,12 @@ export const organizationRouter = createTRPCRouter({ return true; }), setDefault: protectedProcedure + .meta({ + openapi: { + summary: "Set default organization", + description: "Set an organization as the default for the current user. Unsets any previous default and marks the specified organization as the new default.", + }, + }) .input( z.object({ organizationId: z.string().min(1), @@ -509,7 +571,14 @@ export const organizationRouter = createTRPCRouter({ }); return { success: true }; }), - active: protectedProcedure.query(async ({ ctx }) => { + active: protectedProcedure + .meta({ + openapi: { + summary: "Get active organization", + description: "Retrieve the organization that is currently active in the user's session. Returns null if no organization is active.", + }, + }) + .query(async ({ ctx }) => { if (!ctx.session.activeOrganizationId) { return null; } diff --git a/apps/dokploy/server/api/routers/patch.ts b/apps/dokploy/server/api/routers/patch.ts index 333dd1856..568c4e448 100644 --- a/apps/dokploy/server/api/routers/patch.ts +++ b/apps/dokploy/server/api/routers/patch.ts @@ -50,6 +50,12 @@ const resolvePatchServiceId = (patch: { export const patchRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create patch", + description: "Creates a new file patch for an application or compose service. Checks service-level permissions and logs an audit event.", + }, + }) .input(apiCreatePatch) .mutation(async ({ input, ctx }) => { const serviceId = input.applicationId ?? input.composeId; @@ -73,7 +79,15 @@ export const patchRouter = createTRPCRouter({ return result; }), - one: protectedProcedure.input(apiFindPatch).query(async ({ input, ctx }) => { + one: protectedProcedure + .meta({ + openapi: { + summary: "Get patch", + description: "Returns a single patch by ID. Resolves the associated service to verify read permissions.", + }, + }) + .input(apiFindPatch) + .query(async ({ input, ctx }) => { const patch = await findPatchById(input.patchId); const serviceId = resolvePatchServiceId(patch); await checkServicePermissionAndAccess(ctx, serviceId, { @@ -83,6 +97,12 @@ export const patchRouter = createTRPCRouter({ }), byEntityId: protectedProcedure + .meta({ + openapi: { + summary: "List patches by entity", + description: "Returns all patches associated with a given application or compose service.", + }, + }) .input( z.object({ id: z.string(), type: z.enum(["application", "compose"]) }), ) @@ -94,6 +114,12 @@ export const patchRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update patch", + description: "Updates the content or configuration of an existing patch. Resolves the associated service to verify permissions and logs an audit event.", + }, + }) .input(apiUpdatePatch) .mutation(async ({ input, ctx }) => { const patch = await findPatchById(input.patchId); @@ -114,6 +140,12 @@ export const patchRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete patch", + description: "Deletes a patch by ID. Resolves the associated service to verify delete permissions and logs an audit event.", + }, + }) .input(apiDeletePatch) .mutation(async ({ input, ctx }) => { const patch = await findPatchById(input.patchId); @@ -133,6 +165,12 @@ export const patchRouter = createTRPCRouter({ }), toggleEnabled: protectedProcedure + .meta({ + openapi: { + summary: "Toggle patch enabled state", + description: "Enables or disables a patch without deleting it. Resolves the associated service to verify permissions and logs an audit event.", + }, + }) .input(apiTogglePatchEnabled) .mutation(async ({ input, ctx }) => { const patch = await findPatchById(input.patchId); @@ -155,6 +193,12 @@ export const patchRouter = createTRPCRouter({ // Repository Operations ensureRepo: protectedProcedure + .meta({ + openapi: { + summary: "Ensure patch repository exists", + description: "Ensures a patch repository is initialized for the given application or compose service. Creates the repo if it does not exist and logs an audit event.", + }, + }) .input( z.object({ id: z.string(), @@ -179,6 +223,12 @@ export const patchRouter = createTRPCRouter({ }), readRepoDirectories: protectedProcedure + .meta({ + openapi: { + summary: "List patch repository directories", + description: "Reads the directory listing at a given path inside the patch repository for an application or compose service.", + }, + }) .input( z.object({ id: z.string().min(1), @@ -202,6 +252,12 @@ export const patchRouter = createTRPCRouter({ }), readRepoFile: protectedProcedure + .meta({ + openapi: { + summary: "Read patch repository file", + description: "Reads a file from the patch repository. For delete-type patches it returns the current repo content; otherwise returns the patch content if available, falling back to the repo file.", + }, + }) .input( z.object({ id: z.string().min(1), @@ -241,6 +297,12 @@ export const patchRouter = createTRPCRouter({ }), saveFileAsPatch: protectedProcedure + .meta({ + openapi: { + summary: "Save file as patch", + description: "Creates or updates a patch record from file content. If a patch already exists for the file path, it updates the existing patch; otherwise creates a new one.", + }, + }) .input( z.object({ id: z.string().min(1), @@ -291,6 +353,12 @@ export const patchRouter = createTRPCRouter({ }), markFileForDeletion: protectedProcedure + .meta({ + openapi: { + summary: "Mark file for deletion", + description: "Creates a delete-type patch that will remove the specified file from the service on next deployment. Logs an audit event.", + }, + }) .input( z.object({ id: z.string().min(1), @@ -318,6 +386,12 @@ export const patchRouter = createTRPCRouter({ }), cleanPatchRepos: adminProcedure + .meta({ + openapi: { + summary: "Clean patch repositories", + description: "Removes all patch repository working directories on the local or a specified remote server. Admin-only operation that logs an audit event.", + }, + }) .input(z.object({ serverId: z.string().optional() })) .mutation(async ({ input, ctx }) => { await cleanPatchRepos(input.serverId); diff --git a/apps/dokploy/server/api/routers/port.ts b/apps/dokploy/server/api/routers/port.ts index c98081e86..4996bc822 100644 --- a/apps/dokploy/server/api/routers/port.ts +++ b/apps/dokploy/server/api/routers/port.ts @@ -16,6 +16,12 @@ import { export const portRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a port", + description: "Creates a new port mapping for an application, binding a published port to a target port. Logs an audit entry.", + }, + }) .input(apiCreatePort) .mutation(async ({ input, ctx }) => { try { @@ -39,6 +45,12 @@ export const portRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a port", + description: "Returns a single port mapping by its ID, including the associated application details.", + }, + }) .input(apiFindOnePort) .query(async ({ input, ctx }) => { try { @@ -58,6 +70,12 @@ export const portRouter = createTRPCRouter({ } }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a port", + description: "Deletes a port mapping by its ID and logs an audit entry with the published and target port details.", + }, + }) .input(apiFindOnePort) .mutation(async ({ input, ctx }) => { const port = await finPortById(input.portId); @@ -85,6 +103,12 @@ export const portRouter = createTRPCRouter({ } }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a port", + description: "Updates an existing port mapping's configuration and logs an audit entry.", + }, + }) .input(apiUpdatePort) .mutation(async ({ input, ctx }) => { const port = await finPortById(input.portId); diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 0b263100d..f1b3e0252 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -55,6 +55,12 @@ import { cancelJobs } from "@/server/utils/backup"; export const postgresRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a PostgreSQL database", + description: "Creates a new PostgreSQL database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreatePostgres) .mutation(async ({ input, ctx }) => { try { @@ -121,6 +127,12 @@ export const postgresRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a PostgreSQL database by ID", + description: "Returns the full details of a PostgreSQL database service, including its environment and project configuration.", + }, + }) .input(apiFindOnePostgres) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.postgresId, "read"); @@ -139,6 +151,12 @@ export const postgresRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a PostgreSQL database", + description: "Starts the Docker container for the specified PostgreSQL database and sets its status to done.", + }, + }) .input(apiFindOnePostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -164,6 +182,12 @@ export const postgresRouter = createTRPCRouter({ return service; }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a PostgreSQL database", + description: "Stops the Docker container for the specified PostgreSQL database and sets its status to idle.", + }, + }) .input(apiFindOnePostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -188,6 +212,12 @@ export const postgresRouter = createTRPCRouter({ return postgres; }), saveExternalPort: protectedProcedure + .meta({ + openapi: { + summary: "Save the external port for a PostgreSQL database", + description: "Updates the external port mapping for the PostgreSQL database and triggers a redeployment. Validates that the port is not already in use.", + }, + }) .input(apiSaveExternalPortPostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -221,6 +251,12 @@ export const postgresRouter = createTRPCRouter({ return postgres; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a PostgreSQL database", + description: "Triggers a deployment for the specified PostgreSQL database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployPostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -276,6 +312,12 @@ export const postgresRouter = createTRPCRouter({ }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change PostgreSQL database status", + description: "Updates the application status of a PostgreSQL database without starting or stopping the container.", + }, + }) .input(apiChangePostgresStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -294,6 +336,12 @@ export const postgresRouter = createTRPCRouter({ return postgres; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a PostgreSQL database", + description: "Removes the PostgreSQL database service, its Docker container, cancels associated backup jobs, and deletes the database record.", + }, + }) .input(apiFindOnePostgres) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.postgresId, "delete"); @@ -332,6 +380,12 @@ export const postgresRouter = createTRPCRouter({ return postgres; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a PostgreSQL database", + description: "Updates the environment variables for the specified PostgreSQL database service.", + }, + }) .input(apiSaveEnvironmentVariablesPostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -356,6 +410,12 @@ export const postgresRouter = createTRPCRouter({ return true; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a PostgreSQL database", + description: "Restarts the PostgreSQL database by stopping and then starting its Docker container.", + }, + }) .input(apiResetPostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -388,6 +448,12 @@ export const postgresRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a PostgreSQL database", + description: "Updates the configuration of an existing PostgreSQL database service.", + }, + }) .input(apiUpdatePostgres) .mutation(async ({ input, ctx }) => { const { postgresId, ...rest } = input; @@ -415,6 +481,12 @@ export const postgresRouter = createTRPCRouter({ return true; }), changePassword: protectedProcedure + .meta({ + openapi: { + summary: "Change PostgreSQL database password", + description: "Changes the password for the PostgreSQL database user by executing ALTER USER inside the running container and updating the stored password.", + }, + }) .input( z.object({ postgresId: z.string().min(1), @@ -465,6 +537,12 @@ export const postgresRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a PostgreSQL database to another environment", + description: "Moves the PostgreSQL database to a different environment within the same project.", + }, + }) .input( z.object({ postgresId: z.string(), @@ -501,6 +579,12 @@ export const postgresRouter = createTRPCRouter({ return updatedPostgres; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a PostgreSQL database", + description: "Rebuilds the PostgreSQL database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildPostgres) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.postgresId, { @@ -517,6 +601,12 @@ export const postgresRouter = createTRPCRouter({ return true; }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search PostgreSQL databases", + description: "Returns a paginated list of PostgreSQL databases matching the given filters. Supports searching by name, appName, description, project, and environment.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -617,6 +707,12 @@ export const postgresRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read PostgreSQL container logs", + description: "Retrieves the Docker container logs for the specified PostgreSQL database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOnePostgres.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts index a45ef80c5..6bbbba3c3 100644 --- a/apps/dokploy/server/api/routers/preview-deployment.ts +++ b/apps/dokploy/server/api/routers/preview-deployment.ts @@ -16,6 +16,12 @@ import { createTRPCRouter, protectedProcedure } from "../trpc"; export const previewDeploymentRouter = createTRPCRouter({ all: protectedProcedure + .meta({ + openapi: { + summary: "List preview deployments", + description: "Returns all preview deployments associated with the given application.", + }, + }) .input(apiFindAllByApplication) .query(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -25,6 +31,12 @@ export const previewDeploymentRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a preview deployment", + description: "Returns the details of a specific preview deployment by its ID.", + }, + }) .input(z.object({ previewDeploymentId: z.string() })) .query(async ({ input, ctx }) => { const previewDeployment = await findPreviewDeploymentById( @@ -39,6 +51,12 @@ export const previewDeploymentRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a preview deployment", + description: "Permanently removes a preview deployment and its associated resources.", + }, + }) .input(z.object({ previewDeploymentId: z.string() })) .mutation(async ({ input, ctx }) => { const previewDeployment = await findPreviewDeploymentById( @@ -59,6 +77,12 @@ export const previewDeploymentRouter = createTRPCRouter({ }), redeploy: protectedProcedure + .meta({ + openapi: { + summary: "Redeploy a preview deployment", + description: "Triggers a rebuild of an existing preview deployment by adding a new job to the deployment queue.", + }, + }) .input( z.object({ previewDeploymentId: z.string(), diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index cccd389dd..b54637aee 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -67,6 +67,12 @@ import { export const projectRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a project", + description: "Creates a new project in the current organization with a default environment. Validates server availability for cloud deployments.", + }, + }) .input(apiCreateProject) .mutation(async ({ ctx, input }) => { try { @@ -106,6 +112,12 @@ export const projectRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a project", + description: "Retrieves a project by its ID with all environments and services. Filters services based on the user's access permissions.", + }, + }) .input(apiFindOneProject) .query(async ({ input, ctx }) => { if (ctx.user.role !== "owner" && ctx.user.role !== "admin") { @@ -193,7 +205,14 @@ export const projectRouter = createTRPCRouter({ } return project; }), - all: protectedProcedure.query(async ({ ctx }) => { + all: protectedProcedure + .meta({ + openapi: { + summary: "List all projects", + description: "Returns all projects in the current organization with their environments and services. Filters results based on the user's access permissions.", + }, + }) + .query(async ({ ctx }) => { if (ctx.user.role !== "owner" && ctx.user.role !== "admin") { const { accessedProjects, accessedEnvironments, accessedServices } = await findMemberByUserId(ctx.user.id, ctx.session.activeOrganizationId); @@ -375,7 +394,14 @@ export const projectRouter = createTRPCRouter({ }); }), - allForPermissions: withPermission("member", "update").query( + allForPermissions: withPermission("member", "update") + .meta({ + openapi: { + summary: "List all projects for permissions", + description: "Returns all projects with their environments and services for the permissions management UI. Requires member update permission.", + }, + }) + .query( async ({ ctx }) => { return await db.query.projects.findMany({ where: eq(projects.organizationId, ctx.session.activeOrganizationId), @@ -488,6 +514,12 @@ export const projectRouter = createTRPCRouter({ ), search: protectedProcedure + .meta({ + openapi: { + summary: "Search projects", + description: "Searches projects by name or description with pagination. Respects project-level access control for non-admin users.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -565,6 +597,12 @@ export const projectRouter = createTRPCRouter({ }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a project", + description: "Permanently deletes a project and all its associated environments, services, and resources.", + }, + }) .input(apiRemoveProject) .mutation(async ({ input, ctx }) => { try { @@ -592,6 +630,12 @@ export const projectRouter = createTRPCRouter({ } }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a project", + description: "Updates a project's name, description, or environment variables. Validates organization ownership and project-level access permissions.", + }, + }) .input(apiUpdateProject) .mutation(async ({ input, ctx }) => { try { @@ -640,6 +684,12 @@ export const projectRouter = createTRPCRouter({ } }), duplicate: protectedProcedure + .meta({ + openapi: { + summary: "Duplicate a project or environment", + description: "Duplicates services from a source environment into a new project or into the same project. Copies applications, compose services, databases, and their associated domains, mounts, ports, and backups.", + }, + }) .input( z.object({ sourceEnvironmentId: z.string(), diff --git a/apps/dokploy/server/api/routers/redirects.ts b/apps/dokploy/server/api/routers/redirects.ts index 4b95d4fe5..bbc26fba4 100644 --- a/apps/dokploy/server/api/routers/redirects.ts +++ b/apps/dokploy/server/api/routers/redirects.ts @@ -15,6 +15,12 @@ import { export const redirectsRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a redirect", + description: "Creates a new redirect rule for an application using a regex pattern. Logs an audit entry.", + }, + }) .input(apiCreateRedirect) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -31,6 +37,12 @@ export const redirectsRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a redirect", + description: "Returns a single redirect rule by its ID.", + }, + }) .input(apiFindOneRedirect) .query(async ({ input, ctx }) => { const redirect = await findRedirectById(input.redirectId); @@ -41,6 +53,12 @@ export const redirectsRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a redirect", + description: "Deletes a redirect rule by its ID and logs an audit entry.", + }, + }) .input(apiFindOneRedirect) .mutation(async ({ input, ctx }) => { const redirect = await findRedirectById(input.redirectId); @@ -57,6 +75,12 @@ export const redirectsRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a redirect", + description: "Updates an existing redirect rule's configuration and logs an audit entry.", + }, + }) .input(apiUpdateRedirect) .mutation(async ({ input, ctx }) => { const redirect = await findRedirectById(input.redirectId); diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index a1e912e0b..5d51f974f 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -51,6 +51,12 @@ import { } from "@/server/db/schema"; export const redisRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a Redis database", + description: "Creates a new Redis database service with the specified configuration, sets up a persistent data volume, and registers it in the project.", + }, + }) .input(apiCreateRedis) .mutation(async ({ input, ctx }) => { try { @@ -108,6 +114,12 @@ export const redisRouter = createTRPCRouter({ } }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a Redis database by ID", + description: "Returns the full details of a Redis database service, including its environment and project configuration.", + }, + }) .input(apiFindOneRedis) .query(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.redisId, "read"); @@ -126,6 +138,12 @@ export const redisRouter = createTRPCRouter({ }), start: protectedProcedure + .meta({ + openapi: { + summary: "Start a Redis database", + description: "Starts the Docker container for the specified Redis database and sets its status to done.", + }, + }) .input(apiFindOneRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -151,6 +169,12 @@ export const redisRouter = createTRPCRouter({ return redis; }), reload: protectedProcedure + .meta({ + openapi: { + summary: "Reload a Redis database", + description: "Restarts the Redis database by stopping and then starting its Docker container.", + }, + }) .input(apiResetRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -184,6 +208,12 @@ export const redisRouter = createTRPCRouter({ }), stop: protectedProcedure + .meta({ + openapi: { + summary: "Stop a Redis database", + description: "Stops the Docker container for the specified Redis database and sets its status to idle.", + }, + }) .input(apiFindOneRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -208,6 +238,12 @@ export const redisRouter = createTRPCRouter({ return redis; }), saveExternalPort: protectedProcedure + .meta({ + openapi: { + summary: "Save the external port for a Redis database", + description: "Updates the external port mapping for the Redis database and triggers a redeployment. Validates that the port is not already in use.", + }, + }) .input(apiSaveExternalPortRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -241,6 +277,12 @@ export const redisRouter = createTRPCRouter({ return redis; }), deploy: protectedProcedure + .meta({ + openapi: { + summary: "Deploy a Redis database", + description: "Triggers a deployment for the specified Redis database, rebuilding and restarting its Docker container with the current configuration.", + }, + }) .input(apiDeployRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -293,6 +335,12 @@ export const redisRouter = createTRPCRouter({ } }), changeStatus: protectedProcedure + .meta({ + openapi: { + summary: "Change Redis database status", + description: "Updates the application status of a Redis database without starting or stopping the container.", + }, + }) .input(apiChangeRedisStatus) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -311,6 +359,12 @@ export const redisRouter = createTRPCRouter({ return mongo; }), remove: protectedProcedure + .meta({ + openapi: { + summary: "Delete a Redis database", + description: "Removes the Redis database service, its Docker container, and deletes the database record.", + }, + }) .input(apiFindOneRedis) .mutation(async ({ input, ctx }) => { await checkServiceAccess(ctx, input.redisId, "delete"); @@ -346,6 +400,12 @@ export const redisRouter = createTRPCRouter({ return redis; }), saveEnvironment: protectedProcedure + .meta({ + openapi: { + summary: "Save environment variables for a Redis database", + description: "Updates the environment variables for the specified Redis database service.", + }, + }) .input(apiSaveEnvironmentVariablesRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -370,6 +430,12 @@ export const redisRouter = createTRPCRouter({ return true; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a Redis database", + description: "Updates the configuration of an existing Redis database service.", + }, + }) .input(apiUpdateRedis) .mutation(async ({ input, ctx }) => { const { redisId, ...rest } = input; @@ -396,6 +462,12 @@ export const redisRouter = createTRPCRouter({ return true; }), changePassword: protectedProcedure + .meta({ + openapi: { + summary: "Change Redis database password", + description: "Changes the password for the Redis database by executing CONFIG SET requirepass inside the running container and updating the stored password.", + }, + }) .input( z.object({ redisId: z.string().min(1), @@ -446,6 +518,12 @@ export const redisRouter = createTRPCRouter({ return true; }), move: protectedProcedure + .meta({ + openapi: { + summary: "Move a Redis database to another environment", + description: "Moves the Redis database to a different environment within the same project.", + }, + }) .input( z.object({ redisId: z.string(), @@ -482,6 +560,12 @@ export const redisRouter = createTRPCRouter({ return updatedRedis; }), rebuild: protectedProcedure + .meta({ + openapi: { + summary: "Rebuild a Redis database", + description: "Rebuilds the Redis database Docker container from scratch, pulling the latest image and recreating the service.", + }, + }) .input(apiRebuildRedis) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.redisId, { @@ -497,6 +581,12 @@ export const redisRouter = createTRPCRouter({ return true; }), search: protectedProcedure + .meta({ + openapi: { + summary: "Search Redis databases", + description: "Returns a paginated list of Redis databases matching the given filters. Supports searching by name, appName, description, project, and environment.", + }, + }) .input( z.object({ q: z.string().optional(), @@ -590,6 +680,12 @@ export const redisRouter = createTRPCRouter({ }), readLogs: protectedProcedure + .meta({ + openapi: { + summary: "Read Redis container logs", + description: "Retrieves the Docker container logs for the specified Redis database, with support for tail count, time-based filtering, and text search.", + }, + }) .input( apiFindOneRedis.extend({ tail: z.number().int().min(1).max(10000).default(100), diff --git a/apps/dokploy/server/api/routers/registry.ts b/apps/dokploy/server/api/routers/registry.ts index 1b156c069..54a37223d 100644 --- a/apps/dokploy/server/api/routers/registry.ts +++ b/apps/dokploy/server/api/routers/registry.ts @@ -23,6 +23,12 @@ import { import { createTRPCRouter, withPermission } from "../trpc"; export const registryRouter = createTRPCRouter({ create: withPermission("registry", "create") + .meta({ + openapi: { + summary: "Create registry", + description: "Creates a new Docker registry entry for the current organization and logs an audit event.", + }, + }) .input(apiCreateRegistry) .mutation(async ({ ctx, input }) => { const reg = await createRegistry(input, ctx.session.activeOrganizationId); @@ -35,6 +41,12 @@ export const registryRouter = createTRPCRouter({ return reg; }), remove: withPermission("registry", "delete") + .meta({ + openapi: { + summary: "Delete registry", + description: "Removes a Docker registry entry by ID. Verifies organization ownership and logs an audit event before deletion.", + }, + }) .input(apiRemoveRegistry) .mutation(async ({ ctx, input }) => { const registry = await findRegistryById(input.registryId); @@ -53,6 +65,12 @@ export const registryRouter = createTRPCRouter({ return await removeRegistry(input.registryId); }), update: withPermission("registry", "create") + .meta({ + openapi: { + summary: "Update registry", + description: "Updates an existing Docker registry entry. Verifies organization ownership before applying changes and logs an audit event.", + }, + }) .input(apiUpdateRegistry) .mutation(async ({ input, ctx }) => { const { registryId, ...rest } = input; @@ -82,13 +100,26 @@ export const registryRouter = createTRPCRouter({ }); return true; }), - all: withPermission("registry", "read").query(async ({ ctx }) => { + all: withPermission("registry", "read") + .meta({ + openapi: { + summary: "List all registries", + description: "Returns all Docker registry entries for the current organization.", + }, + }) + .query(async ({ ctx }) => { const registryResponse = await db.query.registry.findMany({ where: eq(registry.organizationId, ctx.session.activeOrganizationId), }); return registryResponse; }), one: withPermission("registry", "read") + .meta({ + openapi: { + summary: "Get registry", + description: "Returns a single Docker registry entry by ID. Verifies the caller belongs to the same organization.", + }, + }) .input(apiFindOneRegistry) .query(async ({ input, ctx }) => { const registry = await findRegistryById(input.registryId); @@ -101,6 +132,12 @@ export const registryRouter = createTRPCRouter({ return registry; }), testRegistry: withPermission("registry", "read") + .meta({ + openapi: { + summary: "Test registry credentials", + description: "Attempts a docker login with the provided credentials to verify the registry URL, username, and password are valid. Can run locally or on a remote server.", + }, + }) .input(apiTestRegistry) .mutation(async ({ input }) => { try { @@ -143,6 +180,12 @@ export const registryRouter = createTRPCRouter({ } }), testRegistryById: withPermission("registry", "read") + .meta({ + openapi: { + summary: "Test registry connection by ID", + description: "Looks up a saved registry by ID and attempts a docker login with its stored credentials. Verifies organization ownership before testing.", + }, + }) .input(apiTestRegistryById) .mutation(async ({ input, ctx }) => { try { diff --git a/apps/dokploy/server/api/routers/rollbacks.ts b/apps/dokploy/server/api/routers/rollbacks.ts index 20c641258..8050914ca 100644 --- a/apps/dokploy/server/api/routers/rollbacks.ts +++ b/apps/dokploy/server/api/routers/rollbacks.ts @@ -11,6 +11,12 @@ import { createTRPCRouter, protectedProcedure } from "../trpc"; export const rollbackRouter = createTRPCRouter({ delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a rollback", + description: "Permanently removes a rollback record by its ID.", + }, + }) .input(apiFindOneRollback) .mutation(async ({ input, ctx }) => { try { @@ -40,6 +46,12 @@ export const rollbackRouter = createTRPCRouter({ } }), rollback: protectedProcedure + .meta({ + openapi: { + summary: "Perform a rollback", + description: "Rolls back an application to a previous deployment by restoring its Docker image and redeploying.", + }, + }) .input(apiFindOneRollback) .mutation(async ({ input, ctx }) => { try { diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts index 144f7c74a..ce239d770 100644 --- a/apps/dokploy/server/api/routers/schedule.ts +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -22,6 +22,12 @@ import { removeJob, schedule } from "@/server/utils/backup"; import { createTRPCRouter, protectedProcedure } from "../trpc"; export const scheduleRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a scheduled job", + description: "Creates a new scheduled job for an application or compose service. If enabled, the job is automatically scheduled using the provided cron expression and timezone.", + }, + }) .input(createScheduleSchema) .mutation(async ({ input, ctx }) => { const serviceId = input.applicationId || input.composeId; @@ -54,6 +60,12 @@ export const scheduleRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a scheduled job", + description: "Updates an existing scheduled job configuration. Reschedules or removes the job depending on the enabled state.", + }, + }) .input(updateScheduleSchema) .mutation(async ({ input, ctx }) => { const existingSchedule = await findScheduleById(input.scheduleId); @@ -99,6 +111,12 @@ export const scheduleRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a scheduled job", + description: "Permanently removes a scheduled job and unschedules any associated cron job.", + }, + }) .input(z.object({ scheduleId: z.string() })) .mutation(async ({ input, ctx }) => { const scheduleItem = await findScheduleById(input.scheduleId); @@ -129,6 +147,12 @@ export const scheduleRouter = createTRPCRouter({ }), list: protectedProcedure + .meta({ + openapi: { + summary: "List scheduled jobs", + description: "Returns all scheduled jobs for a given service (application, compose, server, or dokploy-server), including their deployment history.", + }, + }) .input( z.object({ id: z.string(), @@ -170,6 +194,12 @@ export const scheduleRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a scheduled job", + description: "Returns the details of a specific scheduled job by its ID.", + }, + }) .input(z.object({ scheduleId: z.string() })) .query(async ({ input, ctx }) => { const schedule = await findScheduleById(input.scheduleId); @@ -183,6 +213,12 @@ export const scheduleRouter = createTRPCRouter({ }), runManually: protectedProcedure + .meta({ + openapi: { + summary: "Run a scheduled job manually", + description: "Immediately executes a scheduled job outside of its normal cron schedule.", + }, + }) .input(z.object({ scheduleId: z.string().min(1) })) .mutation(async ({ input, ctx }) => { const scheduleItem = await findScheduleById(input.scheduleId); diff --git a/apps/dokploy/server/api/routers/security.ts b/apps/dokploy/server/api/routers/security.ts index f2c8fc5eb..76f720ac6 100644 --- a/apps/dokploy/server/api/routers/security.ts +++ b/apps/dokploy/server/api/routers/security.ts @@ -15,6 +15,12 @@ import { export const securityRouter = createTRPCRouter({ create: protectedProcedure + .meta({ + openapi: { + summary: "Create a security entry", + description: "Creates a new HTTP basic auth security entry for an application with the provided username and password. Logs an audit entry.", + }, + }) .input(apiCreateSecurity) .mutation(async ({ input, ctx }) => { await checkServicePermissionAndAccess(ctx, input.applicationId, { @@ -31,6 +37,12 @@ export const securityRouter = createTRPCRouter({ }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a security entry", + description: "Returns a single HTTP basic auth security entry by its ID.", + }, + }) .input(apiFindOneSecurity) .query(async ({ input, ctx }) => { const security = await findSecurityById(input.securityId); @@ -41,6 +53,12 @@ export const securityRouter = createTRPCRouter({ }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a security entry", + description: "Deletes an HTTP basic auth security entry by its ID and logs an audit entry.", + }, + }) .input(apiFindOneSecurity) .mutation(async ({ input, ctx }) => { const security = await findSecurityById(input.securityId); @@ -57,6 +75,12 @@ export const securityRouter = createTRPCRouter({ }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a security entry", + description: "Updates an existing HTTP basic auth security entry's configuration and logs an audit entry.", + }, + }) .input(apiUpdateSecurity) .mutation(async ({ input, ctx }) => { const security = await findSecurityById(input.securityId); diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts index 85f23ec0e..929633bad 100644 --- a/apps/dokploy/server/api/routers/server.ts +++ b/apps/dokploy/server/api/routers/server.ts @@ -48,6 +48,12 @@ import { export const serverRouter = createTRPCRouter({ create: withPermission("server", "create") + .meta({ + openapi: { + summary: "Create a server", + description: "Creates a new server in the organization. In cloud mode, enforces the user's server quantity limit. Returns the newly created server.", + }, + }) .input(apiCreateServer) .mutation(async ({ ctx, input }) => { try { @@ -80,6 +86,12 @@ export const serverRouter = createTRPCRouter({ }), one: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get a server", + description: "Retrieves a single server by its ID. Validates that the user has access to the server within their organization.", + }, + }) .input(apiFindOneServer) .query(async ({ input, ctx }) => { const server = await findServerById(input.serverId); @@ -101,13 +113,26 @@ export const serverRouter = createTRPCRouter({ return server; }), getDefaultCommand: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get default server command", + description: "Returns the default setup command for a server. The command varies depending on whether the server is a build server or a deploy server.", + }, + }) .input(apiFindOneServer) .query(async ({ input }) => { const server = await findServerById(input.serverId); const isBuildServer = server.serverType === "build"; return defaultCommand(isBuildServer); }), - all: withPermission("server", "read").query(async ({ ctx }) => { + all: withPermission("server", "read") + .meta({ + openapi: { + summary: "List all servers", + description: "Returns all servers in the current organization along with a count of associated services (applications, compose, databases). Results are filtered by the user's accessible server permissions.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleServerIds(ctx.session); const result = await db @@ -130,6 +155,12 @@ export const serverRouter = createTRPCRouter({ return result.filter((s) => accessibleIds.has(s.serverId)); }), allForPermissions: withPermission("member", "update") + .meta({ + openapi: { + summary: "List all servers for permissions", + description: "Returns a minimal list of servers (ID, name, IP, type) used for configuring member permissions. Requires a valid enterprise license.", + }, + }) .use(async ({ ctx, next }) => { const licensed = await hasValidLicense(ctx.session.activeOrganizationId); if (!licensed) { @@ -152,7 +183,14 @@ export const serverRouter = createTRPCRouter({ where: eq(server.organizationId, ctx.session.activeOrganizationId), }); }), - count: protectedProcedure.query(async ({ ctx }) => { + count: protectedProcedure + .meta({ + openapi: { + summary: "Get server count", + description: "Returns the total number of servers across all organizations owned by the current user.", + }, + }) + .query(async ({ ctx }) => { const organizations = await db.query.organization.findMany({ where: eq(organization.ownerId, ctx.user.id), with: { @@ -164,7 +202,14 @@ export const serverRouter = createTRPCRouter({ return servers.length ?? 0; }), - withSSHKey: withPermission("server", "read").query(async ({ ctx }) => { + withSSHKey: withPermission("server", "read") + .meta({ + openapi: { + summary: "List servers with SSH keys", + description: "Returns all deploy-type servers that have an SSH key configured. In cloud mode, only active servers are included. Results are filtered by the user's accessible server permissions.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleServerIds(ctx.session); const result = await db.query.server.findMany({ @@ -184,7 +229,14 @@ export const serverRouter = createTRPCRouter({ }); return result.filter((s) => accessibleIds.has(s.serverId)); }), - buildServers: withPermission("server", "read").query(async ({ ctx }) => { + buildServers: withPermission("server", "read") + .meta({ + openapi: { + summary: "List build servers", + description: "Returns all build-type servers that have an SSH key configured. In cloud mode, only active servers are included. Results are filtered by the user's accessible server permissions.", + }, + }) + .query(async ({ ctx }) => { const accessibleIds = await getAccessibleServerIds(ctx.session); const result = await db.query.server.findMany({ @@ -205,6 +257,12 @@ export const serverRouter = createTRPCRouter({ return result.filter((s) => accessibleIds.has(s.serverId)); }), setup: withPermission("server", "create") + .meta({ + openapi: { + summary: "Setup a server", + description: "Runs the initial setup process on a remote server, installing required dependencies and configuring Docker. An audit log entry is created.", + }, + }) .input(apiFindOneServer) .mutation(async ({ input, ctx }) => { try { @@ -256,6 +314,12 @@ export const serverRouter = createTRPCRouter({ } }), validate: withPermission("server", "read") + .meta({ + openapi: { + summary: "Validate server configuration", + description: "Checks the server for required tools and configuration including Docker, Rclone, Nixpacks, Buildpacks, Railpack, Swarm mode, network setup, and privilege mode.", + }, + }) .input(apiFindOneServer) .query(async ({ input, ctx }) => { try { @@ -304,6 +368,12 @@ export const serverRouter = createTRPCRouter({ }), security: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get server security audit", + description: "Performs a security audit on the server, checking UFW firewall, SSH configuration, non-root user setup, unattended upgrades, and Fail2Ban status.", + }, + }) .input(apiFindOneServer) .query(async ({ input, ctx }) => { try { @@ -354,6 +424,12 @@ export const serverRouter = createTRPCRouter({ } }), setupMonitoring: withPermission("server", "create") + .meta({ + openapi: { + summary: "Setup server monitoring", + description: "Configures and deploys the monitoring agent on a server with the specified metrics configuration including refresh rates, retention, thresholds, and container service filters.", + }, + }) .input(apiUpdateServerMonitoring) .mutation(async ({ input, ctx }) => { try { @@ -402,6 +478,12 @@ export const serverRouter = createTRPCRouter({ } }), remove: withPermission("server", "delete") + .meta({ + openapi: { + summary: "Remove a server", + description: "Deletes a server and removes all associated deployments. Fails if the server has active services. In cloud mode, updates the user's server quantity allocation.", + }, + }) .input(apiRemoveServer) .mutation(async ({ input, ctx }) => { try { @@ -435,6 +517,12 @@ export const serverRouter = createTRPCRouter({ } }), update: withPermission("server", "create") + .meta({ + openapi: { + summary: "Update a server", + description: "Updates the configuration of an existing server. Fails if the server is inactive. An audit log entry is created for the update.", + }, + }) .input(apiUpdateServer) .mutation(async ({ input, ctx }) => { try { @@ -467,14 +555,28 @@ export const serverRouter = createTRPCRouter({ throw error; } }), - publicIp: protectedProcedure.query(async () => { + publicIp: protectedProcedure + .meta({ + openapi: { + summary: "Get public IP address", + description: "Returns the public IP address of the local server. Returns an empty string in cloud mode.", + }, + }) + .query(async () => { if (IS_CLOUD) { return ""; } const ip = await getPublicIpWithFallback(); return ip; }), - getServerTime: protectedProcedure.query(() => { + getServerTime: protectedProcedure + .meta({ + openapi: { + summary: "Get server time", + description: "Returns the current server time and timezone. Returns null in cloud mode.", + }, + }) + .query(() => { if (IS_CLOUD) { return null; } @@ -484,6 +586,12 @@ export const serverRouter = createTRPCRouter({ }; }), getServerMetrics: withPermission("monitoring", "read") + .meta({ + openapi: { + summary: "Get server metrics", + description: "Fetches monitoring metrics (CPU, memory, disk, network) from the server's monitoring agent endpoint. Requires the monitoring service to be configured and running.", + }, + }) .input( z.object({ url: z.string(), diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 9965643f7..405f79bcc 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -52,6 +52,7 @@ import { db } from "@dokploy/server/db"; import { checkPermission } from "@dokploy/server/services/permission"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { TRPCError } from "@trpc/server"; +import { tryCatch } from "bullmq"; import { eq, sql } from "drizzle-orm"; import { scheduledJobs, scheduleJob } from "node-schedule"; import { parse, stringify } from "yaml"; @@ -82,73 +83,120 @@ import { } from "../trpc"; export const settingsRouter = createTRPCRouter({ - getWebServerSettings: protectedProcedure.query(async () => { - if (IS_CLOUD) { - return null; - } - const settings = await getWebServerSettings(); - return settings; - }), - reloadServer: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { + getWebServerSettings: protectedProcedure + .meta({ + openapi: { + summary: "Get web server settings", + description: + "Retrieve the current web server settings. Returns null on cloud.", + }, + }) + .query(async () => { + if (IS_CLOUD) { + return null; + } + const settings = await getWebServerSettings(); + return settings; + }), + reloadServer: adminProcedure + .meta({ + openapi: { + summary: "Reload Dokploy server", + description: + "Reload the Dokploy Docker service with the current version. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + await reloadDockerResource("dokploy", undefined, packageInfo.version); + await audit(ctx, { + action: "reload", + resourceType: "settings", + resourceName: "dokploy", + }); return true; - } - await reloadDockerResource("dokploy", undefined, packageInfo.version); - await audit(ctx, { - action: "reload", - resourceType: "settings", - resourceName: "dokploy", - }); - return true; - }), - cleanRedis: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { + }), + cleanRedis: adminProcedure + .meta({ + openapi: { + summary: "Flush Redis data", + description: + "Execute FLUSHALL on the Dokploy Redis container, removing all cached data. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + + const { stdout: containerId } = await execAsync( + `docker ps --filter "name=dokploy-redis" --filter "status=running" -q | head -n 1`, + ); + + if (!containerId) { + throw new Error("Redis container not found"); + } + + const redisContainerId = containerId.trim(); + + await execAsync(`docker exec -i ${redisContainerId} redis-cli flushall`); + await audit(ctx, { + action: "update", + resourceType: "settings", + resourceName: "clean-redis", + }); return true; - } - - const { stdout: containerId } = await execAsync( - `docker ps --filter "name=dokploy-redis" --filter "status=running" -q | head -n 1`, - ); - - if (!containerId) { - throw new Error("Redis container not found"); - } - - const redisContainerId = containerId.trim(); - - await execAsync(`docker exec -i ${redisContainerId} redis-cli flushall`); - await audit(ctx, { - action: "update", - resourceType: "settings", - resourceName: "clean-redis", - }); - return true; - }), - reloadRedis: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { + }), + reloadRedis: adminProcedure + .meta({ + openapi: { + summary: "Reload Redis service", + description: + "Force-reload the Dokploy Redis Docker service. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + await reloadDockerResource("dokploy-redis"); + await audit(ctx, { + action: "reload", + resourceType: "settings", + resourceName: "dokploy-redis", + }); return true; - } - await reloadDockerResource("dokploy-redis"); - await audit(ctx, { - action: "reload", - resourceType: "settings", - resourceName: "dokploy-redis", - }); - return true; - }), - cleanAllDeploymentQueue: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { - return true; - } - const result = cleanAllDeploymentQueue(); - await audit(ctx, { - action: "update", - resourceType: "settings", - resourceName: "clean-deployment-queue", - }); - return result; - }), + }), + cleanAllDeploymentQueue: adminProcedure + .meta({ + openapi: { + summary: "Clean all deployment queues", + description: + "Remove all pending jobs from the deployment queue. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + const result = cleanAllDeploymentQueue(); + await audit(ctx, { + action: "update", + resourceType: "settings", + resourceName: "clean-deployment-queue", + }); + return result; + }), reloadTraefik: adminProcedure + .meta({ + openapi: { + summary: "Reload Traefik service", + description: + "Force-reload the Dokploy Traefik Docker service. Runs in the background to avoid proxy timeouts. Optionally targets a specific server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { // Run in background so the request returns immediately; avoids proxy timeouts. @@ -165,6 +213,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), toggleDashboard: adminProcedure + .meta({ + openapi: { + summary: "Toggle Traefik dashboard", + description: + "Enable or disable the Traefik dashboard by adding or removing port 8080. Checks for port conflicts before enabling. Runs the Traefik setup in the background.", + }, + }) .input(apiEnableDashboard) .mutation(async ({ input, ctx }) => { const ports = await readPorts("dokploy-traefik", input.serverId); @@ -213,6 +268,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), cleanUnusedImages: adminProcedure + .meta({ + openapi: { + summary: "Clean unused Docker images", + description: + "Remove all unused Docker images from the host or a specified server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { await cleanupImages(input?.serverId); @@ -224,6 +286,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), cleanUnusedVolumes: adminProcedure + .meta({ + openapi: { + summary: "Clean unused Docker volumes", + description: + "Remove all unused Docker volumes from the host or a specified server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { await cleanupVolumes(input?.serverId); @@ -235,6 +304,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), cleanStoppedContainers: adminProcedure + .meta({ + openapi: { + summary: "Clean stopped Docker containers", + description: + "Remove all stopped Docker containers from the host or a specified server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { await cleanupContainers(input?.serverId); @@ -246,6 +322,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), cleanDockerBuilder: adminProcedure + .meta({ + openapi: { + summary: "Clean Docker build cache", + description: + "Remove Docker builder cache from the host or a specified server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { await cleanupBuilders(input?.serverId); @@ -256,6 +339,13 @@ export const settingsRouter = createTRPCRouter({ }); }), cleanDockerPrune: adminProcedure + .meta({ + openapi: { + summary: "Prune Docker system", + description: + "Run a full Docker system prune and builder cache cleanup on the host or a specified server.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { await cleanupSystem(input?.serverId); @@ -268,6 +358,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), cleanAll: adminProcedure + .meta({ + openapi: { + summary: "Clean all Docker resources", + description: + "Run a comprehensive Docker cleanup (images, containers, volumes, builders, system prune) in the background to avoid gateway timeouts.", + }, + }) .input(apiServerSchema) .mutation(async ({ input, ctx }) => { // Execute cleanup in background and return immediately to avoid gateway timeouts @@ -279,26 +376,49 @@ export const settingsRouter = createTRPCRouter({ }); return result; }), - cleanMonitoring: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { + cleanMonitoring: adminProcedure + .meta({ + openapi: { + summary: "Clean monitoring data", + description: + "Delete and recreate the monitoring data directory, removing all collected metrics. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + const { MONITORING_PATH } = paths(); + await recreateDirectory(MONITORING_PATH); + await audit(ctx, { + action: "delete", + resourceType: "settings", + resourceName: "clean-monitoring", + }); return true; - } - const { MONITORING_PATH } = paths(); - await recreateDirectory(MONITORING_PATH); - await audit(ctx, { - action: "delete", - resourceType: "settings", - resourceName: "clean-monitoring", - }); - return true; - }), - getDockerDiskUsage: adminProcedure.query(async () => { - if (IS_CLOUD) { - return []; - } - return getDockerDiskUsage(); - }), + }), + getDockerDiskUsage: adminProcedure + .meta({ + openapi: { + summary: "Get Docker disk usage", + description: + "Retrieve Docker disk usage statistics. Returns an empty array on cloud.", + }, + }) + .query(async () => { + if (IS_CLOUD) { + return []; + } + return getDockerDiskUsage(); + }), saveSSHPrivateKey: adminProcedure + .meta({ + openapi: { + summary: "Save SSH private key", + description: + "Store an SSH private key in the web server settings for remote server access. Disabled on cloud.", + }, + }) .input(apiSaveSSHKey) .mutation(async ({ input, ctx }) => { if (IS_CLOUD) { @@ -315,6 +435,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), assignDomainServer: adminProcedure + .meta({ + openapi: { + summary: "Assign domain to server", + description: + "Configure the server domain, HTTPS settings, certificate type, and Let's Encrypt email. Updates both web server settings and Traefik configuration. Disabled on cloud.", + }, + }) .input(apiAssignDomain) .mutation(async ({ input, ctx }) => { if (IS_CLOUD) { @@ -346,21 +473,36 @@ export const settingsRouter = createTRPCRouter({ }); return settings; }), - cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { + cleanSSHPrivateKey: adminProcedure + .meta({ + openapi: { + summary: "Remove SSH private key", + description: + "Clear the stored SSH private key from web server settings. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + await updateWebServerSettings({ + sshPrivateKey: null, + }); + await audit(ctx, { + action: "delete", + resourceType: "settings", + resourceName: "ssh-private-key", + }); return true; - } - await updateWebServerSettings({ - sshPrivateKey: null, - }); - await audit(ctx, { - action: "delete", - resourceType: "settings", - resourceName: "ssh-private-key", - }); - return true; - }), + }), updateDockerCleanup: adminProcedure + .meta({ + openapi: { + summary: "Update Docker cleanup schedule", + description: + "Enable or disable automatic Docker cleanup for a server or the web server. When enabled, schedules a cron job that periodically removes unused Docker resources and sends notifications.", + }, + }) .input(apiUpdateDockerCleanup) .mutation(async ({ input, ctx }) => { if (input.serverId) { @@ -445,15 +587,30 @@ export const settingsRouter = createTRPCRouter({ return true; }), - readTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readMainConfig(); - return traefikConfig; - }), + readTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Read main Traefik configuration", + description: + "Read the main Traefik configuration file. Disabled on cloud.", + }, + }) + .query(() => { + if (IS_CLOUD) { + return true; + } + const traefikConfig = readMainConfig(); + return traefikConfig; + }), updateTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Update main Traefik configuration", + description: + "Overwrite the main Traefik configuration file with the provided content. Disabled on cloud.", + }, + }) .input(apiTraefikConfig) .mutation(async ({ input, ctx }) => { if (IS_CLOUD) { @@ -468,14 +625,29 @@ export const settingsRouter = createTRPCRouter({ return true; }), - readWebServerTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readConfig("dokploy"); - return traefikConfig; - }), + readWebServerTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Read web server Traefik configuration", + description: + "Read the Dokploy-specific Traefik configuration file. Disabled on cloud.", + }, + }) + .query(() => { + if (IS_CLOUD) { + return true; + } + const traefikConfig = readConfig("dokploy"); + return traefikConfig; + }), updateWebServerTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Update web server Traefik configuration", + description: + "Overwrite the Dokploy-specific Traefik configuration file with the provided content. Disabled on cloud.", + }, + }) .input(apiTraefikConfig) .mutation(async ({ input, ctx }) => { if (IS_CLOUD) { @@ -490,15 +662,30 @@ export const settingsRouter = createTRPCRouter({ return true; }), - readMiddlewareTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readConfig("middlewares"); - return traefikConfig; - }), + readMiddlewareTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Read middleware Traefik configuration", + description: + "Read the Traefik middlewares configuration file. Disabled on cloud.", + }, + }) + .query(() => { + if (IS_CLOUD) { + return true; + } + const traefikConfig = readConfig("middlewares"); + return traefikConfig; + }), updateMiddlewareTraefikConfig: adminProcedure + .meta({ + openapi: { + summary: "Update middleware Traefik configuration", + description: + "Overwrite the Traefik middlewares configuration file with the provided content. Disabled on cloud.", + }, + }) .input(apiTraefikConfig) .mutation(async ({ input, ctx }) => { if (IS_CLOUD) { @@ -512,45 +699,84 @@ export const settingsRouter = createTRPCRouter({ }); return true; }), - getUpdateData: protectedProcedure.mutation(async () => { - if (IS_CLOUD) { - return DEFAULT_UPDATE_DATA; - } + getUpdateData: protectedProcedure + .meta({ + openapi: { + summary: "Check for Dokploy updates", + description: + "Check whether a newer version of Dokploy is available. Returns default data on cloud.", + }, + }) + .mutation(async () => { + if (IS_CLOUD) { + return DEFAULT_UPDATE_DATA; + } + + return await getUpdateData(packageInfo.version); + }), + updateServer: adminProcedure + .meta({ + openapi: { + summary: "Update Dokploy to latest version", + description: + "Pull the latest Dokploy Docker image and update the service if a new version is available. Disabled on cloud.", + }, + }) + .mutation(async ({ ctx }) => { + if (IS_CLOUD) { + return true; + } + + const data = await getUpdateData(packageInfo.version); + if (data.updateAvailable) { + void spawnAsync("docker", [ + "service", + "update", + "--force", + "--image", + `dokploy/dokploy:${data.latestVersion}`, + "dokploy", + ]); + await audit(ctx, { + action: "update", + resourceType: "settings", + resourceName: "dokploy-version", + }); + } - return await getUpdateData(packageInfo.version); - }), - updateServer: adminProcedure.mutation(async ({ ctx }) => { - if (IS_CLOUD) { return true; - } + }), - const data = await getUpdateData(packageInfo.version); - if (data.updateAvailable) { - void spawnAsync("docker", [ - "service", - "update", - "--force", - "--image", - `dokploy/dokploy:${data.latestVersion}`, - "dokploy", - ]); - await audit(ctx, { - action: "update", - resourceType: "settings", - resourceName: "dokploy-version", - }); - } - - return true; - }), - - getDokployVersion: protectedProcedure.query(() => { - return packageInfo.version; - }), - getReleaseTag: protectedProcedure.query(() => { - return getDokployImageTag(); - }), + getDokployVersion: protectedProcedure + .meta({ + openapi: { + summary: "Get Dokploy version", + description: + "Return the currently running Dokploy version from package.json.", + }, + }) + .query(() => { + return packageInfo.version; + }), + getReleaseTag: protectedProcedure + .meta({ + openapi: { + summary: "Get Dokploy release tag", + description: + "Return the Docker image tag of the running Dokploy instance.", + }, + }) + .query(() => { + return getDokployImageTag(); + }), readDirectories: protectedProcedure + .meta({ + openapi: { + summary: "List Traefik configuration directories", + description: + "Read the directory listing of the main Traefik configuration path. Requires traefikFiles.read permission.", + }, + }) .input(apiServerSchema) .query(async ({ ctx, input }) => { try { @@ -564,6 +790,13 @@ export const settingsRouter = createTRPCRouter({ }), updateTraefikFile: protectedProcedure + .meta({ + openapi: { + summary: "Update a Traefik configuration file", + description: + "Write content to a specific Traefik configuration file at the given path. Requires traefikFiles.write permission.", + }, + }) .input(apiModifyTraefikConfig) .mutation(async ({ input, ctx }) => { await checkPermission(ctx, { traefikFiles: ["write"] }); @@ -581,6 +814,13 @@ export const settingsRouter = createTRPCRouter({ }), readTraefikFile: protectedProcedure + .meta({ + openapi: { + summary: "Read a Traefik configuration file", + description: + "Read the content of a specific Traefik configuration file at the given path. Requires traefikFiles.read permission. Validates server ownership when a serverId is provided.", + }, + }) .input(apiReadTraefikConfig) .query(async ({ input, ctx }) => { await checkPermission(ctx, { traefikFiles: ["read"] }); @@ -595,14 +835,29 @@ export const settingsRouter = createTRPCRouter({ return readConfigInPath(input.path, input.serverId); }), - getIp: protectedProcedure.query(async () => { - if (IS_CLOUD) { - return ""; - } - const settings = await getWebServerSettings(); - return settings?.serverIp || ""; - }), + getIp: protectedProcedure + .meta({ + openapi: { + summary: "Get server IP address", + description: + "Return the configured server IP address from web server settings. Returns an empty string on cloud.", + }, + }) + .query(async () => { + if (IS_CLOUD) { + return ""; + } + const settings = await getWebServerSettings(); + return settings?.serverIp || ""; + }), updateServerIp: adminProcedure + .meta({ + openapi: { + summary: "Update server IP address", + description: + "Update the server IP address stored in web server settings. Disabled on cloud.", + }, + }) .input( z.object({ serverIp: z.string(), @@ -623,91 +878,108 @@ export const settingsRouter = createTRPCRouter({ return settings; }), - getOpenApiDocument: protectedProcedure.query( - async ({ ctx }): Promise => { - const protocol = ctx.req.headers["x-forwarded-proto"]; - const url = `${protocol}://${ctx.req.headers.host}/api`; - const openApiDocument = generateOpenApiDocument(appRouter, { - title: "tRPC OpenAPI", - version: packageInfo.version, - baseUrl: url, - docsUrl: `${url}/settings.getOpenApiDocument`, - tags: [ - "admin", - "docker", - "compose", - "registry", - "cluster", - "user", - "domain", - "destination", - "backup", - "deployment", - "mounts", - "certificates", - "settings", - "security", - "redirects", - "port", - "project", - "application", - "mysql", - "postgres", - "redis", - "mongo", - "libsql", - "mariadb", - "sshRouter", - "gitProvider", - "bitbucket", - "ai", - "github", - "gitlab", - "gitea", - "tag", - "patch", - "server", - "volumeBackups", - "environment", - "auditLog", - "customRole", - "whitelabeling", - "sso", - "licenseKey", - "organization", - "previewDeployment", - ], - }); + getOpenApiDocument: protectedProcedure + .meta({ + openapi: { + summary: "Get OpenAPI specification", + description: + "Generate and return the full OpenAPI document for the Dokploy API, including all endpoints, tags, and security schemes.", + }, + }) + .query(async ({ ctx }): Promise => { + try { + const protocol = ctx.req.headers["x-forwarded-proto"]; + const url = `${protocol}://${ctx.req.headers.host}/api`; + const openApiDocument = generateOpenApiDocument(appRouter, { + title: "tRPC OpenAPI", + version: packageInfo.version, + baseUrl: url, + docsUrl: `${url}/settings.getOpenApiDocument`, + tags: [ + "admin", + "docker", + "compose", + "registry", + "cluster", + "user", + "domain", + "destination", + "backup", + "deployment", + "mounts", + "certificates", + "settings", + "security", + "redirects", + "port", + "project", + "application", + "mysql", + "postgres", + "redis", + "mongo", + "libsql", + "mariadb", + "sshRouter", + "gitProvider", + "bitbucket", + "ai", + "github", + "gitlab", + "gitea", + "tag", + "patch", + "server", + "volumeBackups", + "environment", + "auditLog", + "customRole", + "whitelabeling", + "sso", + "licenseKey", + "organization", + "previewDeployment", + ], + }); - openApiDocument.info = { - title: "Dokploy API", - description: "Endpoints for dokploy", - version: packageInfo.version, - }; + openApiDocument.info = { + title: "Dokploy API", + description: "Endpoints for dokploy", + version: packageInfo.version, + }; - // Add security schemes configuration - openApiDocument.components = { - ...openApiDocument.components, - securitySchemes: { - apiKey: { - type: "apiKey", - in: "header", - name: "x-api-key", - description: "API key authentication", + // Add security schemes configuration + openApiDocument.components = { + ...openApiDocument.components, + securitySchemes: { + apiKey: { + type: "apiKey", + in: "header", + name: "x-api-key", + description: "API key authentication", + }, }, - }, - }; + }; - // Apply security globally to all endpoints - openApiDocument.security = [ - { - apiKey: [], - }, - ]; - return openApiDocument; - }, - ), + // Apply security globally to all endpoints + openApiDocument.security = [ + { + apiKey: [], + }, + ]; + return openApiDocument; + } catch (error) { + console.log(error); + } + }), readTraefikEnv: adminProcedure + .meta({ + openapi: { + summary: "Read Traefik environment variables", + description: + "Retrieve the environment variables configured for the Dokploy Traefik service, optionally for a specific server.", + }, + }) .input(apiServerSchema) .query(async ({ input }) => { const envVars = await readEnvironmentVariables( @@ -718,6 +990,13 @@ export const settingsRouter = createTRPCRouter({ }), writeTraefikEnv: adminProcedure + .meta({ + openapi: { + summary: "Write Traefik environment variables", + description: + "Update the environment variables for the Dokploy Traefik service and restart it in the background. Preserves the current port configuration.", + }, + }) .input(z.object({ env: z.string(), serverId: z.string().optional() })) .mutation(async ({ input, ctx }) => { const envs = prepareEnvironmentVariables(input.env); @@ -739,6 +1018,13 @@ export const settingsRouter = createTRPCRouter({ return true; }), haveTraefikDashboardPortEnabled: adminProcedure + .meta({ + openapi: { + summary: "Check Traefik dashboard port status", + description: + "Check whether port 8080 (Traefik dashboard) is currently enabled in the Traefik service port configuration.", + }, + }) .input(apiServerSchema) .query(async ({ input }) => { const ports = await readPorts("dokploy-traefik", input?.serverId); @@ -808,22 +1094,37 @@ export const settingsRouter = createTRPCRouter({ const processedLogs = processLogs(rawConfig as string, input?.dateRange); return processedLogs || []; }), - haveActivateRequests: protectedProcedure.query(async () => { - if (IS_CLOUD) { - return true; - } - const config = readMainConfig(); + haveActivateRequests: protectedProcedure + .meta({ + openapi: { + summary: "Check if request logging is active", + description: + "Check whether Traefik access log (request logging) is enabled by inspecting the main Traefik configuration. Returns true on cloud.", + }, + }) + .query(async () => { + if (IS_CLOUD) { + return true; + } + const config = readMainConfig(); - if (!config) return false; - const parsedConfig = parse(config) as { - accessLog?: { - filePath: string; + if (!config) return false; + const parsedConfig = parse(config) as { + accessLog?: { + filePath: string; + }; }; - }; - return !!parsedConfig?.accessLog?.filePath; - }), + return !!parsedConfig?.accessLog?.filePath; + }), toggleRequests: protectedProcedure + .meta({ + openapi: { + summary: "Toggle request logging", + description: + "Enable or disable Traefik access log (request logging) by updating the main Traefik configuration file. Disabled on cloud.", + }, + }) .input( z.object({ enable: z.boolean(), @@ -863,48 +1164,90 @@ export const settingsRouter = createTRPCRouter({ }); return true; }), - isCloud: publicProcedure.query(async () => { - return IS_CLOUD; - }), - isUserSubscribed: protectedProcedure.query(async ({ ctx }) => { - const haveServers = await db.query.server.findMany({ - where: eq(server.organizationId, ctx.session?.activeOrganizationId || ""), - }); - const haveProjects = await db.query.projects.findMany({ - where: eq( - projects.organizationId, - ctx.session?.activeOrganizationId || "", - ), - }); - return haveServers.length > 0 || haveProjects.length > 0; - }), - health: publicProcedure.query(async () => { - try { - await db.execute(sql`SELECT 1`); - return { status: "ok" }; - } catch (error) { - console.error("Database connection error:", error); - throw error; - } - }), - checkInfrastructureHealth: adminProcedure.query(async () => { - if (IS_CLOUD) { - return { - postgres: { status: "healthy" as const }, - redis: { status: "healthy" as const }, - traefik: { status: "healthy" as const }, - }; - } + isCloud: publicProcedure + .meta({ + openapi: { + summary: "Check if running on cloud", + description: + "Return whether this Dokploy instance is running in cloud mode.", + }, + }) + .query(async () => { + return IS_CLOUD; + }), + isUserSubscribed: protectedProcedure + .meta({ + openapi: { + summary: "Check if user has resources", + description: + "Check whether the current organization has any servers or projects, indicating active usage.", + }, + }) + .query(async ({ ctx }) => { + const haveServers = await db.query.server.findMany({ + where: eq( + server.organizationId, + ctx.session?.activeOrganizationId || "", + ), + }); + const haveProjects = await db.query.projects.findMany({ + where: eq( + projects.organizationId, + ctx.session?.activeOrganizationId || "", + ), + }); + return haveServers.length > 0 || haveProjects.length > 0; + }), + health: publicProcedure + .meta({ + openapi: { + summary: "Health check", + description: + "Verify the application is running and the database connection is healthy by executing a simple query.", + }, + }) + .query(async () => { + try { + await db.execute(sql`SELECT 1`); + return { status: "ok" }; + } catch (error) { + console.error("Database connection error:", error); + throw error; + } + }), + checkInfrastructureHealth: adminProcedure + .meta({ + openapi: { + summary: "Check infrastructure health", + description: + "Check the health status of PostgreSQL, Redis, and Traefik services. Returns healthy for all on cloud.", + }, + }) + .query(async () => { + if (IS_CLOUD) { + return { + postgres: { status: "healthy" as const }, + redis: { status: "healthy" as const }, + traefik: { status: "healthy" as const }, + }; + } - const [postgres, redis, traefik] = await Promise.all([ - checkPostgresHealth(), - checkRedisHealth(), - checkTraefikHealth(), - ]); + const [postgres, redis, traefik] = await Promise.all([ + checkPostgresHealth(), + checkRedisHealth(), + checkTraefikHealth(), + ]); - return { postgres, redis, traefik }; - }), + return { postgres, redis, traefik }; + }), setupGPU: adminProcedure + .meta({ + openapi: { + summary: "Set up GPU support", + description: + "Install and configure GPU support (NVIDIA runtime) on the host or a specified server. On cloud, a serverId is required.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -929,6 +1272,13 @@ export const settingsRouter = createTRPCRouter({ } }), checkGPUStatus: adminProcedure + .meta({ + openapi: { + summary: "Check GPU status", + description: + "Retrieve detailed GPU status including driver version, CUDA support, memory info, available GPUs, and Docker Swarm GPU resources for the host or a specified server.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -963,6 +1313,13 @@ export const settingsRouter = createTRPCRouter({ } }), updateTraefikPorts: adminProcedure + .meta({ + openapi: { + summary: "Update Traefik ports", + description: + "Replace the Traefik service port mappings with the provided list. Checks for port conflicts before applying. Runs the Traefik setup in the background.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -1031,12 +1388,26 @@ export const settingsRouter = createTRPCRouter({ } }), getTraefikPorts: adminProcedure + .meta({ + openapi: { + summary: "Get Traefik ports", + description: + "Retrieve the current port mappings configured for the Dokploy Traefik service.", + }, + }) .input(apiServerSchema) .query(async ({ input }) => { const ports = await readPorts("dokploy-traefik", input?.serverId); return ports; }), updateLogCleanup: protectedProcedure + .meta({ + openapi: { + summary: "Update log cleanup schedule", + description: + "Start or stop the automatic log cleanup cron job. Provide a cron expression to start, or null to stop. Disabled on cloud.", + }, + }) .input( z.object({ cronExpression: z.string().nullable(), @@ -1060,15 +1431,31 @@ export const settingsRouter = createTRPCRouter({ return result; }), - getLogCleanupStatus: protectedProcedure.query(async () => { - return getLogCleanupStatus(); - }), + getLogCleanupStatus: protectedProcedure + .meta({ + openapi: { + summary: "Get log cleanup status", + description: + "Return the current status and schedule of the automatic log cleanup job.", + }, + }) + .query(async () => { + return getLogCleanupStatus(); + }), - getDokployCloudIps: adminProcedure.query(async () => { - if (!IS_CLOUD) { - return []; - } - const ips = process.env.DOKPLOY_CLOUD_IPS?.split(","); - return ips; - }), + getDokployCloudIps: adminProcedure + .meta({ + openapi: { + summary: "Get Dokploy cloud IP addresses", + description: + "Return the list of Dokploy cloud IP addresses from the environment configuration. Returns an empty array in self-hosted mode.", + }, + }) + .query(async () => { + if (!IS_CLOUD) { + return []; + } + const ips = process.env.DOKPLOY_CLOUD_IPS?.split(","); + return ips; + }), }); diff --git a/apps/dokploy/server/api/routers/ssh-key.ts b/apps/dokploy/server/api/routers/ssh-key.ts index aa758a0ea..36798d80f 100644 --- a/apps/dokploy/server/api/routers/ssh-key.ts +++ b/apps/dokploy/server/api/routers/ssh-key.ts @@ -25,6 +25,12 @@ import { export const sshRouter = createTRPCRouter({ create: withPermission("sshKeys", "create") + .meta({ + openapi: { + summary: "Create SSH key", + description: "Stores a new SSH key for the current organization and logs an audit event.", + }, + }) .input(apiCreateSshKey) .mutation(async ({ input, ctx }) => { try { @@ -46,6 +52,12 @@ export const sshRouter = createTRPCRouter({ } }), remove: withPermission("sshKeys", "delete") + .meta({ + openapi: { + summary: "Delete SSH key", + description: "Removes an SSH key by ID. Verifies organization ownership and logs an audit event before deletion.", + }, + }) .input(apiRemoveSshKey) .mutation(async ({ input, ctx }) => { try { @@ -69,6 +81,12 @@ export const sshRouter = createTRPCRouter({ } }), one: withPermission("sshKeys", "read") + .meta({ + openapi: { + summary: "Get SSH key", + description: "Returns a single SSH key by ID. Verifies the caller belongs to the same organization.", + }, + }) .input(apiFindOneSshKey) .query(async ({ input, ctx }) => { const sshKey = await findSSHKeyById(input.sshKeyId); @@ -81,13 +99,27 @@ export const sshRouter = createTRPCRouter({ } return sshKey; }), - all: withPermission("sshKeys", "read").query(async ({ ctx }) => { + all: withPermission("sshKeys", "read") + .meta({ + openapi: { + summary: "List all SSH keys", + description: "Returns all SSH keys for the current organization, ordered by creation date descending.", + }, + }) + .query(async ({ ctx }) => { return await db.query.sshKeys.findMany({ where: eq(sshKeys.organizationId, ctx.session.activeOrganizationId), orderBy: desc(sshKeys.createdAt), }); }), - allForApps: protectedProcedure.query(async ({ ctx }) => { + allForApps: protectedProcedure + .meta({ + openapi: { + summary: "List SSH keys for app selection", + description: "Returns a lightweight list of SSH keys (ID and name only) for the current organization, suitable for dropdown selectors in application forms.", + }, + }) + .query(async ({ ctx }) => { return await db.query.sshKeys.findMany({ columns: { sshKeyId: true, @@ -98,11 +130,23 @@ export const sshRouter = createTRPCRouter({ }); }), generate: withPermission("sshKeys", "read") + .meta({ + openapi: { + summary: "Generate SSH key pair", + description: "Generates a new SSH key pair of the specified type (RSA, ED25519, etc.) and returns both public and private keys.", + }, + }) .input(apiGenerateSSHKey) .mutation(async ({ input }) => { return await generateSSHKey(input.type); }), update: withPermission("sshKeys", "create") + .meta({ + openapi: { + summary: "Update SSH key", + description: "Updates an existing SSH key. Verifies organization ownership before applying changes and logs an audit event.", + }, + }) .input(apiUpdateSshKey) .mutation(async ({ input, ctx }) => { try { diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts index 29f5df10a..a627a7586 100644 --- a/apps/dokploy/server/api/routers/stripe.ts +++ b/apps/dokploy/server/api/routers/stripe.ts @@ -30,7 +30,14 @@ import { export const stripeRouter = createTRPCRouter({ /** Returns the current billing plan for the user's organization. Used to gate features like chat (Startup only). */ - getCurrentPlan: protectedProcedure.query(async ({ ctx }) => { + getCurrentPlan: protectedProcedure + .meta({ + openapi: { + summary: "Get current billing plan", + description: "Returns the active Stripe billing plan (hobby, startup, or legacy) for the caller's organization owner. Returns null if not on cloud or no subscription exists.", + }, + }) + .query(async ({ ctx }) => { if (!IS_CLOUD) return null; const owner = await findUserById(ctx.user.ownerId); if (!owner?.stripeCustomerId) return null; @@ -71,7 +78,14 @@ export const stripeRouter = createTRPCRouter({ return null; }), - getProducts: adminProcedure.query(async ({ ctx }) => { + getProducts: adminProcedure + .meta({ + openapi: { + summary: "List Stripe products and subscriptions", + description: "Returns available Stripe products, the user's active subscriptions, current plan tier, billing interval, and price amount.", + }, + }) + .query(async ({ ctx }) => { const user = await findUserById(ctx.user.ownerId); const stripeCustomerId = user.stripeCustomerId; @@ -162,6 +176,12 @@ export const stripeRouter = createTRPCRouter({ }; }), createCheckoutSession: adminProcedure + .meta({ + openapi: { + summary: "Create Stripe checkout session", + description: "Creates a Stripe checkout session for subscribing to a billing plan. Handles customer creation or reuse and returns the session ID for redirect.", + }, + }) .input( z .object({ @@ -222,7 +242,14 @@ export const stripeRouter = createTRPCRouter({ return { sessionId: session.id }; }), - createCustomerPortalSession: adminProcedure.mutation(async ({ ctx }) => { + createCustomerPortalSession: adminProcedure + .meta({ + openapi: { + summary: "Create Stripe customer portal session", + description: "Creates a Stripe billing portal session URL so the user can manage their subscription, payment methods, and invoices.", + }, + }) + .mutation(async ({ ctx }) => { // Use the organization's owner account for billing portal const owner = await findUserById(ctx.user.ownerId); @@ -253,6 +280,12 @@ export const stripeRouter = createTRPCRouter({ }), upgradeSubscription: adminProcedure + .meta({ + openapi: { + summary: "Upgrade subscription", + description: "Upgrades or changes the current Stripe subscription to a different tier or server quantity. Applies prorated charges for the billing period change.", + }, + }) .input( z .object({ @@ -324,7 +357,14 @@ export const stripeRouter = createTRPCRouter({ return { ok: true }; }), - canCreateMoreServers: withPermission("server", "create").query( + canCreateMoreServers: withPermission("server", "create") + .meta({ + openapi: { + summary: "Check server creation quota", + description: "Returns whether the organization can create more servers based on their subscription's server quantity limit. Always returns true for self-hosted instances.", + }, + }) + .query( async ({ ctx }) => { const user = await findUserById(ctx.user.ownerId); const servers = await findServersByUserId(user.id); @@ -338,6 +378,12 @@ export const stripeRouter = createTRPCRouter({ ), updateInvoiceNotifications: adminProcedure + .meta({ + openapi: { + summary: "Update invoice notification preference", + description: "Enables or disables email notifications for invoice events. Only available on Dokploy Cloud.", + }, + }) .input(z.object({ enabled: z.boolean() })) .mutation(async ({ ctx, input }) => { if (!IS_CLOUD) { @@ -353,7 +399,14 @@ export const stripeRouter = createTRPCRouter({ return { ok: true }; }), - getInvoices: adminProcedure.query(async ({ ctx }) => { + getInvoices: adminProcedure + .meta({ + openapi: { + summary: "List invoices", + description: "Returns up to 100 Stripe invoices for the organization owner, including status, amounts, and download links.", + }, + }) + .query(async ({ ctx }) => { const user = await findUserById(ctx.user.ownerId); const stripeCustomerId = user.stripeCustomerId; diff --git a/apps/dokploy/server/api/routers/swarm.ts b/apps/dokploy/server/api/routers/swarm.ts index f5c8f02c6..ad6e22df5 100644 --- a/apps/dokploy/server/api/routers/swarm.ts +++ b/apps/dokploy/server/api/routers/swarm.ts @@ -13,6 +13,12 @@ import { containerIdRegex } from "./docker"; export const swarmRouter = createTRPCRouter({ getNodes: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get Swarm nodes", + description: "Retrieves all nodes in the Docker Swarm. Optionally targets a remote server by ID.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -22,11 +28,23 @@ export const swarmRouter = createTRPCRouter({ return await getSwarmNodes(input.serverId); }), getNodeInfo: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get Swarm node info", + description: "Retrieves detailed information about a specific Docker Swarm node by its node ID. Optionally targets a remote server.", + }, + }) .input(z.object({ nodeId: z.string(), serverId: z.string().optional() })) .query(async ({ input }) => { return await getNodeInfo(input.nodeId, input.serverId); }), getNodeApps: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get Swarm node applications", + description: "Retrieves all applications (services) running across Docker Swarm nodes. Optionally targets a remote server.", + }, + }) .input( z.object({ serverId: z.string().optional(), @@ -58,6 +76,12 @@ export const swarmRouter = createTRPCRouter({ return await getApplicationInfo(input.appName, input.serverId); }), getContainerStats: withPermission("server", "read") + .meta({ + openapi: { + summary: "Get container stats", + description: "Retrieves resource usage statistics for all containers. Optionally targets a remote server and validates organization access.", + }, + }) .input( z.object({ serverId: z.string().optional(), diff --git a/apps/dokploy/server/api/routers/tag.ts b/apps/dokploy/server/api/routers/tag.ts index b7e466c3a..431e6204f 100644 --- a/apps/dokploy/server/api/routers/tag.ts +++ b/apps/dokploy/server/api/routers/tag.ts @@ -16,6 +16,12 @@ import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc"; export const tagRouter = createTRPCRouter({ create: withPermission("tag", "create") + .meta({ + openapi: { + summary: "Create tag", + description: "Creates a new tag with a name and color for the current organization. Tag names must be unique within the organization.", + }, + }) .input(apiCreateTag) .mutation(async ({ input, ctx }) => { try { @@ -47,7 +53,14 @@ export const tagRouter = createTRPCRouter({ } }), - all: protectedProcedure.query(async ({ ctx }) => { + all: protectedProcedure + .meta({ + openapi: { + summary: "List all tags", + description: "Returns all tags for the current organization, ordered alphabetically by name.", + }, + }) + .query(async ({ ctx }) => { try { const organizationTags = await db.query.tags.findMany({ where: eq(tags.organizationId, ctx.session.activeOrganizationId), @@ -64,7 +77,15 @@ export const tagRouter = createTRPCRouter({ } }), - one: protectedProcedure.input(apiFindOneTag).query(async ({ input, ctx }) => { + one: protectedProcedure + .meta({ + openapi: { + summary: "Get tag", + description: "Returns a single tag by ID. Only returns tags belonging to the caller's organization.", + }, + }) + .input(apiFindOneTag) + .query(async ({ input, ctx }) => { try { const tag = await db.query.tags.findFirst({ where: and( @@ -94,6 +115,12 @@ export const tagRouter = createTRPCRouter({ }), update: withPermission("tag", "update") + .meta({ + openapi: { + summary: "Update tag", + description: "Updates an existing tag's name and/or color. Verifies the tag belongs to the caller's organization. Tag names must remain unique.", + }, + }) .input(apiUpdateTag) .mutation(async ({ input, ctx }) => { try { @@ -144,6 +171,12 @@ export const tagRouter = createTRPCRouter({ }), remove: withPermission("tag", "delete") + .meta({ + openapi: { + summary: "Delete tag", + description: "Deletes a tag by ID. Cascade-deletes all project-tag associations. Verifies the tag belongs to the caller's organization.", + }, + }) .input(apiRemoveTag) .mutation(async ({ input, ctx }) => { try { @@ -179,6 +212,12 @@ export const tagRouter = createTRPCRouter({ }), assignToProject: protectedProcedure + .meta({ + openapi: { + summary: "Assign tag to project", + description: "Associates a tag with a project. Verifies that both the tag and project belong to the caller's organization and that the caller has project access.", + }, + }) .input( z.object({ projectId: z.string().min(1), @@ -267,6 +306,12 @@ export const tagRouter = createTRPCRouter({ }), removeFromProject: protectedProcedure + .meta({ + openapi: { + summary: "Remove tag from project", + description: "Removes a tag-project association. Verifies that both the tag and project belong to the caller's organization and that the caller has project access.", + }, + }) .input( z.object({ projectId: z.string().min(1), @@ -347,6 +392,12 @@ export const tagRouter = createTRPCRouter({ }), bulkAssign: protectedProcedure + .meta({ + openapi: { + summary: "Bulk assign tags to project", + description: "Replaces all tag associations for a project with the provided list of tag IDs. Removes existing associations first, then inserts the new set.", + }, + }) .input( z.object({ projectId: z.string().min(1), diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 63578b099..bc8cb3bab 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -60,7 +60,14 @@ const apiCreateApiKey = z.object({ }); export const userRouter = createTRPCRouter({ - all: withPermission("member", "read").query(async ({ ctx }) => { + all: withPermission("member", "read") + .meta({ + openapi: { + summary: "List all organization members", + description: "Retrieve all members of the current active organization, including their associated user data, ordered by creation date.", + }, + }) + .query(async ({ ctx }) => { return await db.query.member.findMany({ where: eq(member.organizationId, ctx.session.activeOrganizationId), with: { @@ -70,6 +77,12 @@ export const userRouter = createTRPCRouter({ }); }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a user by ID", + description: "Retrieve a specific user's membership and profile within the active organization. Users can view their own data; admins and owners can view any member. Requires member.update permission for non-self lookups.", + }, + }) .input( z.object({ userId: z.string(), @@ -114,7 +127,14 @@ export const userRouter = createTRPCRouter({ return memberResult; }), - session: publicProcedure.query(async ({ ctx }) => { + session: publicProcedure + .meta({ + openapi: { + summary: "Get current session", + description: "Return the current user's ID and active organization ID from the session. Returns null if no valid session exists.", + }, + }) + .query(async ({ ctx }) => { if (!ctx.user || !ctx.session || !ctx.session.activeOrganizationId) { return null; } @@ -127,7 +147,14 @@ export const userRouter = createTRPCRouter({ }, }; }), - get: protectedProcedure.query(async ({ ctx }) => { + get: protectedProcedure + .meta({ + openapi: { + summary: "Get current user profile", + description: "Retrieve the current authenticated user's membership record including user profile and API keys for the active organization.", + }, + }) + .query(async ({ ctx }) => { const memberResult = await db.query.member.findFirst({ where: and( eq(member.userId, ctx.user.id), @@ -144,10 +171,24 @@ export const userRouter = createTRPCRouter({ return memberResult; }), - getPermissions: protectedProcedure.query(async ({ ctx }) => { + getPermissions: protectedProcedure + .meta({ + openapi: { + summary: "Get resolved permissions", + description: "Return the fully resolved permissions for the current user in the active organization, combining role-based and custom permissions.", + }, + }) + .query(async ({ ctx }) => { return resolvePermissions(ctx); }), - haveRootAccess: protectedProcedure.query(async ({ ctx }) => { + haveRootAccess: protectedProcedure + .meta({ + openapi: { + summary: "Check root access", + description: "Check whether the current user has root admin access. Only returns true in cloud mode for the designated admin user or impersonating sessions.", + }, + }) + .query(async ({ ctx }) => { if (!IS_CLOUD) { return false; } @@ -159,7 +200,14 @@ export const userRouter = createTRPCRouter({ } return false; }), - getBackups: adminProcedure.query(async ({ ctx }) => { + getBackups: adminProcedure + .meta({ + openapi: { + summary: "Get user backups", + description: "Retrieve the current admin user's backup configurations including destinations, deployments, and API keys.", + }, + }) + .query(async ({ ctx }) => { const memberResult = await db.query.member.findFirst({ where: and( eq(member.userId, ctx.user.id), @@ -182,8 +230,14 @@ export const userRouter = createTRPCRouter({ return memberResult?.user; }), - getServerMetrics: withPermission("monitoring", "read").query( - async ({ ctx }) => { + getServerMetrics: withPermission("monitoring", "read") + .meta({ + openapi: { + summary: "Get server metrics user", + description: "Retrieve the user record associated with server metrics access for the current organization membership.", + }, + }) + .query(async ({ ctx }) => { const memberResult = await db.query.member.findFirst({ where: and( eq(member.userId, ctx.user.id), @@ -198,6 +252,12 @@ export const userRouter = createTRPCRouter({ }, ), update: protectedProcedure + .meta({ + openapi: { + summary: "Update current user", + description: "Update the current user's profile. If changing the password, the current password must be provided and verified. Logs an audit event on success.", + }, + }) .input(apiUpdateUser) .mutation(async ({ input, ctx }) => { if (input.password || input.currentPassword) { @@ -248,12 +308,24 @@ export const userRouter = createTRPCRouter({ } }), getUserByToken: publicProcedure + .meta({ + openapi: { + summary: "Get user by token", + description: "Look up a user by their authentication token. This is a public endpoint that does not require an active session.", + }, + }) .input(apiFindOneToken) .query(async ({ input }) => { return await getUserByToken(input.token); }), - getMetricsToken: withPermission("monitoring", "read").query( - async ({ ctx }) => { + getMetricsToken: withPermission("monitoring", "read") + .meta({ + openapi: { + summary: "Get metrics token and configuration", + description: "Retrieve the server IP, paid features flag, and monitoring configuration needed for metrics collection.", + }, + }) + .query(async ({ ctx }) => { const user = await findUserById(ctx.user.ownerId); const settings = await getWebServerSettings(); return { @@ -264,6 +336,12 @@ export const userRouter = createTRPCRouter({ }, ), remove: protectedProcedure + .meta({ + openapi: { + summary: "Remove a user", + description: "Delete a user from the organization. Only owners and admins can remove users; owners cannot be removed, and admins cannot remove themselves or other admins. Disabled on cloud.", + }, + }) .input( z.object({ userId: z.string(), @@ -333,6 +411,12 @@ export const userRouter = createTRPCRouter({ return result; }), assignPermissions: withPermission("member", "update") + .meta({ + openapi: { + summary: "Assign member permissions", + description: "Update permissions for a specific member in the organization. Only the organization owner can assign permissions. Git provider and server access restrictions require a valid license.", + }, + }) .input(apiAssignPermissions) .mutation(async ({ input, ctx }) => { try { @@ -383,7 +467,14 @@ export const userRouter = createTRPCRouter({ throw error; } }), - getInvitations: protectedProcedure.query(async ({ ctx }) => { + getInvitations: protectedProcedure + .meta({ + openapi: { + summary: "Get pending invitations for current user", + description: "Retrieve all pending organization invitations for the current user's email that have not yet expired.", + }, + }) + .query(async ({ ctx }) => { return await db.query.invitation.findMany({ where: and( eq(invitation.email, ctx.user.email), @@ -397,6 +488,12 @@ export const userRouter = createTRPCRouter({ }), getContainerMetrics: withPermission("monitoring", "read") + .meta({ + openapi: { + summary: "Get container metrics", + description: "Fetch monitoring metrics for a specific container by querying the metrics endpoint. Requires an application name, metrics URL, and authentication token.", + }, + }) .input( z.object({ url: z.string(), @@ -455,11 +552,24 @@ export const userRouter = createTRPCRouter({ } }), - generateToken: protectedProcedure.mutation(async () => { + generateToken: protectedProcedure + .meta({ + openapi: { + summary: "Generate authentication token", + description: "Generate a new authentication token for the current user.", + }, + }) + .mutation(async () => { return "token"; }), deleteApiKey: protectedProcedure + .meta({ + openapi: { + summary: "Delete an API key", + description: "Delete an API key by ID. Only the owner of the API key can delete it. Logs an audit event on success.", + }, + }) .input( z.object({ apiKeyId: z.string(), @@ -499,6 +609,12 @@ export const userRouter = createTRPCRouter({ }), createApiKey: protectedProcedure + .meta({ + openapi: { + summary: "Create an API key", + description: "Create a new API key for the current user, scoped to a specific organization. Supports optional rate limiting and request limiting configuration.", + }, + }) .input(apiCreateApiKey) .mutation(async ({ input, ctx }) => { // Verify user is a member of the organization specified in metadata @@ -529,6 +645,12 @@ export const userRouter = createTRPCRouter({ }), checkUserOrganizations: protectedProcedure + .meta({ + openapi: { + summary: "Check user organization count", + description: "Return the number of organizations a user belongs to. Users can check their own count; admins and owners can check counts for members in the active organization.", + }, + }) .input( z.object({ userId: z.string(), @@ -570,6 +692,12 @@ export const userRouter = createTRPCRouter({ return organizations.length; }), createUserWithCredentials: withPermission("member", "create") + .meta({ + openapi: { + summary: "Create user with credentials", + description: "Create a new user with email and password and add them to the active organization with the specified role. Only available in self-hosted mode.", + }, + }) .input( z.object({ email: z.string().email(), @@ -601,6 +729,12 @@ export const userRouter = createTRPCRouter({ }); }), sendInvitation: withPermission("member", "create") + .meta({ + openapi: { + summary: "Send invitation email", + description: "Send an invitation email to a pending invitee using a configured email or Resend notification provider. Returns the generated invite link. Disabled on cloud.", + }, + }) .input( z.object({ invitationId: z.string().min(1), @@ -676,7 +810,14 @@ export const userRouter = createTRPCRouter({ return inviteLink; }), - getBookmarkedTemplates: protectedProcedure.query(async ({ ctx }) => { + getBookmarkedTemplates: protectedProcedure + .meta({ + openapi: { + summary: "Get bookmarked templates", + description: "Retrieve the list of template IDs that the current user has bookmarked.", + }, + }) + .query(async ({ ctx }) => { const result = await db.query.user.findFirst({ where: eq(user.id, ctx.user.id), columns: { bookmarkedTemplates: true }, @@ -686,6 +827,12 @@ export const userRouter = createTRPCRouter({ }), toggleTemplateBookmark: protectedProcedure + .meta({ + openapi: { + summary: "Toggle template bookmark", + description: "Add or remove a template from the current user's bookmarks. Returns whether the template is now bookmarked.", + }, + }) .input( z.object({ templateId: z.string().min(1), diff --git a/apps/dokploy/server/api/routers/volume-backups.ts b/apps/dokploy/server/api/routers/volume-backups.ts index 5b50219d2..c1f95abb0 100644 --- a/apps/dokploy/server/api/routers/volume-backups.ts +++ b/apps/dokploy/server/api/routers/volume-backups.ts @@ -30,6 +30,12 @@ import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc"; export const volumeBackupsRouter = createTRPCRouter({ list: protectedProcedure + .meta({ + openapi: { + summary: "List volume backups", + description: "Returns all volume backup configurations for a given service, including related service details.", + }, + }) .input( z.object({ id: z.string().min(1), @@ -65,6 +71,12 @@ export const volumeBackupsRouter = createTRPCRouter({ }); }), create: protectedProcedure + .meta({ + openapi: { + summary: "Create a volume backup", + description: "Creates a new volume backup configuration for a service. If enabled, automatically schedules the backup using the provided cron expression.", + }, + }) .input(createVolumeBackupSchema) .mutation(async ({ input, ctx }) => { const serviceId = @@ -102,6 +114,12 @@ export const volumeBackupsRouter = createTRPCRouter({ return newVolumeBackup; }), one: protectedProcedure + .meta({ + openapi: { + summary: "Get a volume backup", + description: "Returns the details of a specific volume backup configuration by its ID.", + }, + }) .input( z.object({ volumeBackupId: z.string().min(1), @@ -126,6 +144,12 @@ export const volumeBackupsRouter = createTRPCRouter({ return vb; }), delete: protectedProcedure + .meta({ + openapi: { + summary: "Delete a volume backup", + description: "Permanently removes a volume backup configuration by its ID.", + }, + }) .input( z.object({ volumeBackupId: z.string().min(1), @@ -156,6 +180,12 @@ export const volumeBackupsRouter = createTRPCRouter({ return result; }), update: protectedProcedure + .meta({ + openapi: { + summary: "Update a volume backup", + description: "Updates an existing volume backup configuration. Reschedules or removes the backup job depending on the enabled state.", + }, + }) .input(updateVolumeBackupSchema) .mutation(async ({ input, ctx }) => { const existingVb = await findVolumeBackupById(input.volumeBackupId); @@ -216,6 +246,12 @@ export const volumeBackupsRouter = createTRPCRouter({ }), runManually: protectedProcedure + .meta({ + openapi: { + summary: "Run a volume backup manually", + description: "Immediately executes a volume backup outside of its normal cron schedule.", + }, + }) .input(z.object({ volumeBackupId: z.string().min(1) })) .mutation(async ({ input, ctx }) => { const vb = await findVolumeBackupById(input.volumeBackupId); diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index d0b43e76f..7d266b499 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -13,7 +13,12 @@ import { hasValidLicense } from "@dokploy/server/index"; import type { statements } from "@dokploy/server/lib/access-control"; import { validateRequest } from "@dokploy/server/lib/auth"; import { checkPermission } from "@dokploy/server/services/permission"; -import type { OpenApiMeta } from "@dokploy/trpc-openapi"; +import type { OpenApiMeta as _OpenApiMeta } from "@dokploy/trpc-openapi"; + +// method and path are auto-generated by @dokploy/trpc-openapi, make them optional +type OpenApiMeta = { + openapi?: Partial>; +}; import { initTRPC, TRPCError } from "@trpc/server"; import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; import type { Session, User } from "better-auth"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25a2537b0..ab44ff3a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: workspace:* version: link:../../packages/server '@dokploy/trpc-openapi': - specifier: 0.0.18 - version: 0.0.18(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6) + specifier: 0.0.19 + version: 0.0.19(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6) '@faker-js/faker': specifier: ^8.4.1 version: 8.4.1 @@ -1312,8 +1312,8 @@ packages: '@codemirror/view@6.39.15': resolution: {integrity: sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==} - '@dokploy/trpc-openapi@0.0.18': - resolution: {integrity: sha512-CbppvUEe8eK1fiNGQL5AH8KIRRlHk5bGPUEIyc2VBZE0un4kfUs5DXKSKsMLDomoES5ZEdrjT4nKpwYvhDha0w==} + '@dokploy/trpc-openapi@0.0.19': + resolution: {integrity: sha512-pmajIu1tIU3yqqbYdRHKFbA8gP37gO7F61PL9AFNtoyhh6gVxALXQLDabtE7dobKAhNDmIj6vV1a2vyP1Zi7/w==} peerDependencies: '@trpc/server': ^11.1.0 zod: ^4.3.6 @@ -9006,7 +9006,7 @@ snapshots: style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@dokploy/trpc-openapi@0.0.18(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)': + '@dokploy/trpc-openapi@0.0.19(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)': dependencies: '@trpc/server': 11.10.0(typescript@5.9.3) co-body: 6.2.0