chore: update @dokploy/trpc-openapi to version 0.0.19 and enhance API documentation

- Updated the version of @dokploy/trpc-openapi from 0.0.18 to 0.0.19 in pnpm-lock.yaml and package.json.
- Added OpenAPI metadata to various procedures across multiple routers, improving API documentation and clarity for developers.
- Enhanced descriptions and summaries for several API endpoints, ensuring better understanding of their functionality.

These changes improve the overall API usability and documentation quality.
This commit is contained in:
Mauricio Siu
2026-04-11 14:50:28 -06:00
parent 7e89eaed4a
commit ec202c8c6e
46 changed files with 3467 additions and 365 deletions
+1 -1
View File
@@ -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",
+6
View File
@@ -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 {
+82 -4
View File
@@ -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);
@@ -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),
+72
View File
@@ -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(),
+44 -1
View File
@@ -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, {
+32 -1
View File
@@ -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);
@@ -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(),
+180
View File
@@ -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
+52 -2
View File
@@ -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),
+38 -1
View File
@@ -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 {
+54
View File
@@ -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) {
+57 -1
View File
@@ -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(),
@@ -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(),
@@ -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 {
+55 -4
View File
@@ -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;
+43 -4
View File
@@ -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, {
+49 -4
View File
@@ -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) {
+84
View File
@@ -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),
@@ -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),
+96
View File
@@ -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),
+36
View File
@@ -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");
+96
View File
@@ -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),
+250 -2
View File
@@ -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(
@@ -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;
}
+75 -1
View File
@@ -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);
+24
View File
@@ -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);
@@ -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),
@@ -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(),
+52 -2
View File
@@ -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(),
@@ -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);
+96
View File
@@ -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),
+44 -1
View File
@@ -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 {
@@ -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 {
@@ -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);
@@ -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);
+114 -6
View File
@@ -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(),
File diff suppressed because it is too large Load Diff
+46 -2
View File
@@ -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 {
+58 -5
View File
@@ -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;
+24
View File
@@ -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(),
+53 -2
View File
@@ -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),
+160 -13
View File
@@ -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),
@@ -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);
+6 -1
View File
@@ -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<NonNullable<_OpenApiMeta["openapi"]>>;
};
import { initTRPC, TRPCError } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
import type { Session, User } from "better-auth";
+5 -5
View File
@@ -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